Initial commit for project 3..... A lot happend
This commit is contained in:
+10
@@ -281,3 +281,13 @@ src/project2/off_diagonal_finder
|
||||
src/project2/scaling_tester
|
||||
|
||||
src/project2/*.txt
|
||||
|
||||
# Project 3 executables
|
||||
src/project3/two_particles
|
||||
src/project3/many_particles
|
||||
src/project3/results/*.dat
|
||||
src/project3/results/*/*.dat
|
||||
src/project3/results/*/*/*.red
|
||||
*.tar.gz
|
||||
*_timing.txt
|
||||
run_stats.json
|
||||
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
Penning traps are useful appliances to capture charged particles without contact to any matter surrounding the particles \cite[11]{vogel_particle_2024}. This property of Penning traps is especially useful in the study of antimatter, as any contact to matter would lead to the direct annihilation of the antimatter. Therefore, Penning traps have become an essential tool in the antimatter research. Experiments like ALPHA-g \cite{bertsche_prospects_2018} and AEgIS \cite{scampoli_aegis_2014} at CERN have successfully deployed Penning traps for such applications.
|
||||
|
||||
To plan future antimatter experiments it is essential to be able to simulate Penning traps. Without the ability of the behavior of particles within such a trap, a precise planning of the Penning trap and antimatter experiment is not possible. Key criterion for a successful simulation in this context is accuracy of the numerical simulation in terms of precise trajectories and key statistical properties of the captured particles, e.g. the kinetic energy. A second metric that is very important to the usefulness of such numerical simulations is the computational performance of the simulation as this decides whether the simulation is even possible or not with finite computing resources. To find the optimal balance between the finite computing power available and an accurate result, different integration algorithms will be compared to solve the ordinary differential equations at play in the Penning Trap.
|
||||
|
||||
In \cref{sec:methods} the methods employed in the simulation will be expanded on. In detail the implementation of the source code will be discussed as well as the specific Penning trap used for the study, introduced. Thirdly the algorithms behind the numerical integration methods will be explained. \Cref{sec:results_and_discussion} will show the results obtained from our implementation and discuss their practical implications. This section will be split into four parts expanding on the accuracy of the trajectories, the energy conservation in the system, the computational performance of the different algorithms as well as a discussion on the limit of many particles in the trap. Lastly in \cref{sec:conclusion} our findings will be summarized and consequences of the results derived.
|
||||
@@ -0,0 +1,101 @@
|
||||
\subsection*{Penning trap}
|
||||
Penning traps use a combination of a magnetic and electric field to confine the trajectory of a charged particle to a finite volume over long periods of time\cite[p.~9-10]{vogel_particle_2024}. Finite volume in this context refers to volumes in the order of \unit{\mm \cubed} up to \unit{\cm \cubed}. Long time ranges imply a period that exceeds the period of motion of the particle by multiple orders. For the studied case we will use an ideal Penning trap, i.e. there are no inhomogenities in the fields. The magnetic field will be defined as
|
||||
\begin{equation} \label{eq:B_field}
|
||||
B(\vec r, t) = B_0 \Theta\left(d - |\vec r |\right) \cdot \vec e_z,
|
||||
\end{equation}
|
||||
with the Heaviside function $\Theta(x)$, and we define two electric fields via the electric potentials
|
||||
\begin{equation} \label{eq:V_stat}
|
||||
\vstat(\vec r, t) = \frac{V_0 \cdot (2z^2 - x^2 - y^2) \Theta\left(d - |\vec r |\right)}{2d^2}
|
||||
\end{equation}
|
||||
and
|
||||
\begin{equation}
|
||||
\vdyn(\vec r, t) = (1 + f \cos \omega_V t) \cdot \vstat(\vec r, t).
|
||||
\end{equation}
|
||||
|
||||
\subsection*{Equations of motion}
|
||||
Using Newton's equations of motion, $\ddot{\vec r} = \vec F / m$, we can derive ordinary differential equations for the position of a particle which is subject to \cref{eq:B_field,eq:V_stat}. As the magnetic field is parallel to the $z$-axis, the differential equation for the vertical component is straightforward to derive. Using the electric field resulting from $\vstat$ (see \cref{app:efield_equations} for details on all electric field components) we find
|
||||
\begin{equation}
|
||||
\ddot z + \omega_z^2 z \equiv \ddot z + \frac{2 q V_0}{m d^2} z = 0.
|
||||
\end{equation}
|
||||
In the transversal plane, the equations of motion are coupled due to the Lorentz force $\vec F = q \vec v \times \vec B$. Using the electric field resulting from $\vstat$ (see \cref{app:efield_equations} for details on all electric field components) we find
|
||||
\begin{align}
|
||||
\ddot x - \omega_0 \dot y - \frac{1}{2} \omega_z^2 x &\equiv \ddot x - \frac{q B_0}{m} \dot y - \frac{q V_0}{m d^2} x = 0, \label{eq:transv_eom_x}\\
|
||||
\ddot y + \omega_0 \dot x - \frac{1}{2} \omega_z^2 y &\equiv \ddot y + \frac{q B_0}{m} \dot x - \frac{q V_0}{m d^2} y = 0 \label{eq:transv_eom_y}.
|
||||
\end{align}
|
||||
Using the sum of equations: $\eqref{eq:transv_eom_x} + i \eqref{eq:transv_eom_y}$, we can use the general definition $f(t) \equiv x(t) + i y(t)$ to derive a single complex differential equation for the transversal motion
|
||||
\begin{equation} \label{eq:complex_transv_eom}
|
||||
\ddot f + i \omega_0 \dot f - \frac{1}{2} \omega_z^2 f = 0.
|
||||
\end{equation}
|
||||
After solving the differential equation, the real and imaginary parts of $f(t)$ will correspond to the $x$ and $y$ components of the trajectory, respectively.
|
||||
\Cref{eq:complex_transv_eom} is a damped harmonic oscillator equation with the general solution
|
||||
\begin{equation} \label{eq:general_transv_solution}
|
||||
f(t) = A_+ e^{-i (\omega_+ t + \phi_+)} + A_- e^{-i (\omega_- t + \phi_-)},
|
||||
\end{equation}
|
||||
with the two characteristic frequencies
|
||||
\begin{equation}
|
||||
\omega_\pm = \frac{\omega_0 \pm \sqrt{\omega_0^2 - 2 \omega_z^2}}{2}.
|
||||
\end{equation}
|
||||
This solution is valid for $\omega_0^2 > 2 \omega_z^2$. In the case of $\omega_0^2 \leq \omega_z^2$, the oscillation frequencies become complex, leading to exponential growth of the trajectory in the transversal plane. This condition is known as the stability criterion for a Penning trap \cite[p.~67]{vogel_particle_2024}. In terms of the physical parameters of the trap, the stability criterion can be expressed as
|
||||
\begin{equation}
|
||||
\frac{q}{m} B_0^2 > \frac{2 V_0}{d^2}.
|
||||
\end{equation}
|
||||
Given this condition, the solution for the transversal motion will be bound, i.e. $|f(t)| < \infty$ for all $t > 0$. Given the stability criterion, the two terms in \cref{eq:general_transv_solution} can be identified as sinusoidal motions with amplitudes $A_\pm$. If the two terms are in phase the upper limit of $|f| \equiv R_+$ will be $R_+ = A_+ + A_-$, while the lower limit for the case of antiparallel phases will be the absolute of the difference in the two amplitudes, $R_- = |A_+ - A_-|$. A analytical solution for a special case of initial conditions is derived in \cref{app:special_case_analytical_solution}.
|
||||
|
||||
\subsubsection*{Multiple particles}
|
||||
For the case of multiple particles, it is important to include the particle-particle interactions. For this analysis we will only consider the Coulomb interaction between the particles, neglecting magnetic interactions. The force on particle $i$ due to all other particles $j$ is then given by
|
||||
\begin{equation}
|
||||
\vec F_i = \frac{q_i}{4 \pi \epsilon_0} \sum_{j \neq i} \frac{q_j (\vec r_j - \vec r_i)}{|\vec r_j - \vec r_i|^3}.
|
||||
\end{equation}
|
||||
|
||||
\subsection*{Numerical Integration Methods}
|
||||
\subsubsection*{Forward Euler method}
|
||||
The simplest numerical method to solve the ordinary differential equation system is the Forward Euler method. It is a first order method, meaning that the local truncation error per step is on the order of $\mathcal{O}(h^2)$, with $h$ being the step size. The global error after $N$ steps is therefore on the order of $\mathcal{O}(h)$. The method is explicit, meaning that the state of the system at the next time step can be calculated directly from the current state. Given a general ordinary differential equation of the form
|
||||
\begin{equation}
|
||||
\dot y(t) = f(t, y(t)),
|
||||
\end{equation}
|
||||
the Forward Euler method updates the state of the system as follows:
|
||||
\begin{equation}
|
||||
y_{n+1} = y_n + h f(t_n, y_n).
|
||||
\end{equation}
|
||||
For our second order differential equations, we first rewrite them as a system of first order equations. The next state of the system is then calculated using the current position and velocity as
|
||||
\begin{align}
|
||||
\vec r_{n+1} &= \vec r_n + h \vec v_n, \\
|
||||
\vec v_{n+1} &= \vec v_n + h \frac{\vec F(\vec r_n, \vec v_n, t_n)}{m}.
|
||||
\end{align}
|
||||
This method is computationally inexpensive, as we only need a single force evaluation per time step. However, it is known to be not very accurate.
|
||||
\subsubsection*{Runge-Kutta 4 method}
|
||||
The Runge-Kutta 4 (RK4) method is a popular and more accurate method for solving ordinary differential equations. It is a fourth-order method, meaning that the local truncation error per step is on the order of $\mathcal{O}(h^5)$, and the global error after $N$ steps is on the order of $\mathcal{O}(h^4)$. The RK4 method calculates the next state of the system using a weighted average of four different estimates of the slope (the derivative) at different points within the time step. Given a general ordinary differential equation of the form
|
||||
\begin{equation}
|
||||
\dot y(t) = f(t, y(t)),
|
||||
\end{equation}
|
||||
the RK4 method updates the state of the system as follows:
|
||||
\begin{align}
|
||||
k_1 &= h f(t_n, y_n), \\
|
||||
k_2 &= h f\left(t_n + \frac{h}{2}, y_n + \frac{k_1}{2}\right), \\
|
||||
k_3 &= h f\left(t_n + \frac{h}{2}, y_n + \frac{k_2}{2}\right), \\
|
||||
k_4 &= h f(t_n + h, y_n + k_3), \\
|
||||
y_{n+1} &= y_n + \frac{1}{6}(k_1 + 2k_2 + 2k_3 + k_4).
|
||||
\end{align}
|
||||
As this method is again a generalized solver for ODE of the first order, we again rewrite our second order differential equations as a system of first order equations.
|
||||
|
||||
\subsubsection*{Velocity-Verlet method}
|
||||
In contrast to the previous two methods, the Velocity-Verlet method is a symplectic integrator which is specifically designed for second order differential equations of the form
|
||||
\begin{equation}
|
||||
\ddot{\vec r}(t) = \frac{\vec F(\vec r(t), t)}{m}.
|
||||
\end{equation}
|
||||
The method is time-reversible and conserves energy better over long time periods compared to non-symplectic methods like Forward Euler and RK4. The Velocity-Verlet method updates the position and velocity of the system as follows:
|
||||
\begin{align}
|
||||
\vec r_{n+1} &= \vec r_n + h \vec v_n + \frac{h^2}{2} \vec a_n, \\
|
||||
\vec a_{n+1} &= \frac{\vec F(\vec r_{n+1}, t_{n+1})}{m}, \\
|
||||
\vec v_{n+1} &= \vec v_n + \frac{h}{2} (\vec a_n + \vec a_{n+1}),
|
||||
\end{align}
|
||||
It is important to note that the Velocity-Verlet integrator requires forces to be independent of velocity. In the case, that the algorithm is applicable, it is a very efficient method, as it only requires a single force evaluation per time step, while still being a second order method with a local truncation error per step on the order of $\mathcal{O}(h^3)$ and a global error after $N$ steps on the order of $\mathcal{O}(h^2)$.
|
||||
|
||||
\subsection*{Code Structure}
|
||||
The basic framework for the numerical analysis is based on a \texttt{PenningTrap} class, which contains all the particles present in the trap, as well as a parametrization of the electric and magnetic fields. The particles are represented by a \texttt{Particle} class, which contains the physical properties of the particle, as well as its current position and velocity. With this information, the \texttt{PenningTrap} class can calculate the forces acting on each particle, including the external fields and the particle-particle interactions. The particle-particle interactions can be toggled on and off, allowing for a simulation of both scenarios. The external fields can be modified by supplying a field-method of the form \texttt{external\_field(const arma::vec\& r, double t, const PenningTrap\& trap)}. The reference to the \texttt{PenningTrap} allows for the parameters of the field to be stored in the trap object. The implementation of the \texttt{Particle} and \texttt{PenningTrap} classes can be found in \texttt{/src/project3/include/classes.hpp} of the project repository, as well as in the corresponding source file \texttt{/src/project3/src/classes.cpp}.
|
||||
|
||||
\subsection*{Numerical Methods Implementation}
|
||||
All numerical methods are implemented as classes inheriting from a base class \texttt{Solver}. The base class contains a reference to the \texttt{PenningTrap} object, as well as the time step size. The recording of particle properties over time, like postion and velocity is part of the general \texttt{Solver} class. Each derived class implements the \texttt{step()} method, which updates the state of the system by one time step using the respective numerical method. The implementation of the \texttt{Solver} class and its derived classes can be found in \texttt{/src/project3/include/solvers.hpp} of the project repository, as well as in the corresponding source file \texttt{/src/project3/src/solvers.cpp}. The following solvers are implemented:\texttt{Forward\-Euler\-Solver}, \texttt{RK4\-Solver}, \texttt{Velocity\-Verlet\-Solver} and \texttt{Analytical\-Solver}, which implements the analytical solution for a special case of initial conditions (see \cref{app:special_case_analytical_solution}).
|
||||
|
||||
\subsection*{Tools}
|
||||
\textcolor{red}{TODO}
|
||||
@@ -0,0 +1,9 @@
|
||||
\subsection{Numerical Accuracy of Trajectories}
|
||||
|
||||
\subsection{Energy Conservation}
|
||||
|
||||
|
||||
\subsection{Performance and Efficiency}
|
||||
|
||||
\subsection{Many-Body Simulations}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,156 @@
|
||||
% 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[noend]{algpseudocode}
|
||||
\usepackage{subfigure}
|
||||
\usepackage{tikz}
|
||||
\usepackage{cleveref}
|
||||
\usepackage{siunitx}
|
||||
\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{\vstat}{V_\mathrm{stat.}}
|
||||
\newcommand{\vdyn}{V_\mathrm{dyn.}}
|
||||
\newcommand{\estat}{E_\mathrm{stat.}}
|
||||
\newcommand{\edyn}{E_\mathrm{dyn.}}
|
||||
\newcommand{\estatc}[1]{E_{\mathrm{stat.},#1}}
|
||||
\newcommand{\edync}[1]{E_{\mathrm{dyn.},#1}}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\title{Accuracy and Stability of Numerical Integration Methods in Penning Trap Simulations} % self-explanatory
|
||||
\author{Lars Bogner} % self-explanatory
|
||||
\date{\today} % self-explanatory
|
||||
\noaffiliation % ignore this, but keep it.
|
||||
|
||||
%This is how we create an abstract section.
|
||||
\begin{abstract}
|
||||
\textcolor{red}{TODO: ABSTRACT HERE.}
|
||||
\end{abstract}
|
||||
\maketitle
|
||||
|
||||
|
||||
% ===========================================
|
||||
\section{Introduction}
|
||||
\input{chapters/introduction}
|
||||
|
||||
% ===========================================
|
||||
\section{Methods}\label{sec:methods}
|
||||
%
|
||||
\input{chapters/methods}
|
||||
|
||||
|
||||
% ===========================================
|
||||
\section{Results and discussion}\label{sec:results_and_discussion}
|
||||
%
|
||||
\input{chapters/results}
|
||||
|
||||
% ===========================================
|
||||
\section{Conclusion}\label{sec:conclusion}
|
||||
%
|
||||
\input{chapters/conclusion}
|
||||
|
||||
% ===========================================
|
||||
\appendix
|
||||
\section{Electric Field Equations} \label{app:efield_equations}
|
||||
Here we derive the electric field equations used in \cref{sec:methods}. The electric field following from $\vstat$ defined in \cref{eq:V_stat} is given by
|
||||
\begin{align}
|
||||
\estatc{x} &= -\frac{\partial \vstat}{\partial x} = \frac{V_0}{d^2} x, \\
|
||||
\estatc{y} &= -\frac{\partial \vstat}{\partial y} = \frac{V_0}{d^2} y, \\
|
||||
\estatc{z} &= -\frac{\partial \vstat}{\partial z} = -\frac{2 V_0}{d^2} z,
|
||||
\end{align}
|
||||
for all $|\vec r| < d$ and zero otherwise. The electric field following from $\vdyn$ is simply
|
||||
\begin{equation}
|
||||
\edyn = (1 + f \cos \omega_V t) \cdot \estat.
|
||||
\end{equation}
|
||||
|
||||
\section{Special Case Analytical Solution} \label{app:special_case_analytical_solution}
|
||||
In the special case of a single particle in the potential $\vstat$, with the inital conditions $\vec{r}(t_0) = (x_0, 0, z_0)$ and $\vec{v}(t_0) = (0, v_0, 0)$, we can derive an analytical solution for the trajectory. The solution for the $z$ component is straightforward
|
||||
\begin{equation}
|
||||
z(t) = z_0 \cos(\omega_z t).
|
||||
\end{equation}
|
||||
From the equation system
|
||||
\begin{align}
|
||||
f(0) = A_+ e^{-i \phi_+} + A_- e^{-i \phi_-} &\equiv x_0, \\
|
||||
\dot f(0) = -i \omega_+ A_+ e^{-i \phi_+} - i \omega_- A_- e^{-i \phi_-} &\equiv i v_0,
|
||||
\end{align}
|
||||
we derive $\phi_\pm = 0$ as $f(0) \in \mathbb{R}$ and therefore $A_+ + A_- = x_0$ and $\omega_+ A_+ + \omega_- A_- = -v_0$. Solving this system of equations for $A_\pm$ we find
|
||||
\begin{equation}
|
||||
A_\pm =\pm \frac{v_0 + \omega_\mp x_0}{\omega_- - \omega_+}.
|
||||
\end{equation}
|
||||
|
||||
% ===========================================
|
||||
\onecolumngrid
|
||||
% \bibliographystyle{apalike}
|
||||
\bibliographystyle{unsrt}
|
||||
\bibliography{ref}
|
||||
|
||||
|
||||
\end{document}
|
||||
@@ -0,0 +1,187 @@
|
||||
|
||||
@article{harris_array_2020,
|
||||
title = {Array programming with {NumPy}},
|
||||
volume = {585},
|
||||
url = {https://doi.org/10.1038/s41586-020-2649-2},
|
||||
doi = {10.1038/s41586-020-2649-2},
|
||||
pages = {357--362},
|
||||
number = {7825},
|
||||
journaltitle = {Nature},
|
||||
author = {Harris, Charles R. and Millman, K. Jarrod and Walt, Stéfan J. van der 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 Kerkwijk, Marten H. van and Brett, Matthew and Haldane, Allan and Río, Jaime Fernández del and Wiebe, Mark and Peterson, Pearu and Gérard-Marchant, Pierre and Sheppard, Kevin and Reddy, Tyler and Weckesser, Warren and Abbasi, Hameer and Gohlke, Christoph and Oliphant, Travis E.},
|
||||
date = {2020-09},
|
||||
note = {Publisher: Springer Science and Business Media {LLC}},
|
||||
}
|
||||
|
||||
@article{hunter_matplotlib_2007,
|
||||
title = {Matplotlib: A 2D graphics environment},
|
||||
volume = {9},
|
||||
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.},
|
||||
pages = {90--95},
|
||||
number = {3},
|
||||
journaltitle = {Computing in Science \& Engineering},
|
||||
author = {Hunter, J. D.},
|
||||
date = {2007},
|
||||
note = {Publisher: {IEEE} {COMPUTER} {SOC}},
|
||||
}
|
||||
|
||||
@article{pedregosa_scikit-learn_2011,
|
||||
title = {Scikit-learn: Machine Learning in Python},
|
||||
volume = {12},
|
||||
pages = {2825--2830},
|
||||
journaltitle = {Journal of Machine Learning Research},
|
||||
author = {Pedregosa, F. and Varoquaux, G. and Gramfort, A. and Michel, V. and Thirion, B. and Grisel, O. and Blondel, M. and Prettenhofer, P. and Weiss, R. and Dubourg, V. and Vanderplas, J. and Passos, A. and Cournapeau, D. and Brucher, M. and Perrot, M. and Duchesnay, E.},
|
||||
date = {2011},
|
||||
}
|
||||
|
||||
@inreference{noauthor_stochastic_2025,
|
||||
title = {Stochastic gradient descent},
|
||||
rights = {Creative Commons Attribution-{ShareAlike} License},
|
||||
url = {https://en.wikipedia.org/w/index.php?title=Stochastic_gradient_descent&oldid=1309164477},
|
||||
abstract = {Stochastic gradient descent (often abbreviated {SGD}) is an iterative method for optimizing an objective function with suitable smoothness properties (e.g. differentiable or subdifferentiable). It can be regarded as a stochastic approximation of gradient descent optimization, since it replaces the actual gradient (calculated from the entire data set) by an estimate thereof (calculated from a randomly selected subset of the data). Especially in high-dimensional optimization problems this reduces the very high computational burden, achieving faster iterations in exchange for a lower convergence rate.
|
||||
The basic idea behind stochastic approximation can be traced back to the Robbins–Monro algorithm of the 1950s. Today, stochastic gradient descent has become an important optimization method in machine learning.},
|
||||
booktitle = {Wikipedia},
|
||||
urldate = {2025-09-22},
|
||||
date = {2025-09-02},
|
||||
langid = {english},
|
||||
note = {Page Version {ID}: 1309164477},
|
||||
file = {Snapshot:/home/lars/Zotero/storage/B6CVR59B/index.html:text/html},
|
||||
}
|
||||
|
||||
@unpublished{elstner_lecture_2025,
|
||||
location = {Karlsruhe Institute for Technology, Karlsruhe},
|
||||
title = {Lecture: Machine Learning for Chemistry},
|
||||
type = {Lecture},
|
||||
howpublished = {Lecture},
|
||||
author = {Elstner, Marcus and Kubar, Tomas},
|
||||
date = {2025-05-20},
|
||||
langid = {german},
|
||||
file = {PDF:/home/lars/Zotero/storage/5LSLJMK8/Elstner and Kubar - 2025 - Lecture Machine Learning for Chemistry.pdf:application/pdf},
|
||||
}
|
||||
|
||||
@online{lekhansh_lasso_2024,
|
||||
title = {Lasso vs. Ridge Regression: A Detailed Comparison},
|
||||
url = {https://medium.com/@tyagi.lekhansh/lasso-vs-ridge-regression-a-detailed-comparison-140f7832c624},
|
||||
shorttitle = {Lasso vs. Ridge Regression},
|
||||
abstract = {In the realm of regression analysis, Lasso (Least Absolute Shrinkage and Selection Operator) and Ridge Regression are two popular…},
|
||||
titleaddon = {Medium},
|
||||
author = {Lekhansh},
|
||||
urldate = {2025-09-22},
|
||||
date = {2024-09-04},
|
||||
langid = {english},
|
||||
}
|
||||
|
||||
@book{hastie_elements_2009,
|
||||
location = {New York, {NY}},
|
||||
title = {The Elements of Statistical Learning},
|
||||
rights = {http://www.springer.com/tdm},
|
||||
isbn = {978-0-387-84857-0 978-0-387-84858-7},
|
||||
url = {http://link.springer.com/10.1007/978-0-387-84858-7},
|
||||
series = {Springer Series in Statistics},
|
||||
publisher = {Springer},
|
||||
author = {Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome},
|
||||
urldate = {2025-09-22},
|
||||
date = {2009},
|
||||
doi = {10.1007/978-0-387-84858-7},
|
||||
keywords = {Averaging, Boosting, classification, clustering, data mining, machine learning, Projection pursuit, Random Forest, supervised learning, Support Vector Machine, unsupervised learning},
|
||||
file = {Full Text PDF:/home/lars/Zotero/storage/D3N4DVY9/Hastie et al. - 2009 - The Elements of Statistical Learning.pdf:application/pdf},
|
||||
}
|
||||
|
||||
@book{goodfellow_deep_2016,
|
||||
title = {Deep Learning},
|
||||
publisher = {{MIT} Press},
|
||||
author = {Goodfellow, Ian and Bengio, Yoshua and Courville, Aaron},
|
||||
date = {2016},
|
||||
}
|
||||
|
||||
@book{bishop_pattern_2006,
|
||||
location = {New York},
|
||||
title = {Pattern recognition and machine learning},
|
||||
isbn = {978-0-387-31073-2},
|
||||
series = {Information science and statistics},
|
||||
publisher = {Springer},
|
||||
author = {Bishop, Christopher M.},
|
||||
date = {2006},
|
||||
langid = {english},
|
||||
file = {PDF:/home/lars/Zotero/storage/9H5W9BGC/Bishop - 2006 - Pattern recognition and machine learning.pdf:application/pdf},
|
||||
}
|
||||
|
||||
@online{roskam_supercritical_2025,
|
||||
title = {Supercritical Fluid Chromatography for Chiral Analysis, Part 1: Theoretical Background {\textbar} {LCGC} International},
|
||||
url = {https://www.chromatographyonline.com/view/supercritical-fluid-chromatography-for-chiral-analysis-part-1-theoretical-background},
|
||||
shorttitle = {Supercritical Fluid Chromatography for Chiral Analysis, Part 1},
|
||||
abstract = {With the substantial developments carried out over the past years in instrumentation, columns, and detector hyphenation, the interest in chiral supercritical fluid chromatography ({SFC}) has been steadily growing in various fields. In the first part of this review article, the theoretical advantages, technological developments, and common practices in chiral {SFC} are discussed.},
|
||||
author = {Roskam, Gerry and Velde, Bas van de and Gargano, Andrea and Kohler, Isabelle},
|
||||
urldate = {2025-10-05},
|
||||
date = {2025-10-05},
|
||||
langid = {english},
|
||||
file = {Snapshot:/home/lars/Zotero/storage/LWSHJMVJ/supercritical-fluid-chromatography-for-chiral-analysis-part-1-theoretical-background.html:text/html},
|
||||
}
|
||||
|
||||
@article{furet_first_2022,
|
||||
title = {The First Class of Small Molecules Potently Disrupting the {YAP}‐{TEAD} Interaction by Direct Competition},
|
||||
volume = {17},
|
||||
doi = {10.1002/cmdc.202200303},
|
||||
abstract = {Inhibition of the {YAP}‐{TEAD} protein‐protein interaction is an attractive therapeutic concept under intense investigation with the objective to treat cancers associated with a dysregulation of the Hippo pathway. However, owing to the very extended surface of interaction of the two proteins, the identification of small drug‐like molecules able to efficiently prevent {YAP} from binding to {TEAD} by direct competition has been elusive so far. We disclose here the discovery of the first class of small molecules potently inhibiting the {YAP}‐{TEAD} interaction by binding at one of the main interaction sites of {YAP} at the surface of {TEAD}. These inhibitors, providing a path forward to pharmacological intervention in the Hippo pathway, evolved from a weakly active virtual screening hit advanced to high potency by structure‐based design.},
|
||||
journaltitle = {{ChemMedChem}},
|
||||
shortjournal = {{ChemMedChem}},
|
||||
author = {Furet, Pascal and Bordas, Vincent and Douget, Mickaël and Salem, Bahaa and Mesrouze, Yannick and Imbach‐Weese, Patricia and Sellner, Holger and Vögtle, Markus and Soldermann, Nicolas and Chapeau, Emilie and Wartmann, Markus and Scheufler, Clemens and Fernández, César and Kallen, Joerg and Guagnano, Vito and Chene, Patrick and Schmelzle, Tobias},
|
||||
date = {2022-09-02},
|
||||
file = {Full Text PDF:/home/lars/Zotero/storage/J399MX84/Furet et al. - 2022 - The First Class of Small Molecules Potently Disrupting the YAP‐TEAD Interaction by Direct Competitio.pdf:application/pdf},
|
||||
}
|
||||
|
||||
@book{vogel_particle_2024,
|
||||
location = {Cham},
|
||||
title = {Particle Confinement in Penning Traps: An Introduction},
|
||||
volume = {126},
|
||||
rights = {https://www.springernature.com/gp/researchers/text-and-data-mining},
|
||||
isbn = {978-3-031-55419-3 978-3-031-55420-9},
|
||||
url = {https://link.springer.com/10.1007/978-3-031-55420-9},
|
||||
series = {Springer Series on Atomic, Optical, and Plasma Physics},
|
||||
shorttitle = {Particle Confinement in Penning Traps},
|
||||
publisher = {Springer International Publishing},
|
||||
author = {Vogel, Manuel},
|
||||
urldate = {2025-10-09},
|
||||
date = {2024},
|
||||
langid = {english},
|
||||
doi = {10.1007/978-3-031-55420-9},
|
||||
keywords = {Confined ions and plasmas, Highly charged ions, Ion trapping, Laser Cooling, Magnetic moments, Particle confinement, Penning traps, Precision spectroscopy, Resistive Cooling, Stored ions, Trapped charged particles, Unified notation Penning traps},
|
||||
file = {Full Text PDF:/home/lars/Zotero/storage/SI8SF7RF/Vogel - 2024 - Particle Confinement in Penning Traps An Introduction.pdf:application/pdf},
|
||||
}
|
||||
|
||||
@article{scampoli_aegis_2014,
|
||||
title = {The {AEgIS} experiment at {CERN} for the measurement of antihydrogen gravity acceleration},
|
||||
volume = {29},
|
||||
issn = {0217-7323},
|
||||
url = {https://www.worldscientific.com/doi/abs/10.1142/S0217732314300171},
|
||||
doi = {10.1142/S0217732314300171},
|
||||
abstract = {The Antihydrogen Experiment: Gravity, Interferometry, Spectroscopy ({AEgIS}) experiment is conducted by an international collaboration based at {CERN} whose aim is to perform the first direct measurement of the gravitational acceleration of antihydrogen in the local field of the Earth, with Δg/g = 1\% precision as a first achievement. The idea is to produce cold (100 {mK}) antihydrogen through a pulsed charge exchange reaction by overlapping clouds of antiprotons, from the Antiproton Decelerator ({AD}) and positronium atoms inside a Penning trap. The antihydrogen has to be produced in an excited Rydberg state to be subsequently accelerated to form a beam. The deflection of the antihydrogen beam can then be measured by using a moiré deflectometer coupled to a position sensitive detector to register the impact point of the anti-atoms through the vertex reconstruction of their annihilation products. After being approved in late 2008, {AEgIS} started taking data in a commissioning phase in 2012. This paper presents an outline of the experiment with a brief overview of its physics motivation and of the state-of-the-art of the g measurement on antimatter. Particular attention is given to the current status of the emulsion-based position detector needed to measure the sag in {AEgIS}.},
|
||||
pages = {1430017},
|
||||
number = {17},
|
||||
journaltitle = {Modern Physics Letters A},
|
||||
shortjournal = {Mod. Phys. Lett. A},
|
||||
author = {Scampoli, Paola and Storey, James},
|
||||
urldate = {2025-10-09},
|
||||
date = {2014-06-07},
|
||||
note = {Publisher: World Scientific Publishing Co.},
|
||||
keywords = {Antihydrogen, gravity, high resolution tracking detector},
|
||||
}
|
||||
|
||||
@article{bertsche_prospects_2018,
|
||||
title = {Prospects for comparison of matter and antimatter gravitation with {ALPHA}-g},
|
||||
volume = {376},
|
||||
url = {https://royalsocietypublishing.org/doi/full/10.1098/rsta.2017.0265},
|
||||
doi = {10.1098/rsta.2017.0265},
|
||||
abstract = {The {ALPHA} experiment has recently entered an expansion phase of its experimental programme, driven in part by the expected benefits of conducting experiments in the framework of the new {AD} + {ELENA} antiproton facility at {CERN}. With antihydrogen trapping now a routine operation in the {ALPHA} experiment, the collaboration is leading progress towards precision atomic measurements on trapped antihydrogen atoms, with the first excitation of the 1S–2S transition and the first measurement of the antihydrogen hyperfine spectrum (Ahmadi et al. 2017 Nature 541, 506–510 (doi:10.1038/nature21040); Nature 548, 66–69 (doi:10.1038/nature23446)). We are building on these successes to extend our physics programme to include a measurement of antimatter gravitation. We plan to expand a proof-of-principle method (Amole et al. 2013 Nat. Commun. 4, 1785 (doi:10.1038/ncomms2787)), first demonstrated in the original {ALPHA} apparatus, and perform a precise measurement of antimatter gravitational acceleration with the aim of achieving a test of the weak equivalence principle at the 1\% level. The design of this apparatus has drawn from a growing body of experience on the simulation and verification of antihydrogen orbits confined within magnetic-minimum atom traps. The new experiment, {ALPHA}-g, will be an additional atom-trapping apparatus located at the {ALPHA} experiment with the intention of measuring antihydrogen gravitation.
|
||||
|
||||
This article is part of the Theo Murphy meeting issue ‘Antiproton physics in the {ELENA} era’.},
|
||||
pages = {20170265},
|
||||
number = {2116},
|
||||
journaltitle = {Philosophical Transactions of the Royal Society A: Mathematical, Physical and Engineering Sciences},
|
||||
author = {Bertsche, W. A.},
|
||||
urldate = {2025-10-09},
|
||||
date = {2018-02-19},
|
||||
note = {Publisher: Royal Society},
|
||||
keywords = {antigravity, antihydrogen, antimatter, {CPT}, gravity, Lorentz invariance},
|
||||
file = {Full Text PDF:/home/lars/Zotero/storage/7YM83XXD/Bertsche - 2018 - Prospects for comparison of matter and antimatter gravitation with ALPHA-g.pdf:application/pdf},
|
||||
}
|
||||
@@ -8,4 +8,5 @@ dependencies = [
|
||||
"numpy>=2.3.2",
|
||||
"pandas>=2.3.2",
|
||||
"scipy>=1.16.1",
|
||||
"typer>=0.19.2",
|
||||
]
|
||||
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
#!/bin/bash
|
||||
|
||||
PUEUE=true
|
||||
NORMAL_MANY_PARTICLE_SIMULATION=true
|
||||
OSCILLATING_FIELD_SIMULATION=false
|
||||
|
||||
echo "Removing old results..."
|
||||
mkdir -p results
|
||||
rm results/*.dat
|
||||
|
||||
echo "Compiling binaries..."
|
||||
rm -f two_particles many_particles
|
||||
g++ -O3 -larmadillo -I ./include src/* two_particles.cpp -o two_particles
|
||||
g++ -O3 -larmadillo -I ./include src/* many_particles.cpp -o many_particles
|
||||
echo "Finished compiling."
|
||||
|
||||
if $PUEUE; then
|
||||
echo "Using pueue to generate results in parallel..."
|
||||
systemctl --user start pueued
|
||||
pueue parallel 8
|
||||
pueue clean
|
||||
COMMAND_PREFIX="pueue add "
|
||||
else
|
||||
echo "Generating results sequentially..."
|
||||
COMMAND_PREFIX=""
|
||||
fi
|
||||
|
||||
for N in 4000 8000 16000 32000
|
||||
do
|
||||
for interactions in "" "--disable-interactions"
|
||||
do
|
||||
for n in 1 2
|
||||
do
|
||||
$COMMAND_PREFIX ./two_particles -N $N -n $n $interactions
|
||||
$COMMAND_PREFIX ./two_particles -N $N -n $n $interactions -E
|
||||
$COMMAND_PREFIX ./two_particles -N $N -n $n $interactions -V
|
||||
done
|
||||
done
|
||||
# The analytical solution is only valid for one particle (second particle has v_z) without interactions
|
||||
$COMMAND_PREFIX ./two_particles -N $N -n 1 --disable-interactions -A
|
||||
done
|
||||
|
||||
if $NORMAL_MANY_PARTICLE_SIMULATION; then
|
||||
for N in 4000 8000 16000 32000
|
||||
do
|
||||
for interactions in "" "--disable-interactions"
|
||||
do
|
||||
for n in 10 50 100
|
||||
do
|
||||
$COMMAND_PREFIX ./many_particles -N $N -n $n $interactions
|
||||
$COMMAND_PREFIX ./many_particles -N $N -n $n $interactions -E
|
||||
$COMMAND_PREFIX ./many_particles -N $N -n $n $interactions -V
|
||||
done
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
if $OSCILLATING_FIELD_SIMULATION; then
|
||||
N=40000
|
||||
n=100
|
||||
reduced_output="--reduced-output"
|
||||
interactions="--disable-interactions"
|
||||
for amplitude in 0.1 0.4 0.7
|
||||
do
|
||||
for frequency in $(seq 0.2 0.02 2.5)
|
||||
do
|
||||
$COMMAND_PREFIX ./many_particles -N $N -t -n $n $interactions -O -f $amplitude -w $frequency $reduced_output
|
||||
$COMMAND_PREFIX ./many_particles -N $N -t -n $n $interactions -V -O -f $amplitude -w $frequency $reduced_output
|
||||
done
|
||||
done
|
||||
interactions=""
|
||||
for amplitude in 0.1 0.4 0.7
|
||||
do
|
||||
for frequency in $(seq 1.0 0.005 1.8)
|
||||
do
|
||||
$COMMAND_PREFIX ./many_particles -N $N -t -n $n $interactions -O -f $amplitude -w $frequency $reduced_output
|
||||
$COMMAND_PREFIX ./many_particles -N $N -t -n $n $interactions -V -O -f $amplitude -w $frequency $reduced_output
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
echo "All tasks added to pueue."
|
||||
|
||||
echo "Use 'pueue status' to check the status of the tasks."
|
||||
|
||||
pueue status
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
#include <armadillo>
|
||||
#include <ostream>
|
||||
|
||||
#ifndef CLASSES_HPP
|
||||
#define CLASSES_HPP
|
||||
|
||||
class PenningTrap;
|
||||
|
||||
arma::vec standard_external_E_field(const arma::vec& r, double t, const PenningTrap& trap);
|
||||
arma::vec oscillating_external_E_field(const arma::vec& r, double t, const PenningTrap& trap);
|
||||
arma::vec standard_external_B_field(const arma::vec& r, double t, const PenningTrap& trap);
|
||||
arma::vec limited_range_external_B_field(const arma::vec& r, double t, const PenningTrap& trap);
|
||||
|
||||
class Particle {
|
||||
public:
|
||||
arma::vec position;
|
||||
arma::vec velocity;
|
||||
double mass;
|
||||
double charge;
|
||||
Particle(arma::vec pos, arma::vec vel, double m, double q);
|
||||
Particle(double m, double q);
|
||||
std::string info() const;
|
||||
bool inside_trap(double d) const;
|
||||
|
||||
};
|
||||
|
||||
class PenningTrap {
|
||||
private:
|
||||
std::vector<Particle> particles;
|
||||
bool coulomb_interaction = true;
|
||||
bool modified_fields = false;
|
||||
arma::vec (*_external_E_field)(const arma::vec& r, double t, const PenningTrap& trap) = standard_external_E_field;
|
||||
arma::vec (*_external_B_field)(const arma::vec& r, double t, const PenningTrap& trap) = standard_external_B_field;
|
||||
public:
|
||||
double B0;
|
||||
double V0;
|
||||
double d;
|
||||
double f = 0.0; // Amplitude of oscillating potential
|
||||
double omega_V = 0.0; // Angular frequency of oscillating potential
|
||||
PenningTrap(double B0_in, double V0_in, double d_in);
|
||||
void add_particle(Particle& p);
|
||||
void add_n_identical_particles(int n, double m, double q);
|
||||
arma::vec external_E_field(const arma::vec& r, double t) const {
|
||||
return _external_E_field(r, t, *this);
|
||||
}
|
||||
arma::vec external_B_field(const arma::vec& r, double t) const {
|
||||
return _external_B_field(r, t, *this);
|
||||
}
|
||||
void set_external_E_field(arma::vec (*E_field_func)(const arma::vec&, double, const PenningTrap&));
|
||||
void set_external_B_field(arma::vec (*B_field_func)(const arma::vec&, double, const PenningTrap&));
|
||||
arma::vec force_particle(int i, int j) const;
|
||||
arma::vec total_force_external(int i, double t) const;
|
||||
arma::vec total_force_particles(int i) const;
|
||||
arma::vec total_force(int i, double t) const;
|
||||
double potential_energy(int i) const;
|
||||
double total_potential_energy() const;
|
||||
double kinetic_energy(int i) const;
|
||||
double total_kinetic_energy() const;
|
||||
double total_energy() const;
|
||||
Particle& get_particle(int i);
|
||||
const Particle& get_particle(int i) const;
|
||||
int num_particles() const;
|
||||
void enable_interactions() {
|
||||
coulomb_interaction = true;
|
||||
}
|
||||
void disable_interactions() {
|
||||
coulomb_interaction = false;
|
||||
}
|
||||
bool get_coulomb_interaction() const {
|
||||
return coulomb_interaction;
|
||||
}
|
||||
bool get_modified_fields() const {
|
||||
return modified_fields;
|
||||
}
|
||||
std::string info() const;
|
||||
int num_inside_trap() const;
|
||||
};
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Particle& p);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const PenningTrap& trap);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef CONSTANTS_HPP
|
||||
#define CONSTANTS_HPP
|
||||
|
||||
namespace constants {
|
||||
const double k_e = 1.38935333e5; // Unit: u * um^3 / (us^2 * e^2), Coulomb's constant
|
||||
const double tesla = 9.64852558e1; // Conversion factor from tesla to u/(us * e)
|
||||
const double volt = 9.64852558e7; // Conversion factor from volt to u * um^2 / (us^2 * e)
|
||||
const double meter = 1e6; // Conversion factor from meter to micrometer
|
||||
const double second = 1e6; // Conversion factor from second to microsecond
|
||||
const double amu = 1.0; // Atomic mass unit in terms of itself (u)
|
||||
const double elementary_charge = 1.0; // Elementary charge in terms of itself (e)
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,92 @@
|
||||
#include "classes.hpp"
|
||||
#include <vector>
|
||||
#include <armadillo>
|
||||
|
||||
#ifndef SOLVERS_HPP
|
||||
#define SOLVERS_HPP
|
||||
|
||||
|
||||
arma::cube to_cube(const std::vector<arma::mat>& vec);
|
||||
|
||||
void save_to_file(const std::string& filename, const arma::cube& data);
|
||||
void save_to_file(const std::string& filename, const arma::vec& data);
|
||||
|
||||
|
||||
class Solver {
|
||||
protected:
|
||||
PenningTrap& trap;
|
||||
double dt;
|
||||
int steps;
|
||||
std::vector<arma::mat> positions;
|
||||
std::vector<arma::mat> velocities;
|
||||
std::vector<int> inside_trap_count;
|
||||
std::vector<double> potential_energies;
|
||||
std::vector<double> kinetic_energies;
|
||||
std::vector<double> total_energies;
|
||||
virtual void step() = 0;
|
||||
void record_position();
|
||||
void record_velocity();
|
||||
void record_energies();
|
||||
void record_trap_count();
|
||||
public:
|
||||
Solver(PenningTrap& trap_in, double dt_in);
|
||||
void simulate(int num_steps);
|
||||
void simulate_time(double total_time) {
|
||||
int num_steps = total_steps(total_time);
|
||||
simulate(num_steps);
|
||||
}
|
||||
std::vector<arma::mat> get_positions();
|
||||
std::vector<arma::mat> get_velocities();
|
||||
std::vector<double> get_potential_energies();
|
||||
std::vector<double> get_kinetic_energies();
|
||||
std::vector<double> get_total_energies();
|
||||
std::vector<int> get_inside_trap_count();
|
||||
int get_current_step();
|
||||
int total_steps(double total_time);
|
||||
void save(std::string base_filename);
|
||||
void save_counts(std::string base_filename);
|
||||
void record_current_step() {
|
||||
record_position();
|
||||
record_velocity();
|
||||
if (!trap.get_modified_fields()) {
|
||||
record_energies();
|
||||
}
|
||||
record_trap_count();
|
||||
}
|
||||
bool record_positions_and_velocities = true;
|
||||
bool record_inside_trap_count = true;
|
||||
};
|
||||
|
||||
class EulerSolver : public Solver {
|
||||
public:
|
||||
EulerSolver(PenningTrap& trap_in, double dt_in);
|
||||
void step() override;
|
||||
};
|
||||
class RK4Solver : public Solver {
|
||||
public:
|
||||
RK4Solver(PenningTrap& trap_in, double dt_in);
|
||||
void step() override;
|
||||
};
|
||||
class VelocityVerletSolver : public Solver {
|
||||
private:
|
||||
std::vector<arma::vec> accelerations;
|
||||
public:
|
||||
VelocityVerletSolver(PenningTrap& trap_in, double dt_in);
|
||||
void step() override;
|
||||
};
|
||||
|
||||
class AnalyticalSolver : public Solver {
|
||||
private:
|
||||
std::vector<double> w_0;
|
||||
std::vector<double> w_z;
|
||||
std::vector<double> w_plus;
|
||||
std::vector<double> w_minus;
|
||||
std::vector<double> A_plus;
|
||||
std::vector<double> A_minus;
|
||||
std::vector<double> z_0;
|
||||
public:
|
||||
AnalyticalSolver(PenningTrap& trap_in, double dt_in);
|
||||
void step() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,173 @@
|
||||
#include <armadillo>
|
||||
#include "classes.hpp"
|
||||
#include "solvers.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "argparse/argparse.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <ctime>
|
||||
|
||||
using namespace std;
|
||||
|
||||
void print_first_particles(PenningTrap& trap, int num_particles) {
|
||||
for (int i = 0; i < num_particles && i < trap.num_particles(); ++i) {
|
||||
cout << "Particle " << i + 1 << ": " << endl << trap.get_particle(i) << endl;
|
||||
}
|
||||
}
|
||||
|
||||
void save_timing(clock_t start, clock_t end, const string& filename) {
|
||||
double cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
|
||||
ofstream file(filename);
|
||||
if (file.is_open()) {
|
||||
file << "CPU time used: " << cpu_time_used << " seconds" << endl;
|
||||
file.close();
|
||||
} else {
|
||||
cerr << "Unable to open file for writing timing information." << endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
argparse::ArgumentParser program("many_particles");
|
||||
program.add_argument("-v", "--verbose")
|
||||
.help("Enable verbose output of first particles")
|
||||
.flag();
|
||||
program.add_argument("-N", "--steps")
|
||||
.help("Number of time steps")
|
||||
.default_value(4000)
|
||||
.scan<'i', int>();
|
||||
program.add_argument("-n", "--num_particles")
|
||||
.help("Number of particles")
|
||||
.default_value(100)
|
||||
.scan<'i', int>();
|
||||
program.add_argument("-i", "--disable-interactions")
|
||||
.help("Disable Coulomb interactions")
|
||||
.default_value(true)
|
||||
.implicit_value(false);
|
||||
program.add_argument("-O", "--oscillating-potential")
|
||||
.help("Enable oscillating potential")
|
||||
.flag();
|
||||
program.add_argument("-f", "--amplitude")
|
||||
.help("Amplitude of oscillating potential")
|
||||
.default_value(0.0)
|
||||
.scan<'f', double>();
|
||||
program.add_argument("-w", "--frequency")
|
||||
.help("Angular frequency of oscillating potential")
|
||||
.default_value(0.0)
|
||||
.scan<'f', double>();
|
||||
program.add_argument("-r", "--reduced-output")
|
||||
.help("Reduce output for oscillating potential to only final count")
|
||||
.flag();
|
||||
program.add_argument("-E", "--euler")
|
||||
.help("Use Euler solver instead of RK4")
|
||||
.flag();
|
||||
program.add_argument("-V", "--velocity-verlet")
|
||||
.help("Use Velocity Verlet solver instead of RK4")
|
||||
.flag();
|
||||
program.add_argument("-t", "--timing")
|
||||
.help("Enable timing of the simulation")
|
||||
.flag();
|
||||
|
||||
try {
|
||||
program.parse_args(argc, argv);
|
||||
} catch (const std::runtime_error& err) {
|
||||
std::cerr << err.what() << std::endl;
|
||||
std::cerr << program.help().str() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
bool verbose = program.get<bool>("--verbose");
|
||||
|
||||
int N = program.get<int>("--steps");
|
||||
int n_particles = program.get<int>("--num_particles");
|
||||
bool interactions = program.get<bool>("--disable-interactions");
|
||||
bool oscillating_potential = program.get<bool>("--oscillating-potential");
|
||||
bool use_euler = program.get<bool>("--euler");
|
||||
bool use_velocity_verlet = program.get<bool>("--velocity-verlet");
|
||||
bool timing = program.get<bool>("--timing");
|
||||
|
||||
double B_0 = 1.0; // Tesla
|
||||
double V_0 = 0.025; // Volt
|
||||
double d = 500e-6; // meter
|
||||
|
||||
PenningTrap trap(B_0, V_0, d);
|
||||
if (interactions) {
|
||||
trap.enable_interactions();
|
||||
}
|
||||
else {
|
||||
trap.disable_interactions();
|
||||
}
|
||||
|
||||
|
||||
bool reduced_output = program.get<bool>("--reduced-output"); // Only relevant if oscillating potential is enabled
|
||||
if (oscillating_potential) {
|
||||
trap.set_external_E_field(oscillating_external_E_field);
|
||||
trap.set_external_B_field(limited_range_external_B_field);
|
||||
trap.f = program.get<double>("--amplitude");
|
||||
trap.omega_V = program.get<double>("--frequency");
|
||||
}
|
||||
|
||||
trap.add_n_identical_particles(n_particles, 40.078 * constants::amu, 1 * constants::elementary_charge);
|
||||
if (verbose) {
|
||||
cout << trap.info() << endl;
|
||||
print_first_particles(trap, 10);
|
||||
}
|
||||
|
||||
|
||||
double dt = 50e-6 / N; // seconds
|
||||
|
||||
|
||||
unique_ptr<Solver> solver;
|
||||
string solver_name;
|
||||
|
||||
if (use_euler) {
|
||||
solver = make_unique<EulerSolver>(trap, dt);
|
||||
solver_name = "Euler";
|
||||
} else if (use_velocity_verlet) {
|
||||
solver = make_unique<VelocityVerletSolver>(trap, dt);
|
||||
solver_name = "VelocityVerlet";
|
||||
} else {
|
||||
solver = make_unique<RK4Solver>(trap, dt);
|
||||
solver_name = "RK4";
|
||||
}
|
||||
clock_t start, end;
|
||||
start = clock();
|
||||
if (oscillating_potential) {
|
||||
solver->record_positions_and_velocities = false; // Disable recording for performance
|
||||
if (reduced_output) {
|
||||
solver->record_inside_trap_count = false; // Disable recording for performance
|
||||
}
|
||||
solver->simulate(N);
|
||||
end = clock();
|
||||
string folder = "results/" + solver_name + "/many_particles_" + to_string(trap.num_particles()) + "_particles_" + to_string(int(N)) + "_steps_" + (interactions ? "enabled" : "disabled") + "_interactions_oscillating_potential";
|
||||
string filename = "f" + to_string(trap.f) + "_w" + to_string(trap.omega_V);
|
||||
filesystem::create_directories(folder);
|
||||
|
||||
if (reduced_output) {
|
||||
solver->record_current_step(); // Record only final step
|
||||
}
|
||||
solver->save_counts(folder + "/" + filename);
|
||||
cout << "Final number of particles inside trap: " << trap.num_inside_trap() << " out of " << trap.num_particles() << endl;
|
||||
if (timing) {
|
||||
save_timing(start, end, folder + "/" + filename + "_timing.txt");
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
solver->simulate(N);
|
||||
end = clock();
|
||||
string folder = "results/" + solver_name;
|
||||
filesystem::create_directories(folder);
|
||||
string filename = "/many_particles_" + to_string(trap.num_particles()) + "_particles_" + to_string(int(N)) + "_steps_" + (interactions ? "enabled" : "disabled") + "_interactions";
|
||||
solver->save(folder + filename);
|
||||
if (verbose) {
|
||||
vector<arma::mat> positions = solver->get_positions();
|
||||
cout << "Final positions of particles: " << endl;
|
||||
print_first_particles(trap, 10);
|
||||
}
|
||||
if (timing) {
|
||||
save_timing(start, end, folder + filename + "_timing.txt");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
interval=5 # seconds between updates
|
||||
|
||||
while true; do
|
||||
data=$(pueue status --json)
|
||||
|
||||
queued=$(jq '.tasks | map(select(.status=="Queued")) | length' <<< "$data")
|
||||
running=$(jq '.tasks | map(select(.status=="Running")) | length' <<< "$data")
|
||||
total=$(jq '.tasks | length' <<< "$data")
|
||||
|
||||
# average duration of finished tasks (seconds)
|
||||
avg=$(jq '.tasks
|
||||
| map(select(.start != null and .end != null)
|
||||
| ((.end[0:19] + "Z") | fromdate) - ((.start[0:19] + "Z") | fromdate))
|
||||
| if length > 0 then add / length else 0 end' <<< "$data")
|
||||
|
||||
clear
|
||||
echo "Queued: $queued"
|
||||
echo "Running: $running"
|
||||
echo "Total: $total"
|
||||
|
||||
# Only calculate ETA if avg > 0
|
||||
if (( $(echo "$avg > 0" | bc -l) )); then
|
||||
remaining=$((queued + running))
|
||||
eta_seconds=$(echo "$remaining * $avg" | bc -l)
|
||||
finish_time=$(date -d "@$(($(date +%s) + ${eta_seconds%.*}))" +"%Y-%m-%d %H:%M:%S")
|
||||
printf "Average duration: %.2fs\n" "$avg"
|
||||
printf "ETA: ~%.0fs (finishing around %s)\n" "$eta_seconds" "$finish_time"
|
||||
else
|
||||
echo "ETA: Not enough finished jobs in history yet."
|
||||
fi
|
||||
|
||||
sleep $interval
|
||||
done
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import os
|
||||
import typer
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
def load_data(total_energy_file):
|
||||
total_energy = np.loadtxt(total_energy_file)
|
||||
kin_file = total_energy_file.replace("total_energies", "kinetic_energies")
|
||||
pot_file = total_energy_file.replace("total_energies", "potential_energies")
|
||||
kinetic_energy = np.loadtxt(kin_file)
|
||||
potential_energy = np.loadtxt(pot_file)
|
||||
step_count = len(total_energy)
|
||||
return total_energy, kinetic_energy, potential_energy, step_count
|
||||
|
||||
@app.command()
|
||||
def plot_kinetic_energy(energy_files: list[str]=[], labels: list[str]=[], mass: float=1.0):
|
||||
"""Plot kinetic energy from velocity files."""
|
||||
for i, energy_file in enumerate(energy_files):
|
||||
total_energy, kinetic_energy, potential_energy, step_count = load_data(energy_file)
|
||||
time = np.linspace(0, 50e-6, step_count)
|
||||
color = f"C{i % 10}"
|
||||
plt.plot(time, total_energy, label=labels[i], color=color, linestyle="-")
|
||||
plt.plot(time, potential_energy, color=color, linestyle="--")
|
||||
plt.plot(time, kinetic_energy, color=color, linestyle=":")
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Energy (J)')
|
||||
plt.title('Energy Evolution')
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
@@ -0,0 +1,49 @@
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import os
|
||||
import argparse
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument("directory", type=str, help="Directory containing the data files")
|
||||
args = argparser.parse_args()
|
||||
directory = args.directory
|
||||
|
||||
def get_filenames(directory):
|
||||
filenames = [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith(".red")]
|
||||
return filenames
|
||||
|
||||
def get_pars(filename):
|
||||
amplitude = float(filename.split("_")[0][1:])
|
||||
omega = float(filename.split("_")[1][1:])
|
||||
return amplitude, omega
|
||||
|
||||
def load_data(filename):
|
||||
with open(filename, "r") as file:
|
||||
x = int(file.readline().split()[1])
|
||||
return x
|
||||
|
||||
def plot_resonances(amplitudes, omegas, counts):
|
||||
plt.figure(figsize=(8, 6))
|
||||
unique_amps = sorted(set(amplitudes))
|
||||
for amp in unique_amps:
|
||||
mask = np.array(amplitudes) == amp
|
||||
omegas_subset = np.array(omegas)[mask]
|
||||
counts_subset = np.array(counts)[mask]
|
||||
omegas_sorted, counts_sorted = zip(*sorted(zip(omegas_subset, counts_subset)))
|
||||
plt.plot(omegas_sorted, counts_sorted, label=f'Amplitude {amp}')
|
||||
plt.xlabel('Omega')
|
||||
plt.ylabel('Count inside trap')
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
if __name__ == "__main__":
|
||||
filenames = get_filenames(directory)
|
||||
amplitudes, omegas, counts = [], [], []
|
||||
for filename in filenames:
|
||||
amplitude, omega = get_pars(os.path.basename(filename))
|
||||
count = load_data(filename)
|
||||
amplitudes.append(amplitude)
|
||||
omegas.append(omega)
|
||||
counts.append(count)
|
||||
plot_resonances(amplitudes, omegas, counts)
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import os
|
||||
import typer
|
||||
from typing import Literal
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
def load_trajectory_data(filename):
|
||||
data = np.loadtxt(filename)
|
||||
step_count = int(float(filename.split("/")[-1].split("_")[5]))
|
||||
particle_count = int(float(filename.split("/")[-1].split("_")[2]))
|
||||
single_particle = particle_count == 1
|
||||
if single_particle:
|
||||
data = data.reshape((data.shape[0], 1))
|
||||
data = data.reshape(step_count, 3, particle_count).transpose(1, 2, 0)
|
||||
return data, step_count, particle_count
|
||||
|
||||
@app.command()
|
||||
def plot_z_evolution(trajectory_file: str):
|
||||
"""Plot the z-coordinate evolution from a trajectory file."""
|
||||
data, step_count, particle_count = load_trajectory_data(trajectory_file)
|
||||
time = np.linspace(0, 50e-6, step_count)
|
||||
for i in range(particle_count):
|
||||
plt.plot(time, data[2, i, :] * 1e-6, label=f'Particle {i+1}')
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Z Position (m)')
|
||||
plt.title('Z Coordinate Evolution')
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
@app.command()
|
||||
def plot_xy_trajectory(trajectory_file: str, plot_start_end: bool = True, plot_time: bool = False):
|
||||
"""Plot the XY trajectory from a trajectory file."""
|
||||
data, step_count, particle_count = load_trajectory_data(trajectory_file)
|
||||
for i in range(particle_count):
|
||||
if plot_start_end:
|
||||
plt.scatter(data[0, i, 0] * 1e-6, data[1, i, 0] * 1e-6, marker="o", color="black", label="Start" if i == 0 else "")
|
||||
plt.scatter(data[0, i, -1] * 1e-6, data[1, i, -1] * 1e-6, marker="x", color="red", label="End" if i == 0 else "")
|
||||
if plot_time:
|
||||
marker = [".", ",", "o", "v", "^", "<", ">", "1", "2", "3", "4", "8"][i % 12]
|
||||
cm = plt.get_cmap('viridis')
|
||||
c = cm(np.linspace(0, 1, step_count))
|
||||
plt.scatter(data[0, i, :] * 1e-6, data[1, i, :] * 1e-6, label=f'Particle {i+1}', marker=marker, c=c)
|
||||
else:
|
||||
ls = "-"
|
||||
c = f"C{i % 10}"
|
||||
plt.plot(data[0, i, :] * 1e-6, data[1, i, :] * 1e-6, label=f'Particle {i+1}', linestyle=ls, color=c)
|
||||
plt.xlabel('X Position (m)')
|
||||
plt.ylabel('Y Position (m)')
|
||||
plt.title('XY Trajectory')
|
||||
plt.axis('equal')
|
||||
if plot_time:
|
||||
#sm = plt.cm.ScalarMappable(cmap=cm, norm=plt.Normalize(vmin=0, vmax=50e-6))
|
||||
#sm.set_array([])
|
||||
#cbar = plt.colorbar(sm)
|
||||
#cbar.set_label('Time')
|
||||
pass
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
@app.command()
|
||||
def phase_space_plot(trajectory_file: str, velocity_file: str, axis: Literal['x', 'y', 'z'] = 'z'):
|
||||
"""Plot phase space (Z vs Vz) from trajectory and velocity files."""
|
||||
axis_index = {'x': 0, 'y': 1, 'z': 2}[axis]
|
||||
traj_data, step_count, particle_count = load_trajectory_data(trajectory_file)
|
||||
vel_data, _, _ = load_trajectory_data(velocity_file)
|
||||
for i in range(particle_count):
|
||||
plt.plot(traj_data[axis_index, i, :] * 1e-6, vel_data[axis_index, i, :] * 1e-6, label=f'Particle {i+1}')
|
||||
plt.xlabel(f'{axis.upper()} Position (m)')
|
||||
plt.ylabel(f'{axis.upper()} Velocity (m/s)')
|
||||
plt.title(f'Phase Space Plot ({axis.upper()} vs V{axis.upper()})')
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
@app.command()
|
||||
def plot_error(trajectory_files: list[str], analytical_file: str):
|
||||
"""Plot error between numerical and analytical trajectories."""
|
||||
analytical_data, step_count, particle_count = load_trajectory_data(analytical_file)
|
||||
time = np.linspace(0, 50e-6, step_count)
|
||||
for traj_file in trajectory_files:
|
||||
numerical_data, _, _ = load_trajectory_data(traj_file)
|
||||
error = numerical_data - analytical_data
|
||||
relative_error = np.mean(np.linalg.norm(error, axis=0) / np.linalg.norm(analytical_data, axis=0), axis=0)
|
||||
plt.plot(time, relative_error, label=os.path.basename(traj_file))
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Relative Error')
|
||||
plt.title('Relative Error between Numerical and Analytical Trajectories')
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
@@ -0,0 +1,223 @@
|
||||
#include "classes.hpp"
|
||||
#include "constants.hpp"
|
||||
#include <iostream>
|
||||
|
||||
// Initialize particle with position and velocity
|
||||
// Mass in atomic mass units (u), charge in elementary charges (e)
|
||||
// Positions in meters, velocities in meters/second
|
||||
Particle::Particle(arma::vec pos, arma::vec vel, double m, double q)
|
||||
: position(pos * constants::meter), velocity(vel * constants::meter / constants::second), mass(m * constants::amu), charge(q * constants::elementary_charge) {}
|
||||
|
||||
// Initialize particle at origin with zero velocity
|
||||
Particle::Particle(double m, double q)
|
||||
: position(arma::vec({0,0,0})), velocity(arma::vec({0,0,0})), mass(m * constants::amu), charge(q * constants::elementary_charge) {}
|
||||
|
||||
std::string Particle::info() const {
|
||||
std::string info_str = "Particle (m = " + std::to_string(mass) + " u, q = " + std::to_string(charge) + " e)\n";
|
||||
info_str += "Position (um): [" + std::to_string(position(0)) + ", " + std::to_string(position(1)) + ", " + std::to_string(position(2)) + "]\n";
|
||||
info_str += "Velocity (um/us): [" + std::to_string(velocity(0)) + ", " + std::to_string(velocity(1)) + ", " + std::to_string(velocity(2)) + "]\n";
|
||||
return info_str;
|
||||
}
|
||||
|
||||
bool Particle::inside_trap(double d) const {
|
||||
return arma::norm(position) <= d;
|
||||
}
|
||||
|
||||
// Empty Penning Trap with specified field parameters
|
||||
// B0 in tesla, V0 in volt, d in meters
|
||||
PenningTrap::PenningTrap(double B0_in, double V0_in, double d_in)
|
||||
: B0(B0_in * constants::tesla), V0(V0_in * constants::volt), d(d_in * constants::meter) {}
|
||||
|
||||
// Add particle to trap
|
||||
void PenningTrap::add_particle(Particle& p) {
|
||||
particles.push_back(p);
|
||||
}
|
||||
|
||||
// Add n identical particles with mass m and charge q
|
||||
void PenningTrap::add_n_identical_particles(int n, double m, double q) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
arma::vec pos = arma::vec(3).randn() * 0.1 * d; // Random position within 10% of d (unit: um)
|
||||
arma::vec vel = arma::vec(3).randn() * 0.1 * d; // Random velocity within 10% of d/s (unit: um/us)
|
||||
Particle p(pos / constants::meter, vel, m, q);
|
||||
add_particle(p);
|
||||
}
|
||||
}
|
||||
|
||||
void PenningTrap::set_external_E_field(arma::vec (*E_field_func)(const arma::vec&, double, const PenningTrap&)) {
|
||||
_external_E_field = E_field_func;
|
||||
modified_fields = true;
|
||||
}
|
||||
|
||||
void PenningTrap::set_external_B_field(arma::vec (*B_field_func)(const arma::vec&, double, const PenningTrap&)) {
|
||||
_external_B_field = B_field_func;
|
||||
modified_fields = true;
|
||||
}
|
||||
|
||||
|
||||
// Force on particle i from particle j due to Coulomb interaction
|
||||
// Neglecting magnetic fields from moving charges
|
||||
arma::vec PenningTrap::force_particle(int i, int j) const {
|
||||
if (!coulomb_interaction) {
|
||||
return arma::vec({0, 0, 0});
|
||||
}
|
||||
arma::vec r_i = particles[i].position;
|
||||
arma::vec r_j = particles[j].position;
|
||||
arma::vec r_ij = r_i - r_j;
|
||||
double distance = arma::norm(r_ij);
|
||||
if (distance == 0) {
|
||||
return arma::vec({0, 0, 0}); // Avoid division by zero
|
||||
}
|
||||
double force_magnitude_per_dist = (constants::k_e * particles[i].charge * particles[j].charge) / (distance * distance * distance);
|
||||
return force_magnitude_per_dist * r_ij;
|
||||
}
|
||||
|
||||
// Total force on particle i from external fields
|
||||
// Sum of electric and magnetic forces
|
||||
arma::vec PenningTrap::total_force_external(int i, double t) const {
|
||||
arma::vec E = external_E_field(get_particle(i).position, t);
|
||||
arma::vec B = external_B_field(get_particle(i).position, t);
|
||||
arma::vec v = get_particle(i).velocity;
|
||||
arma::vec F_electric = get_particle(i).charge * E;
|
||||
arma::vec F_magnetic = get_particle(i).charge * arma::cross(v, B);
|
||||
return F_electric + F_magnetic;
|
||||
}
|
||||
|
||||
// Total force on particle i from all other particles
|
||||
arma::vec PenningTrap::total_force_particles(int i) const {
|
||||
arma::vec total_force = arma::vec({0, 0, 0});
|
||||
if (!coulomb_interaction) { // Add the check here as well to avoid unnecessary calls to force_particle
|
||||
return total_force;
|
||||
}
|
||||
for (size_t j = 0; j < particles.size(); ++j) {
|
||||
if (j != i) {
|
||||
total_force += force_particle(i, j);
|
||||
}
|
||||
}
|
||||
return total_force;
|
||||
}
|
||||
|
||||
// Total force on particle i from all other particles and external fields
|
||||
arma::vec PenningTrap::total_force(int i, double t) const {
|
||||
return total_force_external(i, t) + total_force_particles(i);
|
||||
}
|
||||
|
||||
// Potential energy of particle i in the trap
|
||||
double PenningTrap::potential_energy(int i) const {
|
||||
if (modified_fields) {
|
||||
throw std::runtime_error("Potential energy calculation not valid with modified external fields.");
|
||||
}
|
||||
double q = particles[i].charge;
|
||||
arma::vec r = particles[i].position;
|
||||
double V = (V0 / (d * d)) * (r(2) * r(2) - 0.5 * (r(0) * r(0) + r(1) * r(1)));
|
||||
if (coulomb_interaction) {
|
||||
// Add contributions from other particles
|
||||
for (size_t j = 0; j < particles.size(); ++j) {
|
||||
if (j != i) {
|
||||
arma::vec r_j = particles[j].position;
|
||||
double distance = arma::norm(r - r_j);
|
||||
if (distance != 0) {
|
||||
V += (constants::k_e * particles[j].charge) / distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return q * V;
|
||||
}
|
||||
|
||||
// Total potential energy of all particles in the trap
|
||||
double PenningTrap::total_potential_energy() const {
|
||||
if (modified_fields) {
|
||||
throw std::runtime_error("Potential energy calculation not valid with modified external fields.");
|
||||
}
|
||||
double total_U = 0.0;
|
||||
for (size_t i = 0; i < particles.size(); ++i) {
|
||||
total_U += potential_energy(i);
|
||||
}
|
||||
return total_U;
|
||||
}
|
||||
|
||||
// Kinetic energy of particle i
|
||||
double PenningTrap::kinetic_energy(int i) const {
|
||||
double m = particles[i].mass;
|
||||
arma::vec v = particles[i].velocity;
|
||||
return 0.5 * m * arma::dot(v, v);
|
||||
}
|
||||
// Total kinetic energy of all particles in the trap
|
||||
double PenningTrap::total_kinetic_energy() const {
|
||||
double total_K = 0.0;
|
||||
for (size_t i = 0; i < particles.size(); ++i) {
|
||||
total_K += kinetic_energy(i);
|
||||
}
|
||||
return total_K;
|
||||
}
|
||||
|
||||
// Total energy of all particles in the trap
|
||||
double PenningTrap::total_energy() const {
|
||||
return total_kinetic_energy() + total_potential_energy();
|
||||
}
|
||||
|
||||
|
||||
// Getter function for particles
|
||||
Particle& PenningTrap::get_particle(int i) {
|
||||
return particles[i];
|
||||
}
|
||||
|
||||
const Particle& PenningTrap::get_particle(int i) const {
|
||||
return particles[i];
|
||||
}
|
||||
|
||||
// Number of particles in trap
|
||||
int PenningTrap::num_particles() const{
|
||||
return particles.size();
|
||||
}
|
||||
|
||||
|
||||
std::string PenningTrap::info() const {
|
||||
std::string info_str = "Penning Trap (B0 = " + std::to_string(B0) + " u/(us*e), V0 = " + std::to_string(V0) + " u*um^2/(us^2*e), d = " + std::to_string(d) + " um)\n";
|
||||
info_str += "Number of particles: " + std::to_string(num_particles()) + "\n";
|
||||
info_str += "Coulomb interactions: " + std::string(coulomb_interaction ? "enabled" : "disabled") + "\n";
|
||||
return info_str;
|
||||
}
|
||||
|
||||
int PenningTrap::num_inside_trap() const {
|
||||
int count = 0;
|
||||
for (int i = 0; i < num_particles(); ++i) {
|
||||
if (particles[i].inside_trap(d)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Electric field at position r and time t
|
||||
// Electric field from V = V0 * (z^2 - (x^2 + y^2)/2) / d^2
|
||||
arma::vec standard_external_E_field(const arma::vec& r, double t, const PenningTrap& trap) {
|
||||
return (trap.V0 / (trap.d * trap.d)) * arma::vec({r(0), r(1), -2 * r(2)});
|
||||
}
|
||||
|
||||
// Magnetic field at position r and time t
|
||||
// Constant magnetic field in z-direction
|
||||
arma::vec standard_external_B_field(const arma::vec& r, double t, const PenningTrap& trap) {
|
||||
return arma::vec({0, 0, trap.B0});
|
||||
}
|
||||
|
||||
arma::vec oscillating_external_E_field(const arma::vec& r, double t, const PenningTrap& trap) {
|
||||
if (arma::norm(r) > trap.d) {
|
||||
return arma::vec({0, 0, 0}); // No electric field outside the trap
|
||||
}
|
||||
return standard_external_E_field(r, t, trap) * (1 + trap.f * cos(trap.omega_V * t));
|
||||
}
|
||||
|
||||
arma::vec limited_range_external_B_field(const arma::vec& r, double t, const PenningTrap& trap) {
|
||||
if (arma::norm(r) > trap.d) {
|
||||
return arma::vec({0, 0, 0}); // No magnetic field outside the trap
|
||||
}
|
||||
return standard_external_B_field(r, t, trap);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Particle& p) {
|
||||
return os << p.info();
|
||||
}
|
||||
std::ostream& operator<<(std::ostream& os, const PenningTrap& trap) {
|
||||
return os << trap.info();
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
#include "classes.hpp"
|
||||
#include "solvers.hpp"
|
||||
#include "constants.hpp"
|
||||
|
||||
|
||||
// Initialize base solver class
|
||||
// dt in seconds
|
||||
Solver::Solver(PenningTrap& trap_in, double dt_in)
|
||||
: trap(trap_in), dt(dt_in * constants::second), steps(0) {}
|
||||
|
||||
// Simulate the system for a given number of steps
|
||||
void Solver::simulate(int num_steps) {
|
||||
for (int i = 0; i < num_steps; i++) {
|
||||
step();
|
||||
if (record_positions_and_velocities) { // Allow disabling of recording for performance
|
||||
// Store positions of all particles after each step
|
||||
record_position();
|
||||
// Store velocities of all particles after each step
|
||||
record_velocity();
|
||||
if (!trap.get_modified_fields()) {
|
||||
record_energies();
|
||||
}
|
||||
}
|
||||
if (record_inside_trap_count) {
|
||||
record_trap_count();
|
||||
}
|
||||
steps++;
|
||||
}
|
||||
}
|
||||
|
||||
void Solver::record_position() {
|
||||
arma::mat current_positions(3, trap.num_particles());
|
||||
for (int j = 0; j < trap.num_particles(); j++) {
|
||||
current_positions.col(j) = trap.get_particle(j).position;
|
||||
}
|
||||
positions.push_back(current_positions);
|
||||
}
|
||||
void Solver::record_velocity() {
|
||||
arma::mat current_velocities(3, trap.num_particles());
|
||||
for (int j = 0; j < trap.num_particles(); j++) {
|
||||
current_velocities.col(j) = trap.get_particle(j).velocity;
|
||||
}
|
||||
velocities.push_back(current_velocities);
|
||||
}
|
||||
|
||||
void Solver::record_energies() {
|
||||
double U = trap.total_potential_energy();
|
||||
double K = trap.total_kinetic_energy();
|
||||
double E = trap.total_energy();
|
||||
potential_energies.push_back(U);
|
||||
kinetic_energies.push_back(K);
|
||||
total_energies.push_back(E);
|
||||
}
|
||||
|
||||
void Solver::record_trap_count() {
|
||||
inside_trap_count.push_back(trap.num_inside_trap());
|
||||
}
|
||||
|
||||
// Get recorded positions of particles
|
||||
std::vector<arma::mat> Solver::get_positions() {
|
||||
return positions;
|
||||
}
|
||||
|
||||
// Get recorded velocities of particles
|
||||
std::vector<arma::mat> Solver::get_velocities() {
|
||||
return velocities;
|
||||
}
|
||||
std::vector<double> Solver::get_potential_energies() {
|
||||
return potential_energies;
|
||||
}
|
||||
std::vector<double> Solver::get_kinetic_energies() {
|
||||
return kinetic_energies;
|
||||
}
|
||||
std::vector<double> Solver::get_total_energies() {
|
||||
return total_energies;
|
||||
}
|
||||
|
||||
std::vector<int> Solver::get_inside_trap_count() {
|
||||
return inside_trap_count;
|
||||
}
|
||||
|
||||
// Get the current step count
|
||||
int Solver::get_current_step() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
// Calculate total steps for a given total time
|
||||
// total_time in seconds
|
||||
int Solver::total_steps(double total_time) {
|
||||
return static_cast<int>(total_time * constants::second / dt);
|
||||
}
|
||||
|
||||
void Solver::save(std::string base_filename) {
|
||||
arma::cube pos_cube = to_cube(positions);
|
||||
save_to_file(base_filename + "_positions.dat", pos_cube);
|
||||
arma::cube vel_cube = to_cube(velocities);
|
||||
save_to_file(base_filename + "_velocities.dat", vel_cube);
|
||||
if (!trap.get_modified_fields()) {
|
||||
arma::vec U = arma::vec(potential_energies);
|
||||
save_to_file(base_filename + "_potential_energies.dat", U);
|
||||
arma::vec K = arma::vec(kinetic_energies);
|
||||
save_to_file(base_filename + "_kinetic_energies.dat", K);
|
||||
arma::vec E = arma::vec(total_energies);
|
||||
save_to_file(base_filename + "_total_energies.dat", E);
|
||||
}
|
||||
}
|
||||
|
||||
void Solver::save_counts(std::string base_filename) {
|
||||
std::string extension = ".dat";
|
||||
if (inside_trap_count.size() == 1) {
|
||||
// Reduced to last element if only one entry
|
||||
extension = ".red";
|
||||
}
|
||||
std::ofstream file(base_filename + "_inside_trap_count" + extension);
|
||||
for (size_t i = 0; i < inside_trap_count.size(); i++) {
|
||||
file << i << " " << inside_trap_count[i] << "\n";
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Initialize Euler solver
|
||||
EulerSolver::EulerSolver(PenningTrap& trap_in, double dt_in)
|
||||
: Solver(trap_in, dt_in) {}
|
||||
|
||||
// Perform a single Euler integration step
|
||||
void EulerSolver::step() {
|
||||
int n = trap.num_particles();
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
arma::vec F = trap.total_force(i, steps * dt);
|
||||
arma::vec a = F / p.mass;
|
||||
p.position += p.velocity * dt;
|
||||
p.velocity += a * dt;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Runge-Kutta 4 solver
|
||||
RK4Solver::RK4Solver(PenningTrap& trap_in, double dt_in)
|
||||
: Solver(trap_in, dt_in) {}
|
||||
|
||||
|
||||
// Perform a single RK4 integration step
|
||||
void RK4Solver::step() {
|
||||
int n = trap.num_particles();
|
||||
std::vector<arma::vec> k1_v(n), k1_r(n);
|
||||
std::vector<arma::vec> k2_v(n), k2_r(n);
|
||||
std::vector<arma::vec> k3_v(n), k3_r(n);
|
||||
std::vector<arma::vec> k4_v(n), k4_r(n);
|
||||
|
||||
std::vector<arma::vec> current_positions(n);
|
||||
std::vector<arma::vec> current_velocities(n);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
current_positions[i] = trap.get_particle(i).position;
|
||||
current_velocities[i] = trap.get_particle(i).velocity;
|
||||
}
|
||||
double t = steps * dt;
|
||||
// Calculate k1
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
arma::vec F = trap.total_force(i, t);
|
||||
arma::vec a = F / p.mass;
|
||||
k1_v[i] = a * dt;
|
||||
k1_r[i] = p.velocity * dt;
|
||||
p.position += k1_r[i] / 2;
|
||||
p.velocity += k1_v[i] / 2;
|
||||
}
|
||||
// Calculate k2
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
arma::vec F = trap.total_force(i, t + dt / 2);
|
||||
arma::vec a = F / p.mass;
|
||||
k2_v[i] = a * dt;
|
||||
k2_r[i] = p.velocity * dt;
|
||||
p.position = current_positions[i] + k2_r[i] / 2;
|
||||
p.velocity = current_velocities[i] + k2_v[i] / 2;
|
||||
}
|
||||
// Calculate k3
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
arma::vec F = trap.total_force(i, t + dt / 2);
|
||||
arma::vec a = F / p.mass;
|
||||
k3_v[i] = a * dt;
|
||||
k3_r[i] = p.velocity * dt;
|
||||
p.position = current_positions[i] + k3_r[i];
|
||||
p.velocity = current_velocities[i] + k3_v[i];
|
||||
}
|
||||
// Calculate k4
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
arma::vec F = trap.total_force(i, t + dt);
|
||||
arma::vec a = F / p.mass;
|
||||
k4_v[i] = a * dt;
|
||||
k4_r[i] = p.velocity * dt;
|
||||
p.position = current_positions[i];
|
||||
p.velocity = current_velocities[i];
|
||||
}
|
||||
// Update positions and velocities
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
p.position = current_positions[i] + (k1_r[i] + 2.0 * k2_r[i] + 2.0 * k3_r[i] + k4_r[i]) / 6.0;
|
||||
p.velocity = current_velocities[i] + (k1_v[i] + 2.0 * k2_v[i] + 2.0 * k3_v[i] + k4_v[i]) / 6.0;
|
||||
}
|
||||
}
|
||||
|
||||
VelocityVerletSolver::VelocityVerletSolver(PenningTrap& trap_in, double dt_in)
|
||||
: Solver(trap_in, dt_in) {
|
||||
// Initialize accelerations
|
||||
int n = trap.num_particles();
|
||||
accelerations.resize(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
arma::vec F = trap.total_force(i, 0.0);
|
||||
accelerations[i] = F / p.mass;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a single Velocity Verlet integration step
|
||||
void VelocityVerletSolver::step() {
|
||||
int n = trap.num_particles();
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
p.position += p.velocity * dt + 0.5 * accelerations[i] * dt * dt;
|
||||
arma::vec new_acceleration = trap.total_force(i, steps * dt) / p.mass;
|
||||
p.velocity += 0.5 * (accelerations[i] + new_acceleration) * dt;
|
||||
accelerations[i] = new_acceleration;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialize Analytical solver
|
||||
AnalyticalSolver::AnalyticalSolver(PenningTrap& trap_in, double dt_in)
|
||||
: Solver(trap_in, dt_in) {
|
||||
// Check for validity of analytical solution
|
||||
if (trap_in.get_coulomb_interaction()) {
|
||||
throw std::runtime_error("Analytical solution not valid with Coulomb interactions enabled.");
|
||||
}
|
||||
if (trap_in.get_modified_fields()) {
|
||||
throw std::runtime_error("Analytical solution not valid with modified external fields.");
|
||||
}
|
||||
for (int i = 0; i < trap_in.num_particles(); i++) {
|
||||
if (trap_in.get_particle(i).velocity(0) != 0 || trap_in.get_particle(i).velocity(2) != 0) {
|
||||
throw std::runtime_error("Analytical solution not valid for non-zero initial velocity in x or z direction.");
|
||||
}
|
||||
if (trap_in.get_particle(i).position(1) != 0) {
|
||||
throw std::runtime_error("Analytical solution not valid for non-zero initial position in y direction.");
|
||||
}
|
||||
}
|
||||
|
||||
trap = trap_in;
|
||||
dt = dt_in * constants::second;
|
||||
steps = 0;
|
||||
int n = trap.num_particles();
|
||||
w_0.resize(n);
|
||||
w_z.resize(n);
|
||||
w_plus.resize(n);
|
||||
w_minus.resize(n);
|
||||
A_plus.resize(n);
|
||||
A_minus.resize(n);
|
||||
z_0.resize(n);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
double q = p.charge;
|
||||
double m = p.mass;
|
||||
double B0 = trap.B0;
|
||||
double V0 = trap.V0;
|
||||
double d = trap.d;
|
||||
|
||||
w_0[i] = q * B0 / m;
|
||||
w_z[i] = std::sqrt(2 * q * V0 / (m * d * d));
|
||||
double discriminant = w_0[i] * w_0[i] - 2 * w_z[i] * w_z[i];
|
||||
if (discriminant < 0) {
|
||||
throw std::runtime_error("Analytical solution not valid: discriminant is negative.");
|
||||
}
|
||||
w_plus[i] = (w_0[i] + std::sqrt(discriminant)) / 2.0;
|
||||
w_minus[i] = (w_0[i] - std::sqrt(discriminant)) / 2.0;
|
||||
|
||||
arma::vec r = p.position;
|
||||
arma::vec v = p.velocity;
|
||||
|
||||
// Initial conditions
|
||||
double x0 = r(0);
|
||||
double y0 = r(1);
|
||||
double z0 = r(2);
|
||||
double vx0 = v(0);
|
||||
double vy0 = v(1);
|
||||
double vz0 = v(2);
|
||||
|
||||
// Calculate amplitudes
|
||||
A_plus[i] = (vy0 + w_minus[i] * x0) / (w_plus[i] - w_minus[i]);
|
||||
A_minus[i] = -(vy0 + w_plus[i] * x0) / (w_plus[i] - w_minus[i]);
|
||||
z_0[i] = z0;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a single Analytical "integration" step
|
||||
void AnalyticalSolver::step() {
|
||||
int n = trap.num_particles();
|
||||
double t = steps * dt;
|
||||
for (int i = 0; i < n; i++) {
|
||||
Particle& p = trap.get_particle(i);
|
||||
double x = -A_plus[i] * std::cos(w_plus[i] * t) - A_minus[i] * std::cos(w_minus[i] * t);
|
||||
double y = A_plus[i] * std::sin(w_plus[i] * t) + A_minus[i] * std::sin(w_minus[i] * t);
|
||||
double z = z_0[i] * std::cos(w_z[i] * t);
|
||||
|
||||
double vx = +A_plus[i] * w_plus[i] * std::sin(w_plus[i] * t) + A_minus[i] * w_minus[i] * std::sin(w_minus[i] * t);
|
||||
double vy = A_plus[i] * w_plus[i] * std::cos(w_plus[i] * t) + A_minus[i] * w_minus[i] * std::cos(w_minus[i] * t);
|
||||
double vz = -z_0[i] * w_z[i] * std::sin(w_z[i] * t);
|
||||
|
||||
p.position = arma::vec({x, y, z});
|
||||
p.velocity = arma::vec({vx, vy, vz});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Convert vector of matrices to a 3D cube for easier data handling
|
||||
arma::cube to_cube(const std::vector<arma::mat>& vec) {
|
||||
if (vec.empty()) {
|
||||
return arma::cube();
|
||||
}
|
||||
int rows = vec[0].n_rows;
|
||||
int cols = vec[0].n_cols;
|
||||
int slices = vec.size();
|
||||
arma::cube cube_data(rows, cols, slices);
|
||||
for (size_t i = 0; i < vec.size(); ++i) {
|
||||
cube_data.slice(i) = vec[i];
|
||||
}
|
||||
return cube_data;
|
||||
}
|
||||
|
||||
// Save cube data to a file
|
||||
void save_to_file(const std::string& filename, const arma::cube& data) {
|
||||
data.save(filename, arma::raw_ascii);
|
||||
}
|
||||
|
||||
void save_to_file(const std::string& filename, const arma::vec& data) {
|
||||
arma::mat mat_data = arma::conv_to<arma::mat>::from(data);
|
||||
mat_data.save(filename, arma::raw_ascii);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
#include <armadillo>
|
||||
#include "classes.hpp"
|
||||
#include "solvers.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "argparse/argparse.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
argparse::ArgumentParser program("two_particles");
|
||||
program.add_argument("-N", "--steps")
|
||||
.help("Number of time steps")
|
||||
.default_value(4000)
|
||||
.scan<'i', int>();
|
||||
program.add_argument("-n", "--num_particles")
|
||||
.help("Number of particles (either 1 or 2)")
|
||||
.default_value(2)
|
||||
.scan<'i', int>();
|
||||
program.add_argument("-i", "--disable-interactions")
|
||||
.help("Disable Coulomb interactions")
|
||||
.default_value(true)
|
||||
.implicit_value(false);
|
||||
program.add_argument("-E", "--euler")
|
||||
.help("Use Euler solver instead of RK4")
|
||||
.flag();
|
||||
program.add_argument("-A", "--analytical")
|
||||
.help("Use Analytical solver instead of RK4 (only valid for particles without interactions and specific initial conditions)")
|
||||
.flag();
|
||||
program.add_argument("-V", "--velocity-verlet")
|
||||
.help("Use Velocity Verlet solver instead of RK4")
|
||||
.flag();
|
||||
|
||||
try {
|
||||
program.parse_args(argc, argv);
|
||||
} catch (const runtime_error& err) {
|
||||
cerr << err.what() << endl;
|
||||
cerr << program.help().str() << endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
double N = program.get<int>("--steps");
|
||||
int n_particles = program.get<int>("--num_particles");
|
||||
bool interactions = program.get<bool>("--disable-interactions");
|
||||
bool use_euler = program.get<bool>("--euler");
|
||||
bool use_analytical = program.get<bool>("--analytical");
|
||||
bool use_velocity_verlet = program.get<bool>("--velocity-verlet");
|
||||
|
||||
double B_0 = 1.0; // Tesla
|
||||
double V_0 = 0.025; // Volt
|
||||
double d = 500e-6; // meter
|
||||
|
||||
PenningTrap trap(B_0, V_0, d);
|
||||
if (interactions) {
|
||||
trap.enable_interactions();
|
||||
}
|
||||
else {
|
||||
trap.disable_interactions();
|
||||
}
|
||||
cout << trap << endl;
|
||||
|
||||
|
||||
Particle p1(arma::vec({20e-6, 0, 20e-6}), arma::vec({0, 25, 0}), 40.078 * constants::amu, 1 * constants::elementary_charge);
|
||||
Particle p2(arma::vec({25e-6, 25e-6, 0}), arma::vec({0, 40, 5}), 40.078 * constants::amu, 1 * constants::elementary_charge);
|
||||
|
||||
trap.add_particle(p1);
|
||||
if (n_particles > 1) {
|
||||
trap.add_particle(p2);
|
||||
}
|
||||
|
||||
double dt = 50e-6 / N; // seconds
|
||||
|
||||
if (use_euler) {
|
||||
EulerSolver solver(trap, dt);
|
||||
solver.simulate(N);
|
||||
solver.save("results/two_particles_" + std::to_string(n_particles) + (interactions ? "_with_interactions_" : "_no_interactions_") + std::to_string(N) + "_steps_euler");
|
||||
vector<arma::mat> positions = solver.get_positions();
|
||||
cout << "Final position of particle 1: " << endl;
|
||||
cout << positions.back().col(0) << endl;
|
||||
if (n_particles > 1) {
|
||||
cout << "Final position of particle 2: " << endl;
|
||||
cout << positions.back().col(1) << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (use_analytical) {
|
||||
AnalyticalSolver solver(trap, dt);
|
||||
solver.simulate(N);
|
||||
solver.save("results/two_particles_" + std::to_string(n_particles) + (interactions ? "_with_interactions_" : "_no_interactions_") + std::to_string(N) + "_steps_analytical");
|
||||
vector<arma::mat> positions = solver.get_positions();
|
||||
cout << "Final position of particle 1: " << endl;
|
||||
cout << positions.back().col(0) << endl;
|
||||
if (n_particles > 1) {
|
||||
cout << "Final position of particle 2: " << endl;
|
||||
cout << positions.back().col(1) << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (use_velocity_verlet) {
|
||||
VelocityVerletSolver solver(trap, dt);
|
||||
solver.simulate(N);
|
||||
solver.save("results/two_particles_" + std::to_string(n_particles) + (interactions ? "_with_interactions_" : "_no_interactions_") + std::to_string(N) + "_steps_velocity_verlet");
|
||||
vector<arma::mat> positions = solver.get_positions();
|
||||
cout << "Final position of particle 1: " << endl;
|
||||
cout << positions.back().col(0) << endl;
|
||||
if (n_particles > 1) {
|
||||
cout << "Final position of particle 2: " << endl;
|
||||
cout << positions.back().col(1) << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
RK4Solver solver(trap, dt);
|
||||
solver.simulate(N);
|
||||
solver.save("results/two_particles_" + std::to_string(n_particles) + (interactions ? "_with_interactions_" : "_no_interactions_") + std::to_string(N) + "_steps_rk4");
|
||||
vector<arma::mat> positions = solver.get_positions();
|
||||
cout << "Final position of particle 1: " << endl;
|
||||
cout << positions.back().col(0) << endl;
|
||||
if (n_particles > 1) {
|
||||
cout << "Final position of particle 2: " << endl;
|
||||
cout << positions.back().col(1) << endl;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
@@ -198,6 +210,7 @@ dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "pandas" },
|
||||
{ name = "scipy" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -207,6 +220,7 @@ requires-dist = [
|
||||
{ name = "numpy", specifier = ">=2.3.2" },
|
||||
{ name = "pandas", specifier = ">=2.3.2" },
|
||||
{ name = "scipy", specifier = ">=1.16.1" },
|
||||
{ name = "typer", specifier = ">=0.19.2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -367,6 +381,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "4.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib"
|
||||
version = "3.10.5"
|
||||
@@ -426,6 +452,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nest-asyncio"
|
||||
version = "1.6.0"
|
||||
@@ -757,6 +792,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/a4/7ee652ea1c77d872f5d99ed937fa8bbd1f6f4b7a39a6d3a0076c286e0c3e/pyzmq-27.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:4108785f2e5ac865d06f678a07a1901e3465611356df21a545eeea8b45f56265", size = 574901, upload-time = "2025-08-21T04:22:17.423Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scipy"
|
||||
version = "1.16.1"
|
||||
@@ -804,6 +852,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
@@ -855,6 +912,30 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "rich" },
|
||||
{ name = "shellingham" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
|
||||
Reference in New Issue
Block a user