Hide code cell content

import mmf_setup; mmf_setup.nbinit()
import os, sys
from pathlib import Path
FIG_DIR = Path(mmf_setup.ROOT) / '../Docs/_build/figures/'
os.makedirs(FIG_DIR, exist_ok=True)
import logging; logging.getLogger("matplotlib").setLevel(logging.CRITICAL)
%matplotlib
import numpy as np, matplotlib.pyplot as plt
try: from myst_nb import glue
except: glue = None

from matplotlib.animation import FuncAnimation
from gpe.contexts import FPS

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.

Using matplotlib backend: module://matplotlib_inline.backend_inline

Viscosity Notes 3#

These are some supporting notes for the document Effective Viscosity.

Saint-Venant Equations#

Here we consider the following 1D version of the Navier-Stokes equations called the Saint-Venant equations:

\[\begin{gather*} \dot{n} + (nu)' = 0, \qquad D_{t}u = \dot{u} + uu_{,x} = -\frac{\Bigl(V(x) + \mathcal{E}'(n)\Bigr)_{,x}}{m} + \frac{\Bigl(\nu(n)u_{,x}\Bigr)_{,x}}{n}. \end{gather*}\]

Numerical Implementation#

We start with a simple numerical implementation of these equations on a periodic lattice. This should work well when everything is smooth, but will likely fail when the density drops to zero which will produce a kink. Another potential failure mode appears if the local flow velocity exceeds the speed of sound. Finite viscosity allows for mild supersonic flow

Hide code cell content

from scipy.integrate import solve_ivp
from gpe.contexts import FPS

%pylab inline
from scipy.integrate import solve_ivp
from gpe.contexts import FPS

Lx = 10.0
Nx = 128
dx = Lx/Nx
x = np.arange(Nx) * dx - Lx/2
k = 2*np.pi * np.fft.fftfreq(Nx, dx)

# Fourier-space representation of derivatives
D = {1: None, 2: None}
D[1] = 1j*k
assert Nx % 2 == 0
D[1][Nx//2] = 0  # Kill highest unmatched frequency
D[2] = -k**2

def diff(f, d=1):
    """Compute the derivative of f."""
    global D
    return np.fft.ifft(D[d] * np.fft.fft(f)).real

def pack(n, u):
    return np.concatenate([n, u])
    
def unpack(y):
    return np.reshape(y, (2, Nx))

def get_V_m(t, x, d=0):
    """Return the dth derivative of V/m."""
    global V0_m, sigma, dV_m_dx
    V_m = V0_m * np.exp(-(x/sigma)**2/2)
    if d == 0:
        dV = dV_m_dx*x
        return V_m + dV - np.min(dV)  # Constant to make V_min = 0
    elif d == 1:
        return - x * V_m / sigma**2 + dV_m_dx

def get_E_m(n, d=0):
    """Return the dth derivative of E/m where E is the energy density."""
    global g_m
    if d == 0:
        return g_m * n**2 / 2 
    elif d == 1:
        return g_m * n
    elif d == 2:
        return g_m

def get_nu_n(n, d=0):
    """Return the dth derivative of the vicosity diff(nu(n), n, d)/n, divided by n."""
    if d == 0:
        return nu # * n / n
    elif d == 1:
        return nu / n
    else:
        return 0
        
def compute_dy_dt(t, y):
    global get_V_m, get_E_m, get_nu_n
    n, u = unpack(y)
    j = u*n    
    dn_dt = -diff(j)
    n_x = diff(n)
    u_x = diff(u)
    u_xx = diff(u, d=2)
    du_dt = -u*u_x
    du_dt -= get_V_m(t, x, d=1) + get_E_m(n, d=2)*n_x
    du_dt += get_nu_n(n, d=1)*n_x*u_x + get_nu_n(n) * u_xx / n
    
    # Correction needed to keep finite j = j_avg
    du = j_avg/n - u
    du_dt += j_corr_factor * du
    return pack(dn_dt, du_dt)
    
# Quick test of derivatives
k = 2*np.pi / Lx
s, c = np.sin(k*x), np.cos(k*x)
f = np.exp(s)
df = k*c*f
ddf = k**2*f*(c**2 - s)
assert np.allclose(diff(f), df)
assert np.allclose(diff(f, 2), ddf)
%pylab is deprecated, use %matplotlib inline and import the required libraries.
Populating the interactive namespace from numpy and matplotlib

Hide code cell source

# Here is a simulation of flow over a bump.
V0_m = 1.0
dV_m_dx = -0.05
sigma = 0.5
g_m = 1.0
n0 = 1.5
u0 = 0.3
j_avg = n0*u0
nu = 0.6
t = 0

b = get_V_m(t, x) / g_m
h = n0
eta = h - b
n = eta
u = j_avg / n
y0 = pack(n, u)

dt = dx / nu * 0.002
j_corr_factor = 0/dt/100

fig, axs = plt.subplots(2, 1, sharex=True)
ax = axs[0]
ax_stream = axs[1]
ax_j = ax_stream.twinx()
ax_c = ax.twinx()
y = y0.copy()
for n in FPS(120, fig=fig, embed=True, fps=20):
    for _ in range(500):
        dy_dt = compute_dy_dt(t, y)
        y += dy_dt * dt
        t += dt
    n, u = unpack(y)
    ax.cla()
    ax.plot(x, n)
    ax.set(ylim=(0, 2.0), title=f"{t=:.4g}, N={sum(n)*dx:.4g}",
           ylabel="$n$")
           
    # Speed of sound
    u_c = np.sqrt(n*get_E_m(n, d=2))
    ax_c.cla()
    ax_c.yaxis.set_label_position("right")
    ax_c.plot(x, u/u_c, '--C1')
    ax_c.axhline(1.0, color='k', ls=':')
    ax_c.set(ylim=(-0.1, 1.3), ylabel="Mach number $u/u_c$")
    
    ax_stream.cla()
    b = get_V_m(t, x) / g_m
    eta = n
    h = b + eta
    ax_stream.plot(x, b, '-k')
    ax_stream.fill_between(x, b, h, color='cyan')
    ax_stream.set(ylim=(0, 2.0), xlim=(x.min(), x.max()),
                  xlabel="$x$", ylabel="Shallow-water analog.")

    ax_j.cla()
    ax_j.plot(x, n*u)
    ax_j.plot(x, u)
    ax_j.yaxis.set_label_position("right")
    ax_j.set(ylim=(-0.2, 1.0), 
             ylabel="$j$ (blue) and $u$ (orange)")

In this example, we add a gradient to our potential, noting that only \(V'(x)\) appears in our equations of motion. Thus, although the potential is not periodic, the force is. This constant is needed to ensure that a finite flow remains.