Exact Solutions#
Here we present the various exact solutions described in
gpe/exact_solutions.py
Harmonic Oscillator#
Here we construct an exact solution for a trapped BEC in a harmonic oscillator:
This potential has the non-linear piece subtracted so that the constructed solution is an exact solution with zero chemical potential (i.e. a stationary state) and energy:
%pylab inline --no-import-all
from gpe.imports import *
from gpe.minimize import MinimizeState
from gpe.exact_solutions import HarmonicOscillator
%pylab is deprecated, use %matplotlib inline and import the required libraries.
Populating the interactive namespace from numpy and matplotlib
[I 03:36:35 numexpr.utils] NumExpr defaulting to 2 threads.
[I 03:36:36 root] Patching zope.interface.document.asReStructuredText to format code
s = HarmonicOscillator(n0=1.0)
s.plot()
assert abs(s.compute_dy_dt(s.copy())[...]).max() < 1e-14
assert np.allclose(s.get_energy(), -s.g * s.n0**2 * s.sigma * np.sqrt(np.pi / 2) / 2)
We can also do this in higher dimensions:
This potential has the non-linear piece subtracted so that the constructed solution is an exact solution with zero chemical potential (i.e. a stationary state) and energy:
Travelling Waves#
The standard GPE admits a family of periodic traveling waves with an analytic form:
where the wave is moving with speed \(v\). We base our solution on the presentation of Hoefer and El El:2016.
The GPE can be expressed in terms of \(\psi(x, t)\) or in terms of \(\psi_v(x_v) = e^{-\I\mu t/\hbar} \psi_v(x- vt)\):
Note: this is not the typical Galilean transformation for quantum mechanical systems which includes an extra factor of \(e^{\I (mvx +mv^2t/2)\hbar}\). This additional phase allows one to shift the momentum, completing the square of the kinetic energy, and removing the \(v\op{p}\) linear term at the expense of shifting the chemical potential. The present form, however, is more general, and works with arbitrary dispersion, so we maintain it.
Hoefer and El El:2016 set \(\hbar=m=g=1\) which defines the following dimensions:
Using the Madelung transform:
The solution can be expressed as:
(Note: \(k=\sqrt{m}\) for \(\sn(z,k)\) in some CASs. We use \(\sn(z;m)\) and \(K(m)\) here)
The full solution (with proper coefficients) is thus:
%pylab inline --no-import-all
from gpe.imports import *
import gpe.exact_solutions
reload(gpe.exact_solutions)
from gpe.exact_solutions import TravellingWaves
# Stationary wave
args = dict(n0=0.9, n1=1.0, Lx=10.0, Nx=16)
s0 = TravellingWaves(v_p=0, **args)
# Travelling wave in the lab frame
s1 = TravellingWaves(v_p=0.6047197603, v_x=0, **args)
# Travelling wave in a co-moving frame.
s2 = TravellingWaves(v_p=0.6047197603, **args)
for ss in evolves([s0, s1, s2], t_max=50.0):
plt.clf()
for s in ss:
s.plot()
# s.twist_x
# s.mu, s.get_mu().real, s._twist
%pylab is deprecated, use %matplotlib inline and import the required libraries.
Populating the interactive namespace from numpy and matplotlib
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[4], line 16
12 # Travelling wave in the lab frame
13 s1 = TravellingWaves(v_p=0.6047197603, v_x=0, **args)
14
15 # Travelling wave in a co-moving frame.
---> 16 s2 = TravellingWaves(v_p=0.6047197603, **args)
17
18 for ss in evolves([s0, s1, s2], t_max=50.0):
19 plt.clf()
File ~/checkouts/readthedocs.org/user_builds/gpe/checkouts/latest/src/gpe/exact_solutions.py:191, in TravellingWaves.__init__(self, Nx, Lx, n0, n1, m, g, hbar, v_p, v_x, twist, **kw)
189 if twist is None:
190 x = np.linspace(-Lx / 2.0, Lx / 2.0, 10000)
--> 191 _psi, twist = self.psi_exact(x=x, Lx=Lx)
192 self._twist = twist
194 if v_x is None:
File ~/checkouts/readthedocs.org/user_builds/gpe/checkouts/latest/src/gpe/exact_solutions.py:230, in TravellingWaves.psi_exact(self, x, Lx)
228 theta = [0]
229 for _n in range(1, len(x)):
--> 230 theta.append(theta[-1] + scipy.integrate.quad(dtheta, x[_n - 1], x[_n])[0])
231 theta = np.array(theta)
232 theta -= theta[len(x) // 2]
File ~/checkouts/readthedocs.org/user_builds/gpe/conda/latest/lib/python3.14/site-packages/scipy/integrate/_quadpack_py.py:479, in quad(func, a, b, args, full_output, epsabs, epsrel, limit, points, weight, wvar, wopts, maxp1, limlst, complex_func)
476 return retval
478 if weight is None:
--> 479 retval = _quad(func, a, b, args, full_output, epsabs, epsrel, limit,
480 points)
481 else:
482 if points is not None:
File ~/checkouts/readthedocs.org/user_builds/gpe/conda/latest/lib/python3.14/site-packages/scipy/integrate/_quadpack_py.py:626, in _quad(func, a, b, args, full_output, epsabs, epsrel, limit, points)
624 if points is None:
625 if infbounds == 0:
--> 626 return _quadpack._qagse(func,a,b,args,full_output,epsabs,epsrel,limit)
627 else:
628 return _quadpack._qagie(func, bound, infbounds, args, full_output,
629 epsabs, epsrel, limit)
File ~/checkouts/readthedocs.org/user_builds/gpe/checkouts/latest/src/gpe/exact_solutions.py:225, in TravellingWaves.psi_exact.<locals>.dtheta(x)
222 _a = self.v_p / self.v_unit
223 C = self._C * self.n_unit
--> 225 def dtheta(x):
226 return _a + C / self.n_exact(x)
228 theta = [0]
KeyboardInterrupt:
As a simple test, we consider the phonon limit \(a = \epsilon \ll 1\). In this limit, we have
consider a \(v=0\) solution with \(\rho_\min = \rho_\max = 1\):
from gpe.exact_solutions import K
s._C
s = TravellingWaves(n0=1.0, n1=1.0, Lx=np.pi, Nx=64, v_p=0.0)
s.plot()
s.twist_x
s._mu, s.get_mu().real, s._twist
e = EvolverABM(s, dt=0.2 * s.t_scale)
with NoInterrupt() as interrupted:
while not interrupted:
e.evolve(1000)
plt.clf()
e.y.plot()
display(plt.gcf())
plt.close("all")
clear_output(wait=True)
x = s.xyz[0]
c = np.sqrt(s.g * s.n1 / s.m)
v = 0.5 * c
u = np.sqrt(c**2 - v**2)
n0 = s.n1 * (v**2 / c**2)
l = s.hbar / s.m / u
psi_soliton = v / c * 1j + u / c * np.tanh(x / l)
twist = np.angle(psi_soliton)[-1] - np.angle(psi_soliton)[0]
s1 = TravellingWaves(n0=n0, n1=s.n1, Lx=s.basis.Lx, Nx=s.basis.Nx, v_p=0, twist=twist)
s1[...] = psi_soliton
s1.plot()
e = EvolverABM(s, dt=0.2 * s.t_scale)
e1 = EvolverABM(s1, dt=0.2 * s1.t_scale)
pe = None
with NoInterrupt() as interrupted:
while not interrupted:
e.evolve(1000)
e1.evolve(1000)
plt.clf()
e.y.plot()
e1.y.plot()
display(plt.gcf())
plt.close("all")
clear_output(wait=True)
Bright Soliton#
Here we demonstrate the analytic bright-soliton for a GPE with attractive interactions. This object moves at a specified speed \(v\), and we consider the solution in three frames: comoving (the soliton is stationary), lab frame (the soliton makes a single oscillation in time \(T=L/v\)) and a frame moving backwards at the same speed (the soliton crosses the box twice in time \(T\)).
This forms the basis of gpe/tests/test_bec.py::test_StateTwist_x_v_x and verifies that the frame velocity argument of StateTwist_x works. Because the density vanishes at the boundaries, this does not test the twist.
import sympy
from sympy import Eq, var, I, exp, cosh, sqrt, Abs
x, t, g = var(["x", "t", "g"], real=True)
eta, n0, mu, m, hbar = var(["eta", "n_0", "mu", "m", "hbar"], positive=True)
mu = -(hbar**2) * eta**2 / 2 / m
g = 2 * mu / n0
psi = exp(mu * t / I / hbar) * sqrt(n_0) / cosh(eta * x)
n = Abs(psi) ** 2
display(Eq(sympy.S("psi"), psi))
Hpsi = -(hbar**2) * psi.diff(x, x) / 2 / m + g * n * psi
display(Eq(sympy.S("H*psi"), Hpsi))
(Hpsi - I * hbar * psi.diff(t)).simplify()
%pylab inline --no-import-all
from gpe.imports import *
import gpe.exact_solutions
reload(gpe.exact_solutions)
from gpe.exact_solutions import BrightSoliton
Nx = 32
Lx = 10.0
v = 2.0
args = dict(Nx=Nx, Lx=Lx, v=v, sigma=1)
s0 = BrightSoliton(v_x=0, **args)
s1 = BrightSoliton(v_x=-v, **args)
s2 = BrightSoliton(v_x=v, **args)
for ss in evolves([s0, s1, s2], t_max=Lx / v):
plt.clf()
for s in ss:
s.plot()
1D GPE (NLSEQ)#
Here we consider the exact solutions to the 1D GPE via the inverse scattering method (ISM). These follow from a so-called Lax representation, which can be expressed as follows. Consider two linear operators \(\op{L}(\lambda)\) and
that commute:
differential equation
We start by expressing the problem as
Define