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 aSolverinstance 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, ifshapeisvec![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_funon 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 configurationpsifornttime steps of durationdteach,pub fn evolve_i(&mut self, psi: &mut [C], dt: R, nt: usize) -> Result<(), SolverError>: evolve the initial configurationpsifornttime steps of durationdteach 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 configurationpsi0fornttime steps of durationdteach, without modifyingpsi0,pub fn evolve_i_oop(&mut self, psi0: &[C], dt: R, nt: usize) -> Result<(), SolverError>: evolve the initial configurationpsi0fornttime steps of durationdteach 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 ofxcoordinates (1 for each curve),y: array of vectors ofycoordinates (1 for each curve;ymust have the same length asxand, or eachibetween0andx.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
Complextype - 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