Solvers¶
Generated from the docstrings in the source code.
split_operator
¶
Generic split-operator propagator for 1D, 2D and 3D grids.
One solver covers:
- 2nd-order Strang splitting and 4th-order Yoshida (triple-jump) composition,
- explicitly time-dependent potentials V(r, t),
- the cubic Gross–Pitaevskii nonlinearity g|ψ|²ψ (BEC mean field, solitons),
- imaginary-time propagation (ground states),
- a rotating frame, H → H − ΩL_z (2D/3D, rotation about the z axis),
- phenomenological damping, (i − γ)ℏ ∂ψ/∂t = Hψ, with the norm held fixed,
- CPU (NumPy/SciPy) or CUDA GPU (CuPy) execution.
Time-dependent potentials are handled by treating t as an extra coordinate: the Strang step S(h) applies exp(−i V(t) h/2ℏ), exp(−i T h/ℏ), exp(−i V(t+h) h/2ℏ). This is a symmetric 2nd-order method, so the Yoshida composition S(w₁h) S(w₀h) S(w₁h) is 4th-order accurate.
The nonlinear sub-flow i ℏ ∂ψ/∂t = g|ψ|²ψ leaves |ψ| unchanged and is therefore integrated exactly by the phase factor exp(−i g|ψ|² h/2ℏ). In imaginary time the state is kept at unit norm and the nonlinear term uses the normalised density g|ψ|²/‖ψ‖² evaluated at the sub-step midpoint, which keeps the converged GP ground state 2nd-order accurate in dτ.
With rotation, −ΩL_z = Ω(y p_x − x p_y) is split together with the kinetic energy into H_x = p_x²/2m + Ω y p_x, diagonal in (k_x, y), and H_yz = (p_y² + p_z²)/2m − Ω x p_y, diagonal in (x, k_y, k_z). Each factor is applied exactly with 1D/2D FFTs, and the symmetric product e^{H_x h/2} e^{H_yz h} e^{H_x h/2} keeps the step 2nd order (and symmetric, so the Yoshida composition stays 4th order).
SplitOperatorSolver(grid, potential, dt: float, hbar: float = 1.0, m: float = 1.0, order: int = 2, nonlinearity: float = 0.0, imaginary_time: bool = False, absorber=None, workers: int = -1, t0: float = 0.0, rotation: float = 0.0, damping: float = 0.0, backend='numpy', device_id: int = 0)
¶
Bases: Solver
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
(Grid1D, Grid2D or Grid3D)
|
|
required |
potential
|
Potential or None
|
Static or time-dependent ( |
required |
dt
|
float
|
Time step (imaginary-time step dτ when |
required |
hbar
|
float
|
Reduced Planck constant and particle mass. |
1.0
|
m
|
float
|
Reduced Planck constant and particle mass. |
1.0
|
order
|
(2, 4)
|
Splitting order: 2 = Strang, 4 = Yoshida triple jump. |
2
|
nonlinearity
|
float
|
Gross–Pitaevskii coupling g in V_eff = V + g|ψ|². With ψ normalised to 1, g includes the particle number (g = 4πℏ²aN/m in 3D). |
0.0
|
imaginary_time
|
bool
|
Propagate in imaginary time t → −iτ and renormalise after every step. Converges to the lowest state not orthogonal to the initial guess. |
False
|
rotation
|
float
|
Angular velocity Ω of the frame about the z axis (2D/3D grids). The frame rotates counter-clockwise for Ω > 0, so states with positive L_z are favoured. The potential is given in the rotating frame. |
0.0
|
damping
|
float
|
Dissipation γ ≥ 0 in (i − γ)ℏ ∂ψ/∂t = Hψ (Choi et al. 1998, Tsubota et al. 2002). The norm is restored after every step, which plays the role of the chemical potential. Order 2 only. |
0.0
|
absorber
|
AbsorbingBoundaryLayer
|
Mask applied after every step. |
None
|
workers
|
int
|
Threads for scipy.fft (-1 = all cores); ignored on the GPU. |
-1
|
t0
|
float
|
Initial time (relevant for time-dependent potentials). |
0.0
|
backend
|
(numpy, cupy, auto)
|
'cupy' runs on a CUDA GPU in double precision; 'auto' uses the GPU
when CuPy and a CUDA device are available. |
'numpy'
|
device_id
|
int
|
CUDA device for the 'cupy' backend. |
0
|
Notes
Every factor is unitary, but for nonlinear problems on a uniform (or
broad) background the split-step method has a parametric numerical
instability when the kinetic phase of the highest grid wavenumber per
(sub)step exceeds π (Weideman & Herbst, SIAM J. Numer. Anal. 23, 485
(1986)). Keep ℏ k_max² / (2m) · |w| dt < π with k_max = π/dx, where
|w| ≤ 1.71 for order 4, otherwise round-off noise in the shortest
wavelengths can grow exponentially. Localised states (solitons, trapped
condensates) are much less sensitive.
Source code in quantumlab/solvers/split_operator.py
t: float
property
¶
Current simulation time.
update_operators()
¶
(Re)build the cached kinetic and static-potential propagators.
Source code in quantumlab/solvers/split_operator.py
potential_at(t: float) -> np.ndarray
¶
V(r, t) as a NumPy array on the host.
evolve(wavefunction, n_steps: int, callback=None, callback_interval: int = 1)
¶
Advance n_steps steps. If given, callback(step, t, wf) is called
every callback_interval steps (step counted from 1 within this call).
On the GPU, ψ stays on the device except for those callbacks.
Source code in quantumlab/solvers/split_operator.py
multicomponent
¶
Coupled Gross–Pitaevskii equations for multi-component condensates (e.g. two hyperfine states of ⁸⁷Rb, or two atomic species of equal mass):
iℏ ∂ψ_i/∂t = [ −ℏ²∇²/2m + V_i(r) + Σ_j g_ij |ψ_j|² − ΩL_z ] ψ_i
The kinetic (and rotation) factor is the one of SplitOperatorSolver and is
applied to every component. The potential sub-flow is local and, in real
time, leaves every |ψ_j| unchanged, so it is integrated exactly with the
densities frozen. In imaginary time or with damping each component keeps its
own norm N_i (the populations are fixed), and the coupled potential uses the
densities rescaled to those norms at the sub-step midpoint, as for a single
component, which keeps the converged states 2nd-order accurate in dτ.
CoupledGPESolver(grid, potentials: Sequence, couplings, dt: float, hbar: float = 1.0, m: float = 1.0, order: int = 2, imaginary_time: bool = False, rotation: float = 0.0, damping: float = 0.0, workers: int = -1, backend='numpy', device_id: int = 0, t0: float = 0.0)
¶
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
(Grid1D, Grid2D or Grid3D)
|
|
required |
potentials
|
sequence of Potential or None
|
One static potential per component (None means free space). |
required |
couplings
|
(n, n) array_like
|
Symmetric interaction matrix g_ij. The nonlinear term uses the actual densities |ψ_j|², so g_ij is per particle when ψ_j is normalised to its particle number. |
required |
dt
|
float
|
Time step (dτ in imaginary time). |
required |
order
|
int
|
As in |
2
|
imaginary_time
|
int
|
As in |
2
|
rotation
|
int
|
As in |
2
|
damping
|
int
|
As in |
2
|
workers
|
int
|
As in |
2
|
backend
|
int
|
As in |
2
|
device_id
|
int
|
As in |
2
|
t0
|
int
|
As in |
2
|
Source code in quantumlab/solvers/multicomponent.py
evolve(states, n_steps: int, callback=None, callback_interval: int = 1)
¶
Advance all components n_steps steps; callback(step, t, states)
is called every callback_interval steps with a tuple of wavefunctions.
Source code in quantumlab/solvers/multicomponent.py
backends
¶
Array backends for the split-operator solver: NumPy/SciPy on the CPU or CuPy on a CUDA GPU. A backend bundles the array module, the FFTs and the host/device transfer functions, so the solver code is identical for both.
cupy_available() -> bool
¶
True if CuPy is importable and at least one CUDA device is visible.
get_backend(backend='numpy', workers: int = -1, device_id: int = 0) -> ArrayBackend
¶
Resolve a backend specification.
'numpy' (default) — SciPy FFTs on the CPU;
'cupy' — CUDA GPU, raises ImportError/RuntimeError if unavailable;
'auto' — CuPy when a CUDA device is present, otherwise NumPy;
an ArrayBackend instance is returned unchanged.
Source code in quantumlab/solvers/backends.py
split_step_1d
¶
split_step_2d
¶
SplitStep2DSolver(grid, potential, dt: float, hbar: float = 1.0, m: float = 1.0)
¶
Bases: Solver
2D Split-Step Fourier Method (SSFM) solver for the time-dependent Schrödinger equation on a 2D spatial grid.
The symmetric splitting operator is applied as: ψ(t+dt) = U_V · FFT2⁻¹[ U_T · FFT2[ U_V · ψ(t) ] ]
where U_V = exp(-i·V·dt/(2ℏ)) and U_T = exp(-i·T_k·dt/ℏ).
Source code in quantumlab/solvers/split_step_2d.py
update_operators()
¶
Pre-compute the position and momentum space propagators.
step(wavefunction: WaveFunction2D) -> WaveFunction2D
¶
Advance the wavefunction by one time step using SSFM.
Source code in quantumlab/solvers/split_step_2d.py
split_step_3d
¶
SplitStep3DSolver(grid, potential, dt: float, hbar: float = 1.0, m: float = 1.0, workers: int = -1)
¶
Bases: Solver
3D Split-Step Fourier Method (SSFM) solver for the time-dependent Schrödinger equation on a 3D spatial grid.
The symmetric Lie-Trotter-Suzuki splitting is applied as: ψ(t+dt) = U_V/2 · IFFT3[ U_T · FFT3[ U_V/2 · ψ(t) ] ]
where U_V = exp(-i·V·dt/(2ℏ)) and U_T = exp(-i·T_k·dt/ℏ), with T_k = ℏ²(K_x² + K_y² + K_z²) / (2m).
This scheme is 2nd-order accurate in time (global error O(dt²)) and exactly unitary when no absorbing boundary is applied.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
Grid3D
|
The 3D spatial grid. |
required |
potential
|
Potential
|
The potential energy operator V(x, y, z). |
required |
dt
|
float
|
Time step. |
required |
hbar
|
float
|
Reduced Planck constant (natural units). |
1.0
|
m
|
float
|
Particle mass (natural units). |
1.0
|
workers
|
int
|
Number of CPU threads for scipy FFT. -1 uses all available cores. |
-1
|
Source code in quantumlab/solvers/split_step_3d.py
update_operators()
¶
Pre-compute the position-space and momentum-space propagators.
step(wavefunction: WaveFunction3D) -> WaveFunction3D
¶
Advance the wavefunction by one time step using symmetric SSFM.
Source code in quantumlab/solvers/split_step_3d.py
split_step_3d_gpu
¶
3D Split-Step Fourier solver with CuPy GPU acceleration & zero PCI-E memory bottleneck. Automatic fallback to CPU (SciPy) if CuPy is unavailable. Deterministic: same grid, same potential, same dt -> bit-identical evolution.
SplitStep3DGPU(grid, potential, dt: float, hbar: float = 1.0, m: float = 1.0, device_id: int = 0, renormalize_interval: int = 0)
¶
Bases: Solver
GPU-accelerated 3D SSFM with double-precision complex FP64 support.
Grid operators are transferred to GPU once at init.
Supports in-VRAM batch stepping via step_sequence() or step_gpu() to eliminate PCI-E transfer bottlenecks.
Source code in quantumlab/solvers/split_step_3d_gpu.py
update_operators() -> None
¶
Re-compute potential and kinetic evolution operators.
Source code in quantumlab/solvers/split_step_3d_gpu.py
step(wavefunction: WaveFunction3D) -> WaveFunction3D
¶
Single-step propagation returning CPU WaveFunction3D object.
Source code in quantumlab/solvers/split_step_3d_gpu.py
step_sequence(initial_wf: WaveFunction3D, steps: int) -> WaveFunction3D
¶
In-VRAM multi-step propagation eliminating PCI-E transfer bottlenecks. Transfers initial psi to GPU ONCE, steps N times on GPU, and downloads final state.