Viscosity Notes 5#
These are some supporting notes for the document Effective Viscosity.
Viscosity#
We now model viscosity through a dissipation term in the “springs”. The idea is that there is a resisting force \(F_{\beta}(\delta q, \delta \dot{q})\) that depends on the extension of the spring \(\delta q\) and the velocity \(\delta \dot{q}\):
E.g., we can generate a Lamé viscosity:
Details
I was not sure how to first express this, so I went back to the idea
In other words:
etc.
We consider the following two cases:
The first is the simple viscosity, while the second is a Lamé viscosity with power \(\alpha = 1\). In both cases:
In the first case, we must have
This has no solutions since \(g(\delta q)\) should be independent of \(\delta \dot{q}\). In the second case
This is as we stated above.
from scipy.integrate import solve_ivp
from scipy.interpolate import InterpolatedUnivariateSpline
class Particles:
Ns = 32 # Number of springs
g_m2 = 1.0 # Coupling constant over mass^2
R = 1.0 # Initial radius
beta = 0.0 # Dissipation
nu = 0.0 # Lame viscosity
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):
x0 = np.arange(0, self.Ns+1)/self.Ns
q0_R = get_q_R(x0)
self.q0 = self.R * q0_R
self.m = 4*self.R**3/3/self.Ns
self.dq0 = 0*self.q0
self.x0 = np.linspace(-self.R, self.R, self.Ns+1)
def pack(self, q, dq):
return np.ravel([q, dq])
def unpack(self, y):
return np.reshape(y, (2, y.shape[0]//2) + y.shape[1:])
def V(self, dq, d=0):
g = self.g_m2 * self.m**2
if d == 0:
return g/2/dq
elif d == 1:
return -g/2/dq**2
def compute_dy_dt(self, t, y):
q, dq_dt = self.unpack(y)
dq = np.concatenate([[0], np.diff(q), [0]])
dV = np.concatenate([[0], self.V(np.diff(q), d=1), [0]])
ddq_dt = np.concatenate([[0], np.diff(dq_dt), [0]])
f = self.m * self.nu * ddq_dt/dq**2
f[0] = f[-1] = 0
#ddq_dt2 = (dV[1:] - dV[:-1]) / self.m + self.beta * (dv[1:] - dv[:-1])
ddq_dt2 = (dV[1:] - dV[:-1] + (f[1:] - f[:-1])) / self.m
return self.pack(dq_dt, ddq_dt2)
p1 = Particles(Ns=32)
p2 = Particles(Ns=256)
ps = [p1, p2]
y0s = [p.pack(p.q0, p.dq0) for p in ps]
T = 2.0
Nt = 100
t_eval = np.linspace(0, T, Nt)
ress = [solve_ivp(p.compute_dy_dt, y0=y0, t_span=(0,T), t_eval=t_eval, method="LSODA")
for p, y0 in zip(ps, y0s)]
for res, fmt in zip(ress, ['--C0', ':C1']):
q, dq = p2.unpack(res.y)
plt.plot(res.t, q.T, fmt);
/tmp/ipykernel_5246/3235063741.py:44: RuntimeWarning: invalid value encountered in divide
f = self.m * self.nu * ddq_dt/dq**2
Nt = len(t_eval)
rhos = []
xs = []
xlim = [0,0]
rho_max = 0
for p, res in zip(ps, ress):
q, dq = p.unpack(res.y)
rho = p.m/np.diff(q, axis=0)
x = (q[1:] + q[:-1])/2
xlim = [min(xlim[0], x.min()), max(xlim[1], x.max())]
rho_max = max(rho_max, rho.max())
rhos.append(rho)
xs.append(x)
args = dict(xlim=xlim, ylim=(0, rho_max))
fig, ax = plt.subplots()
for i in FPS(Nt, fig=fig, embed=True, fps=10):
ax.cla()
for j, p in enumerate(ps):
ax.plot(xs[j][:, i], rhos[j][:, i])
t = t_eval[i]
ax.set(title=f"t={res.t[i]:.4f}", **args)
Here is the Lagrangian hydrodynamic description for comparison:
import scipy.fft
sp = scipy
class Flow:
Nx = 128
A = 0.5 # g/2/m^2
nu = 0.04 # Viscosity coefficients
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):
self._th0 = (2*np.arange(self.Nx) + 1) * np.pi / 2 / self.Nx
self._k = np.arange(self.Nx)
self.x0 = np.cos(self._th0)
# Initial density profile
self.n0 = 0*self.x0 + 1.0
self.n0_x0 = 0*self.x0
self.n0 = (1-self.x0**2)
self.n0_x0 = -2*self.x0
def get_y0(self):
X0 = self.x0
Xt0 = 0*X0
return self.pack(X0, Xt0)
def diff(self, f, d=1):
ft = sp.fft.dct(f, type=2, norm='forward')
th, k = self._th0, self._k
s = np.sin(th)
# Shift the coefficients and pad with zero.
df_dth = sp.fft.dst(np.concatenate([-(ft*k)[1:], [0]]), type=3)
if d == 1:
df_dx = -df_dth / s
return df_dx
else:
d2f_dth2 = sp.fft.idct(-ft*k**2, type=2, norm='forward')
d2f_dx2 = (d2f_dth2 - df_dth / np.tan(th)) / s**2
if d == 2:
return d2f_dx2
return self.diff(df_dx, d=d-2)
def pack(self, X, X_t):
return np.ravel([X, X_t])
def unpack(self, y):
return np.reshape(y, (2, self.Nx))
def unpack_xnu(self, y):
X, X_t = self.unpack(y)
X_x0 = self.diff(X)
n = self.n0 / X_x0
u = X_t
x = X
return x, n, u
def get_a_ext(self):
return 0
def compute_dy_dt(self, t, y):
X, X_t = self.unpack(y)
X_x0 = self.diff(X)
X_x0x0 = self.diff(X, d=2)
n = self.n0 / X_x0
n_x = self.n0_x0 / X_x0**2 - self.n0 * X_x0x0 / X_x0**3
X_tx0 = self.diff(X_t)
u_x = X_tx0/X_x0
u_xx = (self.diff(X_t, d=2)) / X_x0**2 - self.diff(X_t) * X_x0x0 / X_x0**3
# Zero-laplacian boundary conditions
u_xx[0] = u_xx[-1] = 0
a_ext = self.get_a_ext()
X_tt = a_ext - 2*self.A * n_x + self.nu * (n_x * u_x / n + u_xx)
return self.pack(X_t, X_tt)
def solve(self, T=10.0, Nt=100):
y0 = self.get_y0()
t_eval = np.linspace(0, T, Nt)
res = solve_ivp(self.compute_dy_dt, y0=y0, t_span=(0, T),
t_eval=t_eval, method="BDF")
if not res.success:
print(res.message)
self.res = res
return res
%%time
from scipy.integrate import solve_ivp
p1 = Particles(Ns=32)
p2 = Particles(Ns=256)
ps = [p1, p2]
y0s = [p.pack(p.q0, p.dq0) for p in ps]
T = 2.0
Nt = 100
t_eval = np.linspace(0, T, Nt)
ress = [solve_ivp(p.compute_dy_dt, y0=y0, t_span=(0,T), t_eval=t_eval, method="LSODA")
for p, y0 in zip(ps, y0s)]
rhos = []
xs = []
xlim = [0,0]
rho_max = 0
for p, res in zip(ps, ress):
q, dq = p.unpack(res.y)
rho = p.m/np.diff(q, axis=0)
x = (q[1:] + q[:-1])/2
xlim = [min(xlim[0], x.min()), max(xlim[1], x.max())]
rho_max = max(rho_max, rho.max())
rhos.append(rho)
xs.append(x)
f = Flow(A=p1.g_m2/2, nu=0.0)
res = f.solve(T=T, Nt=Nt)
x, n, u = np.einsum('tix->itx', [f.unpack_xnu(y) for y in res.y.T])
args = dict(xlim=(min(xlim[0], x.min()), max(xlim[1], x.max())),
ylim=(0, max(rho_max, n.max())))
Nt = len(res.t)
fig, ax = plt.subplots()
for i in FPS(Nt, fig=fig, embed=True, fps=10):
t = res.t[i]
ax.cla()
ax.plot(x[i], n[i], '-')
for j, p in enumerate(ps):
ax.plot(xs[j][:, i], rhos[j][:, i], '--')
t = t_eval[i]
ax.set(title=f"t={res.t[i]:.4f}", **args)
/tmp/ipykernel_5246/3235063741.py:44: RuntimeWarning: invalid value encountered in divide
f = self.m * self.nu * ddq_dt/dq**2
CPU times: user 7.49 s, sys: 158 ms, total: 7.64 s
Wall time: 10.5 s
%%time
from scipy.integrate import solve_ivp
nu = 1.0
#p1 = Particles(Ns=32, beta=0.5*32**2)
#p2 = Particles(Ns=256, beta=0.5*256**2)
p1 = Particles(Ns=32, nu=nu)
p2 = Particles(Ns=256, nu=nu)
ps = [p1, p2]
y0s = [p.pack(p.q0, p.dq0) for p in ps]
T = 2.0
Nt = 100
t_eval = np.linspace(0, T, Nt)
ress = [solve_ivp(p.compute_dy_dt, y0=y0, t_span=(0,T), t_eval=t_eval, method="LSODA")
for p, y0 in zip(ps, y0s)]
rhos = []
xs = []
xlim = [0,0]
rho_max = 0
for p, res in zip(ps, ress):
q, dq = p.unpack(res.y)
rho = p.m/np.diff(q, axis=0)
x = (q[1:] + q[:-1])/2
xlim = [min(xlim[0], x.min()), max(xlim[1], x.max())]
rho_max = max(rho_max, rho.max())
rhos.append(rho)
xs.append(x)
f = Flow(A=p1.g_m2/2, nu=0.0)
res = f.solve(T=T, Nt=Nt)
x, n, u = np.einsum('tix->itx', [f.unpack_xnu(y) for y in res.y.T])
f = Flow(A=p1.g_m2/2, nu=nu)
res = f.solve(T=T, Nt=Nt)
x, n, u = np.einsum('tix->itx', [f.unpack_xnu(y) for y in res.y.T])
args = dict(xlim=(min(xlim[0], x.min()), max(xlim[1], x.max())),
ylim=(0, max(rho_max, n.max())))
Nt = len(res.t)
fig, ax = plt.subplots()
for i in FPS(Nt, fig=fig, embed=True, fps=10):
t = res.t[i]
ax.cla()
ax.plot(x[i], n[i], '-')
for j, p in enumerate(ps):
ax.plot(xs[j][:, i], rhos[j][:, i], '--')
t = t_eval[i]
ax.set(title=f"t={res.t[i]:.4f}", **args)
/tmp/ipykernel_5246/3235063741.py:44: RuntimeWarning: invalid value encountered in divide
f = self.m * self.nu * ddq_dt/dq**2
CPU times: user 7.64 s, sys: 96 ms, total: 7.73 s
Wall time: 10.5 s