{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The shallow water equations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this chapter we study a model for shallow water waves in one dimension: \n", "\\begin{align}\n", " h_t + (hu)_x & = 0 \\label{SW_mass} \\\\\n", " (hu)_t + \\left(hu^2 + \\frac{1}{2}gh^2\\right)_x & = 0. \\label{SW_mom}\n", "\\end{align} \n", "Here $h$ is the depth, $u$ is the velocity, and $g$ is a constant representing the force of gravity. These equations are \"depth averaged\" and ignore vertical velocity and an any vertical variations in the horizontal velocity. Viscosity and compressibility are neglected, and the pressure is assumed to be hydrostatic. Nevertheless, this is a surprisingly effective model for many applications, particularly when the wavelength is long compared to the fluid depth.\n", "\n", "Previously we have looked at a *linear hyperbolic system* (acoustics) and *scalar hyperbolic equations* (advection, Burgers' equation, traffic flow). The shallow water system is our first example of a *nonlinear hyperbolic system*; solutions of the Riemann problem will consist of two waves (since it is a system of two equations), each of which may be a shock or rarefaction." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Hyperbolic structure\n", "We can write (\\ref{SW_mass})-(\\ref{SW_mom}) in the canonical form $q_t + f(q)_x = 0$ if we define \n", "\\begin{align}\n", "q & = \\begin{pmatrix} h \\\\ hu \\end{pmatrix} & f & = \\begin{pmatrix} hu \\\\ hu^2 + \\frac{1}{2}gh^2 \\end{pmatrix}.\n", "\\end{align} \n", "In terms of the conserved quantities, the flux is \n", "\\begin{align}\n", "f(q) & = \\begin{pmatrix} q_2 \\\\ q_2^2/q_1 + \\frac{1}{2}g q_1^2 \\end{pmatrix}\n", "\\end{align} \n", "Thus the flux jacobian is \n", "\\begin{align}\n", "f'(q) & = \\begin{pmatrix} 0 & 1 \\\\ -(q_2/q_1)^2 + g q_1 & 2 q_2/q_1 \\end{pmatrix} \n", " = \\begin{pmatrix} 0 & 1 \\\\ -u^2 + g h & 2 u \\end{pmatrix}.\n", "\\end{align} \n", "Its eigenvalues are \n", "\\begin{align} \\label{SW:char-speeds}\n", " \\lambda_1 & = u - \\sqrt{gh} & \\lambda_2 & = u + \\sqrt{gh},\n", "\\end{align} \n", "with corresponding eigenvectors \n", "\\begin{align} \\label{SW:fjac-evecs}\n", " r_1 & = \\begin{bmatrix} 1 \\\\ u-\\sqrt{gh} \\end{bmatrix} &\n", " r_2 & = \\begin{bmatrix} 1 \\\\ u+\\sqrt{gh} \\end{bmatrix}\n", "\\end{align} \n", "Notice that -- unlike for acoustics, but similar to the LWR traffic model -- the eigenvalues and eigenvectors depend on $q$. Because of this, the waves appearing in the Riemann problem move at different speeds and may be either jump discontinuities (shocks) or smoothly-varying rarefaction waves." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "hide" ] }, "outputs": [], "source": [ "%matplotlib inline\n", "%config InlineBackend.figure_format = 'svg'\n", "import matplotlib as mpl\n", "mpl.rcParams['font.size'] = 8\n", "figsize =(8,4)\n", "mpl.rcParams['figure.figsize'] = figsize\n", "import warnings\n", "warnings.filterwarnings(\"ignore\")\n", "import seaborn as sns\n", "sns.set_style('white',{'legend.frameon':'True'});\n", "#sns.set_context('paper')\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from exact_solvers import shallow_water\n", "from collections import namedtuple\n", "from utils import riemann_tools\n", "from ipywidgets import interact\n", "from ipywidgets import widgets, FloatSlider, Checkbox, RadioButtons, fixed\n", "State = namedtuple('State', shallow_water.conserved_variables)\n", "Primitive_State = namedtuple('PrimState', shallow_water.primitive_variables)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We set the gravitational constant to $g=1$ for the nondimensional form of the equations, as used for example in (LeVeque, 2002):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g = 1." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we define a function for each characteristic speed, $\\lambda_1 = u-\\sqrt{gh}$ and $\\lambda_2 = u+\\sqrt{gh}$, for use later." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def lambda_1(q, xi, g=1.):\n", " \"Characteristic speed for shallow water 1-waves.\"\n", " h, hu = q\n", " if h > 0:\n", " u = hu/h\n", " return u - np.sqrt(g*h)\n", " else:\n", " return 0\n", "\n", "def lambda_2(q, xi, g=1.):\n", " \"Characteristic speed for shallow water 2-waves.\"\n", " h, hu = q\n", " if h > 0:\n", " u = hu/h\n", " return u + np.sqrt(g*h)\n", " else:\n", " return 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the notebook we also define some plotting functions for displaying plots." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "hide" ] }, "outputs": [], "source": [ "def make_plot_functions(h_l, h_r, u_l, u_r,\n", " g=1.,force_waves=None,extra_lines=None):\n", " \n", "\n", " q_l = State(Depth = h_l,\n", " Momentum = h_l*u_l)\n", " q_r = State(Depth = h_r,\n", " Momentum = h_r*u_r)\n", " states, speeds, reval, wave_types = \\\n", " shallow_water.exact_riemann_solution(q_l,q_r,g, force_waves=force_waves)\n", " \n", " plot_function_stripes = shallow_water.make_demo_plot_function(h_l,h_r,u_l,u_r,\n", " figsize=(7,2),hlim=(0,4.5),ulim=(-2,2),\n", " force_waves=force_waves)\n", " \n", " def plot_function_xt_phase(plot_1_chars=False,plot_2_chars=False):\n", " plt.figure(figsize=(7,2))\n", " ax = plt.subplot(121)\n", " riemann_tools.plot_waves(states, speeds, reval, wave_types, t=0,\n", " ax=ax, color='multi')\n", " if plot_1_chars:\n", " riemann_tools.plot_characteristics(reval,lambda_1,axes=ax,\n", " extra_lines=extra_lines)\n", " if plot_2_chars:\n", " riemann_tools.plot_characteristics(reval,lambda_2,axes=ax,\n", " extra_lines=extra_lines)\n", " ax = plt.subplot(122)\n", " shallow_water.phase_plane_plot(q_l,q_r,g,ax=ax,\n", " force_waves=force_waves,y_axis='u')\n", " plt.title('Phase plane')\n", " plt.show()\n", " return plot_function_stripes, plot_function_xt_phase\n", "\n", "\n", "def plot_riemann_SW(h_l,h_r,u_l,u_r,g=1.,force_waves=None,extra_lines=None):\n", " plot_function_stripes, plot_function_xt_phase = make_plot_functions(h_l,h_r,u_l,u_r,g,\n", " force_waves,extra_lines)\n", " interact(plot_function_stripes, \n", " t=widgets.FloatSlider(value=0.,min=0,max=.9), fig=fixed(0))\n", " interact(plot_function_xt_phase, \n", " plot_1_chars=Checkbox(description='1-characteristics',\n", " value=False),\n", " plot_2_chars=Checkbox(description='2-characteristics'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Riemann problem\n", "Consider the Riemann problem with left and right states\n", "\n", "\\begin{align*}\n", "q_l & = \\begin{bmatrix} h_l \\\\ h_r u_l \\end{bmatrix}, &\n", "q_r & = \\begin{bmatrix} h_r \\\\ h_r u_r \\end{bmatrix}. \n", "\\end{align*}\n", "\n", "Typically the Riemann solution will consist of two waves, one related to each of the eigenvectors in \\eqref{SW:fjac-evecs}. We refer to the wave related to $r_1$ (the first characteristic family) as a 1-wave and the wave related to $r_2$ (the second characteristic family) as a 2-wave. Each wave may be a shock or rarefaction. There will be an intermediate state $q_m$ between them. In the figure below, we illustrate a typical situation in which the 1-wave happens to be a rarefaction and the 2-wave is a shock:\n", "\n", "![Structure of Riemann solution](./figures/shallow_water_riemann.png)\n", "\n", "In the figure we have one wave going in each direction, but since the wave speeds depend on $q$ and can have either sign, it is possible to have both waves going left, or both going right.\n", "\n", "To solve the Riemann problem, we must find $q_m$. To do so we must find a state that can be connected to $q_l$ by a 1-shock or 1-rarefaction and to $q_r$ by a 2-shock or 2-rarefaction. We must also ensure that the resulting waves satisfy the entropy condition." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Shock waves\n", "Let us now consider a shock wave connecting a known state $q_*=(h_*, h_* u_*)$ to an unknown state $q=(h,hu)$. In the context of the Riemann problem, $q_*$ will be the initial left or right state and $q$ will be the middle state.\n", "The Rankine-Hugoniot jump conditions $s(q_* - q) = f(q_*) - f(q)$ for a shock wave moving\n", "at speed $s$ read\n", "\n", "\\begin{align}\n", "s (h_* - h) & = h_* u_* - h u \\\\\n", "s (h_* u_* - h u) & = h_* u_*^2 - h u^2 + \\frac{g}{2}(h_*^2 - h^2).\n", "\\end{align}\n", "\n", "It is convenient to define $\\alpha = h - h_*$. Then we can combine the two equations above to show that the momenta are related by\n", "\n", "\\begin{align} \\label{SW:hugoniot-locus}\n", " h u & = h_* u_* + \\alpha \\left[u_* \\pm \\sqrt{gh_* \\left(1+\\frac{\\alpha}{h_*}\\right)\\left(1+\\frac{\\alpha}{2h_*}\\right)}\\right]\n", "\\end{align}\n", "\n", "for a shock moving with speed\n", "\n", "\\begin{align} \\label{SW:shock-speed}\n", " s & = \\frac{h_* u_* - h u}{h_* - h} = u_* \\mp \\sqrt{gh_* \\left(1+\\frac{\\alpha}{h_*}\\right)\\left(1+\\frac{\\alpha}{2h_*}\\right)} = u_* \\mp \\sqrt{gh \\frac{h_* + h}{2h_*} }\n", "\\end{align}\n", "\n", "Choosing the plus sign in \\eqref{SW:hugoniot-locus} (which yields the minus sign in \\eqref{SW:shock-speed}) gives a 1-shock. Choosing the minus sign in \\eqref{SW:hugoniot-locus} (which yields the plus sign in \\eqref{SW:shock-speed}) gives a 2-shock. Notice from the last expression in \\eqref{SW:shock-speed} that as $h \\to h^*$ the shock speed approaches the corresponding characteristic speed \\eqref{SW:char-speeds}, while the ratio of the jumps in momentum and depth approaches that suggested by the eigenvectors \\eqref{SW:fjac-evecs}.\n", "\n", "We can now consider the set of all possible states $(h, h u)$ satisfying \\eqref{SW:hugoniot-locus}. This curve in the $h - hu$ plane is known as the hugoniot locus; there is a family of hugoniot loci for 1-shocks and another for 2-shocks. Below we plot some members of the 1-loci of curves. *In the interactive notebook you can select the 2-loci instead.*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "widgets.Checkbox" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "interact(shallow_water.plot_hugoniot_loci, y_axis=widgets.fixed('hu'),\n", " plot_1=widgets.Checkbox(description='Plot 1-loci',value=True),\n", " plot_2=widgets.Checkbox(description='Plot 2-loci',value=False));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The plot above shows the Hugoniot loci in the $h$-$hu$ plane, the natural phase plane in terms of the two conserved quantities. Note that they all approach the origin as $h \\rightarrow 0$. Alternatively, we can plot these same curves in the $h$-$u$ plane:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "interact(shallow_water.plot_hugoniot_loci, y_axis=widgets.fixed('u'),\n", " plot_1=widgets.Checkbox(description='Plot 1-loci',value=True),\n", " plot_2=widgets.Checkbox(description='Plot 2-loci',value=False));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that in this plane, the curves in each family are simply vertical translates of one another, and all curves asymptote to $\\pm \\infty$ as $h \\rightarrow 0$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### All-shock Riemann solutions\n", "If we knew *a priori* that both waves in the Riemann solution were shock waves, we could find $q_m$ by finding the intersection of the 1-Hugoniot locus passing through $q_l$ with the 2-Hugoniot locus passing through $q_r$. The Riemann solution would then consist simply of two discontinuities propagating at the speeds given by \\eqref{SW:shock-speed}.\n", "\n", "Here is an example for which this is the correct solution: The initial depth is the same everywhere, $h_l=h_r$, but the velocity $u_l>0$ while $u_r<0$ so the flow is toward $x=0$ from both sides. Note that in this plot the dark and light blue stripes show a passive tracer advected with the flow, which is shown only to help visualize the fluid velocity in these plots." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot_riemann_SW(h_l=2, h_r=2, u_l=1, u_r=-1)" ] }, { "cell_type": "markdown", "metadata": { "tags": [ "online-instructions" ] }, "source": [ "*In the interactive notebook you can check the boxes to plot the characteristic families, and notice that the 1-characteristics impinge on the 1-shock; the 2-characteristics impinge on the 2-shock. Thus these shocks satisfy the entropy condition.*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Dam-break problem: all-shock solution\n", "Here's another example, which you might think of as modeling a dam that suddenly breaks. The water is initially at rest on both sides, but much deeper on the left. We can again construct a 2-shock Riemann solution, but *this is not physically correct in this case!*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot_riemann_SW(h_l=4, h_r=1, u_l=0, u_r=0, force_waves='shock');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*With the interactive widget, you can check the box to plot the 1-characteristics.* Notice that they don't impinge on the 1-shock; instead, characteristics are diverging away from it. This shock does not satisfy the entropy condition and should be replaced with a rarefaction. The corresponding part of the Hugoniot locus is plotted with a dashed line to remind us that it is unphysical.\n", "\n", "*Now check the box to plot the 2-characteristics.* Notice that these *do* impinge on the 2-shock; this is an entropy-satisfying shock, and the Hugoniot locus is plotted as a solid line." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The entropy condition\n", "For the 2-shock Riemann solution to be physically correct, each of the waves must satisfy the Lax entropy condition, with the characteristic speed to the left of the wave greater than that to the right. Notice that the shock speed \\eqref{SW:shock-speed} can be written as\n", "\n", "\\begin{align}\n", " s & = u_* \\pm \\sqrt{gh \\frac{h_* + h}{2h_*} }\n", "\\end{align} \n", "Thus the 1-shock entropy condition reads\n", "\n", "\\begin{align*}\n", "u_l - \\sqrt{gh_l} > u_l - \\sqrt{gh_l \\frac{h_l+h_m}{2h_l}} = u_m - \\sqrt{gh_m \\frac{h_m+h_l}{2h_m}} > u_m - \\sqrt{gh_m}.\n", "\\end{align*} \n", "The second expression for the shock speed is obtained by noticing\n", "that the shock speed is invariant under transposition of the left and right states.\n", "The first and second inequalities both simplify to\n", "\n", "\\begin{align} \\label{SW:1-entropy}\n", " h_m > h_l.\n", "\\end{align} \n", "Similarly, it can be shown that the entropy condition for a 2-shock connecting $q_m$ and $q_r$ is satisfied if and only if\n", "\n", "\\begin{align} \\label{SW:2-entropy}\n", " h_m > h_r.\n", "\\end{align} \n", "If \\eqref{SW:1-entropy} or \\eqref{SW:2-entropy} is violated, then the\n", "corresponding wave must be a rarefaction rather than a shock.\n", "\n", "Go back and look at the phase plane plots above. You'll notice that the 1-Hugoniot locus passing through $q_l$ is plotted with a solid line for $h>h_l$ and with a dashed line for $hh_l$, the momentum connected to $q_l$ by a 1-shock;\n", "- for $h