gpe.bec_basic#

Code for simulating a single component BEC.

This is the simplest code in the project, implementing all of the basic features without excess complications. It is somewhat excessively commented to provide a pedagogical introduction for users unfamiliar with the techniques and using python for scientific computing.

A more complete implementation can be found in bec.py, but this relies on external packages to implement, for example, different bases, making it less suitable for teaching.

Classes#

Units

Units and physical constants.

State

State class implementing the GPE for a single-component BEC.

Module Contents#

class Units[source]#

Units and physical constants.

This class is simply a container for physical constants and units. We have chosen values here that are relevant for 87Rb which is studied at WSU in Peter Engels’ lab.

hbar = 1.0[source]#
micron = 1.0[source]#
mm = 1000.0[source]#
cm = 10000.0[source]#
nm = 0.001[source]#
meter = 1000000.0[source]#
u = 1.0[source]#
kg = 6.0221408585491615e+26[source]#
G = 1.0[source]#
mG = 0.001[source]#
m = 86.909187[source]#
a_B = 5.291772105498088e-05[source]#
Hz = 1.5746097513521148e-05[source]#
kHz = 0.015746097513521146[source]#
MHz = 15.746097513521146[source]#
s = 63507.79925891489[source]#
ms = 63.50779925891489[source]#
nK = 0.002061483743918704[source]#
scattering_lengths[source]#
a[source]#
mu_B[source]#
class State(Nxyz=(2**5, 2**5, 2**5), Lxyz=(30 * u.micron, 50 * u.micron, 50 * u.micron), ws=np.array([np.sqrt(8) * 126.0, 126.0, 126.0]) * u.Hz, g=4 * np.pi * u.hbar**2 * u.a / u.m, m=u.m, mu=None, hbar=u.hbar, x_TF=None, cooling_phase=1.0, symmetric_grid=False, t=0)[source]#

Bases: gpe.mixins.StateMixin, pytimeode.mixins.ArrayStateMixin

State class implementing the GPE for a single-component BEC.

To provide time-evolution functionality, the pytimeode package requires that you provide a State class that implements the required behaviour of at least one of the IStateFor*Evolvers interfaces. Most of the required behaviour can be provided by the mixins.ArrayStateMixin class which we inherit from as long as we provide an array called data which represents the wavefunction.

On top of this, we need to provide the following three methods used by the pytimeode.evolvers:

  • compute_dy_dt(dy): Required by interface IStateForABMEvolvers

    This computes the rhs of the time-dependent differential equation and is all that is needed by the pytimeode.evolvers.EvolverABM evolver. This is a high-order generic evolver. It requires a rather small step size to ensure that it behaves well, but once the step size is small enough, it is converges to high accuracy. For example: there is often a critical step size above which the evolution will blow up. If you reduce this by a factor of 2, then you will typically obtain accuracy to 5 digits or so, at least for modest evolution lengths.

    The ABM evolver requires about 10 copies of the state to start up, and 8 copies of the state to be maintained for evolution. This can be a memory issue if the states are very large.

  • apply_exp_K(dt), apply_exp_V(dt, state): Required by interface IStateForSplitEvolvers

    These two methods allow you to use pytimeode.evolvers.EvolverSplit based on the Trotter decomposition. This is rather specialized to systems like the GPE where the exponential of the kinetic and potential terms can be computed exactly. The ODE solver is not as high order as the ABM method, but is manifestly unitary. Thus, you can often get away with very large step sizes and the system will still behave “reasonably” meaning that the evolution will not be accurate quantitatively, but will be qualitatively reasonable so you can get an idea of what is happening. Unfortunately, to achieve quantitative accuracy, you must generally go to very small step sizes.

    Another advantage of the split operator methods is that they require use only 2 or 3 copies of the state, and so are better for large states that might be a memory issue

  • normalize(): Required by interface IStateWithNormalize

    This method allows the evolvers to continually normalize the state, which can improve numerical performance, or be of use when finding initial states.

The reset of the methods in the code play a supporting role to these methods required by the interfaces and for our convenience (for example, plotting the state).

Parameters:

x_TF (float) – Thomas Fermi “radius” for setting the initial state. The initial state will be set so that the density will fall to zero at this point x=x_TF (with y and z in the middle of the trap). If None, then we default to 80% of the length.

_use_fftw = True[source]#
t = 0[source]#
m = 86.909187[source]#
mu = None[source]#
hbar = 1.0[source]#
Nxyz[source]#
Lxyz[source]#
ws[source]#
x_TF = None[source]#
g[source]#
cooling_phase = 1.0[source]#
symmetric_grid = False[source]#
data[source]#
init()[source]#

