Flow#
Here we consider flow past a weak barrier. The force on the barrier is
# %#matplotlib inline
import numpy as np, matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import mmfutils.performance.fft, mmfutils.plot
from pytimeode.evolvers import EvolverSplit
from gpe.bec import StateTwist_x, Units
from gpe import utils
mmfutils.performance.fft.set_num_threads(1)
class State(StateTwist_x):
def __init__(self, experiment, **kw):
self.experiment = experiment
super().__init__(**kw)
def get_xyz(self, gpu=False):
if gpu:
return self._xyz_
else:
return self.xyz
def _get_Vext_(self):
"""Return the external potential."""
return self.experiment.get_Vext(state=self, gpu=True)
def get_force(self):
dV = self.experiment.get_Vext(state=self, d=1)
n = self.get_density()
return self.integrate(dV * n)
def get_momentum(self):
psi = self.get_psi()
return self.hbar * self.braket(self.basis.get_gradient(psi)[0]).imag
def plot(self, log=False, label=None, comoving=False): # pragma: nocover
n = self.get_density()
def scale(n):
if log:
return np.log10(n)
return n
n = scale(n)
if comoving:
x = self.xyz[0]
xlabel = r"$x$ [$\xi$]"
else:
x = self.x_lab
xlabel = r"$x_{\rm lab}$ [$\xi$]"
inds = np.argsort(x)
x, n = x[inds], n[inds]
x_ = x / self.experiment.healing_length
plt.plot(x_, n, label=label)
ax = plt.gca()
ax.set(xlabel=xlabel)
E = self.get_energy()
N = self.get_N()
t = np.ravel(self.t)[0]
t_ = t / self.experiment.t_unit
t_name = self.experiment.t_name
plt.suptitle(f"$t={t_:.4f}{t_name}$, ${N=:.4f}$, ${E=:.4f}$")
class Experiment:
Nxyz = (256,)
Lxyz = (10.0,)
healing_length_dx = 2.0
hbar = m = 1
n0 = 1
V0_mu = 0.1
V_sigma_healing_length = 3.0
V_t_ = 0.0 # How long to take to turn barrier on in units of t_unit
V_v_c = 0.0 # Speed of potential in units of c_cound
V_integrable_mu = 0.0 # How much to break integrability
State = State
phi0 = 0.01 # Size of random phases
v_x_V_v = 1.0 # Speed of frame in units of V_v
t_cool = 1.0
def __init__(self, **kw):
for key in kw:
if not hasattr(self, key):
raise ValueError(f"Unknown {key=}")
setattr(self, key, kw[key])
self.init()
def init(self):
dx = self.Lxyz[0] / self.Nxyz[0]
self.healing_length = self.healing_length_dx * dx
self.mu = self.hbar**2 / 2 / self.m / self.healing_length**2
self.t_unit = self.hbar / self.mu
self.t_name = r"\hbar/\mu"
self.g = self.mu / self.n0
self.V_sigma = self.V_sigma_healing_length * self.healing_length
self.V0 = self.V0_mu * self.mu
self.V0_t_ = utils.get_smooth_transition(
[0, self.V0], durations=[0], transitions=[self.V_t_]
)
self.c_sound = np.sqrt(self.mu / self.m)
self.V_v = self.V_v_c * self.c_sound
self.v_x = self.v_x_V_v * self.V_v
self.state_args = dict(
experiment=self,
Nxyz=self.Nxyz,
Lxyz=self.Lxyz,
g=self.g,
hbar=self.hbar,
m=self.m,
v_x=self.v_x,
)
self.rng = np.random.default_rng(seed=2)
def get_Vext(self, state, gpu=False, d=0):
"""Return the external potential or it's derivative."""
t = state.t
t_ = t / self.t_unit
x_lab = state.get_xyz(gpu=gpu)[0] + state.v_x * t
x0 = self.V_v * t
Lx = self.Lxyz[0]
x0 = (x0 - x_lab.min()) % Lx + x_lab.min()
ks = np.arange(1, 5) * np.pi / Lx
# Background to break integrability
V = (
self.V_integrable_mu
* self.mu
* sum(np.sin(k * x_lab) for a, k in zip(ks, [1.0, -2.0, 3.0, 1.0]))
)
V += self.V0_t_(t_) * sum(
np.exp(-(((x_lab - x0) / self.V_sigma) ** 2) / 2)
for x0 in [x0 - Lx, x0, x0 + Lx]
)
if d == 0:
return V
elif d == 1:
dV = self.V0 * sum(
-(x_lab - x0)
/ self.V_sigma**2
* np.exp(-(((x_lab - x0) / self.V_sigma) ** 2) / 2)
for x0 in [x0 - Lx, x0, x0 + Lx]
)
return dV
else:
raise NotImplementedError(f"{d=} not supported.")
def get_state(self, cool=False):
state = self.State(**self.state_args)
V = self.get_Vext(state)
n = np.maximum((self.mu - V) / self.g, 0)
phi = 2 * self.phi0 * (self.rng.random(size=n.shape) - 0.5)
state.set_psi(np.sqrt(n) * np.exp(1j * phi))
if cool:
state.cooling_phase = 1j
dt = 0.1 * state.t_scale
steps = max(int(np.ceil(self.t_cool / dt)), 2)
ev = EvolverSplit(state, dt=self.t_cool / steps, normalize=True)
ev.evolve(steps)
psi = ev.y.get_psi()
state = self.State(**self.state_args)
state.set_psi(psi)
return state
e = Experiment()
s = e.get_state()
s.plot()
KeyboardInterrupt
from IPython.display import clear_output, display
from mmfutils.contexts import FPS
from pytimeode.evolvers import EvolverABM
e = Experiment(V_v_c=0.0, phi0=0.2, t_cool=1)
s0 = e.get_state(cool=False)
s0.plot()
plt.figure()
s1 = e.get_state(cool=True)
s1.plot()
assert np.allclose(s0.get_N(), s1.get_N())
s0.get_energy(), s1.get_energy()
f = 2
e = Experiment(V_v_c=0.2, V_sigma_healing_length=10, V_integrable_mu=0.01,
V0_mu=0.1, phi0=0.1*np.pi, t_cool=0.1, Nxyz=(256*f,), Lxyz=(10.*f,))
s = e.get_state(cool=True)
s.cooling_phase = 1
ev = EvolverABM(s, dt=0.1*s.t_scale)
steps = 200
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
Fs = [s.get_force()]
ps = [s.get_momentum()]
ts = [s.t]
skip = 10
for frame in FPS(frames=10000, timeout=60*5):
ev.evolve(steps)
ts.append(ev.y.t)
Fs.append(ev.y.get_force())
ps.append(ev.y.get_momentum())
if frame % skip == 0:
ax = axs[0]
ax.cla()
plt.sca(ax)
ev.y.plot()
ax = axs[1]
ax.cla()
plt.sca(ax)
Fmax = np.abs(Fs).max()
pmax = np.abs(ps).max()
plt.plot(ts, np.divide(Fs, Fmax))
plt.plot(ts, np.divide(ps, pmax))
ax.set(title=f"{Fmax=:0.2g} ({np.mean(Fs):.4g}), {pmax=:0.2g} ({np.mean(ps):.4g})")
#plt.axhline(np.mean(Fs))
ax.set(ylim=(-1,1))
display(fig)
clear_output(wait=True)
s = ev.get_y()
plt.plot(s.x, np.gradient(s.get_Vext(), s.x))
plt.plot(s.x, s.experiment.get_Vext(s, d=1))
Things to Check (for Chance)#
No matter what the state, if \(V(x)\) is independent of time, then the total energy is conserved. This is consistent with the fact that, even if there is a force, there is no displacement, so there is no work done. Derive this.
A time-independent \(V(x)\) does not imply that total momentum is conserved.
A moving potential \(V(x-vt)\) can do both work (energy changes) and can change the momentum.
Thus, a time-independent potential \(V(x)\) in a constantly moving frame must change both energy and momentum. Explain why the derivation in 1) fails.
f = 2
kw = dict(V_v_c=0.2, V0_mu=0.1, Nxyz=(256*f,), Lxyz=(10.*f,), phi0=0.01)
e0 = Experiment(v_x_V_v=0, **kw)
e1 = Experiment(v_x_V_v=1, **kw)
s0 = e0.get_state(cool=False)
s1 = e1.get_state(cool=False)
s0.get_energy(), s1.get_energy()
ev0 = EvolverABM(s0, dt=0.1 * s0.t_scale)
ev1 = EvolverABM(s1, dt=0.1 * s0.t_scale)
[ev.evolve(10000) for ev in [ev0, ev1]]
print([ev.y.get_energy() for ev in [ev0, ev1]])
[ev.y.plot(comoving=False) for ev in [ev0, ev1]]
Review Paper#
Here we explore some of the features described in the paper https://arxiv.org/abs/2306.05048: “Stationary transport above the critical velocity in a one-dimensional superflow past an obstacle.” Instead of solving for the stationary solutions (which can be done with a nice classical mechanical analog as described in the paper), we solve the dynamic problem similar to Paris-Mandoki et al., “Superfluid flow above the critical velocity” Scientific Reports, 7 (2015) 9070 where we slowly turn on the potential.
def go(v=1.5, U0=2, sigma=10.0, f=8, **kw):
kw.update(V_v_c=v, V0_mu=U0, V_t_=50.0, phi0=0.0, V_sigma_healing_length=sigma)
kw.update(Nxyz=(256 * f,), Lxyz=(10.0 * f,))
e = Experiment(**kw)
s = e.get_state(cool=False)
s.cooling_phase = 1
T = e.Lxyz[0] / e.c_sound
ev = EvolverABM(s, dt=0.1 * s.t_scale)
steps = 200
# fig, axs = plt.subplots(1, 2, figsize=(10, 5))
fig, ax = plt.subplots()
axs = [ax]
skip = 10
frames = int(np.ceil(T / ev.dt / steps))
for frame in FPS(frames=frames, timeout=60 * 5):
ev.evolve(steps)
if frame % skip == 0:
ax = axs[0]
ax.cla()
plt.sca(ax)
ev.y.plot(comoving=True)
display(fig)
clear_output(wait=True)
return ev.get_y()
# Some points from Fig. 1.
# b)
go(U0=2.0, v=1.0)
# a) Here g*n_min = mu - U0, v_c/c_sound = sqrt(1-U0/mu)
#go(U0=0.5, v=0.4*np.sqrt(0.5))
s = go(U0=2, v=0.1)
# d)
s = go(U0=-0.4, v=2.0, V_integrable_mu=0.001, f=8)
f = 2
e = Experiment(V_v_c=0.9, v_x_V_v=1, V0_mu=0.1, V_t_=100.0, phi0=0.0, t_cool=0.1,
Nxyz=(256*f,), Lxyz=(10.*f,))
s = e.get_state(cool=True)
s.cooling_phase = 1
ev = EvolverABM(s, dt=0.1*s.t_scale)
steps = 200
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
Fs = [s.get_force()]
ps = [s.get_momentum()]
ts = [s.t]
skip = 10
for frame in FPS(frames=10000, timeout=60*5):
ev.evolve(steps)
ts.append(ev.y.t)
Fs.append(ev.y.get_force())
ps.append(ev.y.get_momentum())
if frame % skip == 0:
ax = axs[0]
ax.cla()
plt.sca(ax)
ev.y.plot(comoving=True)
ax = axs[1]
ax.cla()
plt.sca(ax)
Fmax = np.abs(Fs).max()
pmax = np.abs(ps).max()
plt.plot(ts, np.divide(Fs, Fmax))
plt.plot(ts, np.divide(ps, pmax))
ax.set(title=f"{Fmax=:0.2g} ({np.mean(Fs):.4g}), {pmax=:0.2g} ({np.mean(ps):.4g})")
#plt.axhline(np.mean(Fs))
ax.set(ylim=(-1,1))
display(fig)
clear_output(wait=True)
from ipywidgets import interact
def g(n):
return n
def G(n):
return n**2/2
@interact(v=(0, 3.0), U0=(-1.0, 1.0))
def draw_W(v=0.1, U0=0.0):
n = np.linspace(0.5, 2, 500)[1:]
W = v**2/2*(n + 1/n) + n*g(1) - G(n)
W_1 = v**2/2*(1 + 1/1) + 1*g(1) - G(1)
W0 = W - U0*n
W0_1 = W_1 - U0*1
plt.plot(np.sqrt(n), W-W_1)
plt.plot(np.sqrt(n), W0-W0_1)