Non-Polynomial Schrödinger Equation (NPSEQ) in 1D (Tube)#
Here we summarize the approach and formulation for a 1D non-polynomial Schrödinger equation representing an atomic gas trapped in a tube. We start by noting that the GPE is almost separable:
The idea is to factor the wavefunction as
We then make the “adiabatic” approximation of neglecting the \(z\) and \(t\) dependence in \(\phi\) – the assumption is that the dynamics along \(z\) are slow, so that the radial wavefunction \(\phi\) adjusts instantaneously. Within this approximation, we can separate the GPE:
Our assumption is that the radial wavefunction \(\phi(x, y)\) at a fixed \(z\), \(t\), and density \(n_1\) adjusts instantaneously to the ground state in the transverse direction
Note
The key outcomes of this step are the chemical potential \(\mu_{\perp}(n_1)\) and the solutions \(\phi_{n_1}(x, y)\) from which we will compute various moments and related properties. Here it is important that \(V(x, y, z, t) = V_{\perp}(x, y) + V_z(z, t)\) separate so that these quantities depend only on \(n_1\) and not on \(z\) or \(t\). In principle, a more complicated dependence could be done, but this would not allow us to tabulate everything with 1-dimensional splines like we will do later.
To obtain the NPSEQ, we plug this solution back into the separated GPE above, multiply by \(\phi^\dagger\) and integrate across the transverse directions.
Example: The Standard dr-GPE#
As a check, consider the original formulation of the dr-GPE for axially-symmetric harmonic traps:
The final result with the gaussian approximation is
where the constant \(a=1\) for energy minimization or \(a=2\) for the chemical-potential minimization recommended by [Mateo and Delgado, 2009].
from gpe.Examples.tutorial import StateHOConvergence2
s0 = StateHOConvergence2(g=100, Lx=30.0, Nx=128, dmu=10.0)
axs = s0.plot(label="TF")
s = s0.get_initialized_state()
axs = s.plot(axs=axs, label="Minimized", plot_convergence=True)
axs[0].legend(loc='right');
[I 03:46:10 root] Patching zope.interface.document.asReStructuredText to format code
[I 03:46:10 numexpr.utils] NumExpr defaulting to 2 threads.
Our code solves the 2D GPE with
Here we show the relative error between \(\mu(n_1)\) computed numerically and the formula above from the dr-GPE:
dmus = np.linspace(0.0001, 10.0, 100)
ss = [StateHOConvergence2(g=1, Lx=30.0, Nx=128, dmu=dmu).get_initialized_state()
for dmu in dmus]
mus = np.array([s.get_mu() for s in ss])
Ns = np.array([s.get_N() for s in ss])
gtildes = np.array([s.get_gtilde() for s in ss])
g = 1.
n1s = s.g * Ns / g
s = ss[0]
a = 2
n1 = 1
hbar2_2m = s.hbar**2 / 2 / s.m
mw2_2 = s.m * s.w**2 / 2
gn1s = s.g * Ns
agn_4pi = a * gn1s / 4 / np.pi
sigma2 = np.sqrt((hbar2_2m + agn_4pi) / mw2_2)
mus_ = (hbar2_2m + agn_4pi)/sigma2 + mw2_2 * sigma2
fig, ax = plt.subplots()
ax.plot(n1s, mus/mus_ - 1)
ax.set(xlabel=r"$\mu_1$", ylabel=r"$\mu/\mu_{dr-GPE}-1$")
ax1 = plt.twinx()
ax1.plot(n1s, gtildes)
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[3], line 2
1 dmus = np.linspace(0.0001, 10.0, 100)
----> 2 ss = [StateHOConvergence2(g=1, Lx=30.0, Nx=128, dmu=dmu).get_initialized_state()
3 for dmu in dmus]
4 mus = np.array([s.get_mu() for s in ss])
5 Ns = np.array([s.get_N() for s in ss])
File ~/checkouts/readthedocs.org/user_builds/gpe/checkouts/latest/src/gpe/Examples/tutorial.py:155, in StateHOConvergence1.get_initialized_state(self, fix_N, minimize_kw)
153 m = MinimizeState(s0, fix_N=fix_N)
154 m.check()
--> 155 s1 = m.minimize(**minimize_kw)
156 s = self.copy()
157 s.set_psi(s1.get_psi())
File ~/checkouts/readthedocs.org/user_builds/gpe/checkouts/latest/src/gpe/minimize.py:751, in MinimizeState.minimize(self, psi_tol, E_tol, callback, _debug, **kw)
749 return super().minimize(_debug=_debug, **kw)
750 else:
--> 751 x = super().minimize(**kw)
753 state = self.unpack(x)
755 if self.fix_N:
File ~/checkouts/readthedocs.org/user_builds/gpe/checkouts/latest/src/gpe/minimize.py:282, in Minimize.minimize(self, plot, callback, method, polish, broyden_alpha, broyden_opts, f_tol, x_tol, use_scipy, ignore_f, _test, _debug, _log, use_cache, bounds, **kw)
280 if Version(sp.__version__) > Version("1.15.0"):
281 options.pop("disp", None)
--> 282 res = self._minimize(
283 f=_f,
284 df=_df,
285 x0=_x[0],
286 method=method,
287 callback=callback_,
288 bounds=bounds,
289 options=options,
290 )
292 self.minimize_results = res
294 if not res.success:
File ~/checkouts/readthedocs.org/user_builds/gpe/checkouts/latest/src/gpe/minimize.py:311, in Minimize._minimize(self, f, df, x0, method, callback, bounds, options)
309 def _minimize(self, f, df, x0, method, callback, bounds, options):
310 """Interface to the scipy minimizer."""
--> 311 res = sp.optimize.minimize(
312 fun=f,
313 jac=df,
314 x0=x0,
315 method=method,
316 callback=callback,
317 bounds=bounds,
318 options=options,
319 )
320 res.f = f
321 res.df = df
File ~/checkouts/readthedocs.org/user_builds/gpe/conda/latest/lib/python3.14/site-packages/scipy/optimize/_minimize.py:784, in minimize(fun, x0, args, method, jac, hess, hessp, bounds, constraints, tol, callback, options)
781 res = _minimize_newtoncg(fun, x0, args, jac, hess, hessp, callback,
782 **options)
783 elif meth == 'l-bfgs-b':
--> 784 res = _minimize_lbfgsb(fun, x0, args, jac, bounds,
785 callback=callback, **options)
786 elif meth == 'tnc':
787 res = _minimize_tnc(fun, x0, args, jac, bounds, callback=callback,
788 **options)
File ~/checkouts/readthedocs.org/user_builds/gpe/conda/latest/lib/python3.14/site-packages/scipy/optimize/_lbfgsb_py.py:461, in _minimize_lbfgsb(fun, x0, args, jac, bounds, disp, maxcor, ftol, gtol, eps, maxfun, maxiter, iprint, callback, maxls, finite_diff_rel_step, workers, **unknown_options)
459 g = g.astype(np.float64)
460 # x, f, g, wa, iwa, task, csave, lsave, isave, dsave = \
--> 461 _lbfgsb.setulb(m, x, low_bnd, upper_bnd, nbd, f, g, factr, pgtol, wa,
462 iwa, task, lsave, isave, dsave, maxls, ln_task)
464 if task[0] == 3:
465 # The minimization routine wants f and g at the current x.
466 # Note that interruptions due to maxfun are postponed
467 # until the completion of the current minimization iteration.
468 # Overwrite f and g:
469 f, g = func_and_grad(x)
KeyboardInterrupt: