├── LICENSE ├── Lesson_00_Introduction.ipynb ├── Lesson_01_Weak_Form.ipynb ├── Lesson_02_Theory.ipynb ├── Lesson_03_Characteristics.ipynb ├── Lesson_04_Shallow_Water.ipynb ├── Lesson_05_Approximate_Solvers.ipynb ├── README.md ├── environment.yml └── styles └── custom.css /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ian Hawke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Lesson_00_Introduction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/html": [ 11 | "\n", 12 | "\n", 13 | "\n", 14 | "\n", 15 | "\n", 16 | "\n", 17 | "\n", 138 | "\n", 139 | "\n" 156 | ], 157 | "text/plain": [ 158 | "" 159 | ] 160 | }, 161 | "execution_count": 1, 162 | "metadata": {}, 163 | "output_type": "execute_result" 164 | } 165 | ], 166 | "source": [ 167 | "from IPython.core.display import HTML\n", 168 | "def css_styling():\n", 169 | " styles = open(\"./styles/custom.css\", \"r\").read()\n", 170 | " return HTML(styles)\n", 171 | "css_styling()" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "# Riemann problems" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "Many physical theories can be written in *balance law form*,\n", 186 | "$$\n", 187 | " \\partial_t {\\bf q} + \\partial_x {\\bf f}({\\bf q}) = {\\bf s}({\\bf q}).\n", 188 | "$$\n", 189 | "The standard example would be Euler's equations of gasdynamics, used to model high-speed fluid flow. However, there are a huge number of other models, from simple models of traffic flow, through acoustics, electromagnetics, nonlinear elasticity, plasma physics, and even up to Einstein's equations of general relativity." 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "metadata": {}, 195 | "source": [ 196 | "Equations in balance law form have a number of generic properties. One of the most important is that they admit *discontinuous solutions*. This has two important effects:\n", 197 | "\n", 198 | "1. the balance law form given above makes no sense - if the solution is discontinuous then the derivative terms in the equation such as $\\partial_x {\\bf f}$ do not exist;\n", 199 | "2. the numerical solution of the balance law form becomes significantly more complex than if the solution were smooth." 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "The wide importance of balance laws means that these problems have been tackled and solved, for practical purposes, many times. This set of notebooks introduces the mathematical and numerical techniques needed to solve the simplest, but most important, discontinuous case: the **Riemann problem**." 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "## Background" 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "metadata": {}, 219 | "source": [ 220 | "These notebooks will assume that you have both the necessary mathematical and programming background. Essentially, if you have completed David Ketcheson's [HyperPython](https://github.com/ketch/HyperPython) course then you will have both, as only simple python techniques and packages will be used." 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [ 227 | "A wide variety of books and resources have been written on Riemann problems, much of it concentrating on the Euler equations. In steadily increasing order of complexity I would recommend\n", 228 | "\n", 229 | "1. [Culbert B. Laney, \"Computational Gasdynamics\"](http://www.amazon.com/Computational-Gasdynamics-Culbert-B-Laney/dp/0521625580). Verbose and easy to read, this gives a lot of key details without getting overly complex. Concentrates on the Euler equations.\n", 230 | "2. [Randall J. LeVeque, \"Finite Volume Methods for Conservation Laws\"](http://www.amazon.com/Methods-Hyperbolic-Problems-Cambridge-Mathematics/dp/0521009243). Much more breadth and mathematical detail. The aim is more towards deriving the numerical methods used in Ketcheson's [HyperPython](https://github.com/ketch/HyperPython) course than on the Riemann problem explicitly, but a detailed derivation of key results for the shallow water equations is there.\n", 231 | "3. [Eleuterio F. Toro, \"Riemann Solvers and Numerical Methods for Fluid Dynamics: a practical introduction](http://www.amazon.com/Riemann-Solvers-Numerical-Methods-Dynamics/dp/3642064388). A hugely detailed book on Riemann problems, focusing on the Euler equations, and the links to numerical methods. Comprehensive and repays re-reading, but not straightforward.\n", 232 | "4. [Philippe G. LeFloch, \"Hyperbolic Systems of Conservation Laws: The Theory of Classical and Nonclassical Shock Waves\"](http://www.amazon.com/Hyperbolic-Systems-Conservation-Laws-Nonclassical/dp/3764366877). Detailed mathematical theory both for the standard systems, but also covering what happens when the theory gets more complex (e.g. at phase transitions).\n", 233 | "5. [Alexander Voss, \"Exact Riemann Solution for the Euler Equations with Nonconvex and Nonsmooth Equation of State\" ](http://134.130.184.8/opus/volltexte/2005/1210/) (PhD thesis). A detailed explanation of how to solve generic Riemann problems, even when the system gets more complex." 234 | ] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "Python 3", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.6.4" 254 | } 255 | }, 256 | "nbformat": 4, 257 | "nbformat_minor": 1 258 | } 259 | -------------------------------------------------------------------------------- /Lesson_01_Weak_Form.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# The weak form of a conservation law" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "In [Lesson 0](Lesson_00_Introduction.ipynb) we wrote down the balance law form\n", 15 | "$$\n", 16 | " \\partial_t {\\bf q} + \\partial_x {\\bf f}({\\bf q}) = {\\bf s}({\\bf q}).\n", 17 | "$$\n", 18 | "\n", 19 | "For the purposes of solving the Riemann problem we shall restrict to *conservation laws*\n", 20 | "$$\n", 21 | " \\partial_t {\\bf q} + \\partial_x {\\bf f}({\\bf q}) = {\\bf 0}\n", 22 | "$$\n", 23 | "which retain all of the key features of balance laws over small regions of space and time." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "In [Lesson 0](Lesson_00_Introduction.ipynb) we stated (without showing!) that, for a generic conservation law, we can find discontinuous solutions. An example of this occuring can be seen in Ketcheson's [lesson on the traffic flow equation](http://nbviewer.ipython.org/github/ketch/HyperPython/blob/master/Lesson_02_Traffic.ipynb). When this occurs the conservation law makes no sense. Instead we should consider the *weak form*." 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "Consider single scalar quantity $q$ on a one-dimensional region of space $[x_l, x_r]$. The total amount of \"stuff\" inside this region is\n", 38 | "$$\n", 39 | " \\textrm{Total stuff} = \\int_{x_l}^{x_r} q \\, \\textrm{d}x.\n", 40 | "$$\n", 41 | "The flux of \"stuff\" through the left boundary of the region is $f(q(x_l))$. The flux of stuff through the right boundary of the region is $-f(q(x_r))$ (the choice of sign is conventional, but must be consistent with flow in through the left boundary). Therefore the total rate of change of stuff is\n", 42 | "$$\n", 43 | " \\frac{\\textrm{d}}{\\textrm{d}t} \\int_{x_l}^{x_r} q \\, \\textrm{d}x = f(q(x_l)) - f(q(x_r)).\n", 44 | "$$" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "This form is perfectly valid if the solution is discontinuous, as no spatial derivatives appear. However, we need to show that it is equivalent to the standard conservation law form." 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "We know that the standard form only holds when the derivative makes sense. So we will assume that the solution (and hence the flux) is differentiable. We then note that the right hand side of the form above can be written as\n", 59 | "$$\n", 60 | " f(q(x_l)) - f(q(x_r)) = - \\int_{x_l}^{x_r} \\frac{\\textrm{d}}{\\textrm{d}x} f(q) \\, \\textrm{d}x.\n", 61 | "$$\n", 62 | "This means we can write the integral form above as\n", 63 | "$$\n", 64 | " \\int_{x_l}^{x_r} \\left( \\partial_t q + \\partial_x f(q) \\right) \\, \\textrm{d}x = 0.\n", 65 | "$$\n", 66 | "This only holds if the integrand vanishes, which is the standard conservation law form." 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "## Non-uniqueness" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "At first glance this is very simple. It appears that when we write down a conservation law\n", 81 | "$$\n", 82 | " \\partial_t {\\bf q} + \\partial_x {\\bf f}({\\bf q}) = {\\bf 0}\n", 83 | "$$\n", 84 | "we really *mean* that we're solving the weak form\n", 85 | "$$\n", 86 | " \\frac{\\textrm{d}}{\\textrm{d}t} \\int_{x_l}^{x_r} {\\bf q} \\, \\textrm{d}x = {\\bf f}({\\bf q}(x_l)) - {\\bf f}({\\bf q}(x_r)).\n", 87 | "$$\n", 88 | "With a similar appeal to Gauss' theorem we can extend this to multiple dimensions by considering an arbitrary volume $V$ with boundary $\\partial V$. The weak form\n", 89 | "$$\n", 90 | " \\frac{\\textrm{d}}{\\textrm{d}t} \\int_{V} {\\bf q} \\, \\textrm{d}{\\bf x} = \\oint_{\\partial V} {\\bf f}({\\bf q}) \\cdot \\textrm{d}{\\bf S}\n", 91 | "$$\n", 92 | "is then equivalent to the strong conservation law form\n", 93 | "$$\n", 94 | " \\partial_t {\\bf q} + \\nabla \\cdot {\\bf f}({\\bf q}) = {\\bf 0}.\n", 95 | "$$" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "However, this equivalence is one way only. That is, any weak form is associated with a *unique* strong form. However, any strong form can be associated with *infinitely many* weak forms." 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "To see an example of this, consider *Burger's equation*\n", 110 | "$$\n", 111 | " \\partial_t q + \\partial_x \\left( \\tfrac{1}{2} q^2 \\right) = 0.\n", 112 | "$$\n", 113 | "If the solution is differentiable then Burger's equation is equivalent to the non-conservation law equation\n", 114 | "$$\n", 115 | " \\partial_t q + q \\partial_x q = 0,\n", 116 | "$$\n", 117 | "which is similar to the advection equation.\n", 118 | "\n", 119 | "Now consider the equation\n", 120 | "$$\n", 121 | " \\partial_t \\left( \\tfrac{1}{n} q^n \\right) + \\partial_x \\left( \\tfrac{1}{n+1} q^{n+1} \\right) = 0.\n", 122 | "$$\n", 123 | "It is clear that, for differentiable solutions, and for *any* $n$ that is not equation to $0, -1$ this equation is equivalent to the non-conservation form of Burger's equation\n", 124 | "$$\n", 125 | " \\partial_t q + q \\partial_x q = 0.\n", 126 | "$$\n", 127 | "However, each equation has a different weak form. More importantly, we will show later that each equation has *different behaviour* for discontinuous solutions." 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "metadata": {}, 133 | "source": [ 134 | "Therefore we will always assume that the weak solution is the fundamental form. When deriving the system of equations to solve we should always do it using the integral form, from which we can get an associated strong form. When writing the equations we typically use the strong form for convenience, but the primacy of the weak form should remain in the back of your mind." 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": 1, 140 | "metadata": {}, 141 | "outputs": [ 142 | { 143 | "data": { 144 | "text/html": [ 145 | "\n", 146 | "\n", 147 | "\n", 148 | "\n", 149 | "\n", 150 | "\n", 151 | "\n", 272 | "\n", 273 | "\n" 290 | ], 291 | "text/plain": [ 292 | "" 293 | ] 294 | }, 295 | "execution_count": 1, 296 | "metadata": {}, 297 | "output_type": "execute_result" 298 | } 299 | ], 300 | "source": [ 301 | "from IPython.core.display import HTML\n", 302 | "def css_styling():\n", 303 | " styles = open(\"./styles/custom.css\", \"r\").read()\n", 304 | " return HTML(styles)\n", 305 | "css_styling()" 306 | ] 307 | } 308 | ], 309 | "metadata": { 310 | "kernelspec": { 311 | "display_name": "Python 3", 312 | "language": "python", 313 | "name": "python3" 314 | }, 315 | "language_info": { 316 | "codemirror_mode": { 317 | "name": "ipython", 318 | "version": 3 319 | }, 320 | "file_extension": ".py", 321 | "mimetype": "text/x-python", 322 | "name": "python", 323 | "nbconvert_exporter": "python", 324 | "pygments_lexer": "ipython3", 325 | "version": "3.6.4" 326 | } 327 | }, 328 | "nbformat": 4, 329 | "nbformat_minor": 1 330 | } 331 | -------------------------------------------------------------------------------- /Lesson_02_Theory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 4, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/html": [ 11 | "\n", 12 | "\n", 13 | "\n", 14 | "\n", 15 | "\n", 16 | "\n", 17 | "\n", 135 | "\n", 136 | "\n" 152 | ], 153 | "text/plain": [ 154 | "" 155 | ] 156 | }, 157 | "execution_count": 4, 158 | "metadata": {}, 159 | "output_type": "execute_result" 160 | } 161 | ], 162 | "source": [ 163 | "from IPython.core.display import HTML\n", 164 | "def css_styling():\n", 165 | " styles = open(\"./styles/custom.css\", \"r\").read()\n", 166 | " return HTML(styles)\n", 167 | "css_styling()" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "# Theory results" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "There are a number of key theory results that we need when solving the Riemann problem, written as\n", 182 | "$$\n", 183 | " \\partial_t {\\bf q} + \\partial_x {\\bf f}({\\bf q}) = {\\bf 0}, \\qquad {\\bf q}(x, t=0) = \\begin{cases} {\\bf q}_l & x < 0, \\\\ {\\bf q}_r & x > 0, \\end{cases}\n", 184 | "$$\n", 185 | "where the left and right states ${\\bf q}_{l, r}$ are constant." 186 | ] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": {}, 191 | "source": [ 192 | "## Self similarity" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "We need to show that the solution is *self-similar*, which in this case means the solution can be written as a function of one variable, ${\\bf q} \\equiv {\\bf q}(\\xi)$ where $\\xi = x/t$." 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "First, it is obvious that the equation defining the conservation law,\n", 207 | "$$\n", 208 | " \\partial_t {\\bf q} + \\partial_x {\\bf f}({\\bf q}) = {\\bf 0},\n", 209 | "$$\n", 210 | "is *scale-free*. That is, if we change coordinates by any constant scaling to $t' = a t$ and $x' = a x$ where $a > 0$, the conservation law is unchanged in form, \n", 211 | "$$\n", 212 | " \\partial_{t'} {\\bf q} + \\partial_{x'} {\\bf f}({\\bf q}) = {\\bf 0}.\n", 213 | "$$" 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "metadata": {}, 219 | "source": [ 220 | "Second, we need the initial data to also be scale free. For the Riemann problem this is clearly true: applying the same scaling to the initial data leaves it unchanged." 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [ 227 | "Combining these results we see that the solution must be scale free so that if $\\xi = x / t$ is constant (so that $x$ and $t$ are scaled in the same way), the solution must be constant. So ${\\bf q} \\equiv {\\bf q}(\\xi)$." 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "metadata": {}, 233 | "source": [ 234 | "We will often think of $\\xi$ as a *spatial* coordinate, by considering the result at a fixed time $t = T$, say." 235 | ] 236 | }, 237 | { 238 | "cell_type": "markdown", 239 | "metadata": {}, 240 | "source": [ 241 | "## Differentiable solutions" 242 | ] 243 | }, 244 | { 245 | "cell_type": "markdown", 246 | "metadata": {}, 247 | "source": [ 248 | "For sections of the solution that are differentiable, we can take two steps. First we can write the conservation law in the quasilinear form\n", 249 | "$$\n", 250 | " \\partial_t {\\bf q} + \\frac{\\partial {\\bf f}}{\\partial {\\bf q}} \\partial_x {\\bf q} = {\\bf 0}.\n", 251 | "$$\n", 252 | "Second we can use the self-similarity to replace the partial derivatives, using\n", 253 | "$$\n", 254 | "\\begin{align}\n", 255 | " \\partial_t & = \\frac{\\partial \\xi}{\\partial t} \\partial_{\\xi} & \\partial_x & = \\frac{\\partial \\xi}{\\partial x} \\partial_{\\xi} \\\\\n", 256 | " & = -\\frac{\\xi}{t} \\partial_{\\xi} & & = \\frac{1}{t} \\partial_{\\xi},\n", 257 | "\\end{align}\n", 258 | "$$\n", 259 | "to get\n", 260 | "$$\n", 261 | " \\frac{1}{t} \\left( \\frac{\\partial {\\bf f}}{\\partial {\\bf q}} - \\xi \\textrm{Id} \\right) \\partial_{\\xi} {\\bf q} = {\\bf 0}.\n", 262 | "$$" 263 | ] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": {}, 268 | "source": [ 269 | "For $t > 0$ there are two possible cases:\n", 270 | "\n", 271 | "1. $\\partial_{\\xi} {\\bf q} = {\\bf 0}$: the state is *constant*. \n", 272 | "2. $\\xi$ is an eigenvalue of the Jacobian matrix $\\partial {\\bf f} / \\partial {\\bf q}$, and $\\partial_{\\xi} {\\bf q}$ is the eigenvector corresponding to this eigenvalue." 273 | ] 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "metadata": {}, 278 | "source": [ 279 | "We note two consequences from this. First, it only makes sense if the eigenvalues of the Jacobian are real. This is one of the requirements of the system being *hyperbolic*: the eigenvalues must be real and the eigenvectors independent. Second, if we denote the eigenvalue by $\\lambda$ and its associated eigenvector by ${\\bf r}$, then we have\n", 280 | "$$\n", 281 | "\\begin{align}\n", 282 | " && \\lambda({\\bf q}) & = \\xi \\\\\n", 283 | " \\implies && \\frac{\\partial \\lambda}{\\partial {\\bf q}} \\frac{\\partial {\\bf q}}{\\partial \\xi} & = 1, \n", 284 | "\\end{align}\n", 285 | "$$\n", 286 | "by differentiating with respect to $\\xi$. Then, as $\\partial_{\\xi} {\\bf q}$ is, as noted above, an eigenvector, we write\n", 287 | "$$\n", 288 | " \\frac{\\partial {\\bf q}}{\\partial \\xi} = \\alpha {\\bf r},\n", 289 | "$$\n", 290 | "and combining the two results determines the proportionality constant $\\alpha$ as\n", 291 | "$$\n", 292 | " \\alpha = \\left[ {\\bf r} \\cdot \\frac{\\partial \\lambda}{\\partial {\\bf q}} \\right]^{-1}\n", 293 | "$$\n", 294 | "Therefore we have the *ordinary* differential equation for ${\\bf q}$\n", 295 | "$$\n", 296 | " \\frac{\\partial {\\bf q}}{\\partial \\xi} = \\frac{{\\bf r}}{{\\bf r} \\cdot \\frac{\\partial \\lambda}{\\partial {\\bf q}}}.\n", 297 | "$$\n", 298 | "This gives a continuous solution for ${\\bf q}$, *provided* that ${\\bf r} \\cdot \\frac{\\partial \\lambda}{\\partial {\\bf q}} \\ne 0$. This is the requirement that the solution across this wave is *genuinely nonlinear*." 299 | ] 300 | }, 301 | { 302 | "cell_type": "markdown", 303 | "metadata": {}, 304 | "source": [ 305 | "A final consequence of this section is the *number* of waves. If the system has size $N$ then there are $N$ eigenvalues, and hence $N$ waves. These will separate constant states, so we expect the solution to consist of $N+1$ constant states separated by $N$ waves." 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "metadata": {}, 311 | "source": [ 312 | "## Discontinuous solutions" 313 | ] 314 | }, 315 | { 316 | "cell_type": "markdown", 317 | "metadata": {}, 318 | "source": [ 319 | "There remains only one possibility: the solution is not a continuous function of the similarity coordinate $\\xi$. That such solutions may exist is no surprise, as it is true of the initial data. " 320 | ] 321 | }, 322 | { 323 | "cell_type": "markdown", 324 | "metadata": {}, 325 | "source": [ 326 | "When the solution is discontinuous we must use the weak form\n", 327 | "$$\n", 328 | " \\frac{\\textrm{d}}{\\textrm{d}t} \\int_{x_l}^{x_r} {\\bf q} \\, \\textrm{d}x = {\\bf f}({\\bf q}(x_l)) - {\\bf f}({\\bf q}(x_r)).\n", 329 | "$$" 330 | ] 331 | }, 332 | { 333 | "cell_type": "markdown", 334 | "metadata": {}, 335 | "source": [ 336 | "Let us assume that the solution is discontinuous along the path $X(t)$. Consider the volume close to this discontinuity, so that $x_r$ and $x_l$ remain fixed points, but $x_r - x_l = \\epsilon \\ll 1$. We then split the integrals at the shock, as the data jumps there. In the weak form this gives\n", 337 | "$$\n", 338 | "\\begin{align}\n", 339 | " && \\frac{\\textrm{d}}{\\textrm{d}t} \\int_{x_l}^{X(t)} {\\bf q} \\, \\textrm{d}x + \\frac{\\textrm{d}}{\\textrm{d}t} \\int_{X(t)}^{x_r} {\\bf q} \\, \\textrm{d}x & = {\\bf f}({\\bf q}(x_l)) - {\\bf f}({\\bf q}(x_r)) \\\\\n", 340 | " \\implies && \\frac{\\textrm{d}X}{\\textrm{d}t} \\left\\{ {\\bf q} \\left( X(t)_{-}, t \\right) - {\\bf q} \\left( X(t)_{+}, t \\right) \\right\\} + \\int_{x_l}^{x_r} \\frac{\\partial {\\bf q}}{\\partial t} \\, \\textrm{d}x & = {\\bf f}({\\bf q}(x_l)) - {\\bf f}({\\bf q}(x_r)).\n", 341 | "\\end{align}\n", 342 | "$$\n", 343 | "Taking the limit $\\epsilon \\to 0$ the integral vanishes (as the width of the integral is $\\epsilon$) and we have $x_l \\to X(t)_{-}$ and $x_r \\to X(t)_{+}$. We are left with the *Rankine-Hugoniot conditions*\n", 344 | "$$\n", 345 | " \\frac{\\textrm{d}X}{\\textrm{d}t} \\left[ {\\bf q} \\right] = \\left[ {\\bf f}({\\bf q}) \\right].\n", 346 | "$$\n", 347 | "Here the notation $\\left[ {\\bf u} \\right]$ means the jump in the quantity ${\\bf u}$ across the discontinuity." 348 | ] 349 | }, 350 | { 351 | "cell_type": "markdown", 352 | "metadata": {}, 353 | "source": [ 354 | "As the solution must be self-similar, the shock speed must be constant, $\\textrm{d}X / \\textrm{d}t = V_s$, giving the standard form\n", 355 | "$$\n", 356 | " V_s \\left[ {\\bf q} \\right] = \\left[ {\\bf f}({\\bf q}) \\right].\n", 357 | "$$" 358 | ] 359 | }, 360 | { 361 | "cell_type": "markdown", 362 | "metadata": {}, 363 | "source": [ 364 | "## Summary" 365 | ] 366 | }, 367 | { 368 | "cell_type": "markdown", 369 | "metadata": {}, 370 | "source": [ 371 | "1. The solution to the Riemann problem is self-similar.\n", 372 | "2. The solution contains $N+1$ constant states separated by $N$ waves.\n", 373 | "3. The wave may be continuous, in which case it solves an ordinary differential equation determined by eigenvalues and eigenvectors of the Jacobian matrix.\n", 374 | "4. The wave may be discontinuous, in which case it satisfies the Rankine-Hugoniot equations." 375 | ] 376 | }, 377 | { 378 | "cell_type": "markdown", 379 | "metadata": {}, 380 | "source": [ 381 | "This is not the most general solution covering all cases, but is sufficient for most cases of interest." 382 | ] 383 | } 384 | ], 385 | "metadata": { 386 | "kernelspec": { 387 | "display_name": "Python 3", 388 | "language": "python", 389 | "name": "python3" 390 | }, 391 | "language_info": { 392 | "codemirror_mode": { 393 | "name": "ipython", 394 | "version": 3 395 | }, 396 | "file_extension": ".py", 397 | "mimetype": "text/x-python", 398 | "name": "python", 399 | "nbconvert_exporter": "python", 400 | "pygments_lexer": "ipython3", 401 | "version": "3.6.4" 402 | } 403 | }, 404 | "nbformat": 4, 405 | "nbformat_minor": 1 406 | } 407 | -------------------------------------------------------------------------------- /Lesson_04_Shallow_Water.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/html": [ 11 | "\n", 12 | "\n", 13 | "\n", 14 | "\n", 15 | "\n", 16 | "\n", 17 | "\n", 138 | "\n", 139 | "\n" 156 | ], 157 | "text/plain": [ 158 | "" 159 | ] 160 | }, 161 | "execution_count": 1, 162 | "metadata": {}, 163 | "output_type": "execute_result" 164 | } 165 | ], 166 | "source": [ 167 | "from IPython.core.display import HTML\n", 168 | "def css_styling():\n", 169 | " styles = open(\"./styles/custom.css\", \"r\").read()\n", 170 | " return HTML(styles)\n", 171 | "css_styling()" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "# Shallow Water Equations" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "As a simple example of solving the Riemann problem for a nonlinear system we look at the *shallow water* equations. These are a simplification of the Navier-Stokes equations, reduced here to one spatial dimension $x$, which determine the height $h(x, t)$ of the water with respect to some reference location, and its velocity $u(x, t)$. In the simplest case (where the bed of the channel is flat, and the gravitational constant is renormalised to $1$) these can be written in the conservation law form\n", 186 | "$$\n", 187 | " \\partial_t \\begin{pmatrix} h \\\\ h u \\end{pmatrix} + \\partial_x \\begin{pmatrix} hu \\\\ h u^2 + \\tfrac{1}{2} h^2 \\end{pmatrix} = {\\bf 0}.\n", 188 | "$$\n", 189 | "The *conserved variables* ${\\bf q} = (q_1, q_2)^T = (h, h u)^T$ are effectively the total mass and momentum of the fluid." 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "metadata": {}, 195 | "source": [ 196 | "## Quasilinear form" 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "metadata": {}, 202 | "source": [ 203 | "As seen in the [theory lesson](Lesson_Theory.ipynb), to construct the solution we need the eigenvalues and eigenvectors of the Jacobian matrix. We can construct them directly, by first noting that, in terms of the conserved variables, \n", 204 | "$$\n", 205 | " {\\bf f} = \\begin{pmatrix} f_1 \\\\ f_2 \\end{pmatrix} = \\begin{pmatrix} q_2 \\\\ \\frac{q_2^2}{q_1} + \\frac{q_1^2}{2} \\end{pmatrix}.\n", 206 | "$$\n", 207 | "Therefore the Jacobian is\n", 208 | "$$ \\frac{\\partial {\\bf f}}{\\partial {\\bf q}} = \\begin{pmatrix} 0 & 1 \\\\ -u^2 + h & 2 u \\end{pmatrix}.\n", 209 | "$$\n", 210 | "The eigenvalues and eigenvectors follow immediately as\n", 211 | "$$\n", 212 | "\\begin{align}\n", 213 | " \\lambda_{1} & = u - \\sqrt{h}, & \\lambda_{2} & = u + \\sqrt{h}, \\\\\n", 214 | " {\\bf r}_{1} & = \\begin{pmatrix} 1 \\\\ u - \\sqrt{h} \\end{pmatrix}, & {\\bf r}_{2} & = \\begin{pmatrix} 1 \\\\ u + \\sqrt{h} \\end{pmatrix} .\n", 215 | "\\end{align}\n", 216 | "$$\n", 217 | "Here we have followed the standard convention $\\lambda_1 \\le \\lambda_2 \\le \\dots \\le \\lambda_N$." 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "An alternative approach that may be considerably easier to apply for more complex problems is to write down a different quasilinear form of the equation, which in this case is in terms of the *primitive variables* ${\\bf w} = (h, u)^T$,\n", 225 | "$$\n", 226 | " \\partial_t \\begin{pmatrix} h \\\\ u \\end{pmatrix} + \\begin{pmatrix} u & h \\\\ 1 & u \\end{pmatrix} \\partial_x \\begin{pmatrix} h \\\\ u \\end{pmatrix} = {\\bf 0}.\n", 227 | "$$\n", 228 | "The general form here would be written \n", 229 | "$$\n", 230 | " \\partial_t {\\bf w} + B({\\bf w}) \\partial_x {\\bf w} = {\\bf 0}.\n", 231 | "$$\n", 232 | "It is straightforward to check that \n", 233 | "$$\n", 234 | " B = \\left( \\frac{\\partial {\\bf q}}{\\partial {\\bf w}} \\right)^{-1} \\frac{\\partial {\\bf f}}{\\partial {\\bf w}} = \\left( \\frac{\\partial {\\bf q}}{\\partial {\\bf w}} \\right)^{-1} \\frac{\\partial {\\bf f}}{\\partial {\\bf q}} \\left( \\frac{\\partial {\\bf q}}{\\partial {\\bf w}} \\right).\n", 235 | "$$\n", 236 | "Thus $B$ is *similar* to the Jacobian, so must have the same eigenvalues, which is straightforward to check. We also have that\n", 237 | "$$\n", 238 | "\\begin{align}\n", 239 | " B \\left\\{ \\left( \\frac{\\partial {\\bf q}}{\\partial {\\bf w}} \\right)^{-1} {\\bf r} \\right\\} = \\lambda \\left\\{ \\left( \\frac{\\partial {\\bf q}}{\\partial {\\bf w}} \\right)^{-1} {\\bf r} \\right\\},\n", 240 | "\\end{align}\n", 241 | "$$\n", 242 | "showing that the eigenvectors of the Jacobian can be straightforwardly found from the eigenvectors of $B$, which for the shallow water case are\n", 243 | "$$\n", 244 | "\\begin{align}\n", 245 | " {\\bf \\hat{r}}_1 &= \\begin{pmatrix} -\\sqrt{h} \\\\ 1 \\end{pmatrix} & {\\bf \\hat{r}}_2 &= \\begin{pmatrix} \\sqrt{h} \\\\ 1 \\end{pmatrix}.\n", 246 | "\\end{align}\n", 247 | "$$" 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "metadata": {}, 253 | "source": [ 254 | "## Rarefaction waves" 255 | ] 256 | }, 257 | { 258 | "cell_type": "markdown", 259 | "metadata": {}, 260 | "source": [ 261 | "The solution across a continuous rarefaction wave is given by the solution of the ordinary differential equation\n", 262 | "$$\n", 263 | " \\partial_{\\xi} {\\bf q} = \\frac{{\\bf r}}{{\\bf r} \\cdot \\partial_{{\\bf q}} \\lambda}\n", 264 | "$$\n", 265 | "where $\\lambda, {\\bf r}$ are the eigenvalues and eigenvectors of the Jacobian matrix. Note that we can change variables to get the (physically equivalent) relation differential equation\n", 266 | "$$\n", 267 | " \\partial_{\\xi} {\\bf w} = \\frac{{\\bf r}}{{\\bf r} \\cdot \\partial_{{\\bf w}} \\lambda}\n", 268 | "$$\n", 269 | "where now the eigenvectors are those of the appropriate matrix for the quasilinear form for ${\\bf w}$. Where ${\\bf w}$ are the primitive variables as above, the matrix is $B$ and the eigenvectors given by ${\\bf \\hat{r}}$ as above." 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "For the shallow water equations we will solve this equation for the primitive variables for the first wave only - symmetry gives the other wave straightforwardly. Starting from\n", 277 | "$$\n", 278 | " \\lambda_1 = u - \\sqrt{h}, \\qquad {\\bf \\hat{r}}_1 = \\begin{pmatrix} -\\sqrt{h} \\\\ 1 \\end{pmatrix}\n", 279 | "$$\n", 280 | "we have\n", 281 | "$$\n", 282 | " \\partial_{{\\bf w}} \\lambda_1 = \\begin{pmatrix} -\\frac{1}{2 \\sqrt{h}} \\\\ 1 \\end{pmatrix}\n", 283 | "$$\n", 284 | "and hence\n", 285 | "$$\n", 286 | " {\\bf \\hat{r}}_1 \\cdot \\partial_{{\\bf w}} \\lambda_1 = \\frac{3}{2}\n", 287 | "$$\n", 288 | "from which we have\n", 289 | "$$\n", 290 | " \\partial_{\\xi} \\begin{pmatrix} h \\\\ u \\end{pmatrix} = \\frac{2}{3} \\begin{pmatrix} -\\sqrt{h} \\\\ 1 \\end{pmatrix}.\n", 291 | "$$" 292 | ] 293 | }, 294 | { 295 | "cell_type": "markdown", 296 | "metadata": {}, 297 | "source": [ 298 | "This is straightforwardly integrated to get\n", 299 | "$$\n", 300 | " \\begin{pmatrix} h \\\\ u \\end{pmatrix} = \\begin{pmatrix} \\left( c_1 - \\frac{\\xi}{3} \\right)^2 \\\\ \\frac{2}{3} \\xi + c_2 \\end{pmatrix}. \n", 301 | "$$" 302 | ] 303 | }, 304 | { 305 | "cell_type": "markdown", 306 | "metadata": {}, 307 | "source": [ 308 | "To fix the integration constants $c_{1,2}$ we need to say which state the solution is starting from. As we are looking at the left wave, we expect it to start from the left state ${\\bf w}_l = (h_l, u_l)^T$. The left state will connect to the rarefaction wave when the characteristic speeds match, i.e. when $\\xi = \\xi_l = \\lambda_1 = u_l - \\sqrt{h_l}$. Therefore we have\n", 309 | "$$\n", 310 | " \\begin{pmatrix} h_l \\\\ u_l \\end{pmatrix} = \\begin{pmatrix} \\left( c_1 - \\frac{\\xi_l}{3} \\right)^2 \\\\ \\frac{2}{3} \\xi_l + c_2 \\end{pmatrix},\n", 311 | "$$\n", 312 | "from which we determine\n", 313 | "$$\n", 314 | " c_1 = \\frac{1}{3} \\xi_l + \\sqrt{h_l}, \\qquad c_2 = u_l - \\frac{2}{3} \\xi_l.\n", 315 | "$$" 316 | ] 317 | }, 318 | { 319 | "cell_type": "markdown", 320 | "metadata": {}, 321 | "source": [ 322 | "This gives the final solution\n", 323 | "$$\n", 324 | " \\begin{pmatrix} h \\\\ u \\end{pmatrix} = \\begin{pmatrix} \\left( \\frac{\\xi_l - \\xi}{3} + \\sqrt{h_l} \\right)^2 \\\\ \\frac{2}{3} (\\xi - \\xi_l) + u_l \\end{pmatrix}. \n", 325 | "$$" 326 | ] 327 | }, 328 | { 329 | "cell_type": "markdown", 330 | "metadata": {}, 331 | "source": [ 332 | "### Rarefaction examples" 333 | ] 334 | }, 335 | { 336 | "cell_type": "markdown", 337 | "metadata": {}, 338 | "source": [ 339 | "Let us look at all points that can be connected to a certain state by a rarefaction. We do this in the *phase plane*, which is the $(h, u)$ plane. The \"known state\" will be given by a marker, and all states along the rarefaction curve given by the line, sometimes known as an integral curve." 340 | ] 341 | }, 342 | { 343 | "cell_type": "code", 344 | "execution_count": 1, 345 | "metadata": {}, 346 | "outputs": [], 347 | "source": [ 348 | "%matplotlib inline\n", 349 | "import matplotlib.pyplot as plt\n", 350 | "import numpy as np" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": 2, 356 | "metadata": {}, 357 | "outputs": [], 358 | "source": [ 359 | "hl = np.linspace(0.1, 10.1)\n", 360 | "ul = np.linspace(-1.0, 1.0)\n", 361 | "HL, UL = np.meshgrid(hl, ul)\n", 362 | "XIL = UL - np.sqrt(HL)\n", 363 | "xi_min = np.min(XIL)\n", 364 | "xi_max = np.max(XIL)\n", 365 | "h_min = np.min(hl)\n", 366 | "h_max = np.max(hl)\n", 367 | "u_min = np.min(ul)\n", 368 | "u_max = np.max(ul)\n", 369 | "xi = np.linspace(xi_min, xi_max)" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": 3, 375 | "metadata": {}, 376 | "outputs": [], 377 | "source": [ 378 | "def plot_sw_rarefaction(hl, ul):\n", 379 | " \"Plot the rarefaction curve through the state (hl, ul)\"\n", 380 | " \n", 381 | " xil = ul - np.sqrt(hl)\n", 382 | " h = ((xil - xi) / 3.0 + np.sqrt(hl))**2\n", 383 | " u = 2.0 * (xi - xil) / 3.0 + ul\n", 384 | " \n", 385 | " fig = plt.figure(figsize=(12,8))\n", 386 | " ax = fig.add_subplot(111)\n", 387 | " ax.plot(hl, ul, 'rx', markersize = 16, markeredgewidth = 3)\n", 388 | " ax.plot(h, u, 'k--', linewidth = 2)\n", 389 | " ax.set_xlabel(r\"$h$\")\n", 390 | " ax.set_ylabel(r\"$u$\")\n", 391 | " dh = h_max - h_min\n", 392 | " du = u_max - u_min\n", 393 | " ax.set_xbound(h_min - 0.1 * dh, h_max + 0.1 * dh)\n", 394 | " ax.set_ybound(u_min - 0.1 * du, u_max + 0.1 * du)\n", 395 | " fig.tight_layout()" 396 | ] 397 | }, 398 | { 399 | "cell_type": "code", 400 | "execution_count": 4, 401 | "metadata": {}, 402 | "outputs": [ 403 | { 404 | "data": { 405 | "application/vnd.jupyter.widget-view+json": { 406 | "model_id": "7ec1b63393cd4d3eb936461d251bd4cb", 407 | "version_major": 2, 408 | "version_minor": 0 409 | }, 410 | "text/html": [ 411 | "

Failed to display Jupyter Widget of type interactive.

\n", 412 | "

\n", 413 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 414 | " that the widgets JavaScript is still loading. If this message persists, it\n", 415 | " likely means that the widgets JavaScript library is either not installed or\n", 416 | " not enabled. See the Jupyter\n", 417 | " Widgets Documentation for setup instructions.\n", 418 | "

\n", 419 | "

\n", 420 | " If you're reading this message in another frontend (for example, a static\n", 421 | " rendering on GitHub or NBViewer),\n", 422 | " it may mean that your frontend doesn't currently support widgets.\n", 423 | "

\n" 424 | ], 425 | "text/plain": [ 426 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=0.0, description='ul', max=1.0, min=-1.0), Output()), _dom_classes=('widget-interact',))" 427 | ] 428 | }, 429 | "metadata": {}, 430 | "output_type": "display_data" 431 | } 432 | ], 433 | "source": [ 434 | "from ipywidgets import interactive, FloatSlider\n", 435 | "\n", 436 | "interactive(plot_sw_rarefaction, \n", 437 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 438 | " ul = FloatSlider(min = -1.0, max = 1.0, value = 0.0))" 439 | ] 440 | }, 441 | { 442 | "cell_type": "markdown", 443 | "metadata": {}, 444 | "source": [ 445 | "There is a problem with this: we haven't checked if the states on the curve can be *physically* connected to this point. That is, we haven't checked how the characteristic speed changes along the curve.\n", 446 | "\n", 447 | "Here it is obvious: we know that the characteristics must spread across the rarefaction, so $\\lambda$ must increase, and as $\\xi = \\lambda$ we must have the characteristic coordinate increasing." 448 | ] 449 | }, 450 | { 451 | "cell_type": "code", 452 | "execution_count": 5, 453 | "metadata": {}, 454 | "outputs": [], 455 | "source": [ 456 | "def plot_sw_rarefaction_physical(hl, ul):\n", 457 | " \"Plot the rarefaction curve through the state (hl, ul)\"\n", 458 | " \n", 459 | " xil = ul - np.sqrt(hl)\n", 460 | " xi_physical = np.linspace(xil, xi_max)\n", 461 | " xi_unphysical = np.linspace(xi_min, xil)\n", 462 | " h_physical = ((xil - xi_physical) / 3.0 + np.sqrt(hl))**2\n", 463 | " u_physical = 2.0 * (xi_physical - xil) / 3.0 + ul\n", 464 | " h_unphysical = ((xil - xi_unphysical) / 3.0 + np.sqrt(hl))**2\n", 465 | " u_unphysical = 2.0 * (xi_unphysical - xil) / 3.0 + ul\n", 466 | " \n", 467 | " \n", 468 | " fig = plt.figure(figsize=(12,8))\n", 469 | " ax = fig.add_subplot(111)\n", 470 | " ax.plot(hl, ul, 'rx', markersize = 16, markeredgewidth = 3)\n", 471 | " ax.plot(h_physical, u_physical, 'k-', linewidth = 2, label=\"Physical\")\n", 472 | " ax.plot(h_unphysical, u_unphysical, 'k--', linewidth = 2, label=\"Unphysical\")\n", 473 | " ax.set_xlabel(r\"$h$\")\n", 474 | " ax.set_ylabel(r\"$u$\")\n", 475 | " dh = h_max - h_min\n", 476 | " du = u_max - u_min\n", 477 | " ax.set_xbound(h_min - 0.1 * dh, h_max + 0.1 * dh)\n", 478 | " ax.set_ybound(u_min - 0.1 * du, u_max + 0.1 * du)\n", 479 | " ax.legend()\n", 480 | " fig.tight_layout()" 481 | ] 482 | }, 483 | { 484 | "cell_type": "code", 485 | "execution_count": 6, 486 | "metadata": {}, 487 | "outputs": [ 488 | { 489 | "data": { 490 | "application/vnd.jupyter.widget-view+json": { 491 | "model_id": "ee72c0ff03984da2bd2de728435f057a", 492 | "version_major": 2, 493 | "version_minor": 0 494 | }, 495 | "text/html": [ 496 | "

Failed to display Jupyter Widget of type interactive.

\n", 497 | "

\n", 498 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 499 | " that the widgets JavaScript is still loading. If this message persists, it\n", 500 | " likely means that the widgets JavaScript library is either not installed or\n", 501 | " not enabled. See the Jupyter\n", 502 | " Widgets Documentation for setup instructions.\n", 503 | "

\n", 504 | "

\n", 505 | " If you're reading this message in another frontend (for example, a static\n", 506 | " rendering on GitHub or NBViewer),\n", 507 | " it may mean that your frontend doesn't currently support widgets.\n", 508 | "

\n" 509 | ], 510 | "text/plain": [ 511 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=0.0, description='ul', max=1.0, min=-1.0), Output()), _dom_classes=('widget-interact',))" 512 | ] 513 | }, 514 | "metadata": {}, 515 | "output_type": "display_data" 516 | } 517 | ], 518 | "source": [ 519 | "interactive(plot_sw_rarefaction_physical, \n", 520 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 521 | " ul = FloatSlider(min = -1.0, max = 1.0, value = 0.0))" 522 | ] 523 | }, 524 | { 525 | "cell_type": "markdown", 526 | "metadata": {}, 527 | "source": [ 528 | "We see that along the physical part of the rarefaction curve the height $h$ decreases." 529 | ] 530 | }, 531 | { 532 | "cell_type": "markdown", 533 | "metadata": {}, 534 | "source": [ 535 | "Instead of writing the solution in terms of the similarity coordinate $\\xi$ we can instead write the solution in terms of any other single parameter. It is useful to write it in terms of the height, which can be done simply by re-arranging the equations giving $u$ and $h$ in terms of $\\xi$. So, a state with height $h_m$ to the right of the state $(h_l, u_l)$ can be connected across a rarefaction if\n", 536 | "$$\n", 537 | " u_m = u_l + 2 \\left( \\sqrt{h_l} - \\sqrt{h_m} \\right).\n", 538 | "$$" 539 | ] 540 | }, 541 | { 542 | "cell_type": "markdown", 543 | "metadata": {}, 544 | "source": [ 545 | "In this form we will look at the characteristic curves and the behaviour in state space to cross-check." 546 | ] 547 | }, 548 | { 549 | "cell_type": "code", 550 | "execution_count": 7, 551 | "metadata": {}, 552 | "outputs": [], 553 | "source": [ 554 | "def plot_sw_rarefaction_physical_characteristics(hl, ul, hm):\n", 555 | " \"Plot the rarefaction curve through the state (hl, ul) finishing at (hm, um)\"\n", 556 | " \n", 557 | " um = ul + 2.0 * (np.sqrt(hl) - np.sqrt(hm))\n", 558 | " \n", 559 | " h_maximum = np.max([h_max, hl, hm])\n", 560 | " h_minimum = np.min([h_min, hl, hm])\n", 561 | " u_maximum = np.max([u_max, ul, um])\n", 562 | " u_minimum = np.min([u_min, ul, um])\n", 563 | " dh = h_maximum - h_minimum\n", 564 | " du = u_maximum - u_minimum\n", 565 | " xi_min = u_minimum - np.sqrt(h_maximum)\n", 566 | " xi_max = u_maximum - np.sqrt(h_minimum)\n", 567 | " \n", 568 | " xil = ul - np.sqrt(hl)\n", 569 | " xim = um - np.sqrt(hm)\n", 570 | " xi_physical = np.linspace(xil, xi_max)\n", 571 | " xi_unphysical = np.linspace(xi_min, xil)\n", 572 | " h_physical = ((xil - xi_physical) / 3.0 + np.sqrt(hl))**2\n", 573 | " u_physical = 2.0 * (xi_physical - xil) / 3.0 + ul\n", 574 | " h_unphysical = ((xil - xi_unphysical) / 3.0 + np.sqrt(hl))**2\n", 575 | " u_unphysical = 2.0 * (xi_unphysical - xil) / 3.0 + ul\n", 576 | " \n", 577 | " \n", 578 | " fig = plt.figure(figsize=(12,8))\n", 579 | " ax1 = fig.add_subplot(121)\n", 580 | " ax1.plot(hl, ul, 'rx', markersize = 16, markeredgewidth = 3, label=r\"$(h_l, u_l)$\")\n", 581 | " ax1.plot(hm, um, 'b+', markersize = 16, markeredgewidth = 3, label=r\"$(h_m, u_m)$\")\n", 582 | " ax1.plot(h_physical, u_physical, 'k-', linewidth = 2, label=\"Physical\")\n", 583 | " ax1.plot(h_unphysical, u_unphysical, 'k--', linewidth = 2, label=\"Unphysical\")\n", 584 | " ax1.set_xlabel(r\"$h$\")\n", 585 | " ax1.set_ylabel(r\"$u$\")\n", 586 | " ax1.set_xbound(h_minimum - 0.1 * dh, h_maximum + 0.1 * dh)\n", 587 | " ax1.set_ybound(u_minimum - 0.1 * du, u_maximum + 0.1 * du)\n", 588 | " ax1.legend()\n", 589 | " \n", 590 | " ax2 = fig.add_subplot(122)\n", 591 | " left_edge = np.min([-1.0, -1.0 - xil])\n", 592 | " right_edge = np.max([1.0, 1.0 - xim])\n", 593 | " x_start_points_l = np.linspace(left_edge, 0.0, 20)\n", 594 | " x_start_points_r = np.linspace(0.0, right_edge, 20)\n", 595 | " x_end_points_l = x_start_points_l + xil\n", 596 | " x_end_points_r = x_start_points_r + xim\n", 597 | " \n", 598 | " for xs, xe in zip(x_start_points_l, x_end_points_l):\n", 599 | " ax2.plot([xs, xe], [0.0, 1.0], 'b-')\n", 600 | " for xs, xe in zip(x_start_points_r, x_end_points_r):\n", 601 | " ax2.plot([xs, xe], [0.0, 1.0], 'g-')\n", 602 | " \n", 603 | " # Rarefaction wave\n", 604 | " if (xim > xil):\n", 605 | " xi = np.linspace(xil, xim, 11)\n", 606 | " x_end_rarefaction = xi\n", 607 | " for xe in x_end_rarefaction:\n", 608 | " ax2.plot([0.0, xe], [0.0, 1.0], 'r--')\n", 609 | " else:\n", 610 | " x_fill = [x_end_points_l[-1], x_start_points_l[-1], x_end_points_r[0]]\n", 611 | " t_fill = [1.0, 0.0, 1.0]\n", 612 | " ax2.fill_between(x_fill, t_fill, 1.0, facecolor = 'red', alpha = 0.5)\n", 613 | " \n", 614 | " ax2.set_xbound(-1.0, 1.0)\n", 615 | " ax2.set_ybound(0.0, 1.0)\n", 616 | " ax2.set_xlabel(r\"$x$\")\n", 617 | " ax2.set_ylabel(r\"$t$\")\n", 618 | " fig.tight_layout()" 619 | ] 620 | }, 621 | { 622 | "cell_type": "code", 623 | "execution_count": 8, 624 | "metadata": {}, 625 | "outputs": [ 626 | { 627 | "data": { 628 | "application/vnd.jupyter.widget-view+json": { 629 | "model_id": "f7624fe13abb44eabcf433104b4b4f2f", 630 | "version_major": 2, 631 | "version_minor": 0 632 | }, 633 | "text/html": [ 634 | "

Failed to display Jupyter Widget of type interactive.

\n", 635 | "

\n", 636 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 637 | " that the widgets JavaScript is still loading. If this message persists, it\n", 638 | " likely means that the widgets JavaScript library is either not installed or\n", 639 | " not enabled. See the Jupyter\n", 640 | " Widgets Documentation for setup instructions.\n", 641 | "

\n", 642 | "

\n", 643 | " If you're reading this message in another frontend (for example, a static\n", 644 | " rendering on GitHub or NBViewer),\n", 645 | " it may mean that your frontend doesn't currently support widgets.\n", 646 | "

\n" 647 | ], 648 | "text/plain": [ 649 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=0.0, description='ul', max=1.0, min=-1.0), FloatSlider(value=0.5, description='hm', max=10.0, min=0.1), Output()), _dom_classes=('widget-interact',))" 650 | ] 651 | }, 652 | "metadata": {}, 653 | "output_type": "display_data" 654 | } 655 | ], 656 | "source": [ 657 | "interactive(plot_sw_rarefaction_physical_characteristics, \n", 658 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 659 | " ul = FloatSlider(min = -1.0, max = 1.0, value = 0.0), \n", 660 | " hm = FloatSlider(min = 0.1, max = 10.0, value = 0.5))" 661 | ] 662 | }, 663 | { 664 | "cell_type": "markdown", 665 | "metadata": {}, 666 | "source": [ 667 | "We clearly see that only if $h_m < h_l$ do the characteristics spread as they should for a rarefaction. This is, in fact, already given by results above: we showed that $\\partial_{\\xi} h \\propto -\\sqrt{h}$. As the height $h$ is positive, this means that as $\\xi$ increase across the rarefaction, the height must decrease." 668 | ] 669 | }, 670 | { 671 | "cell_type": "markdown", 672 | "metadata": {}, 673 | "source": [ 674 | "## All rarefaction solution" 675 | ] 676 | }, 677 | { 678 | "cell_type": "markdown", 679 | "metadata": {}, 680 | "source": [ 681 | "The above exercise assumed we knew the left state and found all right states connecting it by a rarefaction. Now we assume we know both left *and* right states, and assume they connect to a central state, *both* along rarefactions." 682 | ] 683 | }, 684 | { 685 | "cell_type": "markdown", 686 | "metadata": {}, 687 | "source": [ 688 | "First, we need to find which states will connect to the right state across a rarefaction." 689 | ] 690 | }, 691 | { 692 | "cell_type": "markdown", 693 | "metadata": {}, 694 | "source": [ 695 | "### Exercise" 696 | ] 697 | }, 698 | { 699 | "cell_type": "markdown", 700 | "metadata": {}, 701 | "source": [ 702 | "Repeat the above calculations for states connecting to a known right state. That is, show that, given the right state $(h_r, u_r)$, the left state that connects to it across a rarefaction satisfies\n", 703 | "$$\n", 704 | " \\begin{pmatrix} h \\\\ u \\end{pmatrix} = \\begin{pmatrix} \\left( -\\frac{\\xi_r - \\xi}{3} + \\sqrt{h_r} \\right)^2 \\\\ \\frac{2}{3} (\\xi - \\xi_r) + u_r \\end{pmatrix}. \n", 705 | "$$\n", 706 | "or equivalently, given $h_m$, that\n", 707 | "$$\n", 708 | " u_m = u_r - 2 \\left( \\sqrt{h_r} - \\sqrt{h_m} \\right).\n", 709 | "$$\n", 710 | "Also check that $h$ decreases across the rarefaction, so for a physical solution $h_m < h_r$." 711 | ] 712 | }, 713 | { 714 | "cell_type": "markdown", 715 | "metadata": {}, 716 | "source": [ 717 | "Then we can plot the curve of all states that can be connected to $(h_l, u_l)$ across a left rarefaction, and the curve of all states that can be connected to $(h_r, u_r)$ across a right rarefaction. *If* they intersect along the *physical* part of the curve, then we have the solution to the Riemann problem. Clearly this only occurs if $h_m < h_l$ *and* $h_m < h_r$." 718 | ] 719 | }, 720 | { 721 | "cell_type": "markdown", 722 | "metadata": {}, 723 | "source": [ 724 | "In this case (and note that this is a special case!) we can solve it analytically. We note that, using our *assumption* that both curves are rarefactions, we have that\n", 725 | "$$\n", 726 | "\\begin{align}\n", 727 | " u_m & = u_l + 2 \\left( \\sqrt{h_l} - \\sqrt{h_m} \\right) \\\\\n", 728 | " & = u_r - 2 \\left( \\sqrt{h_r} - \\sqrt{h_m} \\right)\n", 729 | "\\end{align}\n", 730 | "$$\n", 731 | "Therefore we have\n", 732 | "$$\n", 733 | " h_m = \\frac{1}{16} \\left( u_l - u_r + 2 \\left( \\sqrt{h_l} + \\sqrt{h_r} \\right) \\right)^2.\n", 734 | "$$" 735 | ] 736 | }, 737 | { 738 | "cell_type": "code", 739 | "execution_count": 9, 740 | "metadata": {}, 741 | "outputs": [], 742 | "source": [ 743 | "def plot_sw_all_rarefaction(hl, ul, hr, ur):\n", 744 | " \"Plot the all rarefaction solution curve for states (hl, ul) and (hr, ur)\"\n", 745 | " \n", 746 | " hm = (ul - ur + 2.0 * (np.sqrt(hl) + np.sqrt(hr)))**2 / 16.0\n", 747 | " um = ul + 2.0 * (np.sqrt(hl) - np.sqrt(hm))\n", 748 | " \n", 749 | " h_maximum = np.max([h_max, hl, hr, hm])\n", 750 | " h_minimum = np.min([h_min, hl, hr, hm])\n", 751 | " u_maximum = np.max([u_max, ul, ur, um])\n", 752 | " u_minimum = np.min([u_min, ul, ur, um])\n", 753 | " dh = h_maximum - h_minimum\n", 754 | " du = u_maximum - u_minimum\n", 755 | " xil_min = u_minimum - np.sqrt(h_maximum)\n", 756 | " xil_max = u_maximum - np.sqrt(h_minimum)\n", 757 | " xir_min = u_minimum + np.sqrt(h_minimum)\n", 758 | " xir_max = u_maximum + np.sqrt(h_maximum)\n", 759 | " \n", 760 | " xil = ul - np.sqrt(hl)\n", 761 | " xilm = um - np.sqrt(hm)\n", 762 | " xil_physical = np.linspace(xil, xil_max)\n", 763 | " xil_unphysical = np.linspace(xil_min, xil)\n", 764 | " hl_physical = ((xil - xil_physical) / 3.0 + np.sqrt(hl))**2\n", 765 | " ul_physical = 2.0 * (xil_physical - xil) / 3.0 + ul\n", 766 | " hl_unphysical = ((xil - xil_unphysical) / 3.0 + np.sqrt(hl))**2\n", 767 | " ul_unphysical = 2.0 * (xil_unphysical - xil) / 3.0 + ul\n", 768 | " \n", 769 | " xir = ur + np.sqrt(hr)\n", 770 | " xirm = um + np.sqrt(hm)\n", 771 | " xir_unphysical = np.linspace(xir, xir_max)\n", 772 | " xir_physical = np.linspace(xir_min, xir)\n", 773 | " hr_physical = (-(xir - xir_physical) / 3.0 + np.sqrt(hr))**2\n", 774 | " ur_physical = 2.0 * (xir_physical - xir) / 3.0 + ur\n", 775 | " hr_unphysical = (-(xir - xir_unphysical) / 3.0 + np.sqrt(hr))**2\n", 776 | " ur_unphysical = 2.0 * (xir_unphysical - xir) / 3.0 + ur\n", 777 | " \n", 778 | " fig = plt.figure(figsize=(12,8))\n", 779 | " ax1 = fig.add_subplot(111)\n", 780 | " if (hm < np.min([hl, hr])):\n", 781 | " ax1.plot(hm, um, 'b+', markersize = 16, markeredgewidth = 3, \n", 782 | " label=r\"$(h_m, u_m)$, physical solution\")\n", 783 | " else:\n", 784 | " ax1.plot(hm, um, 'b+', markersize = 16, markeredgewidth = 3, \n", 785 | " label=r\"$(h_m, u_m)$, not physical solution\")\n", 786 | " ax1.plot(hl, ul, 'rx', markersize = 16, markeredgewidth = 3, label=r\"$(h_l, u_l)$\")\n", 787 | " ax1.plot(hr, ur, 'go', markersize = 16, markeredgewidth = 3, label=r\"$(h_r, u_r)$\")\n", 788 | " ax1.plot(hl_physical, ul_physical, 'k-', linewidth = 2, label=\"Physical (left)\")\n", 789 | " ax1.plot(hl_unphysical, ul_unphysical, 'k--', linewidth = 2, label=\"Unphysical (left)\")\n", 790 | " ax1.plot(hr_physical, ur_physical, 'c-', linewidth = 2, label=\"Physical (right)\")\n", 791 | " ax1.plot(hr_unphysical, ur_unphysical, 'c--', linewidth = 2, label=\"Unphysical (right)\")\n", 792 | " ax1.set_xlabel(r\"$h$\")\n", 793 | " ax1.set_ylabel(r\"$u$\")\n", 794 | " ax1.set_xbound(h_minimum - 0.1 * dh, h_maximum + 0.1 * dh)\n", 795 | " ax1.set_ybound(u_minimum - 0.1 * du, u_maximum + 0.1 * du)\n", 796 | " ax1.legend()\n", 797 | " \n", 798 | " fig.tight_layout()" 799 | ] 800 | }, 801 | { 802 | "cell_type": "code", 803 | "execution_count": 10, 804 | "metadata": {}, 805 | "outputs": [ 806 | { 807 | "data": { 808 | "application/vnd.jupyter.widget-view+json": { 809 | "model_id": "36712c27d2a84211a9e2d7bccb645b21", 810 | "version_major": 2, 811 | "version_minor": 0 812 | }, 813 | "text/html": [ 814 | "

Failed to display Jupyter Widget of type interactive.

\n", 815 | "

\n", 816 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 817 | " that the widgets JavaScript is still loading. If this message persists, it\n", 818 | " likely means that the widgets JavaScript library is either not installed or\n", 819 | " not enabled. See the Jupyter\n", 820 | " Widgets Documentation for setup instructions.\n", 821 | "

\n", 822 | "

\n", 823 | " If you're reading this message in another frontend (for example, a static\n", 824 | " rendering on GitHub or NBViewer),\n", 825 | " it may mean that your frontend doesn't currently support widgets.\n", 826 | "

\n" 827 | ], 828 | "text/plain": [ 829 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=-0.5, description='ul', max=1.0, min=-1.0), FloatSlider(value=1.0, description='hr', max=10.0, min=0.1), FloatSlider(value=0.5, description='ur', max=1.0, min=-1.0), Output()), _dom_classes=('widget-interact',))" 830 | ] 831 | }, 832 | "metadata": {}, 833 | "output_type": "display_data" 834 | } 835 | ], 836 | "source": [ 837 | "interactive(plot_sw_all_rarefaction, \n", 838 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 839 | " ul = FloatSlider(min = -1.0, max = 1.0, value = -0.5), \n", 840 | " hr = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 841 | " ur = FloatSlider(min = -1.0, max = 1.0, value = 0.5))" 842 | ] 843 | }, 844 | { 845 | "cell_type": "markdown", 846 | "metadata": {}, 847 | "source": [ 848 | "Given the central state and the relation along rarefaction curves, we can then construct the characteristics and the solution in terms of the similarity coordinate (which, given a time $t$, gives the solution as a function of $x$)." 849 | ] 850 | }, 851 | { 852 | "cell_type": "code", 853 | "execution_count": 11, 854 | "metadata": {}, 855 | "outputs": [], 856 | "source": [ 857 | "def plot_sw_all_rarefaction_solution(hl, ul, hr, ur):\n", 858 | " \"Plot the all rarefaction solution curve for states (hl, ul) and (hr, ur)\"\n", 859 | " \n", 860 | " hm = (ul - ur + 2.0 * (np.sqrt(hl) + np.sqrt(hr)))**2 / 16.0\n", 861 | " um = ul + 2.0 * (np.sqrt(hl) - np.sqrt(hm))\n", 862 | " \n", 863 | " xi1l = ul - np.sqrt(hl)\n", 864 | " xi1m = um - np.sqrt(hm)\n", 865 | " xi1r = ur - np.sqrt(hr)\n", 866 | " hl_raref = np.linspace(hl, hm, 20)\n", 867 | " ul_raref = ul + 2.0 * (np.sqrt(hl) - np.sqrt(hl_raref))\n", 868 | " xil_raref = ul_raref - np.sqrt(hl_raref)\n", 869 | " \n", 870 | " xi2r = ur + np.sqrt(hr)\n", 871 | " xi2m = um + np.sqrt(hm)\n", 872 | " xi2l = ul + np.sqrt(hl)\n", 873 | " hr_raref = np.linspace(hm, hr)\n", 874 | " ur_raref = ur - 2.0 * (np.sqrt(hr) - np.sqrt(hr_raref))\n", 875 | " xir_raref = ur_raref + np.sqrt(hr_raref)\n", 876 | " \n", 877 | " xi_min = np.min([-1.0, xi1l, xi1m, xi2r, xi2m])\n", 878 | " xi_max = np.max([1.0, xi1l, xi1m, xi2r, xi2m])\n", 879 | " d_xi = xi_max - xi_min\n", 880 | " h_max = np.max([hl, hr, hm])\n", 881 | " h_min = np.min([hl, hr, hm])\n", 882 | " d_h = h_max - h_min\n", 883 | " u_max = np.max([ul, ur, um])\n", 884 | " u_min = np.min([ul, ur, um])\n", 885 | " d_u = u_max - u_min\n", 886 | " \n", 887 | " xi = np.array([xi_min - 0.1 * d_xi, xi1l])\n", 888 | " h = np.array([hl, hl])\n", 889 | " u = np.array([ul, ul])\n", 890 | " xi = np.append(xi, xil_raref)\n", 891 | " h = np.append(h, hl_raref)\n", 892 | " u = np.append(u, ul_raref)\n", 893 | " xi = np.append(xi, [xi1m, xi2m])\n", 894 | " h = np.append(h, [hm, hm])\n", 895 | " u = np.append(u, [um, um])\n", 896 | " xi = np.append(xi, xir_raref)\n", 897 | " h = np.append(h, hr_raref)\n", 898 | " u = np.append(u, ur_raref)\n", 899 | " xi = np.append(xi, [xi2r, xi_max + 0.1 * d_xi])\n", 900 | " h = np.append(h, [hr, hr])\n", 901 | " u = np.append(u, [ur, ur])\n", 902 | " \n", 903 | " fig = plt.figure(figsize=(12,8))\n", 904 | " ax1 = fig.add_subplot(221)\n", 905 | " if (hm < np.min([hl, hr])):\n", 906 | " ax1.plot(xi, h, 'b-', label = \"Physical solution\")\n", 907 | " else:\n", 908 | " ax1.plot(xi, h, 'r--', label = \"Unphysical solution\")\n", 909 | " ax1.set_ybound(h_min - 0.1 * d_h, h_max + 0.1 * d_h)\n", 910 | " ax1.set_xlabel(r\"$\\xi$\")\n", 911 | " ax1.set_ylabel(r\"$h$\")\n", 912 | " ax1.legend()\n", 913 | " ax2 = fig.add_subplot(222)\n", 914 | " if (hm < np.min([hl, hr])):\n", 915 | " ax2.plot(xi, u, 'b-', label = \"Physical solution\")\n", 916 | " else:\n", 917 | " ax2.plot(xi, u, 'r--', label = \"Unphysical solution\")\n", 918 | " ax2.set_ybound(u_min - 0.1 * d_u, u_max + 0.1 * d_u)\n", 919 | " ax2.set_xlabel(r\"$\\xi$\")\n", 920 | " ax2.set_ylabel(r\"$u$\")\n", 921 | " ax2.legend()\n", 922 | " \n", 923 | " ax3 = fig.add_subplot(223)\n", 924 | " left_end = np.min([-1.0, 1.1*xi1l])\n", 925 | " right_end = np.max([1.0, 1.1*xi2r])\n", 926 | " left_edge = left_end - xi1l\n", 927 | " right_edge = right_end - xi1r\n", 928 | " x1_start_points_l = np.linspace(np.min([left_edge, left_end]), 0.0, 20)\n", 929 | " x1_start_points_r = np.linspace(0.0, np.max([right_edge, right_end]), 20)\n", 930 | " x1_end_points_l = x1_start_points_l + xi1l\n", 931 | " t1_end_points_r = np.ones_like(x1_start_points_r)\n", 932 | " \n", 933 | " # Look for intersections\n", 934 | " t1_end_points_r = np.minimum(t1_end_points_r, x1_start_points_r / (xi2r - xi1r))\n", 935 | " x1_end_points_r = x1_start_points_r + xi1r * t1_end_points_r\n", 936 | " # Note: here we are cheating, and using the characteristic speed of the middle state, \n", 937 | " # ignoring howo it varies across the rarefaction\n", 938 | " x1_final_points_r = x1_end_points_r + (1.0 - t1_end_points_r) * xi1m\n", 939 | " \n", 940 | " for xs, xe in zip(x1_start_points_l, x1_end_points_l):\n", 941 | " ax3.plot([xs, xe], [0.0, 1.0], 'b-')\n", 942 | " for xs, xe, te in zip(x1_start_points_r, x1_end_points_r, t1_end_points_r):\n", 943 | " ax3.plot([xs, xe], [0.0, te], 'g-')\n", 944 | " for xs, xe, ts in zip(x1_end_points_r, x1_final_points_r, t1_end_points_r):\n", 945 | " ax3.plot([xs, xe], [ts, 1.0], 'g-')\n", 946 | " \n", 947 | " # Highlight the edges of both rarefactions\n", 948 | " ax3.plot([0.0, xi1l], [0.0, 1.0], 'r-', linewidth=2)\n", 949 | " ax3.plot([0.0, xi1m], [0.0, 1.0], 'r-', linewidth=2)\n", 950 | " ax3.plot([0.0, xi2m], [0.0, 1.0], 'r-', linewidth=2)\n", 951 | " ax3.plot([0.0, xi2r], [0.0, 1.0], 'r-', linewidth=2)\n", 952 | " \n", 953 | " # Rarefaction wave\n", 954 | " if (xi1l < xi1m):\n", 955 | " xi = np.linspace(xi1l, xi1m, 11)\n", 956 | " x_end_rarefaction = xi\n", 957 | " for xe in x_end_rarefaction:\n", 958 | " ax3.plot([0.0, xe], [0.0, 1.0], 'r--')\n", 959 | " else:\n", 960 | " x_fill = [xi1l, 0.0, xi1m]\n", 961 | " t_fill = [1.0, 0.0, 1.0]\n", 962 | " ax3.fill_between(x_fill, t_fill, 1.0, facecolor = 'red', alpha = 0.5)\n", 963 | " \n", 964 | " ax3.set_xlabel(r\"$x$\")\n", 965 | " ax3.set_ylabel(r\"$t$\")\n", 966 | " ax3.set_title(\"1-characteristics\")\n", 967 | " ax3.set_xbound(left_end, right_end)\n", 968 | " \n", 969 | " ax4 = fig.add_subplot(224)\n", 970 | " left_end = np.min([-1.0, 1.1*xi1l])\n", 971 | " right_end = np.max([1.0, 1.1*xi2r])\n", 972 | " left_edge = left_end - xi2l\n", 973 | " right_edge = right_end - xi2r\n", 974 | " x2_start_points_l = np.linspace(np.min([left_edge, left_end]), 0.0, 20)\n", 975 | " x2_start_points_r = np.linspace(0.0, np.max([right_edge, right_end]), 20)\n", 976 | " x2_end_points_r = x2_start_points_r + xi2r\n", 977 | " t2_end_points_l = np.ones_like(x2_start_points_l)\n", 978 | " \n", 979 | " # Look for intersections\n", 980 | " t2_end_points_l = np.minimum(t2_end_points_l, x2_start_points_l / (xi1l - xi2r))\n", 981 | " x2_end_points_l = x2_start_points_l + xi2r * t2_end_points_l\n", 982 | " # Note: here we are cheating, and using the characteristic speed of the middle state, \n", 983 | " # ignoring howo it varies across the rarefaction\n", 984 | " x2_final_points_l = x2_end_points_l + (1.0 - t2_end_points_l) * xi2m\n", 985 | " \n", 986 | " for xs, xe in zip(x2_start_points_r, x2_end_points_r):\n", 987 | " ax4.plot([xs, xe], [0.0, 1.0], 'g-')\n", 988 | " for xs, xe, te in zip(x2_start_points_l, x2_end_points_l, t2_end_points_l):\n", 989 | " ax4.plot([xs, xe], [0.0, te], 'b-')\n", 990 | " for xs, xe, ts in zip(x2_end_points_l, x2_final_points_l, t2_end_points_l):\n", 991 | " ax4.plot([xs, xe], [ts, 1.0], 'b-')\n", 992 | " \n", 993 | " # Highlight the edges of both rarefactions\n", 994 | " ax4.plot([0.0, xi1l], [0.0, 1.0], 'r-', linewidth=2)\n", 995 | " ax4.plot([0.0, xi1m], [0.0, 1.0], 'r-', linewidth=2)\n", 996 | " ax4.plot([0.0, xi2m], [0.0, 1.0], 'r-', linewidth=2)\n", 997 | " ax4.plot([0.0, xi2r], [0.0, 1.0], 'r-', linewidth=2)\n", 998 | " \n", 999 | " # Rarefaction wave\n", 1000 | " if (xi2r > xi2m):\n", 1001 | " xi = np.linspace(xi2m, xi2r, 11)\n", 1002 | " x_end_rarefaction = xi\n", 1003 | " for xe in x_end_rarefaction:\n", 1004 | " ax4.plot([0.0, xe], [0.0, 1.0], 'r--')\n", 1005 | " else:\n", 1006 | " x_fill = [xi2m, 0.0, xi2r]\n", 1007 | " t_fill = [1.0, 0.0, 1.0]\n", 1008 | " ax4.fill_between(x_fill, t_fill, 1.0, facecolor = 'red', alpha = 0.5)\n", 1009 | " \n", 1010 | " ax4.set_xlabel(r\"$x$\")\n", 1011 | " ax4.set_ylabel(r\"$t$\")\n", 1012 | " ax4.set_title(\"2-characteristics\")\n", 1013 | " ax4.set_xbound(left_end, right_end)\n", 1014 | " \n", 1015 | " fig.tight_layout()" 1016 | ] 1017 | }, 1018 | { 1019 | "cell_type": "code", 1020 | "execution_count": 12, 1021 | "metadata": {}, 1022 | "outputs": [ 1023 | { 1024 | "data": { 1025 | "application/vnd.jupyter.widget-view+json": { 1026 | "model_id": "839f9bd0806e4e1d9ab0622808b8eec3", 1027 | "version_major": 2, 1028 | "version_minor": 0 1029 | }, 1030 | "text/html": [ 1031 | "

Failed to display Jupyter Widget of type interactive.

\n", 1032 | "

\n", 1033 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 1034 | " that the widgets JavaScript is still loading. If this message persists, it\n", 1035 | " likely means that the widgets JavaScript library is either not installed or\n", 1036 | " not enabled. See the Jupyter\n", 1037 | " Widgets Documentation for setup instructions.\n", 1038 | "

\n", 1039 | "

\n", 1040 | " If you're reading this message in another frontend (for example, a static\n", 1041 | " rendering on GitHub or NBViewer),\n", 1042 | " it may mean that your frontend doesn't currently support widgets.\n", 1043 | "

\n" 1044 | ], 1045 | "text/plain": [ 1046 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=-0.5, description='ul', max=1.0, min=-1.0), FloatSlider(value=1.0, description='hr', max=10.0, min=0.1), FloatSlider(value=0.5, description='ur', max=1.0, min=-1.0), Output()), _dom_classes=('widget-interact',))" 1047 | ] 1048 | }, 1049 | "metadata": {}, 1050 | "output_type": "display_data" 1051 | } 1052 | ], 1053 | "source": [ 1054 | "interactive(plot_sw_all_rarefaction_solution, \n", 1055 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 1056 | " ul = FloatSlider(min = -1.0, max = 1.0, value = -0.5), \n", 1057 | " hr = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 1058 | " ur = FloatSlider(min = -1.0, max = 1.0, value = 0.5))" 1059 | ] 1060 | }, 1061 | { 1062 | "cell_type": "markdown", 1063 | "metadata": {}, 1064 | "source": [ 1065 | "## Shocks" 1066 | ] 1067 | }, 1068 | { 1069 | "cell_type": "markdown", 1070 | "metadata": {}, 1071 | "source": [ 1072 | "We note that the [general theory](Lesson_Theory.ipynb) tells us that across a shock the Rankine-Hugoniot conditions\n", 1073 | "$$\n", 1074 | " V_s \\left[ {\\bf q} \\right] = \\left[ {\\bf f}({\\bf q}) \\right]\n", 1075 | "$$\n", 1076 | "must be satisfied." 1077 | ] 1078 | }, 1079 | { 1080 | "cell_type": "markdown", 1081 | "metadata": {}, 1082 | "source": [ 1083 | "For the shallow water equations we will start, as with the rarefaction case, by assuming we know the left state ${\\bf q}_l = (h_l, u_l)$, and work out which states ${\\bf q}_m$ can be connected to it across a shock. \n", 1084 | "\n", 1085 | "Note here that the procedure is *identical* for the right state as the direction does not matter. However, there will be multiple solutions, and checking which is physically correct does require checking whether the left or the right state is known" 1086 | ] 1087 | }, 1088 | { 1089 | "cell_type": "markdown", 1090 | "metadata": {}, 1091 | "source": [ 1092 | "Writing out the conditions in full we see that\n", 1093 | "$$\n", 1094 | "\\begin{align}\n", 1095 | " V_s \\left( h_m - h_l \\right) & = h_m u_m - h_l u_l \\\\\n", 1096 | " V_s \\left( h_m u_m - h_l u_l \\right) & = h_m u_m^2 + \\tfrac{1}{2} h_m^2 - h_l u_l^2 - \\tfrac{1}{2} h_l^2\n", 1097 | "\\end{align}\n", 1098 | "$$" 1099 | ] 1100 | }, 1101 | { 1102 | "cell_type": "markdown", 1103 | "metadata": {}, 1104 | "source": [ 1105 | "Eliminating the shock speed $V_s$ gives, using the second equation,\n", 1106 | "$$\n", 1107 | " u_m^2 - (2 u_l) u_m + \\left[ u_l^2 - \\tfrac{1}{2} \\left( h_l - h_m \\right) \\left( \\frac{h_l}{h_m} - \\frac{h_m}{h_l} \\right) \\right] = 0.\n", 1108 | "$$\n", 1109 | "This has the solutions (assuming that $h_m$ is known!)\n", 1110 | "$$\n", 1111 | " u_m = u_l \\pm \\sqrt{\\tfrac{1}{2} \\left( h_l - h_m \\right) \\left( \\frac{h_l}{h_m} - \\frac{h_m}{h_l} \\right)}.\n", 1112 | "$$" 1113 | ] 1114 | }, 1115 | { 1116 | "cell_type": "markdown", 1117 | "metadata": {}, 1118 | "source": [ 1119 | "We can again use the Rankine-Hugoniot relations to find the shock speed.\n", 1120 | "$$\n", 1121 | " V_s = u_l \\pm \\frac{h_m}{h_m - h_l} \\sqrt{\\tfrac{1}{2} \\left( h_l - h_m \\right) \\left( \\frac{h_l}{h_m} - \\frac{h_m}{h_l} \\right)}.\n", 1122 | "$$" 1123 | ] 1124 | }, 1125 | { 1126 | "cell_type": "markdown", 1127 | "metadata": {}, 1128 | "source": [ 1129 | "We should at this point find which sign is appropriate. Comparing the shock speeds against the characteristic speed will show that\n", 1130 | "\n", 1131 | "* we need $h_m > h_l$ for the wave to be a shock, and\n", 1132 | "* we take the negative sign if connected to a left state, and the positive if connected to a right state.\n", 1133 | "\n", 1134 | "However, we can see this by plotting the *Hugoniot locus*: the curve of all states that can be connected to $(h_l, u_l)$ across a shock." 1135 | ] 1136 | }, 1137 | { 1138 | "cell_type": "code", 1139 | "execution_count": 13, 1140 | "metadata": {}, 1141 | "outputs": [], 1142 | "source": [ 1143 | "def plot_sw_shock_physical(hl, ul):\n", 1144 | " \"Plot the shock curve through the state (hl, ul)\"\n", 1145 | " \n", 1146 | " h = np.linspace(h_min, h_max, 500)\n", 1147 | " u_negative = ul - np.sqrt(0.5 * (hl - h) * (hl / h - h / hl))\n", 1148 | " u_positive = ul + np.sqrt(0.5 * (hl - h) * (hl / h - h / hl))\n", 1149 | " \n", 1150 | " vs_negative = ul - h / (h - hl) * np.sqrt(0.5 * (hl - h) * (hl / h - h / hl))\n", 1151 | " vs_positive = ul + h / (h - hl) * np.sqrt(0.5 * (hl - h) * (hl / h - h / hl))\n", 1152 | " \n", 1153 | " xi1_negative = u_negative - np.sqrt(h) \n", 1154 | " xi1_positive = u_positive - np.sqrt(h)\n", 1155 | " xi2_negative = u_negative + np.sqrt(h) \n", 1156 | " xi2_positive = u_positive + np.sqrt(h)\n", 1157 | " \n", 1158 | " xi1_l = ul - np.sqrt(hl)\n", 1159 | " xi2_l = ul + np.sqrt(hl)\n", 1160 | " \n", 1161 | " h1_physical = h[np.logical_and(xi1_negative <= vs_negative, xi1_l >= vs_negative)]\n", 1162 | " u1_physical = u_negative[np.logical_and(xi1_negative <= vs_negative, xi1_l >= vs_negative)]\n", 1163 | " h2_physical = h[np.logical_and(xi2_positive >= vs_positive, xi2_l <= vs_positive)]\n", 1164 | " u2_physical = u_positive[np.logical_and(xi2_positive >= vs_positive, xi2_l <= vs_positive)]\n", 1165 | " h1_unphysical = h[np.logical_or(xi1_negative >= vs_negative, xi1_l <= vs_negative)]\n", 1166 | " u1_unphysical = u_negative[np.logical_or(xi1_negative >= vs_negative, xi1_l <= vs_negative)]\n", 1167 | " h2_unphysical = h[np.logical_or(xi2_positive <= vs_positive, xi2_l >= vs_positive)]\n", 1168 | " u2_unphysical = u_positive[np.logical_or(xi2_positive <= vs_positive, xi2_l >= vs_positive)]\n", 1169 | " \n", 1170 | " fig = plt.figure(figsize=(12,8))\n", 1171 | " ax = fig.add_subplot(111)\n", 1172 | " ax.plot(hl, ul, 'rx', markersize = 16, markeredgewidth = 3)\n", 1173 | " ax.plot(h1_physical, u1_physical, 'b-', linewidth = 2, \n", 1174 | " label=\"Physical, 1-shock\")\n", 1175 | " ax.plot(h1_unphysical, u1_unphysical, 'b--', linewidth = 2, \n", 1176 | " label=\"Unphysical, 1-shock\")\n", 1177 | " ax.plot(h2_physical, u2_physical, 'g-', linewidth = 2, \n", 1178 | " label=\"Physical, 2-shock\")\n", 1179 | " ax.plot(h2_unphysical, u2_unphysical, 'g--', linewidth = 2, \n", 1180 | " label=\"Unphysical, 2-shock\")\n", 1181 | " ax.plot(h[::5], u_negative[::5], 'co', markersize = 12, markeredgewidth = 2, alpha = 0.3,\n", 1182 | " label=\"Negative branch\")\n", 1183 | " ax.plot(h[::5], u_positive[::5], 'ro', markersize = 12, markeredgewidth = 2, alpha = 0.3,\n", 1184 | " label=\"Positive branch\")\n", 1185 | " ax.set_xlabel(r\"$h$\")\n", 1186 | " ax.set_ylabel(r\"$u$\")\n", 1187 | " dh = h_max - h_min\n", 1188 | " du = u_max - u_min\n", 1189 | " ax.set_xbound(h_min, h_max)\n", 1190 | " ax.set_ybound(u_min, u_max)\n", 1191 | " ax.legend()\n", 1192 | " fig.tight_layout()" 1193 | ] 1194 | }, 1195 | { 1196 | "cell_type": "code", 1197 | "execution_count": 14, 1198 | "metadata": {}, 1199 | "outputs": [ 1200 | { 1201 | "data": { 1202 | "application/vnd.jupyter.widget-view+json": { 1203 | "model_id": "9d5b8698f1f74b47abc98feff828adff", 1204 | "version_major": 2, 1205 | "version_minor": 0 1206 | }, 1207 | "text/html": [ 1208 | "

Failed to display Jupyter Widget of type interactive.

\n", 1209 | "

\n", 1210 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 1211 | " that the widgets JavaScript is still loading. If this message persists, it\n", 1212 | " likely means that the widgets JavaScript library is either not installed or\n", 1213 | " not enabled. See the Jupyter\n", 1214 | " Widgets Documentation for setup instructions.\n", 1215 | "

\n", 1216 | "

\n", 1217 | " If you're reading this message in another frontend (for example, a static\n", 1218 | " rendering on GitHub or NBViewer),\n", 1219 | " it may mean that your frontend doesn't currently support widgets.\n", 1220 | "

\n" 1221 | ], 1222 | "text/plain": [ 1223 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=0.0, description='ul', max=1.0, min=-1.0), Output()), _dom_classes=('widget-interact',))" 1224 | ] 1225 | }, 1226 | "metadata": {}, 1227 | "output_type": "display_data" 1228 | } 1229 | ], 1230 | "source": [ 1231 | "interactive(plot_sw_shock_physical, \n", 1232 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 1233 | " ul = FloatSlider(min = -1.0, max = 1.0, value = 0.0))" 1234 | ] 1235 | }, 1236 | { 1237 | "cell_type": "markdown", 1238 | "metadata": {}, 1239 | "source": [ 1240 | "We see from these results, as claimed above, that\n", 1241 | "\n", 1242 | "* we need $h_m > h_l$ (or $h_m > h_r$) for the wave to be a shock, and\n", 1243 | "* we take the negative sign if connected to a left state, and the positive if connected to a right state." 1244 | ] 1245 | }, 1246 | { 1247 | "cell_type": "markdown", 1248 | "metadata": {}, 1249 | "source": [ 1250 | "## All shock solution" 1251 | ] 1252 | }, 1253 | { 1254 | "cell_type": "markdown", 1255 | "metadata": {}, 1256 | "source": [ 1257 | "When we assumed the solution contained two rarefactions it was possible to write the full solution in closed form. If we assume the solution contains two shocks then it is not possible to do this. However, it is straightforward to find the solution numerically. " 1258 | ] 1259 | }, 1260 | { 1261 | "cell_type": "markdown", 1262 | "metadata": {}, 1263 | "source": [ 1264 | "We assume the left state ${\\bf w}_l = (h_l, u_l)$ and the right state ${\\bf w}_r = (h_r, u_r)$ are known, and that they both connect to the central state ${\\bf w}_m = (h_m, u_m)$ through shocks. We know that\n", 1265 | "$$\n", 1266 | "\\begin{align}\n", 1267 | " u_m & = u_l - \\sqrt{\\tfrac{1}{2} \\left( h_l - h_m \\right) \\left( \\frac{h_l}{h_m} - \\frac{h_m}{h_l} \\right)}, \\\\\n", 1268 | " u_m & = u_r + \\sqrt{\\tfrac{1}{2} \\left( h_r - h_m \\right) \\left( \\frac{h_r}{h_m} - \\frac{h_m}{h_r} \\right)}.\n", 1269 | "\\end{align}\n", 1270 | "$$\n", 1271 | "We schematically write these equations as\n", 1272 | "$$\n", 1273 | "\\begin{align}\n", 1274 | " u_m & = \\phi_l \\left( h_m; {\\bf w}_l \\right), \\\\\n", 1275 | " u_m & = \\phi_r \\left( h_m; {\\bf w}_r \\right),\n", 1276 | "\\end{align}\n", 1277 | "$$\n", 1278 | "to indicate that the velocity in the central state, $u_m$, can be written as a function of the single unknown $h_m$ and known data.\n", 1279 | "\n", 1280 | "We immediately see that $h_m$ is a root of the nonlinear equation\n", 1281 | "$$\n", 1282 | " \\phi \\left( h_m; {\\bf w}_l, {\\bf w}_r \\right) = \\phi_l \\left( h_m; {\\bf w}_l \\right) - \\phi_r \\left( h_m; {\\bf w}_r \\right) = 0.\n", 1283 | "$$\n", 1284 | "\n", 1285 | "Finding the roots of scalar nonlinear equations is a standard problem in numerical methods, with methods such as bisection, Newton-Raphson and more being well-known. `scipy` provides a number of standard algorithms - here we will use the recommended `brentq` method.\n", 1286 | "\n", 1287 | "Note that as soon as we have numerically determined $h_m$ then either formula above gives $u_m$, and the shock speeds follow." 1288 | ] 1289 | }, 1290 | { 1291 | "cell_type": "code", 1292 | "execution_count": 15, 1293 | "metadata": {}, 1294 | "outputs": [], 1295 | "source": [ 1296 | "def plot_sw_all_shock(hl, ul, hr, ur):\n", 1297 | " \"Plot the all shock solution curve for states (hl, ul) and (hr, ur)\"\n", 1298 | " \n", 1299 | " from scipy.optimize import brentq\n", 1300 | " \n", 1301 | " def phi(hstar):\n", 1302 | " \"Function defining the root\"\n", 1303 | " \n", 1304 | " phi_l = ul - np.sqrt(0.5 * (hl - hstar) * (hl / hstar - hstar / hl))\n", 1305 | " phi_r = ur + np.sqrt(0.5 * (hr - hstar) * (hr / hstar - hstar / hr))\n", 1306 | " \n", 1307 | " return phi_l - phi_r\n", 1308 | " \n", 1309 | " # There is a solution only in the physical case. \n", 1310 | " physical_solution = True\n", 1311 | " try:\n", 1312 | " hm = brentq(phi, np.max([hl, hr]), 10.0 * h_max)\n", 1313 | " except ValueError:\n", 1314 | " physical_solution = False\n", 1315 | " hm = hl\n", 1316 | " um = ul - np.sqrt(0.5 * (hl - hm) * (hl / hm - hm / hl))\n", 1317 | " \n", 1318 | " h = np.linspace(h_min, h_max, 500)\n", 1319 | " u_negative = ul - np.sqrt(0.5 * (hl - h) * (hl / h - h / hl))\n", 1320 | " u_positive = ur + np.sqrt(0.5 * (hr - h) * (hr / h - h / hr))\n", 1321 | " \n", 1322 | " h_maximum = np.max([h_max, hl, hr, hm])\n", 1323 | " h_minimum = np.min([h_min, hl, hr, hm])\n", 1324 | " u_maximum = np.max([u_max, ul, ur, um])\n", 1325 | " u_minimum = np.min([u_min, ul, ur, um])\n", 1326 | " dh = h_maximum - h_minimum\n", 1327 | " du = u_maximum - u_minimum\n", 1328 | " xil_min = u_minimum - np.sqrt(h_maximum)\n", 1329 | " xil_max = u_maximum - np.sqrt(h_minimum)\n", 1330 | " xir_min = u_minimum + np.sqrt(h_minimum)\n", 1331 | " xir_max = u_maximum + np.sqrt(h_maximum)\n", 1332 | " \n", 1333 | " vs_negative = ul - h / (h - hl) * np.sqrt(0.5 * (hl - h) * (hl / h - h / hl))\n", 1334 | " vs_positive = ur + h / (h - hr) * np.sqrt(0.5 * (hr - h) * (hr / h - h / hr))\n", 1335 | " \n", 1336 | " xi1_negative = u_negative - np.sqrt(h) \n", 1337 | " xi1_positive = u_positive - np.sqrt(h)\n", 1338 | " xi2_negative = u_negative + np.sqrt(h) \n", 1339 | " xi2_positive = u_positive + np.sqrt(h)\n", 1340 | " \n", 1341 | " xi1_l = ul - np.sqrt(hl)\n", 1342 | " xi2_r = ur + np.sqrt(hr)\n", 1343 | " \n", 1344 | " h1_physical = h[np.logical_and(xi1_negative <= vs_negative, xi1_l >= vs_negative)]\n", 1345 | " u1_physical = u_negative[np.logical_and(xi1_negative <= vs_negative, xi1_l >= vs_negative)]\n", 1346 | " h2_physical = h[np.logical_and(xi2_positive >= vs_positive, xi2_r <= vs_positive)]\n", 1347 | " u2_physical = u_positive[np.logical_and(xi2_positive >= vs_positive, xi2_r <= vs_positive)]\n", 1348 | " h1_unphysical = h[np.logical_or(xi1_negative >= vs_negative, xi1_l <= vs_negative)]\n", 1349 | " u1_unphysical = u_negative[np.logical_or(xi1_negative >= vs_negative, xi1_l <= vs_negative)]\n", 1350 | " h2_unphysical = h[np.logical_or(xi2_positive <= vs_positive, xi2_r >= vs_positive)]\n", 1351 | " u2_unphysical = u_positive[np.logical_or(xi2_positive <= vs_positive, xi2_r >= vs_positive)]\n", 1352 | " \n", 1353 | " fig = plt.figure(figsize=(12,8))\n", 1354 | " ax = fig.add_subplot(111)\n", 1355 | " ax.plot(hl, ul, 'rx', markersize = 16, markeredgewidth = 3, label = r\"${\\bf w}_l$\")\n", 1356 | " ax.plot(hr, ur, 'r+', markersize = 16, markeredgewidth = 3, label = r\"${\\bf w}_r$\")\n", 1357 | " if physical_solution:\n", 1358 | " ax.plot(hm, um, 'ro', markersize = 16, markeredgewidth = 3, label = r\"${\\bf w}_m$\")\n", 1359 | " ax.plot(h1_physical, u1_physical, 'b-', linewidth = 2, \n", 1360 | " label=\"Physical, 1-shock\")\n", 1361 | " ax.plot(h1_unphysical, u1_unphysical, 'b--', linewidth = 2, \n", 1362 | " label=\"Unphysical, 1-shock\")\n", 1363 | " ax.plot(h2_physical, u2_physical, 'g-', linewidth = 2, \n", 1364 | " label=\"Physical, 2-shock\")\n", 1365 | " ax.plot(h2_unphysical, u2_unphysical, 'g--', linewidth = 2, \n", 1366 | " label=\"Unphysical, 2-shock\")\n", 1367 | " ax.set_xlabel(r\"$h$\")\n", 1368 | " ax.set_ylabel(r\"$u$\")\n", 1369 | " ax.set_xbound(h_minimum - 0.1 * dh, h_maximum + 0.1 * dh)\n", 1370 | " ax.set_ybound(u_minimum - 0.1 * du, u_maximum + 0.1 * du)\n", 1371 | " ax.legend()\n", 1372 | " \n", 1373 | " fig.tight_layout()" 1374 | ] 1375 | }, 1376 | { 1377 | "cell_type": "code", 1378 | "execution_count": 16, 1379 | "metadata": {}, 1380 | "outputs": [ 1381 | { 1382 | "data": { 1383 | "application/vnd.jupyter.widget-view+json": { 1384 | "model_id": "0304279e49bc4e8181530c6e6d4469c5", 1385 | "version_major": 2, 1386 | "version_minor": 0 1387 | }, 1388 | "text/html": [ 1389 | "

Failed to display Jupyter Widget of type interactive.

\n", 1390 | "

\n", 1391 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 1392 | " that the widgets JavaScript is still loading. If this message persists, it\n", 1393 | " likely means that the widgets JavaScript library is either not installed or\n", 1394 | " not enabled. See the Jupyter\n", 1395 | " Widgets Documentation for setup instructions.\n", 1396 | "

\n", 1397 | "

\n", 1398 | " If you're reading this message in another frontend (for example, a static\n", 1399 | " rendering on GitHub or NBViewer),\n", 1400 | " it may mean that your frontend doesn't currently support widgets.\n", 1401 | "

\n" 1402 | ], 1403 | "text/plain": [ 1404 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=0.2, description='ul', max=1.0, min=-1.0), FloatSlider(value=1.0, description='hr', max=10.0, min=0.1), FloatSlider(value=-0.2, description='ur', max=1.0, min=-1.0), Output()), _dom_classes=('widget-interact',))" 1405 | ] 1406 | }, 1407 | "metadata": {}, 1408 | "output_type": "display_data" 1409 | } 1410 | ], 1411 | "source": [ 1412 | "interactive(plot_sw_all_shock, \n", 1413 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 1414 | " ul = FloatSlider(min = -1.0, max = 1.0, value = 0.2), \n", 1415 | " hr = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 1416 | " ur = FloatSlider(min = -1.0, max = 1.0, value = -0.2))" 1417 | ] 1418 | }, 1419 | { 1420 | "cell_type": "markdown", 1421 | "metadata": {}, 1422 | "source": [ 1423 | "Finally, we can plot the solution in physical space." 1424 | ] 1425 | }, 1426 | { 1427 | "cell_type": "code", 1428 | "execution_count": 17, 1429 | "metadata": {}, 1430 | "outputs": [], 1431 | "source": [ 1432 | "def plot_sw_all_shock_solution(hl, ul, hr, ur):\n", 1433 | " \"Plot the all shock solution for states (hl, ul) and (hr, ur)\"\n", 1434 | " \n", 1435 | " from scipy.optimize import brentq\n", 1436 | " \n", 1437 | " def phi(hstar):\n", 1438 | " \"Function defining the root\"\n", 1439 | " \n", 1440 | " phi_l = ul - np.sqrt(0.5 * (hl - hstar) * (hl / hstar - hstar / hl))\n", 1441 | " phi_r = ur + np.sqrt(0.5 * (hr - hstar) * (hr / hstar - hstar / hr))\n", 1442 | " \n", 1443 | " return phi_l - phi_r\n", 1444 | " \n", 1445 | " # There is a solution only in the physical case. \n", 1446 | " physical_solution = True\n", 1447 | " try:\n", 1448 | " hm = brentq(phi, np.max([hl, hr]), 10.0 * h_max)\n", 1449 | " except ValueError:\n", 1450 | " physical_solution = False\n", 1451 | " hm = hl\n", 1452 | " um = ul - np.sqrt(0.5 * (hl - hm) * (hl / hm - hm / hl))\n", 1453 | " \n", 1454 | " xi1l = ul - np.sqrt(hl)\n", 1455 | " xi1m = um - np.sqrt(hm)\n", 1456 | " xi1r = ur - np.sqrt(hr)\n", 1457 | " if physical_solution:\n", 1458 | " vsl = ul - hm / (hm - hl) * np.sqrt(0.5 * (hl - hm) * (hl / hm - hm / hl))\n", 1459 | " else:\n", 1460 | " vsl = xi1l\n", 1461 | " \n", 1462 | " xi2r = ur + np.sqrt(hr)\n", 1463 | " xi2m = um + np.sqrt(hm)\n", 1464 | " xi2l = ul + np.sqrt(hl)\n", 1465 | " if physical_solution:\n", 1466 | " vsr = ur + hm / (hm - hr) * np.sqrt(0.5 * (hr - hm) * (hr / hm - hm / hr))\n", 1467 | " else:\n", 1468 | " vsr = xi2r\n", 1469 | " \n", 1470 | " xi_min = np.min([-1.0, xi1l, xi1m, xi2r, xi2m])\n", 1471 | " xi_max = np.max([1.0, xi1l, xi1m, xi2r, xi2m])\n", 1472 | " d_xi = xi_max - xi_min\n", 1473 | " h_maximum = np.max([hl, hr, hm])\n", 1474 | " h_minimum = np.min([hl, hr, hm])\n", 1475 | " d_h = h_maximum - h_minimum\n", 1476 | " u_maximum = np.max([ul, ur, um])\n", 1477 | " u_minimum = np.min([ul, ur, um])\n", 1478 | " d_u = u_maximum - u_minimum\n", 1479 | " \n", 1480 | " xi = np.array([xi_min - 0.1 * d_xi, vsl, vsl, vsr, vsr, xi_max + 0.1 * d_xi])\n", 1481 | " h = np.array([hl, hl, hm, hm, hr, hr])\n", 1482 | " u = np.array([ul, ul, um, um, ur, ur])\n", 1483 | " \n", 1484 | " fig = plt.figure(figsize=(12,8))\n", 1485 | " ax1 = fig.add_subplot(221)\n", 1486 | " if (hm > np.max([hl, hr])):\n", 1487 | " ax1.plot(xi, h, 'b-', label = \"Physical solution\")\n", 1488 | " else:\n", 1489 | " ax1.plot(xi, h, 'r--', label = \"Unphysical solution\")\n", 1490 | " ax1.set_ybound(h_minimum - 0.1 * d_h, h_maximum + 0.1 * d_h)\n", 1491 | " ax1.set_xlabel(r\"$\\xi$\")\n", 1492 | " ax1.set_ylabel(r\"$h$\")\n", 1493 | " ax1.legend()\n", 1494 | " ax2 = fig.add_subplot(222)\n", 1495 | " if (hm > np.max([hl, hr])):\n", 1496 | " ax2.plot(xi, u, 'b-', label = \"Physical solution\")\n", 1497 | " else:\n", 1498 | " ax2.plot(xi, u, 'r--', label = \"Unphysical solution\")\n", 1499 | " ax2.set_ybound(u_minimum - 0.1 * d_u, u_maximum + 0.1 * d_u)\n", 1500 | " ax2.set_xlabel(r\"$\\xi$\")\n", 1501 | " ax2.set_ylabel(r\"$u$\")\n", 1502 | " ax2.legend()\n", 1503 | " \n", 1504 | " ax3 = fig.add_subplot(223)\n", 1505 | " left_end = np.min([-1.0, 1.1*xi1l])\n", 1506 | " right_end = np.max([1.0, 1.1*xi2r])\n", 1507 | " left_edge = left_end - xi1l\n", 1508 | " right_edge = right_end - xi1r\n", 1509 | " x1_start_points_l = np.linspace(np.min([left_edge, left_end]), 0.0, 20)\n", 1510 | " x1_start_points_r = np.linspace(0.0, np.max([right_edge, right_end]), 20)\n", 1511 | " t1_end_points_l = np.ones_like(x1_start_points_l)\n", 1512 | " t1_end_points_r = np.ones_like(x1_start_points_r)\n", 1513 | " \n", 1514 | " # Look for intersections\n", 1515 | " t1_end_points_l = np.minimum(t1_end_points_l, x1_start_points_l / (vsl - xi1l))\n", 1516 | " x1_end_points_l = x1_start_points_l + xi1l * t1_end_points_l\n", 1517 | " t1_end_points_r = np.minimum(t1_end_points_r, x1_start_points_r / (vsr - xi1r))\n", 1518 | " x1_end_points_r = x1_start_points_r + xi1r * t1_end_points_r\n", 1519 | " # Note: here we are cheating, and using the characteristic speed of the middle state, \n", 1520 | " # ignoring how it varies across the rarefaction\n", 1521 | " t1_final_points_r = np.ones_like(x1_start_points_r)\n", 1522 | " t1_final_points_r = np.minimum(t1_final_points_r, \n", 1523 | " (x1_end_points_r - t1_end_points_r * xi1m) / (vsl - xi1m))\n", 1524 | " x1_final_points_r = x1_end_points_r + (t1_final_points_r - t1_end_points_r) * xi1m\n", 1525 | " \n", 1526 | " for xs, xe, te in zip(x1_start_points_l, x1_end_points_l, t1_end_points_l):\n", 1527 | " ax3.plot([xs, xe], [0.0, te], 'b-')\n", 1528 | " for xs, xe, te in zip(x1_start_points_r, x1_end_points_r, t1_end_points_r):\n", 1529 | " ax3.plot([xs, xe], [0.0, te], 'g-')\n", 1530 | " for xs, xe, ts, te in zip(x1_end_points_r, x1_final_points_r, t1_end_points_r, \n", 1531 | " t1_final_points_r):\n", 1532 | " ax3.plot([xs, xe], [ts, te], 'g-')\n", 1533 | " \n", 1534 | " # Highlight the shocks\n", 1535 | " ax3.plot([0.0, vsl], [0.0, 1.0], 'r-', linewidth=2)\n", 1536 | " ax3.plot([0.0, vsr], [0.0, 1.0], 'r-', linewidth=2)\n", 1537 | " \n", 1538 | " # Unphysical shock\n", 1539 | " if not physical_solution:\n", 1540 | " x_fill = []\n", 1541 | " if xi1l < xi1m:\n", 1542 | " x_fill = [xi1l, 0.0, xi1m]\n", 1543 | " elif xi1l < vsl:\n", 1544 | " x_fill = [xi1l, 0.0, vsl]\n", 1545 | " elif vsl < xi1m:\n", 1546 | " x_fill = [vsl, 0.0, xi1m]\n", 1547 | " if len(x_fill) > 0:\n", 1548 | " t_fill = [1.0, 0.0, 1.0]\n", 1549 | " ax3.fill_between(x_fill, t_fill, 1.0, facecolor = 'red', alpha = 0.5)\n", 1550 | " \n", 1551 | " x_fill = []\n", 1552 | " if xi2r > xi2m:\n", 1553 | " x_fill = [xi2m, 0.0, xi2r]\n", 1554 | " elif xi2m < vsr:\n", 1555 | " x_fill = [xi2m, 0.0, vsr]\n", 1556 | " elif vsr < xi2r:\n", 1557 | " x_fill = [vsr, 0.0, xi2r]\n", 1558 | " if len(x_fill) > 0:\n", 1559 | " t_fill = [1.0, 0.0, 1.0]\n", 1560 | " ax3.fill_between(x_fill, t_fill, 1.0, facecolor = 'red', alpha = 0.5)\n", 1561 | " \n", 1562 | " ax3.set_xlabel(r\"$x$\")\n", 1563 | " ax3.set_ylabel(r\"$t$\")\n", 1564 | " ax3.set_title(\"1-characteristics\")\n", 1565 | " ax3.set_xbound(left_end, right_end)\n", 1566 | " \n", 1567 | " ax4 = fig.add_subplot(224)\n", 1568 | " left_end = np.min([-1.0, 1.1*xi1l])\n", 1569 | " right_end = np.max([1.0, 1.1*xi2r])\n", 1570 | " left_edge = left_end - xi2l\n", 1571 | " right_edge = right_end - xi2r\n", 1572 | " x2_start_points_l = np.linspace(np.min([left_edge, left_end]), 0.0, 20)\n", 1573 | " x2_start_points_r = np.linspace(0.0, np.max([right_edge, right_end]), 20)\n", 1574 | " x2_end_points_r = x2_start_points_r + xi2r\n", 1575 | " t2_end_points_l = np.ones_like(x2_start_points_l)\n", 1576 | " t2_end_points_r = np.ones_like(x2_start_points_r)\n", 1577 | " \n", 1578 | " # Look for intersections\n", 1579 | " t2_end_points_r = np.minimum(t2_end_points_r, x2_start_points_r / (vsr - xi2r))\n", 1580 | " x2_end_points_r = x2_start_points_r + xi2r * t2_end_points_r\n", 1581 | " t2_end_points_l = np.minimum(t2_end_points_l, x2_start_points_l / (vsl - xi2l))\n", 1582 | " x2_end_points_l = x2_start_points_l + xi2l * t2_end_points_l\n", 1583 | " # Note: here we are cheating, and using the characteristic speed of the middle state, \n", 1584 | " # ignoring how it varies across the rarefaction\n", 1585 | " t2_final_points_l = np.ones_like(x2_start_points_l)\n", 1586 | " t2_final_points_l = np.minimum(t2_final_points_l, \n", 1587 | " (x2_end_points_l - t2_end_points_l * xi2m) / (vsr - xi2m))\n", 1588 | " x2_final_points_l = x2_end_points_l + (t2_final_points_l - t2_end_points_l) * xi2m\n", 1589 | " \n", 1590 | " for xs, xe, te in zip(x2_start_points_r, x2_end_points_r, t2_end_points_r):\n", 1591 | " ax4.plot([xs, xe], [0.0, te], 'b-')\n", 1592 | " for xs, xe, te in zip(x2_start_points_l, x2_end_points_l, t2_end_points_l):\n", 1593 | " ax4.plot([xs, xe], [0.0, te], 'g-')\n", 1594 | " for xs, xe, ts, te in zip(x2_end_points_l, x2_final_points_l, t2_end_points_l, \n", 1595 | " t2_final_points_l):\n", 1596 | " ax4.plot([xs, xe], [ts, te], 'g-')\n", 1597 | " \n", 1598 | " # Highlight the shocks\n", 1599 | " ax4.plot([0.0, vsl], [0.0, 1.0], 'r-', linewidth=2)\n", 1600 | " ax4.plot([0.0, vsr], [0.0, 1.0], 'r-', linewidth=2)\n", 1601 | " \n", 1602 | " # Unphysical shock\n", 1603 | " if not physical_solution:\n", 1604 | " x_fill = []\n", 1605 | " if xi1l < xi1m:\n", 1606 | " x_fill = [xi1l, 0.0, xi1m]\n", 1607 | " elif xi1l < vsl:\n", 1608 | " x_fill = [xi1l, 0.0, vsl]\n", 1609 | " elif vsl < xi1m:\n", 1610 | " x_fill = [vsl, 0.0, xi1m]\n", 1611 | " if len(x_fill) > 0:\n", 1612 | " t_fill = [1.0, 0.0, 1.0]\n", 1613 | " ax4.fill_between(x_fill, t_fill, 1.0, facecolor = 'red', alpha = 0.5)\n", 1614 | " \n", 1615 | " x_fill = []\n", 1616 | " if xi2r > xi2m:\n", 1617 | " x_fill = [xi2m, 0.0, xi2r]\n", 1618 | " elif xi2m < vsr:\n", 1619 | " x_fill = [xi2m, 0.0, vsr]\n", 1620 | " elif vsr < xi2r:\n", 1621 | " x_fill = [vsr, 0.0, xi2r]\n", 1622 | " if len(x_fill) > 0:\n", 1623 | " t_fill = [1.0, 0.0, 1.0]\n", 1624 | " ax4.fill_between(x_fill, t_fill, 1.0, facecolor = 'red', alpha = 0.5)\n", 1625 | " \n", 1626 | " ax4.set_xlabel(r\"$x$\")\n", 1627 | " ax4.set_ylabel(r\"$t$\")\n", 1628 | " ax4.set_title(\"2-characteristics\")\n", 1629 | " ax4.set_xbound(left_end, right_end)\n", 1630 | " \n", 1631 | " fig.tight_layout()" 1632 | ] 1633 | }, 1634 | { 1635 | "cell_type": "code", 1636 | "execution_count": 18, 1637 | "metadata": {}, 1638 | "outputs": [ 1639 | { 1640 | "data": { 1641 | "application/vnd.jupyter.widget-view+json": { 1642 | "model_id": "134b60c42f554224ab328d7a48805c53", 1643 | "version_major": 2, 1644 | "version_minor": 0 1645 | }, 1646 | "text/html": [ 1647 | "

Failed to display Jupyter Widget of type interactive.

\n", 1648 | "

\n", 1649 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 1650 | " that the widgets JavaScript is still loading. If this message persists, it\n", 1651 | " likely means that the widgets JavaScript library is either not installed or\n", 1652 | " not enabled. See the Jupyter\n", 1653 | " Widgets Documentation for setup instructions.\n", 1654 | "

\n", 1655 | "

\n", 1656 | " If you're reading this message in another frontend (for example, a static\n", 1657 | " rendering on GitHub or NBViewer),\n", 1658 | " it may mean that your frontend doesn't currently support widgets.\n", 1659 | "

\n" 1660 | ], 1661 | "text/plain": [ 1662 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=0.2, description='ul', max=1.0, min=-1.0), FloatSlider(value=1.0, description='hr', max=10.0, min=0.1), FloatSlider(value=-0.2, description='ur', max=1.0, min=-1.0), Output()), _dom_classes=('widget-interact',))" 1663 | ] 1664 | }, 1665 | "metadata": {}, 1666 | "output_type": "display_data" 1667 | } 1668 | ], 1669 | "source": [ 1670 | "interactive(plot_sw_all_shock_solution, \n", 1671 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 1672 | " ul = FloatSlider(min = -1.0, max = 1.0, value = 0.2), \n", 1673 | " hr = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 1674 | " ur = FloatSlider(min = -1.0, max = 1.0, value = -0.2))" 1675 | ] 1676 | }, 1677 | { 1678 | "cell_type": "markdown", 1679 | "metadata": {}, 1680 | "source": [ 1681 | "## Full solution" 1682 | ] 1683 | }, 1684 | { 1685 | "cell_type": "markdown", 1686 | "metadata": {}, 1687 | "source": [ 1688 | "The all shock solution illustrates how the full solution can be obtained. We know that \n", 1689 | "\n", 1690 | "1. the central state ${\\bf w}_m$ will be connected to the known states ${\\bf w}_{l, r}$ across waves that are either shocks or rarefactions,\n", 1691 | "2. if $h_m > h_{l, r}$ then the wave will be a shock, otherwise it will be a rarefaction, and\n", 1692 | "3. given $h_m$ and the known data, we can compute $u_m$ for either a shock or a rarefaction.\n", 1693 | "\n", 1694 | "So, using the results above, we can find the full solution to the Riemann problem by solving the nonlinear algebraic root-finding problem\n", 1695 | "$$\n", 1696 | " \\Phi \\left( h_m ; {\\bf w}_l, {\\bf w}_r \\right) = 0,\n", 1697 | "$$\n", 1698 | "where\n", 1699 | "$$\n", 1700 | " \\Phi \\left( h_m ; {\\bf w}_l, {\\bf w}_r \\right) = \\Phi_l \\left( h_m ; {\\bf w}_l \\right) - \\Phi_r \\left( h_m ; {\\bf w}_r \\right),\n", 1701 | "$$\n", 1702 | "and\n", 1703 | "$$\n", 1704 | "\\begin{align}\n", 1705 | " \\Phi_l & = u_m \\left( h_m ; {\\bf w}_l \\right) & \\Phi_r & = u_m \\left( h_m ; {\\bf w}_r \\right) \\\\\n", 1706 | " & = \\begin{cases} u_l + 2 \\left( \\sqrt{h_l} - \\sqrt{h_m} \\right) & h_l > h_m \\\\ u_l - \\sqrt{\\tfrac{1}{2} \\left( h_l - h_m \\right) \\left( \\frac{h_l}{h_m} - \\frac{h_m}{h_l} \\right)} & h_l < h_m \\end{cases} & & = \\begin{cases} u_r - 2 \\left( \\sqrt{h_r} - \\sqrt{h_m} \\right) & h_r > h_m \\\\ u_r + \\sqrt{\\tfrac{1}{2} \\left( h_r - h_m \\right) \\left( \\frac{h_r}{h_m} - \\frac{h_m}{h_r} \\right)} & h_r < h_m \\end{cases}.\n", 1707 | "\\end{align}\n", 1708 | "$$" 1709 | ] 1710 | }, 1711 | { 1712 | "cell_type": "code", 1713 | "execution_count": 19, 1714 | "metadata": {}, 1715 | "outputs": [], 1716 | "source": [ 1717 | "def plot_sw_Riemann_curves(hl, ul, hr, ur):\n", 1718 | " \"Plot the solution curves for states (hl, ul) and (hr, ur)\"\n", 1719 | " \n", 1720 | " from scipy.optimize import brentq\n", 1721 | " \n", 1722 | " def phi(hstar):\n", 1723 | " \"Function defining the root\"\n", 1724 | " \n", 1725 | " if hl < hstar:\n", 1726 | " phi_l = ul - np.sqrt(0.5 * (hl - hstar) * (hl / hstar - hstar / hl))\n", 1727 | " else:\n", 1728 | " phi_l = ul + 2.0 * (np.sqrt(hl) - np.sqrt(hstar))\n", 1729 | " if hr < hstar:\n", 1730 | " phi_r = ur + np.sqrt(0.5 * (hr - hstar) * (hr / hstar - hstar / hr))\n", 1731 | " else:\n", 1732 | " phi_r = ur - 2.0 * (np.sqrt(hr) - np.sqrt(hstar))\n", 1733 | " \n", 1734 | " return phi_l - phi_r\n", 1735 | " \n", 1736 | " hm = brentq(phi, 0.1 * h_min, 10.0 * h_max)\n", 1737 | " if hl < hm:\n", 1738 | " um = ul - np.sqrt(0.5 * (hl - hm) * (hl / hm - hm / hl))\n", 1739 | " else:\n", 1740 | " um = ul + 2.0 * (np.sqrt(hl) - np.sqrt(hm))\n", 1741 | " \n", 1742 | " h_maximum = np.max([h_max, hl, hr, hm])\n", 1743 | " h_minimum = np.min([h_min, hl, hr, hm])\n", 1744 | " u_maximum = np.max([u_max, ul, ur, um])\n", 1745 | " u_minimum = np.min([u_min, ul, ur, um])\n", 1746 | " dh = h_maximum - h_minimum\n", 1747 | " du = u_maximum - u_minimum\n", 1748 | " \n", 1749 | " # Now plot the rarefaction and shock curves as appropriate\n", 1750 | " # Here we only plot the physical pieces.\n", 1751 | " \n", 1752 | " h1_shock = np.linspace(hl, h_max)\n", 1753 | " u1_shock = ul - np.sqrt(0.5 * (hl - h1_shock) * (hl / h1_shock - h1_shock / hl))\n", 1754 | " h2_shock = np.linspace(hr, h_max)\n", 1755 | " u2_shock = ur + np.sqrt(0.5 * (hr - h2_shock) * (hr / h2_shock - h2_shock / hr))\n", 1756 | " \n", 1757 | " h1_rarefaction = np.linspace(h_min, hl)\n", 1758 | " u1_rarefaction = ul + 2.0 * (np.sqrt(hl) - np.sqrt(h1_rarefaction))\n", 1759 | " h2_rarefaction = np.linspace(h_min, hr)\n", 1760 | " u2_rarefaction = ur - 2.0 * (np.sqrt(hr) - np.sqrt(h2_rarefaction))\n", 1761 | " \n", 1762 | " fig = plt.figure(figsize=(12,8))\n", 1763 | " ax = fig.add_subplot(111)\n", 1764 | " ax.plot(hl, ul, 'rx', markersize = 16, markeredgewidth = 3, label = r\"${\\bf w}_l$\")\n", 1765 | " ax.plot(hr, ur, 'r+', markersize = 16, markeredgewidth = 3, label = r\"${\\bf w}_r$\")\n", 1766 | " ax.plot(hm, um, 'ro', markersize = 16, markeredgewidth = 3, label = r\"${\\bf w}_m$\")\n", 1767 | " ax.plot(h1_shock, u1_shock, 'b-', linewidth = 2, \n", 1768 | " label=\"1-shock\")\n", 1769 | " ax.plot(h1_rarefaction, u1_rarefaction, 'b-.', linewidth = 2, \n", 1770 | " label=\"1-rarefaction\")\n", 1771 | " ax.plot(h2_shock, u2_shock, 'g-', linewidth = 2, \n", 1772 | " label=\"2-shock\")\n", 1773 | " ax.plot(h2_rarefaction, u2_rarefaction, 'g-.', linewidth = 2, \n", 1774 | " label=\"2-rarefaction\")\n", 1775 | " ax.set_xlabel(r\"$h$\")\n", 1776 | " ax.set_ylabel(r\"$u$\")\n", 1777 | " ax.set_xbound(h_minimum - 0.1 * dh, h_maximum + 0.1 * dh)\n", 1778 | " ax.set_ybound(u_minimum - 0.1 * du, u_maximum + 0.1 * du)\n", 1779 | " ax.legend()\n", 1780 | " \n", 1781 | " fig.tight_layout()" 1782 | ] 1783 | }, 1784 | { 1785 | "cell_type": "code", 1786 | "execution_count": 20, 1787 | "metadata": {}, 1788 | "outputs": [ 1789 | { 1790 | "data": { 1791 | "application/vnd.jupyter.widget-view+json": { 1792 | "model_id": "1c183cd1cc1043fcb9148ee6e2f78523", 1793 | "version_major": 2, 1794 | "version_minor": 0 1795 | }, 1796 | "text/html": [ 1797 | "

Failed to display Jupyter Widget of type interactive.

\n", 1798 | "

\n", 1799 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 1800 | " that the widgets JavaScript is still loading. If this message persists, it\n", 1801 | " likely means that the widgets JavaScript library is either not installed or\n", 1802 | " not enabled. See the Jupyter\n", 1803 | " Widgets Documentation for setup instructions.\n", 1804 | "

\n", 1805 | "

\n", 1806 | " If you're reading this message in another frontend (for example, a static\n", 1807 | " rendering on GitHub or NBViewer),\n", 1808 | " it may mean that your frontend doesn't currently support widgets.\n", 1809 | "

\n" 1810 | ], 1811 | "text/plain": [ 1812 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=0.2, description='ul', max=1.0, min=-1.0), FloatSlider(value=1.0, description='hr', max=10.0, min=0.1), FloatSlider(value=-0.2, description='ur', max=1.0, min=-1.0), Output()), _dom_classes=('widget-interact',))" 1813 | ] 1814 | }, 1815 | "metadata": {}, 1816 | "output_type": "display_data" 1817 | } 1818 | ], 1819 | "source": [ 1820 | "interactive(plot_sw_Riemann_curves, \n", 1821 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 1822 | " ul = FloatSlider(min = -1.0, max = 1.0, value = 0.2), \n", 1823 | " hr = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 1824 | " ur = FloatSlider(min = -1.0, max = 1.0, value = -0.2))" 1825 | ] 1826 | }, 1827 | { 1828 | "cell_type": "markdown", 1829 | "metadata": {}, 1830 | "source": [ 1831 | "Finally, we can plot the solution in physical space." 1832 | ] 1833 | }, 1834 | { 1835 | "cell_type": "code", 1836 | "execution_count": 21, 1837 | "metadata": {}, 1838 | "outputs": [], 1839 | "source": [ 1840 | "def plot_sw_Riemann_solution(hl, ul, hr, ur):\n", 1841 | " \"Plot the Riemann problem solution for states (hl, ul) and (hr, ur)\"\n", 1842 | " \n", 1843 | " from scipy.optimize import brentq\n", 1844 | " \n", 1845 | " def phi(hstar):\n", 1846 | " \"Function defining the root\"\n", 1847 | " \n", 1848 | " if hl < hstar:\n", 1849 | " phi_l = ul - np.sqrt(0.5 * (hl - hstar) * (hl / hstar - hstar / hl))\n", 1850 | " else:\n", 1851 | " phi_l = ul + 2.0 * (np.sqrt(hl) - np.sqrt(hstar))\n", 1852 | " if hr < hstar:\n", 1853 | " phi_r = ur + np.sqrt(0.5 * (hr - hstar) * (hr / hstar - hstar / hr))\n", 1854 | " else:\n", 1855 | " phi_r = ur - 2.0 * (np.sqrt(hr) - np.sqrt(hstar))\n", 1856 | " \n", 1857 | " return phi_l - phi_r\n", 1858 | " \n", 1859 | " left_raref = False\n", 1860 | " left_shock = False\n", 1861 | " right_raref = False\n", 1862 | " right_shock = False\n", 1863 | " \n", 1864 | " hm = brentq(phi, 0.1 * h_min, 10.0 * h_max)\n", 1865 | " if hl < hm:\n", 1866 | " um = ul - np.sqrt(0.5 * (hl - hm) * (hl / hm - hm / hl))\n", 1867 | " else:\n", 1868 | " um = ul + 2.0 * (np.sqrt(hl) - np.sqrt(hm))\n", 1869 | " \n", 1870 | " h_maximum = np.max([h_max, hl, hr, hm])\n", 1871 | " h_minimum = np.min([h_min, hl, hr, hm])\n", 1872 | " u_maximum = np.max([u_max, ul, ur, um])\n", 1873 | " u_minimum = np.min([u_min, ul, ur, um])\n", 1874 | " dh = h_maximum - h_minimum\n", 1875 | " du = u_maximum - u_minimum\n", 1876 | " \n", 1877 | " xi1l = ul - np.sqrt(hl)\n", 1878 | " xi1m = um - np.sqrt(hm)\n", 1879 | " xi1r = ur - np.sqrt(hr)\n", 1880 | " if hm > hl:\n", 1881 | " left_shock = True\n", 1882 | " vsl = ul - hm / (hm - hl) * np.sqrt(0.5 * (hl - hm) * (hl / hm - hm / hl))\n", 1883 | " else:\n", 1884 | " left_raref = True\n", 1885 | " hl_raref = np.linspace(hl, hm, 20)\n", 1886 | " ul_raref = ul + 2.0 * (np.sqrt(hl) - np.sqrt(hl_raref))\n", 1887 | " xil_raref = ul_raref - np.sqrt(hl_raref)\n", 1888 | " \n", 1889 | " xi2r = ur + np.sqrt(hr)\n", 1890 | " xi2m = um + np.sqrt(hm)\n", 1891 | " xi2l = ul + np.sqrt(hl)\n", 1892 | " if hm > hr:\n", 1893 | " right_shock = True\n", 1894 | " vsr = ur + hm / (hm - hr) * np.sqrt(0.5 * (hr - hm) * (hr / hm - hm / hr))\n", 1895 | " else:\n", 1896 | " right_raref = True\n", 1897 | " hr_raref = np.linspace(hm, hr)\n", 1898 | " ur_raref = ur - 2.0 * (np.sqrt(hr) - np.sqrt(hr_raref))\n", 1899 | " xir_raref = ur_raref + np.sqrt(hr_raref)\n", 1900 | " \n", 1901 | " xi_min = np.min([-1.0, xi1l, xi1m, xi2r, xi2m])\n", 1902 | " xi_max = np.max([1.0, xi1l, xi1m, xi2r, xi2m])\n", 1903 | " d_xi = xi_max - xi_min\n", 1904 | " h_maximum = np.max([hl, hr, hm])\n", 1905 | " h_minimum = np.min([hl, hr, hm])\n", 1906 | " d_h = h_maximum - h_minimum\n", 1907 | " u_maximum = np.max([ul, ur, um])\n", 1908 | " u_minimum = np.min([ul, ur, um])\n", 1909 | " d_u = u_maximum - u_minimum\n", 1910 | " \n", 1911 | " xi = np.array([xi_min - 0.1 * d_xi])\n", 1912 | " h = np.array([hl])\n", 1913 | " u = np.array([ul])\n", 1914 | " if left_shock:\n", 1915 | " xi = np.append(xi, [vsl, vsl])\n", 1916 | " h = np.append(h, [hl, hm])\n", 1917 | " u = np.append(u, [ul, um])\n", 1918 | " else:\n", 1919 | " xi = np.append(xi, xil_raref)\n", 1920 | " h = np.append(h, hl_raref)\n", 1921 | " u = np.append(u, ul_raref)\n", 1922 | " if right_shock:\n", 1923 | " xi = np.append(xi, [vsr, vsr])\n", 1924 | " h = np.append(h, [hm, hr])\n", 1925 | " u = np.append(u, [um, ur])\n", 1926 | " else:\n", 1927 | " xi = np.append(xi, xir_raref)\n", 1928 | " h = np.append(h, hr_raref)\n", 1929 | " u = np.append(u, ur_raref)\n", 1930 | " xi = np.append(xi, [xi_max + 0.1 * d_xi])\n", 1931 | " h = np.append(h, [hr])\n", 1932 | " u = np.append(u, [ur])\n", 1933 | " \n", 1934 | " fig = plt.figure(figsize=(12,8))\n", 1935 | " ax1 = fig.add_subplot(221)\n", 1936 | " ax1.plot(xi, h, 'b-', label = \"True solution\")\n", 1937 | " ax1.set_ybound(h_minimum - 0.1 * d_h, h_maximum + 0.1 * d_h)\n", 1938 | " ax1.set_xlabel(r\"$\\xi$\")\n", 1939 | " ax1.set_ylabel(r\"$h$\")\n", 1940 | " ax1.legend()\n", 1941 | " ax2 = fig.add_subplot(222)\n", 1942 | " ax2.plot(xi, u, 'b-', label = \"True solution\")\n", 1943 | " ax2.set_ybound(u_minimum - 0.1 * d_u, u_maximum + 0.1 * d_u)\n", 1944 | " ax2.set_xlabel(r\"$\\xi$\")\n", 1945 | " ax2.set_ylabel(r\"$u$\")\n", 1946 | " ax2.legend()\n", 1947 | " \n", 1948 | " ax3 = fig.add_subplot(223)\n", 1949 | " left_end = np.min([-1.0, 1.1*xi1l])\n", 1950 | " right_end = np.max([1.0, 1.1*xi2r])\n", 1951 | " left_edge = left_end - xi1l\n", 1952 | " right_edge = right_end - xi1r\n", 1953 | " x1_start_points_l = np.linspace(np.min([left_edge, left_end]), 0.0, 20)\n", 1954 | " x1_start_points_r = np.linspace(0.0, np.max([right_edge, right_end]), 20)\n", 1955 | " t1_end_points_l = np.ones_like(x1_start_points_l)\n", 1956 | " t1_end_points_r = np.ones_like(x1_start_points_r)\n", 1957 | " \n", 1958 | " # Look for intersections\n", 1959 | " if left_shock:\n", 1960 | " t1_end_points_l = np.minimum(t1_end_points_l, x1_start_points_l / (vsl - xi1l))\n", 1961 | " x1_end_points_l = x1_start_points_l + xi1l * t1_end_points_l\n", 1962 | " if right_shock:\n", 1963 | " t1_end_points_r = np.minimum(t1_end_points_r, x1_start_points_r / (vsr - xi1r))\n", 1964 | " else:\n", 1965 | " t1_end_points_r = np.minimum(t1_end_points_r, x1_start_points_r / (xi2r - xi1r))\n", 1966 | " x1_end_points_r = x1_start_points_r + xi1r * t1_end_points_r\n", 1967 | " # Note: here we are cheating, and using the characteristic speed of the middle state, \n", 1968 | " # ignoring how it varies across the rarefaction\n", 1969 | " t1_final_points_r = np.ones_like(x1_start_points_r)\n", 1970 | " if left_shock:\n", 1971 | " t1_final_points_r = np.minimum(t1_final_points_r, \n", 1972 | " (x1_end_points_r - t1_end_points_r * xi1m) / \n", 1973 | " (vsl - xi1m))\n", 1974 | " x1_final_points_r = x1_end_points_r + (t1_final_points_r - t1_end_points_r) * xi1m\n", 1975 | " \n", 1976 | " for xs, xe, te in zip(x1_start_points_l, x1_end_points_l, t1_end_points_l):\n", 1977 | " ax3.plot([xs, xe], [0.0, te], 'b-')\n", 1978 | " for xs, xe, te in zip(x1_start_points_r, x1_end_points_r, t1_end_points_r):\n", 1979 | " ax3.plot([xs, xe], [0.0, te], 'g-')\n", 1980 | " for xs, xe, ts, te in zip(x1_end_points_r, x1_final_points_r, t1_end_points_r, \n", 1981 | " t1_final_points_r):\n", 1982 | " ax3.plot([xs, xe], [ts, te], 'g-')\n", 1983 | " \n", 1984 | " # Highlight the waves\n", 1985 | " if left_shock:\n", 1986 | " ax3.plot([0.0, vsl], [0.0, 1.0], 'r-', linewidth=2)\n", 1987 | " else:\n", 1988 | " ax3.plot([0.0, xi1l], [0.0, 1.0], 'r-', linewidth=2)\n", 1989 | " ax3.plot([0.0, xi1m], [0.0, 1.0], 'r-', linewidth=2)\n", 1990 | " xi = np.linspace(xi1l, xi1m, 11)\n", 1991 | " x_end_rarefaction = xi\n", 1992 | " for xe in x_end_rarefaction:\n", 1993 | " ax3.plot([0.0, xe], [0.0, 1.0], 'r--')\n", 1994 | " if right_shock:\n", 1995 | " ax3.plot([0.0, vsr], [0.0, 1.0], 'r-', linewidth=2)\n", 1996 | " else:\n", 1997 | " ax3.plot([0.0, xi2m], [0.0, 1.0], 'r-', linewidth=2)\n", 1998 | " ax3.plot([0.0, xi2r], [0.0, 1.0], 'r-', linewidth=2)\n", 1999 | " \n", 2000 | " ax3.set_xlabel(r\"$x$\")\n", 2001 | " ax3.set_ylabel(r\"$t$\")\n", 2002 | " ax3.set_title(\"1-characteristics\")\n", 2003 | " ax3.set_xbound(left_end, right_end)\n", 2004 | " \n", 2005 | " ax4 = fig.add_subplot(224)\n", 2006 | " left_end = np.min([-1.0, 1.1*xi1l])\n", 2007 | " right_end = np.max([1.0, 1.1*xi2r])\n", 2008 | " left_edge = left_end - xi2l\n", 2009 | " right_edge = right_end - xi2r\n", 2010 | " x2_start_points_l = np.linspace(np.min([left_edge, left_end]), 0.0, 20)\n", 2011 | " x2_start_points_r = np.linspace(0.0, np.max([right_edge, right_end]), 20)\n", 2012 | " x2_end_points_r = x2_start_points_r + xi2r\n", 2013 | " t2_end_points_l = np.ones_like(x2_start_points_l)\n", 2014 | " t2_end_points_r = np.ones_like(x2_start_points_r)\n", 2015 | " \n", 2016 | " # Look for intersections\n", 2017 | " if right_shock:\n", 2018 | " t2_end_points_r = np.minimum(t2_end_points_r, x2_start_points_r / (vsr - xi2r))\n", 2019 | " x2_end_points_r = x2_start_points_r + xi2r * t2_end_points_r\n", 2020 | " if left_shock:\n", 2021 | " t2_end_points_l = np.minimum(t2_end_points_l, x2_start_points_l / (vsl - xi2l))\n", 2022 | " else:\n", 2023 | " t2_end_points_l = np.minimum(t2_end_points_l, x2_start_points_l / (xi1l - xi2l))\n", 2024 | " x2_end_points_l = x2_start_points_l + xi2l * t2_end_points_l\n", 2025 | " # Note: here we are cheating, and using the characteristic speed of the middle state, \n", 2026 | " # ignoring how it varies across the rarefaction\n", 2027 | " t2_final_points_l = np.ones_like(x2_start_points_l)\n", 2028 | " if right_shock:\n", 2029 | " t2_final_points_l = np.minimum(t2_final_points_l, \n", 2030 | " (x2_end_points_l - t2_end_points_l * xi2m) / \n", 2031 | " (vsr - xi2m))\n", 2032 | " x2_final_points_l = x2_end_points_l + (t2_final_points_l - t2_end_points_l) * xi2m\n", 2033 | " \n", 2034 | " for xs, xe, te in zip(x2_start_points_r, x2_end_points_r, t2_end_points_r):\n", 2035 | " ax4.plot([xs, xe], [0.0, te], 'b-')\n", 2036 | " for xs, xe, te in zip(x2_start_points_l, x2_end_points_l, t2_end_points_l):\n", 2037 | " ax4.plot([xs, xe], [0.0, te], 'g-')\n", 2038 | " for xs, xe, ts, te in zip(x2_end_points_l, x2_final_points_l, t2_end_points_l, \n", 2039 | " t2_final_points_l):\n", 2040 | " ax4.plot([xs, xe], [ts, te], 'g-')\n", 2041 | " \n", 2042 | " # Highlight the waves\n", 2043 | " if left_shock:\n", 2044 | " ax4.plot([0.0, vsl], [0.0, 1.0], 'r-', linewidth=2)\n", 2045 | " else:\n", 2046 | " ax4.plot([0.0, xi1l], [0.0, 1.0], 'r-', linewidth=2)\n", 2047 | " ax4.plot([0.0, xi1m], [0.0, 1.0], 'r-', linewidth=2)\n", 2048 | " if right_shock:\n", 2049 | " ax4.plot([0.0, vsr], [0.0, 1.0], 'r-', linewidth=2)\n", 2050 | " else:\n", 2051 | " ax4.plot([0.0, xi2m], [0.0, 1.0], 'r-', linewidth=2)\n", 2052 | " ax4.plot([0.0, xi2r], [0.0, 1.0], 'r-', linewidth=2)\n", 2053 | " xi = np.linspace(xi2m, xi2r, 11)\n", 2054 | " x_end_rarefaction = xi\n", 2055 | " for xe in x_end_rarefaction:\n", 2056 | " ax4.plot([0.0, xe], [0.0, 1.0], 'r--')\n", 2057 | " \n", 2058 | " ax4.set_xlabel(r\"$x$\")\n", 2059 | " ax4.set_ylabel(r\"$t$\")\n", 2060 | " ax4.set_title(\"2-characteristics\")\n", 2061 | " ax4.set_xbound(left_end, right_end)\n", 2062 | " \n", 2063 | " fig.tight_layout()" 2064 | ] 2065 | }, 2066 | { 2067 | "cell_type": "code", 2068 | "execution_count": 22, 2069 | "metadata": {}, 2070 | "outputs": [ 2071 | { 2072 | "data": { 2073 | "application/vnd.jupyter.widget-view+json": { 2074 | "model_id": "29ed701b74d14c70a6acb95f4dd2e7a3", 2075 | "version_major": 2, 2076 | "version_minor": 0 2077 | }, 2078 | "text/html": [ 2079 | "

Failed to display Jupyter Widget of type interactive.

\n", 2080 | "

\n", 2081 | " If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n", 2082 | " that the widgets JavaScript is still loading. If this message persists, it\n", 2083 | " likely means that the widgets JavaScript library is either not installed or\n", 2084 | " not enabled. See the Jupyter\n", 2085 | " Widgets Documentation for setup instructions.\n", 2086 | "

\n", 2087 | "

\n", 2088 | " If you're reading this message in another frontend (for example, a static\n", 2089 | " rendering on GitHub or NBViewer),\n", 2090 | " it may mean that your frontend doesn't currently support widgets.\n", 2091 | "

\n" 2092 | ], 2093 | "text/plain": [ 2094 | "interactive(children=(FloatSlider(value=1.0, description='hl', max=10.0, min=0.1), FloatSlider(value=0.2, description='ul', max=1.0, min=-1.0), FloatSlider(value=1.0, description='hr', max=10.0, min=0.1), FloatSlider(value=-0.2, description='ur', max=1.0, min=-1.0), Output()), _dom_classes=('widget-interact',))" 2095 | ] 2096 | }, 2097 | "metadata": {}, 2098 | "output_type": "display_data" 2099 | } 2100 | ], 2101 | "source": [ 2102 | "interactive(plot_sw_Riemann_solution, \n", 2103 | " hl = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 2104 | " ul = FloatSlider(min = -1.0, max = 1.0, value = 0.2), \n", 2105 | " hr = FloatSlider(min = 0.1, max = 10.0, value = 1.0), \n", 2106 | " ur = FloatSlider(min = -1.0, max = 1.0, value = -0.2))" 2107 | ] 2108 | } 2109 | ], 2110 | "metadata": { 2111 | "kernelspec": { 2112 | "display_name": "Python 3", 2113 | "language": "python", 2114 | "name": "python3" 2115 | }, 2116 | "language_info": { 2117 | "codemirror_mode": { 2118 | "name": "ipython", 2119 | "version": 3 2120 | }, 2121 | "file_extension": ".py", 2122 | "mimetype": "text/x-python", 2123 | "name": "python", 2124 | "nbconvert_exporter": "python", 2125 | "pygments_lexer": "ipython3", 2126 | "version": "3.6.4" 2127 | } 2128 | }, 2129 | "nbformat": 4, 2130 | "nbformat_minor": 1 2131 | } 2132 | -------------------------------------------------------------------------------- /Lesson_05_Approximate_Solvers.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/html": [ 11 | "\n", 12 | "\n", 13 | "\n", 14 | "\n", 15 | "\n", 16 | "\n", 17 | "\n", 138 | "\n", 139 | "\n" 156 | ], 157 | "text/plain": [ 158 | "" 159 | ] 160 | }, 161 | "execution_count": 1, 162 | "metadata": {}, 163 | "output_type": "execute_result" 164 | } 165 | ], 166 | "source": [ 167 | "from IPython.core.display import HTML\n", 168 | "def css_styling():\n", 169 | " styles = open(\"./styles/custom.css\", \"r\").read()\n", 170 | " return HTML(styles)\n", 171 | "css_styling()" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "# Approximate solutions to the Riemann Problem" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "## Solutions in practice" 186 | ] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": {}, 191 | "source": [ 192 | "Solutions to the Riemann problem are mainly used in two contexts:\n", 193 | "\n", 194 | "1. As reference solutions against which a numerical method is benchmarked, or\n", 195 | "2. As part of a numerical method, such as a high resolution shock capturing method, where the flux between two numerical cells is required." 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "In the first case, accuracy is paramount and the complete solution (all wave speeds, and all intermediate states) is required. In the second case only one thing is required: the flux ${\\bf f}^*$ between the cells, which is the flux on the characteristic line $\\xi = x / t = 0$." 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "metadata": {}, 208 | "source": [ 209 | "In this second case, the numerical method will have to repeatedly solve the Riemann problem. In a general problem, the solution may be needed tens of times *per cell, per timestep*, leading to millions (or more!) solutions in a simulation. The speed of the solution is then extremely important, and approximate solutions are often used." 210 | ] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "metadata": {}, 215 | "source": [ 216 | "## Roe-type solutions" 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "metadata": {}, 222 | "source": [ 223 | "The most obvious simplification is to reduce the nonlinear problem\n", 224 | "\n", 225 | "\\begin{equation}\n", 226 | " \\partial_t {\\bf q} + \\partial_x {\\bf f}({\\bf q}) = {\\bf 0}\n", 227 | "\\end{equation}\n", 228 | "\n", 229 | "to the *linear* problem\n", 230 | "\n", 231 | "\\begin{equation}\n", 232 | " \\partial_t {\\bf q} + A \\partial_x {\\bf q} = {\\bf 0},\n", 233 | "\\end{equation}\n", 234 | "\n", 235 | "where $A$ is a *constant* matrix that approximates the Jacobian $\\partial {\\bf f} / \\partial {\\bf q}$. We can then solve the linear problem exactly (e.g. by diagonalising the matrix and solving the resulting uncoupled advection equations), to find\n", 236 | "\n", 237 | "\\begin{align}\n", 238 | " {\\bf q}(x, t) & = {\\bf q}_l + \\sum_{p: \\lambda^{(p)} < \\tfrac{x}{t}} \\left\\{ {\\bf l}^{(p)} \\cdot \\left( {\\bf q}_r - {\\bf q}_l \\right) \\right\\} {\\bf r}^{(p)}, \\\\\n", 239 | " & = {\\bf q}_r - \\sum_{p: \\lambda^{(p)} > \\tfrac{x}{t}} \\left\\{ {\\bf l}^{(p)} \\cdot \\left( {\\bf q}_r - {\\bf q}_l \\right) \\right\\} {\\bf r}^{(p)}, \\\\\n", 240 | " & = \\frac{1}{2} \\left( {\\bf q}_l + {\\bf q}_r \\right) + \\sum_{p: \\lambda^{(p)} < \\tfrac{x}{t}} \\left\\{ {\\bf l}^{(p)} \\cdot \\left( {\\bf q}_r - {\\bf q}_l \\right) \\right\\} {\\bf r}^{(p)} - \\sum_{p: \\lambda^{(p)} > \\tfrac{x}{t}} \\left\\{ {\\bf l}^{(p)} \\cdot \\left( {\\bf q}_r - {\\bf q}_l \\right) \\right\\} {\\bf r}^{(p)}.\n", 241 | "\\end{align}\n", 242 | "\n", 243 | "where $\\lambda^{(p)}, {\\bf r}^{(p)},$ and ${\\bf l}^{(p)}$ are the eigenvalues and the (right and left respectively) eigenvectors of $A$, ordered such that $\\lambda^{(1)} \\le \\dots \\le \\lambda^{(N)}$ as usual. All three solutions are equivalent; the last is typically used." 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "Given this complete solution, it is easily evaluated along $x = 0$, and the flux calculated from the result." 251 | ] 252 | }, 253 | { 254 | "cell_type": "markdown", 255 | "metadata": {}, 256 | "source": [ 257 | "An even greater shortcut can be found by noting that we are approximating ${\\bf f} = A {\\bf q}$. Therefore the standard form is to write\n", 258 | "\n", 259 | "\\begin{equation}\n", 260 | " {\\bf f}^* = \\frac{1}{2} \\left( {\\bf f}_l + {\\bf f}_r \\right) + \\sum_{p} \\left| \\lambda^{(p)} \\right| \\left\\{ {\\bf l}^{(p)} \\cdot \\left( {\\bf q}_r - {\\bf q}_l \\right) \\right\\} {\\bf r}^{(p)},\n", 261 | "\\end{equation}\n", 262 | "\n", 263 | "where now we are summing over all eigenvalues and eigenvectors. It should be noted that ${\\bf f}^* \\ne {\\bf f}({\\bf q}^*)$ in general, as the calculation of ${\\bf f}^*$ relied on an approximation to the flux." 264 | ] 265 | }, 266 | { 267 | "cell_type": "markdown", 268 | "metadata": {}, 269 | "source": [ 270 | "In order to complete this specification of the solver, we only need to say how $A$ is defined. Roe gave the suggestion that\n", 271 | "\n", 272 | "\\begin{equation}\n", 273 | " A = A({\\bf q}_{\\textrm{Roe}}) = \\left. \\frac{\\partial {\\bf f}}{\\partial {\\bf q}} \\right|_{{\\bf q}_{\\textrm{Roe}}},\n", 274 | "\\end{equation}\n", 275 | "\n", 276 | "where the *Roe average* ${\\bf q}_{\\textrm{Roe}}$ satisfies\n", 277 | "\n", 278 | "1. $A({\\bf q}_{\\textrm{Roe}}) \\left( {\\bf q}_r - {\\bf q}_l \\right) = {\\bf f}_r - {\\bf f}_l$,\n", 279 | "2. $A({\\bf q}_{\\textrm{Roe}})$ is diagonalizable with real eigenvalues, and\n", 280 | "3. $A({\\bf q}_{\\textrm{Roe}}) \\to \\partial {\\bf f} / \\partial {\\bf q}$ smoothly as ${\\bf q}_{\\textrm{Roe}} \\to {\\bf q}$." 281 | ] 282 | }, 283 | { 284 | "cell_type": "markdown", 285 | "metadata": {}, 286 | "source": [ 287 | "It is *possible* to construct the Roe average for many systems (such as the Euler equations, and the relativistic Euler equations). However, a simple arithmetic average is often nearly as good - in the sense that the algorithm will fail only slightly more often than the algorithm with the full Roe average!" 288 | ] 289 | }, 290 | { 291 | "cell_type": "markdown", 292 | "metadata": {}, 293 | "source": [ 294 | "The problem with Roe type solvers is that it approximates all waves as discontinuities. This leads to inaccuracies near rarefactions, and these can be catastrophically bad when the rarefaction fan crosses $\\xi = 0$ (a *sonic rarefaction*). It is possible to detect when these problems will occur (e.g. by looking at when $\\lambda^{(p)}$ changes sign between the left and right states) and change the approximation at this point, often known as an *entropy fix*. More systematic and complex methods that extend the Roe approach whilst avoiding this problem include the *Marquina* solver." 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "metadata": {}, 300 | "source": [ 301 | "## HLL-type solutions" 302 | ] 303 | }, 304 | { 305 | "cell_type": "markdown", 306 | "metadata": {}, 307 | "source": [ 308 | "An alternative type of method simplifies the wave structure even more, by simplifying the number of waves. HLL (for Harten, Lax and van Leer) type solutions assume that\n", 309 | "\n", 310 | "1. there are two waves, both discontinuities, separating a constant central state in the solution, and\n", 311 | "2. the waves propagate at the (known) speeds $\\xi_{(\\pm)}$." 312 | ] 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "metadata": {}, 317 | "source": [ 318 | "From these assumptions, and the Rankine-Hugoniot conditions, we have the two equations\n", 319 | "\n", 320 | "\\begin{align}\n", 321 | " \\xi_{(-)} \\left[ {\\bf q}_m - {\\bf q}_l \\right] & = {\\bf f}_m - {\\bf f}_l, \\\\\n", 322 | " \\xi_{(+)} \\left[ {\\bf q}_r - {\\bf q}_m \\right] & = {\\bf f}_r - {\\bf f}_m.\n", 323 | "\\end{align}\n", 324 | "\n", 325 | "These are immediately solved to give\n", 326 | "\n", 327 | "\\begin{align}\n", 328 | " {\\bf q}_m & = \\frac{\\xi_{(+)} {\\bf q}_r - \\xi_{(-)} {\\bf q}_l - {\\bf f}_r + {\\bf f}_l}{\\xi_{(+)} - \\xi_{(-)}}, \\\\\n", 329 | " {\\bf f}_m & = \\frac{\\hat{\\xi}_{(+)} {\\bf f}_l - \\hat{\\xi}_{(-)} {\\bf f}_r + \\hat{\\xi}_{(+)} \\hat{\\xi}_{(-)} \\left( {\\bf q}_r - {\\bf q}_r \\right)}{\\hat{\\xi}_{(+)} - \\hat{\\xi}_{(-)}},\n", 330 | "\\end{align}\n", 331 | "\n", 332 | "where\n", 333 | "\n", 334 | "\\begin{equation}\n", 335 | " \\hat{\\xi}_{(-)} = \\min(0, \\xi_{(-)}), \\qquad \\hat{\\xi}_{(+)} = \\max(0, \\xi_{(+)}).\n", 336 | "\\end{equation}" 337 | ] 338 | }, 339 | { 340 | "cell_type": "markdown", 341 | "metadata": {}, 342 | "source": [ 343 | "Again it should be noted that, in general, ${\\bf f}_m \\ne {\\bf f}({\\bf q}_m)$." 344 | ] 345 | }, 346 | { 347 | "cell_type": "markdown", 348 | "metadata": {}, 349 | "source": [ 350 | "We still need some way to compute the wave speeds $\\xi_{(\\pm)}$. The simplest method is to make them as large as possible, compatible with stability. This means (via the CFL condition) setting\n", 351 | "\n", 352 | "\\begin{equation}\n", 353 | " -\\xi_{(-)} = \\xi_{(+)} = \\frac{\\Delta x}{\\Delta t}\n", 354 | "\\end{equation}\n", 355 | "\n", 356 | "which implies that (as the central state is now guaranteed to include the origin, as the waves have different signs)\n", 357 | "\n", 358 | "\\begin{equation}\n", 359 | " {\\bf f}^* = \\frac{1}{2} \\left( {\\bf f}_l + {\\bf f}_r + \\frac{\\Delta x}{\\Delta t} \\left[ {\\bf q}_l - {\\bf q}_r \\right] \\right).\n", 360 | "\\end{equation}\n", 361 | "\n", 362 | "This is the *Lax-Friedrichs* flux, as [used in HyperPython](https://github.com/ketch/HyperPython). We can also easily see how the *local* Lax-Friedrichs method, [used in lesson 3 of HyperPython](http://nbviewer.ipython.org/github/ketch/HyperPython/blob/master/Lesson_03_High-resolution_methods.ipynb), comes about: simply choose\n", 363 | "\n", 364 | "\\begin{equation}\n", 365 | " -\\xi_{(-)} = \\xi_{(+)} = \\alpha = \\min \\left( \\left| \\lambda \\left( {\\bf q}_l \\right) \\right|, \\left| \\lambda \\left( {\\bf q}_r \\right) \\right| \\right)\n", 366 | "\\end{equation}\n", 367 | "\n", 368 | "to get\n", 369 | "\n", 370 | "\\begin{equation}\n", 371 | " {\\bf f}^* = \\frac{1}{2} \\left( {\\bf f}_l + {\\bf f}_r + \\alpha \\left[ {\\bf q}_l - {\\bf q}_r \\right] \\right).\n", 372 | "\\end{equation}" 373 | ] 374 | }, 375 | { 376 | "cell_type": "markdown", 377 | "metadata": {}, 378 | "source": [ 379 | "HLL type methods are straightforward to use but typically do not capture linear waves, such as the contact wave in the Euler equations, well. Extending the HLL method by including more waves is possible (see the *HLLC* method in Toro's book as an example), but rapidly increases the complexity of the solver." 380 | ] 381 | } 382 | ], 383 | "metadata": { 384 | "kernelspec": { 385 | "display_name": "Python 3", 386 | "language": "python", 387 | "name": "python3" 388 | }, 389 | "language_info": { 390 | "codemirror_mode": { 391 | "name": "ipython", 392 | "version": 3 393 | }, 394 | "file_extension": ".py", 395 | "mimetype": "text/x-python", 396 | "name": "python", 397 | "nbconvert_exporter": "python", 398 | "pygments_lexer": "ipython3", 399 | "version": "3.6.4" 400 | } 401 | }, 402 | "nbformat": 4, 403 | "nbformat_minor": 1 404 | } 405 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RiemannPython 2 | ============= 3 | 4 | Illustrating how to solve the Riemann problem for hyperbolic conservation laws. 5 | 6 | This will be a set of IPython notebooks based around David Ketcheson's [HyperPython](https://github.com/ketch/HyperPython) course, which itself derives a lot of material from Lorena Barba's [CFDPython](https://github.com/barbagroup/CFDPython). 7 | 8 | As the material is so heavily based on Ketcheson's course, the software requirements are similar: 9 | 10 | - Python >= 2.7 11 | - IPython >= 2.0 12 | - Numpy 13 | - Matplotlib 14 | - Clawpack >= 5.1 15 | 16 | To start the course, do 17 | 18 | git clone https://github.com/IanHawke/RiemannPython.git 19 | cd RiemannPython 20 | ipython notebook 21 | 22 | and click on Lesson 0. 23 | 24 | In case it isn't already clear enough, the material relies heavily on the motivation and examples of Ketcheson's [HyperPython](https://github.com/ketch/HyperPython) and Barba's [CFDPython](https://github.com/barbagroup/CFDPython) and [AeroPython](https://github.com/barbagroup/AeroPython). Before using this material you should definitely study the HyperPython course, both for motivating examples and essential python coding skills. 25 | 26 | Pull requests, comments and issues are welcome. 27 | 28 | Content provided under a Creative Commons Attribution license, CC-BY 4.0; code under MIT License. 29 | 30 | (c)2014 Ian Hawke 31 | 32 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: riemannpython 2 | dependencies: 3 | - numpy 4 | - matplotlib 5 | - jupyter 6 | - ipywidgets 7 | - scipy 8 | 9 | -------------------------------------------------------------------------------- /styles/custom.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 125 | 126 | 142 | --------------------------------------------------------------------------------