Finalize project 5
This commit is contained in:
@@ -303,3 +303,9 @@ src/project4/main
|
||||
src/project4/analytical
|
||||
src/project4/single_threaded_performance
|
||||
src/project4/multi_threaded_performance
|
||||
|
||||
# Project 5 executables
|
||||
src/project5/main
|
||||
src/project5/debug
|
||||
src/project5/*.bin
|
||||
/build/
|
||||
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
This project successfully developed and implemented a numerical framework for solving the two-dimensional, time-dependent Schrödinger equation. Using the Crank–Nicolson finite difference scheme, we were able to simulate quantum wave packet dynamics stably and accurately. The implicit method enabled comparatively large time steps to be taken while maintaining second-order accuracy in both space and time.
|
||||
|
||||
The C++ implementation leveraged the Armadillo library for efficient sparse linear algebra operations, enabling the simulation of systems with high spatial resolution. A key strength of the framework is its configurability via TOML input files, which allows for straightforward experimentation with various initial conditions, potential landscapes and discretisation parameters, eliminating the need for recompilation.
|
||||
|
||||
Our results demonstrate the numerical robustness of the approach. The total probability was conserved to within an order of \num{e-15} over the simulation duration, indicating excellent numerical stability. The simulations correctly reproduced fundamental quantum mechanical phenomena, including the diffraction and interference of a wave packet passing through single, double, and triple slit potentials. The characteristic interference patterns observed on a virtual detector screen qualitatively match theoretical expectations, validating the physical correctness of the implementation.
|
||||
|
||||
The framework's output of the complete wave function at each time step allows for maximum flexibility in post-processing. As demonstrated, this enables the computation of arbitrary observables, such as probability density slices at any spatial location, extending beyond the simple screen detection example.
|
||||
|
||||
Several extensions are promising for future work. For example, the potential function could be generalised to model more complex systems, such as quantum dots, wells or tunnelling barriers. Implementing absorbing boundary conditions, such as complex absorbing potentials (CAP) or perfectly matched layers (PML), would enable the simulation of scattering problems and open systems without reflections. Furthermore, adapting the code to leverage GPU acceleration or parallel computing techniques would enable the simulation of larger domains or three-dimensional systems, significantly expanding the range of physically interesting problems that can be explored.
|
||||
|
||||
In summary, this project provides a reliable and extensible foundation for the numerical investigation of time-dependent quantum mechanical phenomena in two dimensions, bridging the gap between theoretical concepts and computational experimentation.
|
||||
@@ -0,0 +1,9 @@
|
||||
The Schrödinger equation, introduced in 1925, is a fundamental equation in quantum mechanics that describes how the quantum state of a physical system changes over time. It is a key result of wave mechanics, one of the two main formulations of quantum mechanics (the other being matrix mechanics). The equation is named after Erwin Schrödinger, who developed it and won the Nobel Prize in Physics in 1933 for his work.
|
||||
|
||||
The time-dependent Schrödinger equation is given by:
|
||||
\begin{equation}
|
||||
i\hbar \frac{\partial}{\partial t} \psi = \hat{H} \psi,
|
||||
\end{equation}
|
||||
where \(i\) is the imaginary unit, \(\hbar\) is the reduced Planck constant, and \(\psi\) is the wave function of the quantum system. The Hamiltonian operator, \(\hat{H}\), represents the total energy of the system, i.e., the sum of kinetic and potential energies.
|
||||
|
||||
In \cref{sec:methods}, we describe the numerical methods used to solve the two-dimensional Schrödinger equation. In \cref{sec:results_and_discussion}, we present and discuss the results obtained from our numerical simulations. Finally, in \cref{sec:conclusion}, we summarize our findings and discuss potential future work.
|
||||
@@ -0,0 +1,131 @@
|
||||
\subsection{Discretization of the Schrödinger equation}
|
||||
All numerical methods for solving differential equations rely on the discretization of continuous variables. In this project, we will discretize both space and time. We will begin with the two-dimensional time-dependent Schrödinger equation:
|
||||
\begin{align}
|
||||
i\hbar \frac{\partial}{\partial t} \psi(x,y,t) &= -\frac{\hbar^2}{2m} \left( \frac{\partial^2}{\partial x^2} + \frac{\partial^2}{\partial y^2} \right) \psi(x,y,t) \nonumber\\&+ V(x,y) \psi(x,y,t).
|
||||
\end{align}
|
||||
We define a grid with spacing \(h\) in both the \(x\) and \(y\) directions to discretize space. The grid points are given by \(x_i = i h\) and \(y_j = j h\), where \(i\) and \(j\) are integers. The wave function at these grid points is denoted as \(\psi_{i,j}(t) = \psi(x_i, y_j, t)\) . We also discretize time with a time step of size \(\Delta t\), such that \(t_n = n \Delta t\), where \(n\) is an integer. The wave function at time \(t_n\) is denoted as \(\psi_{i,j}^n = \psi_{i,j}(t_n)\).
|
||||
For simplicity, we set both \(\hbar = \qty{1}{\per \s}\) and \(m = 1\). This yields the dimensionless form of the Schrödinger equation:
|
||||
\begin{align}
|
||||
i \frac{\partial}{\partial t} u(x,y,t) &= - \frac{\partial^2}{\partial x^2} u(x,y,t) - \frac{\partial^2}{\partial y^2} u(x,y,t) \nonumber\\&+ v(x,y) u(x,y,t).
|
||||
\end{align}
|
||||
In this form, we substitute \(u\) for \(\psi\) and \(v\) for \(V\) to eliminate constants. We can then reobtain the physical probability density, $P = \psi^\ast \psi$, by appropriately normalizing the wave function:
|
||||
\begin{equation}
|
||||
P(x,y,t) = \frac{u^\ast(x,y,t) u(x,y,t)}{\int \int u^\ast(x,y,t) u(x,y,t) \, dx \, dy}.
|
||||
\end{equation}
|
||||
In practice, the integral in the denominator is equal to the sum of all grid points multiplied by the area element \(h^2\).
|
||||
|
||||
Using the Crank-Nicolson scheme, we can approximate the time evolution of the wave function. The Crank-Nicolson method is a second-order accurate, unconditionally stable implicit finite difference method in both space and time. The update rule for the wave function using the Crank-Nicolson scheme is given by the following formula \cite{liuCrankNicolsonMethod2022}:
|
||||
\begin{equation}
|
||||
\left( I + \frac{i \Delta t}{2} H \right) u^{n+1} = \left( I - \frac{i \Delta t}{2} H \right) u^n,
|
||||
\end{equation}
|
||||
where \(I\) is the identity matrix, \(H\) is the discretized Hamiltonian operator, and \(u^n\) and \(u^{n+1}\) are the wave functions at time steps \(n\) and \(n+1\), respectively. The Hamiltonian operator \(H\) can be discretized using finite difference approximations for the second derivatives:
|
||||
\begin{align}
|
||||
H_{i,j} &= -\frac{1}{h^2} \left( u_{i+1,j} + u_{i-1,j} + u_{i,j+1} + u_{i,j-1} - 4u_{i,j} \right) \nonumber\\&+ v_{i,j} u_{i,j}.
|
||||
\end{align}
|
||||
By defining the constant as \(r \equiv \frac{i \Delta t}{2 h^2}\), we can rewrite the update rule as follows:
|
||||
\begin{align}
|
||||
&u_{i,j}^{n+1} + r \left( u_{i+1,j}^{n+1} + u_{i-1,j}^{n+1} + u_{i,j+1}^{n+1} + u_{i,j-1}^{n+1} - 4u_{i,j}^{n+1} \right) \nonumber\\&= u_{i,j}^n - r \left( u_{i+1,j}^n + u_{i-1,j}^n + u_{i,j+1}^n + u_{i,j-1}^n - 4u_{i,j}^n \right)\nonumber\\& - i \Delta t v_{i,j} \frac{u_{i,j}^{n+1} + u_{i,j}^n}{2}.
|
||||
\end{align}
|
||||
We can rearrange this equation to form a linear system that can be solved for \(u^{n+1}\) at each time step. We define the index mapping \(k = i + j M\), where \(M\) is the number of grid points in one dimension and \(M = \frac{1}{h} + 1\). This allows us to rewrite the two-dimensional grid as a one-dimensional array \cite{anderskvellestadProject5FYS3150}. The resulting linear system can be expressed in matrix form as follows:
|
||||
\begin{equation}
|
||||
A u^{n+1} = B u^n,
|
||||
\end{equation}
|
||||
where \(A\) and \(B\) are matrices that depend on the discretization parameters and the potential \(v\). Only the internal points of the problem are contained in \(A,\,B,\,u^n\). Boundary conditions are applied separately. For this project, we will use Dirichlet boundary conditions and set the wave function to zero at the grid boundaries.
|
||||
Matrix \(A\) is tridiagonal with additional diagonals due to the two-dimensional nature of the problem. The matrix \(B\) has a similar structure. To solve for \(u^{n+1}\), first calculate $b^n = B u^n$, then solve the linear system \(A u^{n+1} = b^n\) using an appropriate numerical method, such as LU decomposition or iterative solvers.
|
||||
|
||||
\subsection{Initial conditions and potential}
|
||||
To simulate the time evolution of the wave function, we must specify the initial conditions and the potential, \(v(x,y)\). A common choice for the initial wave function is a Gaussian wave packet, defined as follows \cite{anderskvellestadProject5FYS3150}:
|
||||
\begin{align}
|
||||
u(x,y,0) &= \exp\left( -\frac{(x-x_C)^2}{2\sigma_x^2} - \frac{(y-y_C)^2}{2\sigma_y^2}\right) \nonumber\\&\cdot \exp \left( i(p_x x + p_y y) \right),
|
||||
\end{align}
|
||||
where \((x_C, y_C)\) is the center of the wave packet and where \(\sigma_x, \sigma_y)\) are the widths of the packet in the \(x\) and \(y\) directions, respectively, and where the initial momenta in the respective directions are represented by \((p_x, p_y)\). The potential, \(v(x,y)\), can be chosen based on the physical system being modeled. For example, a double-slit-like potential can be used. To implement slits in the potential grid, we use the algorithm outlined in \cref{alg:add_slits}. This algorithm modifies the potential grid, \(V\), by adding walls of a specified thickness and position while leaving apertures for the slits. An initialization function finalizes the potential setup by calculating the matrices \(A\) and \(B\) based on the modified potential.
|
||||
|
||||
|
||||
\begin{algorithm}[H]
|
||||
\caption{Add Slits to Potential Grid}
|
||||
\label{alg:add_slits}
|
||||
\begin{algorithmic}[1]
|
||||
\Require $h$, $M$, $V$, $V_{\text{wall}}$, $ \text{wall\_thickness}$, $\text{wall\_position}$, $\text{slit\_aperture}$, $\text{slit\_separation}$, $\text{num\_slits}$
|
||||
\State $x_{\text{start}} \gets \left\lfloor \dfrac{\text{wall\_position} - 0.5\,\text{wall\_thickness}}{h} \right\rfloor$
|
||||
\State $x_{\text{end}} \gets \left\lfloor \dfrac{\text{wall\_position} + 0.5\,\text{wall\_thickness}}{h} \right\rfloor$
|
||||
\State $y_c \gets M/2$
|
||||
\State $\text{slit\_half} \gets \left\lfloor \dfrac{0.5\,\text{slit\_aperture}}{h} \right\rfloor$
|
||||
\State $\text{pitch} \gets \text{slit\_aperture} + \text{slit\_separation}$
|
||||
\State $\text{pitch}_{\text{idx}} \gets \text{pitch}/h$
|
||||
|
||||
\For{$i = x_{\text{start}}$ \textbf{to} $x_{\text{end}}$}
|
||||
\For{$j = 0$ \textbf{to} $M-1$}
|
||||
\State $\text{in\_slit} \gets \text{false}$
|
||||
\For{$s = 0$ \textbf{to} $\text{num\_slits}-1$}
|
||||
\State $\Delta \gets \left(s - \dfrac{\text{num\_slits}-1}{2}\right)\,\text{pitch}_{\text{idx}}$
|
||||
\State $c_s \gets \left\lfloor y_c + \Delta + 0.5 \right\rfloor$ \Comment{rounded center index}
|
||||
\If{$j \in [\,c_s - \text{slit\_half}, \; c_s + \text{slit\_half}\,]$}
|
||||
\State $\text{in\_slit} \gets \text{true}$
|
||||
\State \textbf{break}
|
||||
\EndIf
|
||||
\EndFor
|
||||
\If{\textbf{not} $\text{in\_slit}$}
|
||||
\State $V[i,j] \gets V_{\text{wall}}$
|
||||
\EndIf
|
||||
\EndFor
|
||||
\EndFor
|
||||
|
||||
\State \Call{initialize\_potential}{}
|
||||
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\subsection{Implementation details}
|
||||
To optimize performance, the numerical methods were implemented in C++, compiled with optimization flags, and compiled using the \texttt{g++} compiler. Version \texttt{g++ (GCC) 15.2.1 20251111 (Red Hat 15.2.1-4)} was used for compilation. The Armadillo library \cite{sandersonArmadilloEfficientFramework2025,sandersonPracticalSparseMatrices2019} was used for all linear algebra operations because it provides efficient implementations of matrix and vector operations. Matrices \(A\) and \(B\) are constructed as sparse \cite{sandersonPracticalSparseMatrices2019}, complex matrices to save memory and improve computational efficiency. We solved the linear system \(A u^{n+1} = b^n\) using the \texttt{spsolve} function from the Armadillo library. This function is optimized for sparse matrices and uses an implementation provided by SuperLU \cite{demmelSuperLUUsersGuide1999}.
|
||||
|
||||
To allow for easy experimentation with different parameters, the program reads a configuration file in TOML format during runtime. The configuration file specifies parameters in four main categories:
|
||||
Discretization parameters: \(h, \Delta t\), and total simulation time \(T\); Initial wave packet parameters (center position, width, and momentum); Potential parameters (wall height, wall thickness, slit aperture, slit separation, and number of slits); Output parameters (output file name). The program uses Armadillo's built-in functions to parse the wave function results at each time step and write them to a file in \texttt{arma\_ascii} format.
|
||||
|
||||
All analyses and visualizations of the results are performed in Python using the NumPy \cite{harrisArrayProgrammingNumPy2020} and Matplotlib \cite{hunterMatplotlib2DGraphics2007} libraries. The probability density is computed from the wave function data, and various plots are generated to illustrate the time evolution of the wave packet and the effects of the potential. A self-developed parser for the Armadillo ASCII format reads the output files generated by the C++ program, as described in \cref{alg:pythonparser}.
|
||||
|
||||
\begin{algorithm}[H]
|
||||
\caption{Load Armadillo ASCII Complex Cube}\label{alg:pythonparser}
|
||||
\begin{algorithmic}[1]
|
||||
|
||||
\Procedure{LoadCube}{path, slices\_first}
|
||||
|
||||
\State txt $\gets$ read file at \textit{path}
|
||||
\State lines $\gets$ split(txt)
|
||||
\State header $\gets$ first two lines
|
||||
\State $(n_{\text{rows}}, n_{\text{cols}}, n_{\text{slices}}) \gets$ parse(header[1])
|
||||
|
||||
\State rest $\gets$ remaining lines
|
||||
|
||||
\For{each line in rest}
|
||||
\State remove all ``('' and ``)''
|
||||
\State apply regex
|
||||
\[
|
||||
\texttt{([+-]?\d[\d.eE+-]*)\ ,\ ([+-]?\d[\d.eE+-]*)}
|
||||
\]
|
||||
which captures two floating-point values \(r\) and \(i\), possibly with
|
||||
signs or scientific notation
|
||||
\State replace with
|
||||
\[
|
||||
\texttt{\$1+\$2j}
|
||||
\]
|
||||
yielding Python-style complex numbers \(r + ij\)
|
||||
\State append transformed line to fixed
|
||||
\EndFor
|
||||
|
||||
\State arr$_{2D}$ $\gets$ \Call{numpy.loadtxt}{fixed}
|
||||
\State arr$_{3D}$ $\gets$ reshape(arr$_{2D}$, $(n_{\text{slices}}, n_{\text{cols}}, n_{\text{rows}})$)
|
||||
|
||||
\If{slices\_first}
|
||||
\State \Return arr$_{3D}$
|
||||
\Else
|
||||
\State \Return transpose(arr$_{3D}$)
|
||||
\EndIf
|
||||
|
||||
\EndProcedure
|
||||
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\subsubsection*{Use of Artificial Intelligence}
|
||||
In this project, artificial intelligence in the form of large language models (LLMs) was employed to assist with various stages of the research and development process. The LLMs generated initial drafts of code snippets that the researcher reviewed and refined for correctness and efficiency. GitHub Copilot, an LLM, was used for this task. LLMs were also used to edit the report's language by providing grammar and style suggestions. For this task, the DeepL Write service by DeepL GmbH was used.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
We evaluate the accuracy of the resulting solutions by assessing the conservation of probability in the system with and without a double slit, i.e., with \(V_\text{wall} = 0\) and in the presence of such a potential. We simulate the systems for a total time of \qty{0.008}{\s} in steps of \(\Delta t = \qty{2.5e-5}{\s}\). Spatial discretization is carried out using a step size of \(h = \num{0.005}\). In the setup with a non-zero potential wall, the initial wave packet is centered at \(x=\num{0.25}\) and \(y=\num{0.5}\), with widths of \(\sigma_x=\num{0.05}\) and \(\sigma_y=\num{0.10}\), respectively. Its momenta are \(p_x=\num{200.0}\) and \(p_y=\num{0.0}\), resulting in strongly directed motion along \(x\).
|
||||
A double-slit barrier with a thickness of \(\num{0.02}\) is placed at \(x=\num{0.5}\).
|
||||
Each slit has an aperture of \(\num{0.05}\) and is separated by \(\num{0.05}\). The wall potential is effectively infinite at \(V_{\text{wall}}=\num{1.0e10}\). In the case of a constant potential, the wave packet has widths of \(\sigma = \num{0.05}\) in both \(x\) and \(y\). Otherwise, the setup is identical. The resulting wave functions are evaluated for loss of probability.
|
||||
\begin{equation}
|
||||
\delta = 1 - \iint u^\ast(x,y,t) u(x,y,t) \mathrm{d}x \mathrm{d}y,
|
||||
\end{equation}
|
||||
where \(\iint u^\ast(x,y,0) u(x,y,0) \mathrm{d}x \mathrm{d}y = 1\) was fixed using normalization. In a perfect physical system, this quantity is conserved; however, due to limited numerical accuracy, especially in the derivatives and solving of the linear system, it is not conserved. Therefore, it is a good indication of the algorithm's potential accuracy. \Cref{fig:prob_conservation} shows the resulting analysis. The maximum deviation is about \num{2e-15}, demonstrating performance that is only one order of magnitude worse than floating-point precision. Notably, the accuracy of the system without a double slit is lower than the corresponding values with the perturbation. Additionally, the simulation's dependence on potential is evident, precluding any general attestation of performance for arbitrary potentials. Nevertheless, it can be assumed that the simulation will produce very accurate results for many potentials, attesting to the numerical accuracy of the results.
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=\columnwidth]{include/probability_conservation.pdf}
|
||||
\caption{Loss of total probability $\delta$ in dependence on the simulation duration for two systems of different potential.}
|
||||
\label{fig:prob_conservation}
|
||||
\end{figure}
|
||||
|
||||
In addition to the quantitative evaluation of performance, we conduct a qualitative analysis of the system's evolution. To correctly assess the evolution of the wave packet, we perform a double slit experiment on a system for a total duration of \qty{0.002}{\s}. Three time points are sampled to demonstrate the wave packet's interference with itself behind the double slit. The results of this analysis are presented in \cref{fig:t_0_evolution,fig:t_1_evolution,fig:t_2_evolution} in Appendix \cref{app:evolv}.
|
||||
We observe the expected qualitative behavior with respect to interference and the correct diffusion of the wave function. Therefore, we can conclude that the simulation framework behaves correctly. To evaluate the evolution of the probability distribution, we produced animations of the entire runtime for each simulation. These animations can be accessed via the repository of this paper, though they are not essential to understanding the paper.
|
||||
|
||||
Lastly, we demonstrate the flexibility of our approach to simulating the numerical evolution of such a system by simulating the probability of detecting a particle on a screen inserted into the system at a specific time. We computed the probability density function (PDF) for a screen position of \(y = \num{0.8}\) at \(T = \qty{0.002}{\s}\) for three systems: a single slit, a double slit, and a triple slit. The detection PDF is given by the slice
|
||||
\begin{equation}
|
||||
P(x) = \frac{u^\ast(x,\num{0.8},\qty{0.002}{\s}) u(x,\num{0.8},\qty{0.002}{\s})}{\int \mathrm{d}x u^\ast(x,\num{0.8},\qty{0.002}{\s}) u(x,\num{0.8},\qty{0.002}{\s})},
|
||||
\end{equation}
|
||||
where the integral is evaluated as a sum over the total time slice. The resulting PDF is displayed in \cref{fig:screen}. Once again, the interference of the wave packet with itself is clearly visible, leading to the expected result. More importantly, this shows how such a numerical simulation can be used to compute arbitrary metrics because the entire numerical solution of the wave function is saved to a file that can be repurposed in multiple ways.
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=\columnwidth]{include/detector_screens.pdf}
|
||||
\caption{PDF of detecting the wave packet at a certain position on a screen inserted at $y=\num{0.8}$ and $T = \qty{0.002}{\s}$.}
|
||||
\label{fig:screen}
|
||||
\end{figure}
|
||||
@@ -0,0 +1,102 @@
|
||||
@misc{anderskvellestadProject5FYS3150,
|
||||
title = {Project 5 --- {{FYS3150}}/{{FYS4150}} Course Material},
|
||||
author = {{Anders Kvellestad}},
|
||||
urldate = {2025-12-09},
|
||||
howpublished = {https://anderkve.github.io/FYS3150/book/projects/project5.html},
|
||||
file = {/home/lars/Zotero/storage/IVLD4EHN/project5.html}
|
||||
}
|
||||
|
||||
@techreport{demmelSuperLUUsersGuide1999,
|
||||
title = {{{SuperLU}} Users' Guide},
|
||||
author = {Demmel, James W. and Gilbert, John R. and Li, Xiaoye S.},
|
||||
year = 1999,
|
||||
month = nov,
|
||||
number = {LBNL--44289, 751785},
|
||||
pages = {LBNL--44289, 751785},
|
||||
doi = {10.2172/751785},
|
||||
urldate = {2025-12-09},
|
||||
langid = {english},
|
||||
file = {/home/lars/Zotero/storage/8FELF93A/Demmel et al. - 1999 - SuperLU users' guide.pdf}
|
||||
}
|
||||
|
||||
@article{harrisArrayProgrammingNumPy2020,
|
||||
title = {Array Programming with {{NumPy}}},
|
||||
author = {Harris, Charles R. and Millman, K. Jarrod and van der Walt, St{\'e}fan J. and Gommers, Ralf and Virtanen, Pauli and Cournapeau, David and Wieser, Eric and Taylor, Julian and Berg, Sebastian and Smith, Nathaniel J. and Kern, Robert and Picus, Matti and Hoyer, Stephan and van Kerkwijk, Marten H. and Brett, Matthew and Haldane, Allan and del R{\'i}o, Jaime Fern{\'a}ndez and Wiebe, Mark and Peterson, Pearu and {G{\'e}rard-Marchant}, Pierre and Sheppard, Kevin and Reddy, Tyler and Weckesser, Warren and Abbasi, Hameer and Gohlke, Christoph and Oliphant, Travis E.},
|
||||
year = 2020,
|
||||
month = sep,
|
||||
journal = {Nature},
|
||||
volume = {585},
|
||||
number = {7825},
|
||||
pages = {357--362},
|
||||
publisher = {{Springer Science and Business Media LLC}},
|
||||
doi = {10.1038/s41586-020-2649-2}
|
||||
}
|
||||
|
||||
@article{hunterMatplotlib2DGraphics2007,
|
||||
title = {Matplotlib: {{A 2D}} Graphics Environment},
|
||||
author = {Hunter, J. D.},
|
||||
year = 2007,
|
||||
journal = {Computing in Science \& Engineering},
|
||||
volume = {9},
|
||||
number = {3},
|
||||
pages = {90--95},
|
||||
publisher = {IEEE COMPUTER SOC},
|
||||
doi = {10.1109/MCSE.2007.55},
|
||||
abstract = {Matplotlib is a 2D graphics package used for Python for application development, interactive scripting, and publication-quality image generation across user interfaces and operating systems.}
|
||||
}
|
||||
|
||||
@article{liuCrankNicolsonMethod2022,
|
||||
title = {Crank--{{Nicolson}} Method for Solving Uncertain Heat Equation},
|
||||
author = {Liu, Jin and Hao, Yifei},
|
||||
year = 2022,
|
||||
month = feb,
|
||||
journal = {Soft Computing},
|
||||
volume = {26},
|
||||
number = {3},
|
||||
pages = {937--945},
|
||||
issn = {1433-7479},
|
||||
doi = {10.1007/s00500-021-06565-9},
|
||||
urldate = {2025-12-09},
|
||||
abstract = {For usual uncertain heat equations, it is challenging to acquire their analytic solutions. A forward difference Euler method has been used to compute the uncertain heat equations' numerical solutions. Nevertheless, the Euler scheme is instability in some cases. This paper proposes an implicit task to overcome this disadvantage, namely the Crank--Nicolson method, which is unconditional stability. An example shows that the Crank--Nicolson scheme is more stable than the previous scheme (Euler scheme). Moreover, the Crank--Nicolson method is also applied to compute two characteristics of uncertain heat equation's solution---expected value and extreme value. Some examples of uncertain heat equations are designed to show the availability of the Crank--Nicolson method.},
|
||||
langid = {english},
|
||||
keywords = {Crank-Nicolson method,Heat equation,Liu process,Numerical solution},
|
||||
file = {/home/lars/Zotero/storage/Y67L5UFB/Liu and Hao - 2022 - Crank–Nicolson method for solving uncertain heat equation.pdf}
|
||||
}
|
||||
|
||||
@inproceedings{sandersonArmadilloEfficientFramework2025,
|
||||
title = {Armadillo: {{An Efficient Framework}} for {{Numerical Linear Algebra}}},
|
||||
shorttitle = {Armadillo},
|
||||
booktitle = {2025 17th {{International Conference}} on {{Computer}} and {{Automation Engineering}} ({{ICCAE}})},
|
||||
author = {Sanderson, Conrad and Curtin, Ryan},
|
||||
year = 2025,
|
||||
month = mar,
|
||||
eprint = {2502.03000},
|
||||
primaryclass = {cs},
|
||||
pages = {303--307},
|
||||
doi = {10.1109/ICCAE64891.2025.10980539},
|
||||
urldate = {2025-10-16},
|
||||
abstract = {A major challenge in the deployment of scientific software solutions is the adaptation of research prototypes to production-grade code. While high-level languages like MATLAB are useful for rapid prototyping, they lack the resource efficiency required for scalable production applications, necessitating translation into lower level languages like C++. Further, for machine learning and signal processing applications, the underlying linear algebra primitives, generally provided by the standard BLAS and LAPACK libraries, are unwieldy and difficult to use, requiring manual memory management and other tedium. To address this challenge, the Armadillo C++ linear algebra library provides an intuitive interface for writing linear algebra expressions that are easily compiled into efficient production-grade implementations. We describe the expression optimisations we have implemented in Armadillo, exploiting template metaprogramming. We demonstrate that these optimisations result in considerable efficiency gains on a variety of benchmark linear algebra expressions.},
|
||||
archiveprefix = {arXiv},
|
||||
keywords = {Computer Science - Mathematical Software},
|
||||
file = {/home/lars/Zotero/storage/UNJD6AR5/Sanderson and Curtin - 2025 - Armadillo An Efficient Framework for Numerical Linear Algebra.pdf;/home/lars/Zotero/storage/RHNN68A2/2502.html}
|
||||
}
|
||||
|
||||
@article{sandersonPracticalSparseMatrices2019,
|
||||
title = {Practical {{Sparse Matrices}} in {{C}}++ with {{Hybrid Storage}} and {{Template-Based Expression Optimisation}}},
|
||||
author = {Sanderson, Conrad and Curtin, Ryan},
|
||||
year = 2019,
|
||||
month = jul,
|
||||
journal = {Mathematical and Computational Applications},
|
||||
volume = {24},
|
||||
number = {3},
|
||||
eprint = {1811.08768},
|
||||
primaryclass = {cs},
|
||||
pages = {70},
|
||||
issn = {2297-8747},
|
||||
doi = {10.3390/mca24030070},
|
||||
urldate = {2025-10-16},
|
||||
abstract = {Despite the importance of sparse matrices in numerous fields of science, software implementations remain difficult to use for non-expert users, generally requiring the understanding of underlying details of the chosen sparse matrix storage format. In addition, to achieve good performance, several formats may need to be used in one program, requiring explicit selection and conversion between the formats. This can be both tedious and error-prone, especially for non-expert users. Motivated by these issues, we present a user-friendly and open-source sparse matrix class for the C++ language, with a high-level application programming interface deliberately similar to the widely used MATLAB language. This facilitates prototyping directly in C++ and aids the conversion of research code into production environments. The class internally uses two main approaches to achieve efficient execution: (i) a hybrid storage framework, which automatically and seamlessly switches between three underlying storage formats (compressed sparse column, Red-Black tree, coordinate list) depending on which format is best suited and/or available for specific operations, and (ii) a template-based meta-programming framework to automatically detect and optimise execution of common expression patterns. Empirical evaluations on large sparse matrices with various densities of non-zero elements demonstrate the advantages of the hybrid storage framework and the expression optimisation mechanism.},
|
||||
archiveprefix = {arXiv},
|
||||
keywords = {Computer Science - Mathematical Software},
|
||||
file = {/home/lars/Zotero/storage/PZ5ZIXJU/Sanderson and Curtin - 2019 - Practical Sparse Matrices in C++ with Hybrid Storage and Template-Based Expression Optimisation.pdf;/home/lars/Zotero/storage/CQNT3AKH/1811.html}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,155 @@
|
||||
% USEFUL LINKS:
|
||||
% -------------
|
||||
%
|
||||
% - UiO LaTeX guides: https://www.mn.uio.no/ifi/tjenester/it/hjelp/latex/
|
||||
% - Mathematics: https://en.wikibooks.org/wiki/LaTeX/Mathematics
|
||||
% - Physics: https://ctan.uib.no/macros/latex/contrib/physics/physics.pdf
|
||||
% - Basics of Tikz: https://en.wikibooks.org/wiki/LaTeX/PGF/Tikz
|
||||
% - All the colors! https://en.wikibooks.org/wiki/LaTeX/Colors
|
||||
% - How to make tables: https://en.wikibooks.org/wiki/LaTeX/Tables
|
||||
% - Code listing styles: https://en.wikibooks.org/wiki/LaTeX/Source_Code_Listings
|
||||
% - \includegraphics https://en.wikibooks.org/wiki/LaTeX/Importing_Graphics
|
||||
% - Learn more about figures: https://en.wikibooks.org/wiki/LaTeX/Floats,_Figures_and_Captions
|
||||
% - Automagic bibliography: https://en.wikibooks.org/wiki/LaTeX/Bibliography_Management (this one is kinda difficult the first time)
|
||||
%
|
||||
% (This document is of class "revtex4-1", the REVTeX Guide explains how the class works)
|
||||
% REVTeX Guide: http://www.physics.csbsju.edu/370/papers/Journal_Style_Manuals/auguide4-1.pdf
|
||||
%
|
||||
%
|
||||
% COMPILING THE .pdf FILE IN THE LINUX TERMINAL
|
||||
% ---------------------------------------------
|
||||
%
|
||||
% [terminal]$ pdflatex report_example.tex
|
||||
%
|
||||
% Run the command twice, always.
|
||||
%
|
||||
% When using references, footnotes, etc. you should run the following chain of commands:
|
||||
%
|
||||
% [terminal]$ pdflatex report_example.tex
|
||||
% [terminal]$ bibtex report_example
|
||||
% [terminal]$ pdflatex report_example.tex
|
||||
% [terminal]$ pdflatex report_example.tex
|
||||
%
|
||||
% This series of commands can of course be gathered into a single-line command:
|
||||
% [terminal]$ pdflatex report_example.tex && bibtex report_example.aux && pdflatex report_example.tex && pdflatex report_example.tex
|
||||
%
|
||||
% ----------------------------------------------------
|
||||
|
||||
|
||||
|
||||
% \documentclass[english,notitlepage,reprint,nofootinbib]{revtex4-2} % defines the basic parameters of the document
|
||||
\documentclass[english,notitlepage,reprint,nofootinbib]{revtex4-2} % defines the basic parameters of the document
|
||||
% If you want a single-column, remove "reprint"
|
||||
\usepackage{silence}
|
||||
\WarningFilter{revtex4-2}{Repair the float}
|
||||
|
||||
% Allows special characters (including æøå)
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[english]{babel}
|
||||
|
||||
% Note that you may need to download some of these packages manually, it depends on your setup.
|
||||
% It may be usefult to download TeXMaker, because it includes a large library of the most common packages.
|
||||
|
||||
\usepackage{amssymb} % mathematical symbols (physics imports amsmath)
|
||||
\usepackage{amsmath}
|
||||
\usepackage{graphicx} % include graphics such as plots
|
||||
\usepackage{xcolor} % set colors
|
||||
\usepackage{hyperref} % automagic cross-referencing
|
||||
\usepackage{listings} % display code
|
||||
\usepackage{subfigure} % imports a lot of cool and useful figure commands
|
||||
\usepackage{float}
|
||||
%\usepackage[section]{placeins}
|
||||
\usepackage{algorithm}
|
||||
\usepackage{booktabs}
|
||||
\usepackage[noend]{algpseudocode}
|
||||
\usepackage{subfigure}
|
||||
\usepackage{tikz}
|
||||
\usepackage[noabbrev,nameinlink]{cleveref}
|
||||
\usepackage[separate-uncertainty=true]{siunitx}
|
||||
% Define amu = Dalton
|
||||
\DeclareSIUnit\amu{u}
|
||||
|
||||
\usepackage{todonotes}
|
||||
\usetikzlibrary{quantikz}
|
||||
% defines the color of hyperref objects
|
||||
% Blending two colors: blue!80!black = 80% blue and 20% black
|
||||
\hypersetup{ % this is just my personal choice, feel free to change things
|
||||
colorlinks,
|
||||
linkcolor={red!50!black},
|
||||
citecolor={blue!50!black},
|
||||
urlcolor={blue!80!black}}
|
||||
|
||||
|
||||
% ===========================================
|
||||
|
||||
\newcommand{\expect}[1]{\left\langle#1\right\rangle}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\title{Solving the Two-dimensional Schrödinger Equation Numerically} % self-explanatory
|
||||
\author{Lars Bogner} % self-explanatory
|
||||
\date{\today} % self-explanatory
|
||||
\affiliation{{\url{https://github.uio.no/larsbog/FYS4150}}} % ignore this, but keep it.
|
||||
|
||||
%This is how we create an abstract section.
|
||||
\begin{abstract}
|
||||
We present a numerical framework for solving the two-dimensional, time-dependent Schrödinger equation. Our method uses the unconditionally stable, second-order accurate in both space and time Crank-Nicolson finite difference scheme. We implement the discretized system efficiently in C++ using the Armadillo linear algebra library to handle sparse matrix operations. Our framework is highly configurable and allows us to simulate various potentials, with a specific focus on modeling single- and multiple-slit experiments. Our results demonstrate excellent numerical accuracy, maintaining probability conservation to within an order of \num{e-15}. The simulations successfully reproduce key quantum mechanical phenomena, including wave packet diffraction and the characteristic interference patterns from double and triple slits. Additionally, we demonstrate the approach's flexibility by computing the probability distribution of particle detection on a virtual screen. This work establishes a robust, extensible foundation for computational exploration of two-dimensional time-dependent quantum dynamics.
|
||||
\end{abstract}
|
||||
|
||||
\maketitle
|
||||
\tableofcontents
|
||||
|
||||
|
||||
% ===========================================
|
||||
\section{Introduction}
|
||||
\input{chapters/introduction.tex}
|
||||
|
||||
|
||||
% ===========================================
|
||||
\section{Methods}\label{sec:methods}
|
||||
\input{chapters/methods.tex}
|
||||
|
||||
% ===========================================
|
||||
\section{Results and discussion}\label{sec:results_and_discussion}
|
||||
%
|
||||
\input{chapters/results.tex}
|
||||
|
||||
|
||||
% ===========================================
|
||||
\section{Conclusion}\label{sec:conclusion}
|
||||
\input{chapters/conclusion.tex}
|
||||
|
||||
|
||||
% ===========================================
|
||||
\appendix
|
||||
\section{Evolution of wave function in presence of a double slit} \label{app:evolv}
|
||||
The simulation uses a grid spacing \(h = \num{0.005}\) and a time step of \(\Delta t = \num{2.5e-5}\,\mathrm{s}\). The double slit has a thickness of \(\num{0.02}\), an aperture of \(\num{0.05}\), a separation of \(\num{0.05}\), and a barrier potential \(V_\mathrm{wall} = \num{1.0e10}\). The wave packet is initially broader in the \(y\)-direction to enhance the visibility of interference effects. Each figure displays three subplots: the probability density in 2D, and the real and imaginary parts of the wave function \(u\), illustrating the full complex evolution of the quantum state.
|
||||
The time evolution of the wave function in the double-slit setup is illustrated in \cref{fig:t_0_evolution,fig:t_1_evolution,fig:t_2_evolution}. Initially, as shown in \cref{fig:t_0_evolution}, the wave packet is localized before the barrier, with its probability distribution concentrated around the initial position and negligible interference structure. By the intermediate time in \cref{fig:t_1_evolution}, the wave packet has reached the slits, and partial transmission has produced oscillations in the real and imaginary components, while faint interference fringes start forming in the probability distribution. Finally, after the wave has passed the slits, \cref{fig:t_2_evolution} shows well-developed interference fringes in $|u|^2$, and alternating positive and negative regions in the real and imaginary parts, reflecting the coherent superposition of the two transmitted components.
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\includegraphics[width=\columnwidth]{include/wavefunction_t_0.000s.pdf}
|
||||
\caption{Initial state of the double-slit simulation at $t = \num{0.000}\,\mathrm{s}$. The three panels show (from top to bottom) the probability distribution $|u|^2$, the real part of the wave function $\mathrm{Re}(u)$, and the imaginary part $\mathrm{Im}(u)$. The wave packet is initially centered at $\left(x_c, y_c\right) = \left(\num{0.25}, \num{0.5}\right)$ with widths $\sigma_x = \num{0.05}$ and $\sigma_y = \num{0.20}$, moving primarily in the $x$-direction with momentum $p_x = \num{200.0}$.}
|
||||
\label{fig:t_0_evolution}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\includegraphics[width=\columnwidth]{include/wavefunction_t_0.001s.pdf}
|
||||
\caption{Evolution of the wave function at $t = \num{0.001}\,\mathrm{s}$. The wave packet approaches the double slit located at $x = \num{0.5}$. The interference pattern begins to emerge in the probability distribution $|u|^2$, while the real and imaginary parts of the wave function start to show oscillatory structure due to partial transmission through the slits.}
|
||||
\label{fig:t_1_evolution}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\includegraphics[width=\columnwidth]{include/wavefunction_t_0.002s.pdf}
|
||||
\caption{Wave function after passing the slits at $t = \num{0.002}\,\mathrm{s}$. Clear interference fringes are visible in the probability distribution $|u|^2$, indicating coherent superposition of the transmitted wave packets. The real and imaginary components exhibit alternating regions of positive and negative amplitude, reflecting the phase differences between the paths through the two slits.}
|
||||
\label{fig:t_2_evolution}
|
||||
\end{figure}
|
||||
|
||||
\onecolumngrid
|
||||
% \bibliographystyle{apalike}
|
||||
\bibliographystyle{unsrt}
|
||||
\bibliography{include/project5}
|
||||
|
||||
|
||||
\end{document}
|
||||
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(SchrodingerSim LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
|
||||
|
||||
include_directories(${CMAKE_SOURCE_DIR}/include)
|
||||
|
||||
# Gather sources
|
||||
file(GLOB SRC_FILES ${CMAKE_SOURCE_DIR}/src/*.cpp)
|
||||
|
||||
add_executable(main ${SRC_FILES} main.cpp)
|
||||
add_executable(debug ${SRC_FILES} debug.cpp)
|
||||
|
||||
# Find Armadillo and link it
|
||||
find_package(Armadillo REQUIRED)
|
||||
target_link_libraries(main PRIVATE armadillo)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
[system]
|
||||
h = 0.05
|
||||
delta_t = 2.5e-5
|
||||
T = 0.002
|
||||
|
||||
[initial_state]
|
||||
x_c = 0.25
|
||||
y_c = 0.5
|
||||
sigma_x = 0.05
|
||||
# broader in y for this run
|
||||
sigma_y = 0.10
|
||||
p_x = 200.0
|
||||
p_y = 0.0
|
||||
|
||||
[slit]
|
||||
wall_thickness = 0.02
|
||||
wall_position = 0.5
|
||||
slit_aperture = 0.05
|
||||
slit_separation = 0.05
|
||||
num_slits = 2
|
||||
V_wall = 1.0e10
|
||||
|
||||
[output]
|
||||
filename = "wavefunction_evolution.bin"
|
||||
@@ -0,0 +1,24 @@
|
||||
[system]
|
||||
h = 0.005
|
||||
delta_t = 2.5e-5
|
||||
T = 0.008
|
||||
|
||||
[initial_state]
|
||||
x_c = 0.25
|
||||
y_c = 0.5
|
||||
sigma_x = 0.05
|
||||
# broader in y for this run
|
||||
sigma_y = 0.10
|
||||
p_x = 200.0
|
||||
p_y = 0.0
|
||||
|
||||
[slit]
|
||||
wall_thickness = 0.02
|
||||
wall_position = 0.5
|
||||
slit_aperture = 0.05
|
||||
slit_separation = 0.05
|
||||
num_slits = 2
|
||||
V_wall = 1.0e10
|
||||
|
||||
[output]
|
||||
filename = "problem7_double_slit.bin"
|
||||
@@ -0,0 +1,24 @@
|
||||
[system]
|
||||
h = 0.005
|
||||
delta_t = 2.5e-5
|
||||
T = 0.008
|
||||
|
||||
[initial_state]
|
||||
x_c = 0.25
|
||||
y_c = 0.5
|
||||
sigma_x = 0.05
|
||||
sigma_y = 0.05
|
||||
p_x = 200.0
|
||||
p_y = 0.0
|
||||
|
||||
[slit]
|
||||
# No barrier for this run
|
||||
wall_thickness = 0.02
|
||||
wall_position = 0.5
|
||||
slit_aperture = 0.05
|
||||
slit_separation = 0.05
|
||||
num_slits = 0
|
||||
V_wall = 0.0
|
||||
|
||||
[output]
|
||||
filename = "problem7_no_wall.bin"
|
||||
@@ -0,0 +1,23 @@
|
||||
[system]
|
||||
h = 0.005
|
||||
delta_t = 2.5e-5
|
||||
T = 0.002
|
||||
|
||||
[initial_state]
|
||||
x_c = 0.25
|
||||
y_c = 0.5
|
||||
sigma_x = 0.05
|
||||
sigma_y = 0.20
|
||||
p_x = 200.0
|
||||
p_y = 0.0
|
||||
|
||||
[slit]
|
||||
wall_thickness = 0.02
|
||||
wall_position = 0.5
|
||||
slit_aperture = 0.05
|
||||
slit_separation = 0.05
|
||||
num_slits = 2
|
||||
V_wall = 1.0e10
|
||||
|
||||
[output]
|
||||
filename = "problem8_double_slit.bin"
|
||||
@@ -0,0 +1,23 @@
|
||||
[system]
|
||||
h = 0.005
|
||||
delta_t = 2.5e-5
|
||||
T = 0.002
|
||||
|
||||
[initial_state]
|
||||
x_c = 0.25
|
||||
y_c = 0.5
|
||||
sigma_x = 0.05
|
||||
sigma_y = 0.20
|
||||
p_x = 200.0
|
||||
p_y = 0.0
|
||||
|
||||
[slit]
|
||||
wall_thickness = 0.02
|
||||
wall_position = 0.5
|
||||
slit_aperture = 0.05
|
||||
slit_separation = 0.05
|
||||
num_slits = 2
|
||||
V_wall = 1.0e10
|
||||
|
||||
[output]
|
||||
filename = "problem9_double_slit.bin"
|
||||
@@ -0,0 +1,24 @@
|
||||
[system]
|
||||
h = 0.005
|
||||
delta_t = 2.5e-5
|
||||
T = 0.002
|
||||
|
||||
[initial_state]
|
||||
x_c = 0.25
|
||||
y_c = 0.5
|
||||
sigma_x = 0.05
|
||||
sigma_y = 0.20
|
||||
p_x = 200.0
|
||||
p_y = 0.0
|
||||
|
||||
[slit]
|
||||
wall_thickness = 0.02
|
||||
wall_position = 0.5
|
||||
slit_aperture = 0.05
|
||||
# For single-slit, slit_separation is irrelevant but kept for parser stability
|
||||
slit_separation = 0.05
|
||||
num_slits = 1
|
||||
V_wall = 1.0e10
|
||||
|
||||
[output]
|
||||
filename = "problem9_single_slit.bin"
|
||||
@@ -0,0 +1,24 @@
|
||||
[system]
|
||||
h = 0.005
|
||||
delta_t = 2.5e-5
|
||||
T = 0.002
|
||||
|
||||
[initial_state]
|
||||
x_c = 0.25
|
||||
y_c = 0.5
|
||||
sigma_x = 0.05
|
||||
sigma_y = 0.20
|
||||
p_x = 200.0
|
||||
p_y = 0.0
|
||||
|
||||
[slit]
|
||||
wall_thickness = 0.02
|
||||
wall_position = 0.5
|
||||
slit_aperture = 0.05
|
||||
# centre-to-centre spacing between adjacent slits (approx); adjust parser if you want inner-edge spacing
|
||||
slit_separation = 0.05
|
||||
num_slits = 3
|
||||
V_wall = 1.0e10
|
||||
|
||||
[output]
|
||||
filename = "problem9_triple_slit.bin"
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "schroding.hpp"
|
||||
|
||||
int main(){
|
||||
DoubleSlitBox box(0.005, 1e-5, 0.001, 0.25, 0.5, 0.05, 0.05, 0.0, 0.0);
|
||||
box.add_slits(0.02, 0.5, 0.05, 0.1, 2, 1e6);
|
||||
box.save_potential("potential_debug.txt");
|
||||
}
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
cmake --build ../../build --target main
|
||||
EXECUTABLE=../../build/main
|
||||
CONFIG_DIR=./config
|
||||
|
||||
for CONFIG_FILE in $CONFIG_DIR/*.toml; do
|
||||
echo "Running simulation with config: $CONFIG_FILE"
|
||||
$EXECUTABLE $CONFIG_FILE
|
||||
done
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef SCHRODING_HPP
|
||||
#define SCHRODING_HPP
|
||||
#define ARMA_USE_SUPERLU
|
||||
|
||||
#if defined(SCHRODING_DEBUG)
|
||||
#define SCHRODING_DEBUG_FREQ 1
|
||||
#else
|
||||
#define SCHRODING_DEBUG_FREQ 100
|
||||
#endif
|
||||
|
||||
#include<armadillo>
|
||||
#include<complex>
|
||||
#include<iostream>
|
||||
#include<vector>
|
||||
#include<string>
|
||||
|
||||
class DoubleSlitBox {
|
||||
public:
|
||||
DoubleSlitBox(double h, double delta_t, double T, double x_c, double y_c, double sigma_x, double sigma_y, double p_x, double p_y);
|
||||
void add_slits(double wall_thickness = 0.02, double wall_position = 0.5, double slit_aperture = 0.05, double slit_separation = 0.1, int num_slits = 2, double V_wall = 1e6);
|
||||
void print_matrices() const;
|
||||
void run_simulation();
|
||||
arma::cx_mat get_wavefunction_at_time(double time) const {
|
||||
int time_index = static_cast<int>(time / delta_t_);
|
||||
return psi_time_evolution_.slice(time_index);
|
||||
}
|
||||
void save(const std::string& filename) const {
|
||||
psi_time_evolution_.save(filename, arma::arma_ascii);
|
||||
}
|
||||
void save_potential(const std::string& filename) const {
|
||||
V_.save(filename, arma::arma_ascii);
|
||||
}
|
||||
private:
|
||||
double h_; // Spatial step
|
||||
int M_; // Number of spatial points per dimension
|
||||
double delta_t_; // Time step
|
||||
double T_; // Total time
|
||||
double x_c_; // Initial x center
|
||||
double y_c_; // Initial y center
|
||||
double sigma_x_; // Initial x width
|
||||
double sigma_y_; // Initial y width
|
||||
double p_x_; // Initial x momentum
|
||||
double p_y_; // Initial y momentum
|
||||
|
||||
std::complex<double> r_; // Coefficient for matrices
|
||||
|
||||
arma::cx_mat psi_; // Wavefunction
|
||||
arma::mat V_; // Potential
|
||||
arma::sp_cx_mat A_; // Matrix A
|
||||
arma::sp_cx_mat B_; // Matrix B
|
||||
|
||||
arma::cx_cube psi_time_evolution_; // To store wavefunction at each time step
|
||||
|
||||
void initialize_wavefunction();
|
||||
void initialize_potential();
|
||||
|
||||
int get_vector_index(const int i, const int j) const {
|
||||
// Maps 2D grid indices to 1D vector index, excluding boundary points
|
||||
return i * (M_ - 2) + j;
|
||||
}
|
||||
|
||||
arma::sp_cx_mat construct_A_matrix(const std::complex<double> r, const arma::cx_vec& a) const;
|
||||
arma::sp_cx_mat construct_B_matrix(const std::complex<double> r, const arma::cx_vec& b) const;
|
||||
void add_diagonal(arma::sp_cx_mat& mat, const arma::cx_vec& diag_elements, const int i, const int j, const int N) const;
|
||||
void add_diagonal(arma::sp_cx_mat& mat, const std::complex<double> diag_element, const int i, const int j, const int N) const;
|
||||
void add_offdiagonal(arma::sp_cx_mat& mat, const std::complex<double> offdiag_element, const int i, const int j, const int N, const int offset) const;
|
||||
void add_center_matrix(arma::sp_cx_mat& mat, const std::complex<double> offdiag_element, const int i, const int j, const int N) const {
|
||||
add_offdiagonal(mat, offdiag_element, i, j, N, 1);
|
||||
add_offdiagonal(mat, offdiag_element, i, j, N, -1);
|
||||
}
|
||||
void construct_matrices(arma::sp_cx_mat& mat, const std::complex<double> offdiag_element, const arma::cx_vec& diag_elements) const;
|
||||
|
||||
void time_step();
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
void print_sp_matrix_structure(const arma::sp_cx_mat& A);
|
||||
|
||||
|
||||
#endif // SCHRODING_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
#include "schroding.hpp"
|
||||
#include "toml++/toml.hpp"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::string config_file;
|
||||
if (argc == 2) {
|
||||
config_file = argv[1];
|
||||
} else if (argc == 1) {
|
||||
// Use default config file
|
||||
config_file = "config.toml";
|
||||
} else {
|
||||
std::cerr << "Usage: " << argv[0] << " [config_file.toml]" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
auto config = toml::parse_file(config_file);
|
||||
|
||||
double h = config["system"]["h"].value_or(0.005);
|
||||
double delta_t = config["system"]["delta_t"].value_or(1e-5);
|
||||
double T = config["system"]["T"].value_or(0.001);
|
||||
|
||||
double x_c = config["initial_state"]["x_c"].value_or(0.25); // Initial x center
|
||||
double y_c = config["initial_state"]["y_c"].value_or(0.5); // Initial y center
|
||||
double sigma_x = config["initial_state"]["sigma_x"].value_or(0.05); // Initial x width
|
||||
double sigma_y = config["initial_state"]["sigma_y"].value_or(0.05); // Initial y width
|
||||
double p_x = config["initial_state"]["p_x"].value_or(0.0); // Initial x momentum
|
||||
double p_y = config["initial_state"]["p_y"].value_or(0.0); // Initial y momentum
|
||||
|
||||
// Create a DoubleSlitBox instance
|
||||
DoubleSlitBox box(h, delta_t, T, x_c, y_c, sigma_x, sigma_y, p_x, p_y);
|
||||
|
||||
// Add slits to the potential
|
||||
double wall_thickness = config["slit"]["wall_thickness"].value_or(0.02);
|
||||
double wall_position = config["slit"]["wall_position"].value_or(0.5);
|
||||
double slit_aperture = config["slit"]["slit_aperture"].value_or(0.05);
|
||||
double slit_separation = config["slit"]["slit_separation"].value_or(0.1);
|
||||
int num_slits = config["slit"]["num_slits"].value_or(2);
|
||||
double V_wall = config["slit"]["V_wall"].value_or(1e6);
|
||||
box.add_slits(wall_thickness, wall_position, slit_aperture, slit_separation, num_slits, V_wall);
|
||||
|
||||
box.run_simulation();
|
||||
std::string output_filename = config["output"]["filename"].value_or("wavefunction_evolution.bin");
|
||||
box.save(output_filename);
|
||||
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,53 @@
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
import re
|
||||
import argparse
|
||||
import tomllib
|
||||
|
||||
def load_cx_cube_arma_ascii(path, slices_first=True):
|
||||
txt = Path(path).read_text()
|
||||
|
||||
# Drop header lines (first 2 lines)
|
||||
lines = txt.splitlines()
|
||||
header = lines[:2]
|
||||
rest = lines[2:]
|
||||
|
||||
# Extract dims
|
||||
n_rows, n_cols, n_slices = map(int, header[1].split())
|
||||
|
||||
# Convert each "(r,i)" → "r+i j"
|
||||
fixed = []
|
||||
for line in rest:
|
||||
# Remove parentheses
|
||||
line = line.replace("(", "").replace(")", "")
|
||||
# Convert a,b → a+bj
|
||||
# But be careful to only transform the pair; use regex
|
||||
line = re.sub(r'([+-]?\d[\d.eE+-]*)\s*,\s*([+-]?\d[\d.eE+-]*)',
|
||||
r'\1+\2j', line)
|
||||
fixed.append(line)
|
||||
|
||||
# Join and load
|
||||
arr2d = np.loadtxt(fixed, dtype=np.complex128)
|
||||
|
||||
# Now reshape to cube
|
||||
arr3 = arr2d.reshape(n_slices, n_cols, n_rows)
|
||||
|
||||
if slices_first:
|
||||
return arr3
|
||||
else:
|
||||
return arr3.transpose(2, 1, 0)
|
||||
|
||||
def load_config_file(path):
|
||||
with open(path, "rb") as f:
|
||||
config = tomllib.load(f)
|
||||
return config, load_cx_cube_arma_ascii("../" + config["output"]["filename"])
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Load and print ARMA ASCII complex cube.")
|
||||
parser.add_argument("path", type=str, help="Path to the ARMA ASCII file.")
|
||||
parser.add_argument("--slices_first", action="store_true", help="If set, output will have slices as the first dimension.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
cube = load_cx_cube_arma_ascii(args.path, slices_first=args.slices_first)
|
||||
print(cube)
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,34 @@
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
def get_rc_params():
|
||||
colors = ["FF220C", "70D6FF", "8AAA79", "666370", "1C1F33"]
|
||||
rcParams = plt.rcParams
|
||||
# Use LaTeX for rendering
|
||||
# Setup fonts
|
||||
rcParams["text.usetex"] = True
|
||||
rcParams["font.family"] = "serif"
|
||||
rcParams["font.size"] = 10
|
||||
rcParams["axes.labelsize"] = 10
|
||||
rcParams["axes.titlesize"] = 10
|
||||
rcParams["legend.fontsize"] = 8
|
||||
rcParams["xtick.labelsize"] = 8
|
||||
rcParams["ytick.labelsize"] = 8
|
||||
# Figure size and resolution
|
||||
rcParams["figure.figsize"] = (4.5, 3)
|
||||
rcParams["figure.dpi"] = 300
|
||||
# Use colors from the palette
|
||||
rcParams["axes.prop_cycle"] = plt.cycler(color=[f"#{color}" for color in colors])
|
||||
# Grid
|
||||
rcParams["axes.grid"] = True
|
||||
rcParams["grid.alpha"] = 0.5
|
||||
rcParams["grid.linestyle"] = "--"
|
||||
# Point ticks to the inside of the axes
|
||||
rcParams["xtick.direction"] = "in"
|
||||
rcParams["ytick.direction"] = "in"
|
||||
rcParams["xtick.top"] = True
|
||||
rcParams["ytick.right"] = True
|
||||
return rcParams
|
||||
|
||||
|
||||
plt.rcParams.update(get_rc_params())
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,204 @@
|
||||
#include "schroding.hpp"
|
||||
|
||||
DoubleSlitBox::DoubleSlitBox(double h, double delta_t, double T, double x_c, double y_c, double sigma_x, double sigma_y, double p_x, double p_y)
|
||||
: h_(h), delta_t_(delta_t), T_(T), x_c_(x_c), y_c_(y_c), sigma_x_(sigma_x), sigma_y_(sigma_y), p_x_(p_x), p_y_(p_y) {
|
||||
M_ = static_cast<int>(1.0 / h_) + 1; // Assuming box size is 1.0
|
||||
psi_.set_size(M_, M_);
|
||||
psi_time_evolution_.set_size(M_, M_, static_cast<int>(T_ / delta_t_));
|
||||
V_.set_size(M_, M_);
|
||||
V_.zeros();
|
||||
r_ = std::complex<double>(0, delta_t_) / (2.0 * h_ * h_);
|
||||
initialize_wavefunction();
|
||||
initialize_potential();
|
||||
}
|
||||
|
||||
void DoubleSlitBox::add_slits(double wall_thickness, double wall_position, double slit_aperture, double slit_separation, int num_slits, double V_wall) {
|
||||
|
||||
int wall_x_start = static_cast<int>((wall_position - wall_thickness * 0.5) / h_);
|
||||
int wall_x_end = static_cast<int>((wall_position + wall_thickness * 0.5) / h_);
|
||||
|
||||
int center_y = M_ / 2;
|
||||
|
||||
int slit_half_height = static_cast<int>(slit_aperture * 0.5 / h_);
|
||||
double slit_pitch = slit_aperture + slit_separation;
|
||||
double slit_pitch_idx = slit_pitch / h_;
|
||||
|
||||
for (int i = wall_x_start; i <= wall_x_end; ++i) {
|
||||
for (int j = 0; j < M_; ++j) {
|
||||
|
||||
bool in_slit = false;
|
||||
|
||||
for (int s = 0; s < num_slits; ++s) {
|
||||
double center_offset_idx =
|
||||
(s - (num_slits - 1) / 2.0) * slit_pitch_idx;
|
||||
|
||||
int slit_center_idx = static_cast<int>(std::round(center_y + center_offset_idx));
|
||||
|
||||
if (j >= slit_center_idx - slit_half_height &&
|
||||
j <= slit_center_idx + slit_half_height) {
|
||||
in_slit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_slit) V_(i, j) = V_wall;
|
||||
}
|
||||
}
|
||||
|
||||
initialize_potential();
|
||||
}
|
||||
|
||||
|
||||
void DoubleSlitBox::run_simulation() {
|
||||
int num_time_steps = static_cast<int>(T_ / delta_t_);
|
||||
for (int t = 0; t < num_time_steps; ++t) {
|
||||
if (t % SCHRODING_DEBUG_FREQ == 0) {
|
||||
std::cout << "Time step " << t << " / " << num_time_steps << std::endl;
|
||||
}
|
||||
time_step();
|
||||
psi_time_evolution_.slice(t) = psi_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DoubleSlitBox::initialize_wavefunction() {
|
||||
for (int i = 0; i < M_; ++i) {
|
||||
for (int j = 0; j < M_; ++j) {
|
||||
if (i == 0 || i == M_ - 1 || j == 0 || j == M_ - 1) {
|
||||
psi_(i, j) = 0.0; // Boundary conditions
|
||||
continue;
|
||||
}
|
||||
double x = i * h_;
|
||||
double y = j * h_;
|
||||
double real_exponent = -((x - x_c_) * (x - x_c_)) / (2 * sigma_x_ * sigma_x_)
|
||||
- ((y - y_c_) * (y - y_c_)) / (2 * sigma_y_ * sigma_y_);
|
||||
double imag_exponent = p_x_ * x + p_y_ * y;
|
||||
psi_(i, j) = std::exp(std::complex<double>(real_exponent, imag_exponent));
|
||||
}
|
||||
}
|
||||
double norm = std::sqrt(arma::accu(arma::abs(psi_) % arma::abs(psi_)));
|
||||
psi_ /= norm; // Normalize the wavefunction
|
||||
}
|
||||
|
||||
void DoubleSlitBox::initialize_potential() {
|
||||
arma::vec V_flat = arma::vectorise(V_.submat(1, 1, M_ - 2, M_ - 2));
|
||||
arma::cx_vec V_flat_cx = arma::conv_to<arma::cx_vec>::from(V_flat);
|
||||
arma::cx_vec a = 1.0 + 4.0 * r_ + V_flat_cx * std::complex<double>(0, delta_t_ / 2.0);
|
||||
arma::cx_vec b = 1.0 - 4.0 * r_ - V_flat_cx * std::complex<double>(0, delta_t_ / 2.0);
|
||||
|
||||
A_ = construct_A_matrix(r_, a);
|
||||
B_ = construct_B_matrix(r_, b);
|
||||
}
|
||||
|
||||
void DoubleSlitBox::print_matrices() const {
|
||||
print_sp_matrix_structure(A_);
|
||||
print_sp_matrix_structure(B_);
|
||||
}
|
||||
|
||||
void DoubleSlitBox::add_diagonal(arma::sp_cx_mat& mat, const arma::cx_vec& diag_elements, const int i, const int j, const int N) const {
|
||||
for (int idx = 0; idx < N; ++idx) {
|
||||
mat(i + idx, j + idx) += diag_elements(idx);
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleSlitBox::add_diagonal(arma::sp_cx_mat& mat, const std::complex<double> diag_element, const int i, const int j, const int N) const {
|
||||
for (int idx = 0; idx < N; ++idx) {
|
||||
mat(i + idx, j + idx) += diag_element;
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleSlitBox::add_offdiagonal(arma::sp_cx_mat& mat, const std::complex<double> offdiag_element, const int i, const int j, const int N, const int offset) const {
|
||||
for (int idx = 0; idx < N; ++idx) {
|
||||
if ((idx + offset) < 0 || (idx + offset) >= N) continue; // Skip out-of-bounds
|
||||
mat(i + idx, j + idx + offset) += offdiag_element;
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleSlitBox::construct_matrices(arma::sp_cx_mat& mat, const std::complex<double> offdiag_element, const arma::cx_vec& diag_elements) const {
|
||||
const int n = M_ - 2; // Size of submatrices
|
||||
const int N = n * n; // Total number of interior points
|
||||
|
||||
add_diagonal(mat, diag_elements, 0, 0, N);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
add_center_matrix(mat, offdiag_element, i * n, i * n, n);
|
||||
if (i < n - 1) {
|
||||
add_diagonal(mat, offdiag_element, i * n, (i + 1) * n, n);
|
||||
add_diagonal(mat, offdiag_element, (i + 1) * n, i * n, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arma::sp_cx_mat DoubleSlitBox::construct_A_matrix(const std::complex<double> r, const arma::cx_vec& a) const {
|
||||
const int n = M_ - 2; // Size of submatrices
|
||||
const int N = n * n; // Total number of interior points
|
||||
arma::sp_cx_mat A(N, N);
|
||||
|
||||
construct_matrices(A, -r, a);
|
||||
return A;
|
||||
}
|
||||
|
||||
arma::sp_cx_mat DoubleSlitBox::construct_B_matrix(const std::complex<double> r, const arma::cx_vec& b) const {
|
||||
const int n = M_ - 2; // Size of submatrices
|
||||
const int N = n * n; // Total number of interior points
|
||||
arma::sp_cx_mat B(N, N);
|
||||
|
||||
construct_matrices(B, r, b);
|
||||
return B;
|
||||
}
|
||||
|
||||
void DoubleSlitBox::time_step() {
|
||||
arma::cx_vec psi_vec = arma::vectorise(psi_.submat(1, 1, M_ - 2, M_ - 2));
|
||||
arma::cx_vec b = B_ * psi_vec;
|
||||
|
||||
arma::cx_vec psi_new_vec = arma::spsolve(A_, b);
|
||||
|
||||
psi_.submat(1, 1, M_ - 2, M_ - 2) = arma::reshape(psi_new_vec, M_ - 2, M_ - 2);
|
||||
}
|
||||
|
||||
void print_sp_matrix_structure(const arma::sp_cx_mat& A)
|
||||
{
|
||||
using namespace std;
|
||||
using namespace arma;
|
||||
|
||||
// Declare a C-style 2D array of strings.
|
||||
string S[A.n_rows][A.n_cols];
|
||||
|
||||
// Initialise all the strings to " ".
|
||||
for (int i =0; i < A.n_rows; i++)
|
||||
{
|
||||
for (int j = 0; j < A.n_cols; j++)
|
||||
{
|
||||
S[i][j] = " ";
|
||||
}
|
||||
}
|
||||
|
||||
// Next, we want to set the string to a dot at each non-zero element.
|
||||
// To do this we use the special loop iterator from the sp_cx_mat class
|
||||
// to help us loop over only the non-zero matrix elements.
|
||||
sp_cx_mat::const_iterator it = A.begin();
|
||||
sp_cx_mat::const_iterator it_end = A.end();
|
||||
|
||||
int nnz = 0;
|
||||
for(it; it != it_end; ++it)
|
||||
{
|
||||
S[it.row()][it.col()] = "•";
|
||||
nnz++;
|
||||
}
|
||||
|
||||
// Finally, print the matrix to screen.
|
||||
cout << endl;
|
||||
for (int i =0; i < A.n_rows; i++)
|
||||
{
|
||||
cout << "| ";
|
||||
for (int j = 0; j < A.n_cols; j++)
|
||||
{
|
||||
cout << S[i][j] << " ";
|
||||
}
|
||||
cout << "|\n";
|
||||
}
|
||||
|
||||
cout << endl;
|
||||
cout << "matrix size: " << A.n_rows << "x" << A.n_cols << endl;
|
||||
cout << "non-zero elements: " << nnz << endl ;
|
||||
cout << endl;
|
||||
}
|
||||
Reference in New Issue
Block a user