This is a simple numerical solver for the nonlinear Schrödinger equations using a spectral approach.
My aim is to design it from scratch as much as possible as a learning experience. For this reason, it is less efficient that solvers built on existing well-optimised crates like num
for defining the Complex
type and fftw3
for the Fourier transforms. If you would like to use this project but performance is an issue, please let me know—I'll be happy to write a version using faster libraries than my custom ones. (Or, of course, feel free to do it yourself and submit a pull request!)
The Fourier transform engine is primarily designed for power-of-two sizes. It should work with non-power-of-two sizes too, but with worse performances.
The outside-facing part of the crate can be imported using
use nonlinear_schrodinger::prelude::*;
This line will import
R
: The real type used by the crate (currentlyf64
),C
: The complex type used by the crate (currently memory equivalent to[C; 2]
),Solver
: the solver class,SolverError
: An error type returned by aSolver
instance when an operation fails.
A new Solver
instance is built using the Solver::new
function, with signature
pub fn new(shape: Vec<usize>,
steps: Vec<R>,
potential_fun: Box<dyn Fn(&[R]) -> R>,
g: Box<dyn Fn(R) -> R>)
-> Result<Solver, SolverError>
It takes four arguments:
-
shape
: length of the grid in each space direction; for instance, ifshape
isvec![10, 20, 30]
, the solver will solve the non-linear Schrödinger equation on a 3-dimensional grid with shape 10 by 20 by 30; -
steps
: space step in each direction (must have the same length asshape
); -
potential_fun
: external potential as a function from$\mathbb{R}^N$ to$\mathbb{R}$ , where$N$ is the length ofshape
; -
g
: nonlinear term, as a function of the density.
A solver
instance has the following member functions:
pub fn eval_psi(&self, psi_fun: Box<dyn Fn(&[R]) -> C>) -> Vec<C>
: generate a discretized version of the functionpsi_fun
on the solver's numerical grid (mostly useful for setting the initial condition),pub fn mass(&self, psi: &[C]) -> Result<R, SolverError>
: compute the mass from the (discretized) condensate wave functionpsi
,pub fn momentum(&mut self, psi: &[C]) -> Result<Vec<R>, SolverError>
: compute the momentum from the (discretized) condensate wave functionpsi
,pub fn evolve(&mut self, psi: &mut [C], dt: R, nt: usize) -> Result<(), SolverError>
: evolve the initial configurationpsi
fornt
time steps of durationdt
each,pub fn evolve_i(&mut self, psi: &mut [C], dt: R, nt: usize) -> Result<(), SolverError>
: evolve the initial configurationpsi
fornt
time steps of durationdt
each in imaginary time (mostly useful for finding the lowest-energy configuration while preserving the other conserved quantities),pub fn evolve_oop(&mut self, psi0: &[C], dt: R, nt: usize) -> Result<(), SolverError>
: evolve the initial configurationpsi0
fornt
time steps of durationdt
each, without modifyingpsi0
,pub fn evolve_i_oop(&mut self, psi0: &[C], dt: R, nt: usize) -> Result<(), SolverError>
: evolve the initial configurationpsi0
fornt
time steps of durationdt
each in imaginary time, without modifyingpsi0
.
The crate also provides basic 1D and 2D plot functions, plotters::plot_1d
and plotters::plot_2d
, using the plotters
crate.
The function signature is:
pub fn plot_1d(x: &[Vec<R>],
y: &[Vec<R>],
fname: &str,
title: &str,
xlabel: &str,
ylabel: &str,
labels: &[String],
col1: (u8, u8, u8),
col2: (u8, u8, u8))
-> Result<(), Box<dyn std::error::Error>>
Arguments:
x
: array of vectors ofx
coordinates (1 for each curve),y
: array of vectors ofy
coordinates (1 for each curve;y
must have the same length asx
and, or eachi
between0
andx.len()-1
,y[i]
mush have the same length asx[i]
),fname
: file name to use for saving the plot,title
: the plot title,xlabel
: label for the horizontal axis,ylabel
: label for the vertical axis,labels
: label for each curve (must have the same length asx
),col1
: colour of the first curve,col2
: colour of the last curve (ifx.len() > 1
).
The function signature is:
pub fn plot_2d(z: &[R],
fname: &str,
x_min: R,
x_max: R,
y_min: R,
y_max: R,
nx: usize,
ny: usize,
show_axes: bool)
-> Result<(), Box<dyn std::error::Error>>
Arguments:
z
: z coordinate for each point,fname
: file name to use for saving the plot,x_min
: minimum value of the x coordinate,x_max
: maximum value of the x coordinate,y_min
: minimum value of the y coordinate,y_max
: maximum value of the y coordinate,nx
: number of points in the x direction,ny
: number of points in the y direction,show_axes
: whether to show the x and y axes.
Running the examples requires a Rust 2021 compiler (see the rust-lang website for installation instructions). Cargo is also recommended. Each example can be run using the command
cargo run --release --example [example]
One-dimensional simulation with a harmonic potential and a Gaussian initial condition. The evolution of the density is saved as an svg
file (Results/1d_harmonic_gaussian.svg
).
One-dimensional imaginary-time simulation with a harmonic potential and a Gaussian initial condition. The evolution of the density is saved as an svg
file (Results/1d_harmonic_imag.svg
).
Two-dimensional simulation with a symmetric harmonic potential and a Gaussian initial condition. The initial and final densities are saved as png
files (Results/2d_harmonic_gaussian_initial_density.png
and Results/2d_harmonic_gaussian_final_density.png
).
Two-dimensional simulation with a symmetric harmonic potential and an initial condition with two Gaussians. Snapshots of the density profile are saved in the folder Results/2d_harmonic_two_gaussians
.
- custom
Complex
type - custom 1D Fourier transform
- multi-dimensional fft on CPU
- implementation of a first solver
- implement a 1D visualiser
- implement a 2D visualiser
- test the solver on a 1D quasi-stationary solution
- test the solver on a 2D quasi-stationary solution
- add documentation on the nonlinear Schrödinger equations
- 1D fft using OpenCL
- multi-dimensional fft using OpenCL
- link the OpenCL fft to the solver
- more tests
- benchmarks