import mmf_setup
mmf_setup.nbinit()
This cell adds /home/docs/checkouts/readthedocs.org/user_builds/gpe/checkouts/latest/src to your path, and contains some definitions for equations and some CSS for styling the notebook. If things look a bit strange, please try the following:
- Choose "Trust Notebook" from the "File" menu.
- Re-execute this cell.
- Reload the notebook.
Travelling Waves#
A traveling wave with velocity \(v\) has the following form:
By appropriately adjusting \(\mu\) we may set \(\omega = 0\), hence we may drop the factor \(e^{-\I\omega t}\) from the wavefunction:
The last equation above now represents a stationary solution in a moving frame with coordinate \(y = x-vt\):
where \(p_v = mv\). Finally, we may recast this in terms of the original GPE by transforming \(\psi(y)\) as follows:
If the original function \(\psi(y+L) = \psi(y)\) was periodic, then \(\tilde{\psi}(y+L) = e^{\I p_v L/\hbar}\tilde{\psi}(y)\). In other words, twisted boundary conditions should be applied with a twist \(\theta = p_vL/\hbar\).
GPE#
Here we consider the problem of finding traveling wave solutions in a BEC. From a numerical perspective, it is highly beneficial if the problem can be stated in terms of a well-defined minimization problem. To start, we consider the available analytic solution for the conventional GPE. These solutions are presented in [El:2016]:
The physical interpretations are:
\(a\): Amplitude
\(v\): Phase velocity
\(u\):
A special limit is when \(m=1\):
First we test these. To match units we set \(\hbar = m = g = 1\).
K(0.9)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 1
----> 1 K(0.9)
NameError: name 'K' is not defined
from gpe.imports import *
from scipy.special import ellipj, ellipk
import gpe.bec
def sn(u, m):
return ellipj(u, m)[0]
def K(m):
return ellipk(m)
class State(gpe.bec.State):
def __init__(self, Nx=32, rs=[0.1, 0.2, 0.3, 0.4], xi0=0):
r1, r2, r3, r4 = rs = sorted(rs)
k = np.sqrt((r4 - r2) * (r3 - r1))
m = (r2 - r1) * (r4 - r3) / (r4 - r2) / (r3 - r1)
v = sum(rs) / 2.0
L = 2.0 * K(m) / k
gpe.bec.State.__init__(self, Nxyz=(Nx,), Lxyz=(L,), m=1.0, g=1.0, hbar=1.0)
x = self.xyz[0]
self.T = abs(L / v)
t = 0
xi = x - v * t - xi0
a = (r4 - r3) * (r2 - r1)
# a = m**2*k**2
# rho_0 = (r4-r3-r2+r1)**2/4.0
rho_0 = (r4 - r3 + r2 - r1) ** 2 / 4.0
rho = rho_0 + a * sn(k * xi, m) ** 2
self.a_ = self.a = a
self.m_ = m
self.k_ = self.k = k
self.rho_0_ = self.rho_0 = rho_0
k_ = self.m * v / self.hbar
self[...] = np.exp(1j * k_ * x) * np.sqrt(rho)
def get_Vext(self):
return 0.0
s = State(Nx=256, rs=[-2.0, -1.0, 1.0, 2.0])
[I 03:38:46 numexpr.utils] NumExpr defaulting to 2 threads.
[I 03:38:46 root] Patching zope.interface.document.asReStructuredText to format code
/home/docs/checkouts/readthedocs.org/user_builds/gpe/checkouts/latest/src/gpe/utils.py:2755: PerformanceWarning: Default State.get_Vext_GPU() delegates to non-GPU version.
This is a potential performance issue, even if not using the GPU. To ensure optimal
performance, you should make sure that you overload get_Vext_GPU() instead.
warnings.warn(
/tmp/ipykernel_4984/1645578402.py:23: RuntimeWarning: divide by zero encountered in scalar divide
self.T = abs(L / v)
n = s.get_density()
x = s.xyz[0]
x_ = x[1:-1]
psi_ = s[1:-1]
ddpsi_ = np.diff(np.diff(s[...])) / np.diff(x)[1:] ** 2
plt.plot(x_, ddpsi_ / psi_ / 2 + abs(psi_) ** 2)
/home/docs/checkouts/readthedocs.org/user_builds/gpe/conda/latest/lib/python3.14/site-packages/matplotlib/cbook.py:1719: ComplexWarning: Casting complex values to real discards the imaginary part
return math.isfinite(val)
/home/docs/checkouts/readthedocs.org/user_builds/gpe/conda/latest/lib/python3.14/site-packages/matplotlib/cbook.py:1355: ComplexWarning: Casting complex values to real discards the imaginary part
return np.asarray(x, float)
[<matplotlib.lines.Line2D at 0x76bb13d0d940>]
Check the soliton limit (2.122)
s = State(Nx=128, rs=[-4.000001, 1.0, 1.000001, 2.0])
x = s.xyz[0]
s.plot()
rho_min = abs(s[...]).min() ** 2
rho_max = abs(s[...]).max() ** 2
plt.axhspan(rho_min, rho_min + s.a, fc="y", alpha=0.5)
# plt.plot(x, rho_max-s.a/np.cosh(np.sqrt(s.a)*x)**2, '+:')
<matplotlib.patches.Rectangle at 0x76bb13c020d0>
rho_0 = s.rho_0
s[...] = np.sqrt(rho_0) * np.tanh(np.sqrt(rho_0) * x)
s.plot()
[<Axes: >]
s.cooling_phase = 1
e = EvolverABM(s, dt=0.2 * s.t_scale)
s = State(Nx=256, rs=[-2.0, -1.0, 1.0, 2.0])
with NoInterrupt(ignore=True) as interrupted:
while e.t < e.y.T and not interrupted:
e.evolve(1000)
plt.clf()
e.y.plot()
display(plt.gcf())
clear_output(wait=True)
%debug
Solitons#
The GPE admits grey solitons with infinite period. These solitons arise from the previous solutions in the limit where \(m\rightarrow 1\) so that \(\sn\rightarrow\tanh\) and \(\cn\rightarrow\dn\rightarrow \sech\).
The soliton solution is:
This can be expressed as
or, in the same units \(m=\hbar = g = 1\) as El and Hoefer:
To compare with (2.117):
we have \(m=1\), \(\sn = \tanh\) and
Everything here is reasonable except the definition of the phase velocity \(V\) which differs from the soliton velocity \(v\).
The background density is
To compare with (2.122):
, the stationary solution must have \(\bar{\rho} = a_s\) or:
If \(r_1 = -r_4-2r_2\), then
Issues#
I was having some issues finding solutions so I considered stationary solutions:
Symbolically solving for solutions to the GPE (in Maple) gives the following solutions:
(assuming \(a,k\neq 0\) and real \(k\)) gives:
restart;
X_:=JacobiSN(k*x,sqrt(m2));
psi:=sqrt(rho[0] + a*X_^2);
Rho:=psi^2:
res:=collect(numer(simplify(-diff(psi, x, x)/2/psi + Rho - mu)), X_):
mu:=expand(solve(coeff(res, X_, 0), mu));
m2:=solve(coeff(res, X_^6), m2);
solve([coeffs(res, X_)], [a,k^2]);
The dark soliton solution has \(m=1\), \(a=k^2\)
s.m
s = State(rs=[-4.1, 1.0, 1.1, 2.0])
x = s.xyz[0]
m = s.m_
a = s.a_
k = s.k_
rho_0 = s.rho_0_
print (m, a, m ** 2 * k ** 2, rho_0, k)
k = np.sqrt(rho_0)
plt.plot(x, 1 + sn(k * x, m) ** 2)
# s[...] = np.sqrt(rho_0*(1-sn(x,m)))
To do this, we first consider boosting to a moving frame with velocity \(v\) so that in this frame, the traveling wave solution is stationary and periodic. We second Here we are looking for solutions that satisfy:
Minimization#
Here we consider the following hypothesis:
The traveling waves can be found as minimum energy solutions in a box of period \(L\) with twisted boundary conditions holding both the total particle number fixed and the value of the wavefunction at one point.
We start with the soliton solution:
At the core, the density is:
from gpe.imports import *
from scipy.special import ellipj, ellipk
import gpe.bec, gpe.minimize
reload(gpe.bec)
class State(gpe.bec.StateTwist):
def __init__(
self,
Nx=32,
L=10.0,
mu=1.0,
psi_0=1.0,
ind=None,
v=0.0,
twist=None,
m=1.0,
hbar=1.0,
g=1.0,
):
if ind is None:
ind = Nx // 2
self.ind = ind
self.psi_0 = psi_0
self.p_v = p_v = m * v
if twist is None:
twist = np.pi + p_v * L / hbar
gpe.bec.StateTwist.__init__(
self, Nxyz=(Nx,), Lxyz=(L,), m=m, hbar=hbar, g=g, mu=mu, twist=(twist,)
)
self[self.ind] = psi_0
def exact_psi(self):
"""Return the analytic soliton"""
v = abs(self.psi_0)
u = np.sqrt(self.mu - v ** 2)
return 1j * v + u * np.tanh(u * self.xyz[0])
def get_Vext(self):
return -(self.mu + self.p_v ** 2 / 2.0 / self.m)
def _compute_dy_dt(self, dy, subtract_mu=False):
dy = gpe.bec.StateTwist.compute_dy_dt(self, dy=dy, subtract_mu=subtract_mu)
# dy[self.ind] = 0
return dy
class MinimizeState(gpe.minimize.MinimizeState):
def __init__(self, state, **kw):
self.psi_0 = state.psi_0
self.ind = state.Nxyz[0] // 2
gpe.minimize.MinimizeState.__init__(self, state, **kw)
def pack(self, psi):
psi[self.ind] = self.psi_0
fact = 1 if self.real else 2
x = gpe.minimize.MinimizeState.pack(self, psi)
return np.concatenate([x[: self.ind * fact], x[(self.ind + 1) * fact :]])
def unpack(self, x, state=None):
fact = 1 if self.real else 2
x = np.concatenate([x[: self.ind * fact], [0, 0], x[self.ind * fact :]])
state = gpe.minimize.MinimizeState.unpack(self, x, state=state)
assert np.allclose(state[self.ind], 0)
state[self.ind] = self.psi_0
# plt.clf()
# state.plot()
# plt.twinx()
# plt.plot(state.xyz[0], np.angle(state[...]/state.twist_phase), 'r--')
# display(plt.gcf())
# clear_output(wait=True)
return state
s = State(Nx=128 * 8, L=120.0, mu=1.0, psi_0=0.5, v=v)
psi_s = s.exact_psi()
v = (
(np.angle(psi_s[-1]) - np.angle(psi_s[0]) + np.pi)
* s.hbar
/ s.m
/ (s.Lxyz[0] - s.Lxyz[0] / s.Nxyz[0])
)
s = State(Nx=128 * 8, L=120.0, mu=1.0, psi_0=0.5, v=v)
s[...] = s.exact_psi()
print (v)
plt.plot(s[...] / s.twist_phase)
v
s = s1
s.cooling_phase = 1.0
e = EvolverABM(s, dt=0.5 * s.t_scale)
with NoInterrupt(ignore=True) as interrupted:
while not interrupted:
e.evolve(100)
plt.clf()
e.y.plot()
display(plt.gcf())
clear_output(wait=True)
s = State(Nx=128 * 4, L=20.0, mu=1.0, psi_0=0.1, v=0.1)
s[...] = 1.0
m = MinimizeState(s, fix_N=False)
s1 = m.minimize(use_scipy=True)
plt.plot(s1.xyz[0], s1.get_density() - abs(s1.exact_psi()) ** 2)
# s1.plot()
abs(s1.get_density() - abs(s1.exact_psi()) ** 2).max()
plt.plot(s1.xyz[0], np.angle(s1[...]))
plt.plot(s1.xyz[0], np.angle(s1.exact_psi()))
vs = np.linspace(0, 0.4, 10)
errs = []
for v in vs:
s = State(Nx=128 * 4, L=20.0, mu=1.0, psi_0=0.2, v=v)
m = MinimizeState(s, fix_N=False)
s1 = m.minimize(use_scipy=True)
errs.append(abs(s1.get_density() - abs(s1.exact_psi()) ** 2).max())
plt.plot(vs, errs, "-+")
s = gpe.bec.State(Nxyz=(64,), Lxyz=(10.0,))
s[...] = 1.0
m = gpe.minimize.MinimizeState(
fix_N=False,
)
s1 = m.minimize(use_scipy=True)
s1.plot()
Es = []
m = MinimizeState(s)
m.check()
s1 = m.minimize(use_scipy=True, fix_N=False, callback=callback)
# plt.plot(s1.xyz[0], np.log10(abs(abs(s1[...])**2-s.mu)))
s1.plot()
plt.plot(s1.xyz[0], abs(s1.exact_psi()) ** 2)
plt.plot(Es)
s1.get_density().max(), 1 + (s1.k_B[0] ** 2) / 2
-s1.mu, s1.get_Vext()
Es = []
twists = np.linspace(0, 2 * np.pi, 10)
for twist in twists:
s = State(Nx=128, L=50.0, mu=1.0, psi_0=0.05, twist=twist)
m = MinimizeState(s)
s1 = m.minimize(use_scipy=True, fix_N=False)
Es.append(s1.get_energy())
print twist, Es[-1]
s1.plot()
plt.plot(twists, Es)
m = MinimizeState(s)
s1 = m.minimize(use_scipy=True)
plt.clf()
s1.plot()
s1.get_energy()