Initialize the state.

This method defines the basis positions, momenta, etc. for use later on. We define these here rather than in the constructor __init__() so that the user can change them later and the reinitialize the state. We also call this function from the pre_evolve_hook() so that it is called before any evolution takes place. For this reason, we should not modify the state here.

property E_max[source]#

Return the maximum kinetic energy in the basis.

This is useful when using evolvers as convergence should be obtained when the time-step is roughly:

dt = 0.1 * state.hbar / state.E_max

See t_scale.

property t_scale[source]#

Return the smallest time-scale for the problem.

Evolvers - especially the ABM evolvers - should use a dt=0.1*t_scale or so. If much smaller dt values are required for convergence, then it usually indicates that your lattice spacing is too larger. Likewise, if you can get away with much larger dt values, then your lattice spacing might be unnecessarily small.

Required by Simulation.

pre_evolve_hook()[source]#

This method is called by the evolvers at the start of evolution to ensure that the state is properly initialized.

set_initial_state(mu=None, x_TF=None)[source]#

Set the state using the Thomas Fermi (TF) approximation from either mu or x_TF (pick only one or the other).

Parameters:
  • mu (float) – Fixed chemical potential.

  • x_TF (float) – Position defining the Thomas Fermi “radius”. (The external potential is evaluated at this position and this is used to set mu.)

get_n_TF(mu, V_ext=None)[source]#

Return the Thomas Fermi density profile n from mu.

get_mu_TF(x_TF, V_ext=None)[source]#

Return the Thomas Fermi chemical potential at x_TF.

Parameters:

x_TF (float) – Position defining the Thomas Fermi “radius”. (The external potential is evaluated at this position and this is used to get mu.)

get_mu_HO(N)[source]#

Return the chemical potential required to set the particle number in the Thomas Fermi (TF) approximation for a 3D harmonic oscillator.

property dim[source]#

Dimension of the state.

property shape[source]#

Shape of the state Nxyz.

get_Vext()[source]#

Return the external potential.

Overload this method if you want to change the external potential. If the potential should be time dependent, use self.t which will be updated by the evolvers.

If a chemical potential self.mu is defined, then this is subtracted from Vext. This allows gpe.minimize to find states at a constant chemical potential. Note: for general evolution, it is better not to set the chemical potential as this is automatically set by compute_dy_dt and will then cause get_energy to return the thermodynamic potential instead.

get_density()[source]#

Return the density of the state.

get_N()[source]#

Return the total D-dimensional particle number in the state.

integrate(a)[source]#

Return the integral of a over the box.

braket(a, b)[source]#

Return the inner product of the functions a and b.

Note: a is conjugated and the metric is applied.

get_V()[source]#

Return the complete potential V - internal and external.

This function defines the non-linear interaction of the equations. For the GPE, the energy-density has \(gn^2/2\), so we have the derivative gn here.

fft(y)[source]#

Return the FFT of y over the spatial indices.

ifft(y)[source]#

Return the IFFT of y over the spatial indices.

_fftn(y)[source]#

Return the FFT of y over the spatial indices. These versions with an underscore may be overloaded for performance.

_ifftn(y)[source]#

Return the IFFT of y over the spatial indices. These versions with an underscore may be overloaded for performance.

compute_dy_dt(dy, subtract_mu=True)[source]#

Return dy_dt storing the results in dy.

Parameters:

subtract_mu (bool) –

If True, then subtract the chemical potential such that dy_dt is orthogonal to the original state y. This will minimize the evolution of the overall phase during real-time evolution (which can reduce numerical errors) and will ensure that evolution under imaginary or complex time will preserve particle number.

This should not be set if computing physical energy of the state, however, which is why it is a parameter.

linear = False[source]#
apply_exp_K(dt)[source]#

Apply \(e^{-i K dt}\) in place.

apply_exp_V(dt, state)[source]#

Apply \(e^{-i V dt}\) in place using state for any nonlinear dependence in V. (Linear problems should ignore state.)

normalize(s=None)[source]#

Normalize the state, return the scale factors and number (s, N).

get_energy()[source]#

Return the energy of the state. Useful for minimization.

get_energy_density()[source]#

Return the energy density.

get_H(subtract_mu=False)[source]#

Return the single-particle Hamiltonian for convenience only.

Note

May be huge!

This is a square matrix whose sides are of dimension prod(Nxyz) which can be huge. It is not needed for general computations, but might be useful for analysis such as exploring the BdG in 1D systems.

get_mu()[source]#

Compute the chemical potential for convenience only.

get_Hy(subtract_mu=False)[source]#

Return H(y) for convenience only.

plot(log=False, label=None)[source]#