├── CITING.md ├── Guide_to_limiters.ipynb ├── LICENSE ├── Lesson_00_Python.ipynb ├── Lesson_01_Advection.ipynb ├── Lesson_02_Traffic.ipynb ├── Lesson_03_High-resolution_methods.ipynb ├── Lesson_04_Fluid_dynamics.ipynb ├── Lesson_05_PyClaw.ipynb ├── README.md ├── custom.css ├── figures ├── FV.png ├── LWR-Velocity.png ├── entropy_condition.png ├── entropy_condition_rarefaction.png ├── entropy_condition_shock.png ├── finite_volume.png ├── ghost-cell.png ├── linear_reconstruction.png ├── shock_diagram_traffic_a.png └── shock_diagram_traffic_b.png └── util ├── ianimate.py └── lxf_rk2.py /CITING.md: -------------------------------------------------------------------------------- 1 | Please use the following format to cite this material: 2 | 3 | D. I. Ketcheson. *HyperPython: An introduction to hyperbolic PDEs in Python*, 4 | http://github.com/ketch/HyperPython/, 2014. 5 | 6 | 7 | Bibtex: 8 | 9 | 10 | @misc{hyperpython, 11 | Author = {D. I. Ketcheson}, 12 | Howpublished = {http://github.com/ketch/HyperPython/}, 13 | Title = {HyperPython: An introduction to hyperbolic PDEs in Python}, 14 | Year = {2014}} 15 | -------------------------------------------------------------------------------- /Guide_to_limiters.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "###### Content provided under a Creative Commons Attribution license, CC-BY 4.0; code under MIT License. (c)2014 [David I. Ketcheson](http://davidketcheson.info)" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# An illustrated guide to limiters\n", 15 | "## Or: how to interpolate non-smooth data without creating wiggles" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "Many interesting wave phenomena -- like fluid dynamics, lasers, and water waves -- are described by nonlinear hyperbolic partial differential equations. The solutions of these problems are discontinuous. So-called **limiters** (sometimes referred to as *slope limiters* or *flux limiters* are one of the key ingredients in approximating these discontinuous solutions." 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "## Table of contents\n", 30 | "- [Motivation: interpolation and wiggles](#Interpolation-and-wiggles)\n", 31 | "- [The simplest limiter: Minmod](#The-simplest-limiter:-Minmod)\n", 32 | "- [Other TVD limiters](#TVD-limiters)\n", 33 | "- [WENO](#Higher-order-interpolation:-WENO)" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "%matplotlib inline\n", 43 | "import matplotlib.pyplot as plt\n", 44 | "import numpy as np\n", 45 | "import matplotlib\n", 46 | "matplotlib.rcParams.update({'font.size': 22})" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "import mpld3 # Skip this cell if you don't have mpld3 installed\n", 56 | "mpld3.enable_notebook() # or just go and do it now: pip install mpld3\n", 57 | " # This allows you to zoom and pan the plots." 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "## Interpolation and wiggles" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "Suppose you're given a set of data samples:" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "k = 5\n", 81 | "x=np.arange(-k+1,k)\n", 82 | "y=np.sin(x/2.)+1.\n", 83 | "width = 12\n", 84 | "size = (width,4)\n", 85 | "plt.figure(figsize=size)\n", 86 | "plt.plot(x,y,'or',markersize=10,alpha=0.5)\n", 87 | "plt.axis( (-k, k, -0.1, 2.1) );" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "Now what you really want to know is, what is the state of the system at the points halfway between your samples? And to figure that out, you need to guess at what's going on in the times in-between those samples. The simplest approximation would be to assume that the system just jumps from one value to the next somewhere in-between:" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "metadata": {}, 101 | "outputs": [], 102 | "source": [ 103 | "def piecewise_constant_interp(x,y,xx):\n", 104 | " \"From samples (x,y) generate piecewise constant function sampled at points xx.\"\n", 105 | " diff = np.abs(x.reshape(1,-1) - xx.reshape(-1,1)) # Here we use numpy broadcasting.\n", 106 | " closest = np.argmin(diff,axis=1)\n", 107 | " return y[closest]\n", 108 | "\n", 109 | "xx = np.linspace(-k+1,k-1,1000)\n", 110 | "yy = piecewise_constant_interp(x,y,xx)\n", 111 | "plt.figure(figsize=size)\n", 112 | "plt.plot(xx,yy,'-k',lw=2)\n", 113 | "plt.plot(x,y,'or',markersize=10,alpha=0.5)\n", 114 | "plt.axis( (-k, k, -0.1, 2.1) );\n", 115 | "plt.title('Piecewise-constant approximation',fontsize=20);" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "For this set of data, you don't really believe that's what's happening, do you? But our goal is to deal with systems that exhibit non-smooth (possibly discontinuous) behavior, so we need to at least admit the possibility of sudden jumps. That's why we won't simply \"connect the dots\" to get a continuous approximation.\n", 123 | "\n", 124 | "Instead, we can try to approximate the slope around each of our sample points. The simplest way to do so is using finite differences. If we let $\\sigma_i$ denote our approximation of the slope at $x_i$, then three common approximations are:\n", 125 | "\n", 126 | "- Forward difference: $\\sigma_i = \\frac{y_{i+1}-y_i}{x_{i+1}-x_i}$\n", 127 | "\n", 128 | "\n", 129 | "- Backward difference: $\\sigma_i = \\frac{y_i - y_{i-1}}{x_i - x_{i-1}}$\n", 130 | "\n", 131 | "\n", 132 | "- Centered difference: $\\sigma_i = \\frac{y_{i+1}-y_{i-1}}{x_{i+1}-x_{i-1}}$\n", 133 | "\n", 134 | "Here's what each of these approximations looks like for our data:" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "def piecewise_linear_interp(x,y,xx, fd='centered'):\n", 144 | " \"From samples (x,y) generate piecewise-linear function sampled at points xx using finite difference slopes.\"\n", 145 | " diff = np.abs(x.reshape(1,-1) - xx.reshape(-1,1))\n", 146 | " closest = np.argmin(diff,axis=1)\n", 147 | " \n", 148 | " sigma = np.zeros_like(y)\n", 149 | " if fd == 'centered':\n", 150 | " sigma[1:-1] = (y[2:]-y[:-2])/(x[2:]-x[:-2])\n", 151 | " elif fd == 'forward':\n", 152 | " sigma[:-1] = (y[1:]-y[:-1])/(x[1:]-x[:-1])\n", 153 | " elif fd == 'backward':\n", 154 | " sigma[1:] = (y[1:]-y[:-1])/(x[1:]-x[:-1])\n", 155 | " return y[closest] + sigma[closest]*(xx-x[closest])\n", 156 | "\n", 157 | "def compare_fd(x,y,xx, axis=(-4, 4, -0.1, 2.1)):\n", 158 | " fig, ax = plt.subplots(3,1,figsize=(width,8))\n", 159 | " for i, fd in enumerate( ('centered','forward','backward') ):\n", 160 | " yy = piecewise_linear_interp(x,y,xx,fd=fd)\n", 161 | " ax[i].plot(xx,yy,'-k',lw=2)\n", 162 | " ax[i].plot(x,y,'or',markersize=10,alpha=0.5)\n", 163 | " ax[i].axis( axis );\n", 164 | " ax[i].text(.5,.9,fd,\n", 165 | " horizontalalignment='center',\n", 166 | " transform=ax[i].transAxes,fontsize=20)\n", 167 | " \n", 168 | "compare_fd(x,y,xx)" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "I've used $\\sigma=0$ for the points at the edges where we don't have enough data to compute the appropriate slope." 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "## The problem: overshoots!" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "Looking closely, you can see that each of these approximations adds little jumps (called \"overshoots\") in some region where the data itself was monotone. Worse still, each of them generates negative values, whereas the original values were non-negative! If our data represent concentrations or probabilities, then we have no way to make sense of negative values.\n", 190 | "\n", 191 | "Things look even worse if we take data samples from a function that is in fact discontinuous:" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": null, 197 | "metadata": {}, 198 | "outputs": [], 199 | "source": [ 200 | "y = np.sin(x/2.)+1. + 2.*(x>0)\n", 201 | "compare_fd(x,y,xx,axis=(-4,4,-0.5,4.8))" 202 | ] 203 | }, 204 | { 205 | "cell_type": "markdown", 206 | "metadata": {}, 207 | "source": [ 208 | "Now all three approaches have large, obvious overshoots. This becomes even more problematic when actually solving a hyperbolic PDE; see [Lesson 3 of my HyperPython course](http://nbviewer.ipython.org/github/ketch/HyperPython/blob/master/Lesson_03_High-resolution_methods.ipynb) for details.\n", 209 | "\n", 210 | "Is there a better way?" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "## The simplest limiter: Minmod" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "We'd like to avoid those overshoots and ensure that monotone regions of the data give monotone interpolations. We can do that by choosing the slope $\\sigma_i$ small enough that the interpolant near $x_i$ stays bounded between the neighboring averages $(y_{i-1}+y_i)/2$ and $(y_i+y_{i+1})/2$. There's an easy way to do that: just compute the forward and backward differences (like we did above), and then use *whichever is smaller* in absolute value. If $y_i$ is an extremum, then to avoid increasing the overall range of the data we always choose $\\sigma_i=0$.\n", 225 | "\n", 226 | "Here's what that looks like:" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": null, 232 | "metadata": {}, 233 | "outputs": [], 234 | "source": [ 235 | "def pw_minmod(x,y,xx):\n", 236 | " \"From samples (x,y) generate piecewise-linear function sampled at points xx using Minmod slopes.\"\n", 237 | " diff = np.abs(x.reshape(1,-1) - xx.reshape(-1,1))\n", 238 | " closest = np.argmin(diff,axis=1)\n", 239 | " \n", 240 | " forward = np.zeros_like(y)\n", 241 | " backward = np.zeros_like(y)\n", 242 | " sigma = np.zeros_like(y)\n", 243 | " \n", 244 | " forward[:-1] = (y[1:]-y[:-1])/(x[1:]-x[:-1])\n", 245 | " backward[1:] = (y[1:]-y[:-1])/(x[1:]-x[:-1])\n", 246 | "\n", 247 | " sigma = (np.sign(forward)+np.sign(backward))/2. * np.minimum(np.abs(forward),np.abs(backward))\n", 248 | " \n", 249 | " return y[closest] + sigma[closest]*(xx-x[closest])" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": null, 255 | "metadata": {}, 256 | "outputs": [], 257 | "source": [ 258 | "yy = pw_minmod(x,y,xx)\n", 259 | "plt.figure(figsize=size)\n", 260 | "plt.plot(xx,yy,'-k',lw=2)\n", 261 | "plt.plot(x,y,'or',markersize=10,alpha=0.5)\n", 262 | "plt.axis( (-4,4,-0.5,4.8) );\n", 263 | "plt.title('Minmod approximation',fontsize=20);" 264 | ] 265 | }, 266 | { 267 | "cell_type": "markdown", 268 | "metadata": {}, 269 | "source": [ 270 | "Let's apply minmod to a monotone sequence of values, to illustrate the average-boundedness property:" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": null, 276 | "metadata": {}, 277 | "outputs": [], 278 | "source": [ 279 | "from matplotlib.patches import Rectangle\n", 280 | "\n", 281 | "y = np.exp(x/3.)\n", 282 | "yy = pw_minmod(x,y,xx)\n", 283 | "plt.figure(figsize=(width,6))\n", 284 | "plt.plot(xx,yy,'-k',lw=2)\n", 285 | "plt.plot(x,y,'or',markersize=10,alpha=0.5)\n", 286 | "plt.axis( (-4,4,-0.1,4.1) );\n", 287 | "plt.title('minmod approximation',fontsize=20);\n", 288 | "for i in range(len(y)-1):\n", 289 | " if 1<=i0)\n", 391 | "fig, ax = plt.subplots(4,1,figsize=(width,10))\n", 392 | "for i, limiter in enumerate( ('minmod', 'vanleer','superbee','MC') ):\n", 393 | " yy = pw_limited(x,y,xx,limiter=limiter)\n", 394 | " ax[i].plot(xx,yy,'-k',lw=2)\n", 395 | " ax[i].plot(x,y,'or',markersize=10,alpha=0.5)\n", 396 | " ax[i].axis( (-4,4,-0.1,4.4) );\n", 397 | " ax[i].text(.8,.2,limiter,\n", 398 | " horizontalalignment='center',\n", 399 | " transform=ax[i].transAxes,fontsize=20)" 400 | ] 401 | }, 402 | { 403 | "cell_type": "markdown", 404 | "metadata": {}, 405 | "source": [ 406 | "Compare these with [the finite difference approximations above](#The-problem:-overshoots!).\n", 407 | "\n", 408 | "If you look closely (or zoom in) you'll notice that -- except for minmod -- all the limiters *do* produce some overshoot near the discontinuity. What gives? Well, these limiters are used within a larger algorithm for solving hyperbolic PDEs, and it turns out that if the overshoots are small enough, they'll go away in a full step of the algorithm. These limiters produce \"small enough\" overshoots so that no oscillations appear in the PDE solution." 409 | ] 410 | }, 411 | { 412 | "cell_type": "markdown", 413 | "metadata": {}, 414 | "source": [ 415 | "# Interactive comparison\n", 416 | "In each region, these limiter take three data points and give back a linear interpolant. It's illuminating to compare their behavior on a single set of 3 points. Note that the interactive plot below doesn't work on nbviewer; you'll need to download and run the notebook yourself." 417 | ] 418 | }, 419 | { 420 | "cell_type": "code", 421 | "execution_count": null, 422 | "metadata": {}, 423 | "outputs": [], 424 | "source": [ 425 | "from ipywidgets import interact, FloatSlider, RadioButtons\n", 426 | "from IPython.display import display\n", 427 | "%matplotlib inline" 428 | ] 429 | }, 430 | { 431 | "cell_type": "code", 432 | "execution_count": null, 433 | "metadata": {}, 434 | "outputs": [], 435 | "source": [ 436 | "xx = np.linspace(-0.5,0.5)\n", 437 | "\n", 438 | "def compare_limiters(y1,y3):\n", 439 | " fig, ax = plt.subplots(figsize=(width,4))\n", 440 | " x = np.array((-1.,0.,1.))\n", 441 | " y = np.array((y1,0.,y3))\n", 442 | " ax.set_xlim(-1.1,1.1)\n", 443 | " ax.set_ylim(-1.1,1.1)\n", 444 | " \n", 445 | " if y1 == 0:\n", 446 | " theta = y3\n", 447 | " else:\n", 448 | " theta = y3/(-y1)\n", 449 | " forward_slope = y3\n", 450 | " backward_slope = -y1\n", 451 | " plt.fill_between(xx,xx*forward_slope,xx*backward_slope,color='k',alpha=0.2,zorder=0)\n", 452 | " for limiter in ('minmod', 'vanleer','superbee','MC'):\n", 453 | " sigma = phi(np.array(theta),limiter)*(-y1)\n", 454 | " ax.plot(xx,sigma*xx,alpha=0.5,lw=2)\n", 455 | " ax.legend( ('','minmod', 'vanleer','superbee','MC'), loc='best' )\n", 456 | " ax.plot(x,y,'ok',markersize=15,alpha=0.5)\n", 457 | "\n", 458 | " #return fig\n", 459 | "\n", 460 | "interact(compare_limiters,y1=FloatSlider(min=-1., max=1., step=0.1, value=-0.3,description='$y_{i-1}$',labelcolor='k'),#,orientation='vertical'),\n", 461 | " y3=FloatSlider(min=-1., max=1., step=0.1, value=0.8,description='$y_{i+1}$'));#,orientation='vertical'));" 462 | ] 463 | }, 464 | { 465 | "cell_type": "markdown", 466 | "metadata": {}, 467 | "source": [ 468 | "The shaded region in the plot above shows the range of slopes that would give at least 2nd-order accuracy. Play with the sliders and answer the following questions:\n", 469 | "\n", 470 | "- Which limiter usually chooses the flattest approximation? Does it always?\n", 471 | "- Which limiter usually chooses the steepest approximation? Does it always?\n", 472 | "- In which situations do all the limiters give the same slope? Why?" 473 | ] 474 | }, 475 | { 476 | "cell_type": "markdown", 477 | "metadata": {}, 478 | "source": [ 479 | "# Higher-order interpolation: WENO\n", 480 | "If we want to get higher-order accuracy (in smooth regions), then we have to give up the TVD property -- at least, in the sense defined above. The most common approach for higher order non-oscillatory piecewise interpolation is known as weighted essentially non-oscillatory (WENO) interpolation.\n", 481 | "\n", 482 | "WENO is a very effective technique for interpolating or reconstructing functions that contain discontinuities without introducing oscillations. We'll focus on 5th-order WENO interpolation, which is the most commonly used.\n", 483 | "\n", 484 | "Let's generate some function values to interpolate:" 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": null, 490 | "metadata": {}, 491 | "outputs": [], 492 | "source": [ 493 | "# Note: uses PyWENO v. 0.11.2\n", 494 | "import sympy\n", 495 | "from pyweno import symbolic" 496 | ] 497 | }, 498 | { 499 | "cell_type": "code", 500 | "execution_count": null, 501 | "metadata": {}, 502 | "outputs": [], 503 | "source": [ 504 | "import mpld3 # Skip this cell if you don't have mpld3 installed\n", 505 | "mpld3.enable_notebook() # or just go and do it now: pip install mpld3\n", 506 | " # This allows you to zoom and pan the plots.\n", 507 | "matplotlib.rcParams.update({'font.size': 18})\n", 508 | "colors = 'brg'" 509 | ] 510 | }, 511 | { 512 | "cell_type": "code", 513 | "execution_count": null, 514 | "metadata": {}, 515 | "outputs": [], 516 | "source": [ 517 | "weno_order = 5 # must be odd\n", 518 | "k = (weno_order+1)/2\n", 519 | "\n", 520 | "size = (width,4)\n", 521 | "plt.figure(figsize=size)\n", 522 | "x=np.arange(-k+1,k)\n", 523 | "y=np.random.rand(len(x))\n", 524 | "#y = np.array((1.,1.,1.,0.,0.))\n", 525 | "plt.plot(x,y,'ok')\n", 526 | "plt.axis((-(k-.5),k-.5,-0.5,2.1));" 527 | ] 528 | }, 529 | { 530 | "cell_type": "code", 531 | "execution_count": null, 532 | "metadata": {}, 533 | "outputs": [], 534 | "source": [ 535 | "def stencil_interpolant(x,y,n,offset):\n", 536 | " \"\"\"Return the polynomial interpolant (of degree n-1) \n", 537 | " through the points (x_j,y_j) for offset <= j <= offset+n-1.\n", 538 | " \"\"\"\n", 539 | " return np.poly1d(np.polyfit(x[offset:offset+n],y[offset:offset+n],n-1))\n", 540 | "\n", 541 | "def plot_interpolants(x,y,interpolants,axis=None,color='kbrg'):\n", 542 | " if axis is None:\n", 543 | " fig, axis = plt.subplots(figsize=size)\n", 544 | " xc = np.linspace(-0.5,0.5)\n", 545 | " xx = np.linspace(-(k-1),k-1)\n", 546 | " for i, interpolant in enumerate(interpolants):\n", 547 | " axis.plot(xx,interpolant(xx),'-'+color[i])\n", 548 | " axis.plot(xc,interpolant(xc),'-'+color[i],linewidth=5,alpha=0.5)\n", 549 | " axis.plot(x,y,'ok')\n", 550 | " axis.axis((-(k-.5),k-.5,-0.5,2.1));" 551 | ] 552 | }, 553 | { 554 | "cell_type": "markdown", 555 | "metadata": {}, 556 | "source": [ 557 | "Ordinary polynomial interpolation yields an oscillatory polynomial that also exceeds the bounds of the data:" 558 | ] 559 | }, 560 | { 561 | "cell_type": "markdown", 562 | "metadata": {}, 563 | "source": [ 564 | "For application to hyperbolic conservation laws, our main interest is in getting values of the function at the half-integer points (interfaces). Let's suppose we're trying to interpolate around $x=0$, at $x=\\pm 1/2$. Instead of using all 5 points, we could just use three points, which might give us a less oscillatory interpolant, at least in that interval. Using the 5 points we're given, there are three natural choices of interpolation stencil: the leftmost three, the middle three, or the rightmost three. Let's see what each of these quadratic interpolants looks like." 565 | ] 566 | }, 567 | { 568 | "cell_type": "code", 569 | "execution_count": null, 570 | "metadata": {}, 571 | "outputs": [], 572 | "source": [ 573 | "p_opt = stencil_interpolant(x,y,5,0)\n", 574 | "plot_interpolants(x,y,[p_opt])\n", 575 | "plt.title('Quartic interpolant');" 576 | ] 577 | }, 578 | { 579 | "cell_type": "code", 580 | "execution_count": null, 581 | "metadata": {}, 582 | "outputs": [], 583 | "source": [ 584 | "fig, ax = plt.subplots(3,1,figsize=(width,10))\n", 585 | "names = ['left','right','center']\n", 586 | "p = []\n", 587 | "for i in range(int(k)):\n", 588 | " p.append(stencil_interpolant(x,y,k,i))\n", 589 | " plot_interpolants(x,y,[p[i]],axis=ax[i],color=[colors[i]])\n", 590 | " ax[i].set_title(names[i]+' interpolant')" 591 | ] 592 | }, 593 | { 594 | "cell_type": "markdown", 595 | "metadata": {}, 596 | "source": [ 597 | "Here are all three quadratic interpolants together with the quartic interpolant for comparison:" 598 | ] 599 | }, 600 | { 601 | "cell_type": "code", 602 | "execution_count": null, 603 | "metadata": {}, 604 | "outputs": [], 605 | "source": [ 606 | "plot_interpolants(x,y,p+[p_opt],color='brgk')" 607 | ] 608 | }, 609 | { 610 | "cell_type": "markdown", 611 | "metadata": {}, 612 | "source": [ 613 | "The quadratic interpolants look less oscillatory, but they're also less accurate. The WENO idea is to use the high-order interpolant (with all 5 points) if the data is smooth, but to use one of the lower-order interpolants (or a combination of them) if the data is not smooth. This is achieved by computing point values of the interpolant as weighted averages of the point values of the candidate polynomials, e.g.\n", 614 | "\n", 615 | "$$y_{x_{i-1/2}} = w_{1,-1/2} p_\\text{left}(x_{i-1/2}) + w_{2,-1/2} p_\\text{center}(x_{i-1/2}) + w_{3,-1/2} p_\\text{right}(x_{i-1/2}).$$\n", 616 | "\n", 617 | "Of course, there is some particular set of weights that gives the quartic interpolant:\n", 618 | "\n", 619 | "$$y_{x_{i-1/2}} = \\gamma_{1,-1/2} p_\\text{left}(x_{i-1/2}) + \\gamma_{2,-1/2} p_\\text{center}(x_{i-1/2}) + \\gamma_{3,-1/2} p_\\text{right}(x_{i-1/2}).$$\n", 620 | "\n", 621 | "We will want to have $w_{j,-1/2} \\approx \\gamma_{j,-1/2}$ for smooth data." 622 | ] 623 | }, 624 | { 625 | "cell_type": "code", 626 | "execution_count": null, 627 | "metadata": {}, 628 | "outputs": [], 629 | "source": [ 630 | "def compute_opt_weights(k,xi):\n", 631 | " \"\"\"\n", 632 | " Get the optimal weights (gamma) at points xi.\n", 633 | " \"\"\"\n", 634 | " if not hasattr(xi,'__iter__'): xi = [xi]\n", 635 | " opt_weights = symbolic.optimal_weights(k,xi)\n", 636 | " gamma = {}\n", 637 | " for i, xi_val in enumerate(xi):\n", 638 | " gamma[xi_val] = np.empty(k)\n", 639 | " for j in range(k):\n", 640 | " gamma[xi_val][j] = opt_weights[0][(i,j)]\n", 641 | "\n", 642 | " return gamma\n", 643 | "\n", 644 | "gamma = compute_opt_weights(k,(-1,0.5,1))\n", 645 | "\n", 646 | "print \"$\\gamma_{j,-1/2}$:\", gamma[-1]\n", 647 | "print \"$\\gamma_{j,+1/2}$:\", gamma[1]" 648 | ] 649 | }, 650 | { 651 | "cell_type": "markdown", 652 | "metadata": {}, 653 | "source": [ 654 | "How does one determine if a polynomial is non-oscillatory? There are several ways proposed in the literature, but the original and most widely used is the weighted Sobolev norm:\n", 655 | "\n", 656 | "$$\\beta = \\sum_{l=1}^k \\Delta x^{2l-1} \\int_{x_{i-1/2}}^{x_{i+1/2}} \\left(\\frac{d^l}{dx^l}p(x)\\right)^2 dx.$$\n", 657 | "\n", 658 | "Put simply, $\\beta$ is a scaled sum of the square $L^2$ norms of all the derivatives of the polynomial over the interval where it will be used. The scaling is chosen to make the \"smoothness indicator\" $\\beta$ independent of the choice of $\\Delta x$ (note that $\\Delta x = 1$ in our example data).\n", 659 | "\n", 660 | "As each of the interpolants above is a linear function of the values $y_i$, the smoothness indicators are quadratic functions of the $y_i$ and can be expressed in the generic form\n", 661 | "\n", 662 | "$$\\beta = \\sum_{m=-2}^{2} \\sum_{n=-2}^{m}\n", 663 | " C_{m,n} y_{i-k+m} y_{i-k+n}$$\n", 664 | "\n", 665 | "Of course, the coefficients $C_{m,n}$ will be different for each of the candidate polynomials $p_\\text{left},p_\\text{center},p_\\text{right}$. We can use the Python package PyWeno to automatically compute these coefficients and then apply them to our data." 666 | ] 667 | }, 668 | { 669 | "cell_type": "code", 670 | "execution_count": null, 671 | "metadata": {}, 672 | "outputs": [], 673 | "source": [ 674 | "def compute_smoothness_indicators(y,k):\n", 675 | " C = symbolic.jiang_shu_smoothness_coefficients(k)\n", 676 | " beta = np.zeros((k,1))\n", 677 | " for m in range(k):\n", 678 | " for n in range(m+1):\n", 679 | " for r in range(len(beta)):\n", 680 | " beta[r] = beta[r] + C[(r,n,m)] * y[r+m] * y[r+n]\n", 681 | " return beta\n", 682 | "\n", 683 | "beta = compute_smoothness_indicators(y,k)\n", 684 | "print beta" 685 | ] 686 | }, 687 | { 688 | "cell_type": "markdown", 689 | "metadata": {}, 690 | "source": [ 691 | "Next we use these smoothness indicators to determine a weighting for the candidate polynomials. Observe that a large smoothness indicator means a polynomial has large derivatives, so we will want to give it less weight (perhaps they should be called non-smoothness indicators). \n", 692 | "\n", 693 | "$$\\tilde{w}_j = \\frac{\\gamma_j}{(\\epsilon + \\beta_j)^2}$$\n", 694 | "\n", 695 | "Here $\\epsilon$ is a small number used to avoid division by zero. We also normalize the weights so that they sum to unity:\n", 696 | "\n", 697 | "$$w_j = \\frac{\\tilde{w}_j}{\\sum_j\\tilde{w}_j}$$" 698 | ] 699 | }, 700 | { 701 | "cell_type": "code", 702 | "execution_count": null, 703 | "metadata": {}, 704 | "outputs": [], 705 | "source": [ 706 | "def compute_weights(gamma, beta, epsilon=1.e-6):\n", 707 | " k = len(beta)\n", 708 | " w = np.empty(k)\n", 709 | " for j in range(k):\n", 710 | " w[j] = gamma[j]/(epsilon+beta[j])**2\n", 711 | " \n", 712 | " wsum = np.sum(w)\n", 713 | " return w/wsum" 714 | ] 715 | }, 716 | { 717 | "cell_type": "code", 718 | "execution_count": null, 719 | "metadata": {}, 720 | "outputs": [], 721 | "source": [ 722 | "q = {}\n", 723 | "for xi in (-1,1):\n", 724 | " w = compute_weights(gamma[xi],beta)\n", 725 | " q[xi] = w[0]*p[0](xi/2.) + w[1]*p[1](xi/2.) + w[2]*p[2](xi/2.)" 726 | ] 727 | }, 728 | { 729 | "cell_type": "markdown", 730 | "metadata": {}, 731 | "source": [ 732 | "Here are the final reconstructed values given by WENO (indicated by the large grey circles):" 733 | ] 734 | }, 735 | { 736 | "cell_type": "code", 737 | "execution_count": null, 738 | "metadata": {}, 739 | "outputs": [], 740 | "source": [ 741 | "plot_interpolants(x,y,p+[p_opt],color=['b','r','g','k'])\n", 742 | "plt.hold(True)\n", 743 | "plt.plot(-0.5,q[-1],'ok',alpha=0.3,markersize=15)\n", 744 | "plt.plot(0.5,q[1],'ok',alpha=0.3,markersize=15)\n", 745 | "\n", 746 | "plt.axis((-(k-.5),k-.5,-0.5,2.1));" 747 | ] 748 | }, 749 | { 750 | "cell_type": "markdown", 751 | "metadata": {}, 752 | "source": [ 753 | "Here's some code to plot everything for some given $(x,y)$ values." 754 | ] 755 | }, 756 | { 757 | "cell_type": "code", 758 | "execution_count": null, 759 | "metadata": {}, 760 | "outputs": [], 761 | "source": [ 762 | "styles = { 'left' : 'b', 'center' : 'r', 'right' : 'g'}\n", 763 | "size = (16,4); fs = 20\n", 764 | "def WENO_visualization(x,y,xi=(-1,1)):\n", 765 | " \"\"\"\n", 766 | " (x,y): data to interpolate\n", 767 | " xi: points at which to evaluate interpolant (w.r.t. (-1,1) reference interval)\n", 768 | " \"\"\"\n", 769 | " xx = np.linspace(np.min(x),np.max(x))\n", 770 | " color=['b','r','g']\n", 771 | " plt.figure(figsize=size)\n", 772 | " plt.hold(True)\n", 773 | " ax1 = plt.subplot2grid((1,8), (0,0), colspan=6)\n", 774 | " ax2 = plt.subplot2grid((1,8), (0,6))\n", 775 | " ax3 = plt.subplot2grid((1,8), (0,7))\n", 776 | "\n", 777 | " K = len(y)\n", 778 | " k = (K+1)/2\n", 779 | " assert len(x)==K\n", 780 | " p_opt=np.poly1d(np.polyfit(x,y,K-1))\n", 781 | " p = {}\n", 782 | " for name, offset in zip(('left','right','center'),range(k)):\n", 783 | " p[name] = stencil_interpolant(x,y,k,offset)\n", 784 | " \n", 785 | " gamma = compute_opt_weights(k,xi)\n", 786 | " beta = compute_smoothness_indicators(y,k)\n", 787 | " \n", 788 | " w = {}; q = {}\n", 789 | " for loc in xi:\n", 790 | " w[loc] = compute_weights(gamma[loc],beta)\n", 791 | " q[loc] = w[loc][0]*p['left'](loc/2.) \\\n", 792 | " + w[loc][1]*p['center'](loc/2.) \\\n", 793 | " + w[loc][2]*p['right'](loc/2.)\n", 794 | " ax2.bar(range(3),w[-1],color=color,align='center'); \n", 795 | " ax2.set_title(r'$w_{i-1/2}$',fontsize=fs)\n", 796 | " ax3.bar(range(3),w[1],color=color,align='center')\n", 797 | " ax3.set_title(r'$w_{i+1/2}$',fontsize=fs)\n", 798 | " for ax in (ax2,ax3):\n", 799 | " ax.set_xticks(range(3)); \n", 800 | " ax.set_xticklabels(('left','center','right'))\n", 801 | " ax.set_ylim(0,1); ax.set_yticks((0,1))\n", 802 | "\n", 803 | " for name, interpolant in p.iteritems():\n", 804 | " ax1.plot(xx,interpolant(xx),styles[name])\n", 805 | " xc = np.linspace(-0.5,0.5)\n", 806 | " ax1.plot(xc,interpolant(xc),styles[name],linewidth=5)\n", 807 | " ax1.plot(x,y,'ok')\n", 808 | " ax1.hold(True)\n", 809 | " ax1.plot(xx,p_opt(xx),'-k',x,y,'ok',linewidth=2)\n", 810 | " for loc in xi:\n", 811 | " ax1.plot(loc/2.,q[loc],'ok', alpha=0.3,markersize=15)\n", 812 | " ax1.plot(loc/2.,q[loc],'ok',alpha=0.3,markersize=15)\n", 813 | " \n", 814 | " ax1.axis((-(k-0.8),k-0.8,-0.5,2.1));" 815 | ] 816 | }, 817 | { 818 | "cell_type": "code", 819 | "execution_count": null, 820 | "metadata": {}, 821 | "outputs": [], 822 | "source": [ 823 | "%matplotlib inline\n", 824 | "y=np.random.rand(len(x))\n", 825 | "WENO_visualization(x,y)" 826 | ] 827 | }, 828 | { 829 | "cell_type": "markdown", 830 | "metadata": {}, 831 | "source": [ 832 | "The bar charts on the right show the relative weight given to each of the quadratic interpolants when computing the left and right interpolated values.\n", 833 | "Try running the box above a few times, or insert your own $y$ values. What happens if you use a step function for $y$? Let's see:" 834 | ] 835 | }, 836 | { 837 | "cell_type": "code", 838 | "execution_count": null, 839 | "metadata": {}, 840 | "outputs": [], 841 | "source": [ 842 | "y=np.array( (1,1,1,0,0) )\n", 843 | "WENO_visualization(x,y)" 844 | ] 845 | }, 846 | { 847 | "cell_type": "markdown", 848 | "metadata": {}, 849 | "source": [ 850 | "For a perfect step function, WENO picks the flat interpolant, just like any TVD limiter would!" 851 | ] 852 | }, 853 | { 854 | "cell_type": "markdown", 855 | "metadata": {}, 856 | "source": [ 857 | "## Comparison of several limiters for advection\n", 858 | "In practice, all of these limiters are used as components of numerical solvers for hyperbolic PDEs. The simplest hyperbolic PDE is the advection equation:\n", 859 | "\n", 860 | "$$ q_t + a q_x = 0.$$\n", 861 | "\n", 862 | "The solution $q$ simply translates at velocity $a$; if you're not familiar with this, take a look at my [HyperPython lesson on advection](http://nbviewer.ipython.org/github/ketch/HyperPython/blob/master/Lesson_01_Advection.ipynb) and then the [lesson on high-resolution methods](http://nbviewer.ipython.org/github/ketch/HyperPython/blob/master/Lesson_03_High-resolution_methods.ipynb).\n", 863 | "\n", 864 | "The cells below solve the advection equation using several of the limiters we've discussed. To run this part, you need to [install PyClaw](http://www.clawpack.org/installing.html) and Visclaw, which can be most easily accomplished via\n", 865 | "\n", 866 | " pip install clawpack" 867 | ] 868 | }, 869 | { 870 | "cell_type": "code", 871 | "execution_count": null, 872 | "metadata": {}, 873 | "outputs": [], 874 | "source": [ 875 | "from clawpack import pyclaw\n", 876 | "from clawpack import riemann\n", 877 | "import matplotlib\n", 878 | "from matplotlib import animation\n", 879 | "from clawpack.visclaw.JSAnimation import IPython_display\n", 880 | "\n", 881 | "def setup(scheme='minmod',cfl_max=0.9,IC='gauss_square',mx=100):\n", 882 | " if 'weno' in scheme:\n", 883 | " solver = pyclaw.SharpClawSolver1D(riemann.advection_1D)\n", 884 | " else:\n", 885 | " solver = pyclaw.ClawSolver1D(riemann.advection_1D)\n", 886 | "\n", 887 | " solver.bc_lower[0] = pyclaw.BC.periodic\n", 888 | " solver.bc_upper[0] = pyclaw.BC.periodic\n", 889 | " \n", 890 | " if scheme in ('minmod','superbee','MC','vanleer'):\n", 891 | " solver.limiters = getattr(pyclaw.limiters.tvd,scheme)\n", 892 | " #elif scheme == 'CT':\n", 893 | " #solver.limiters = pyclaw.limiters.tvd.cada_torrilhon_limiter\n", 894 | " elif scheme == 'Lax-Wendroff':\n", 895 | " solver.limiters = 0\n", 896 | " elif scheme == 'first-order':\n", 897 | " solver.order = 1\n", 898 | " elif 'weno' in scheme:\n", 899 | " solver.weno_order = int(scheme[4:]) #weno5, weno7, ...\n", 900 | " else:\n", 901 | " raise Exception('Unrecognized limiter')\n", 902 | "\n", 903 | " solver.cfl_max = cfl_max\n", 904 | " solver.cfl_desired = cfl_max*0.9\n", 905 | "\n", 906 | " x = pyclaw.Dimension(0.0,1.0,mx)\n", 907 | " domain = pyclaw.Domain(x)\n", 908 | " num_eqn = 1\n", 909 | " state = pyclaw.State(domain,num_eqn)\n", 910 | " state.problem_data['u']=1.\n", 911 | "\n", 912 | " grid = state.grid\n", 913 | " xc = grid.x.centers\n", 914 | " if IC=='gauss_square':\n", 915 | " beta=200.; x0=0.3\n", 916 | " state.q[0,:] = np.exp(-beta * (xc-x0)**2) + (xc>0.6)*(xc<0.8)\n", 917 | " elif IC=='wavepacket':\n", 918 | " beta=100.; x0=0.5\n", 919 | " state.q[0,:] = np.exp(-beta * (xc-x0)**2) * np.sin(80.*xc)\n", 920 | " else:\n", 921 | " raise Exception('Unrecognized initial condition.')\n", 922 | "\n", 923 | " claw = pyclaw.Controller()\n", 924 | " claw.solution = pyclaw.Solution(state,domain)\n", 925 | " claw.solver = solver\n", 926 | " claw.keep_copy = True\n", 927 | " claw.output_format = None\n", 928 | "\n", 929 | " claw.tfinal =10.0\n", 930 | " return claw" 931 | ] 932 | }, 933 | { 934 | "cell_type": "code", 935 | "execution_count": null, 936 | "metadata": {}, 937 | "outputs": [], 938 | "source": [ 939 | "#This cell may take a few seconds to run\n", 940 | "results = []\n", 941 | "schemes = ('first-order','Lax-Wendroff','minmod','superbee','MC','vanleer','weno5','weno7','weno9')\n", 942 | "for scheme in schemes:\n", 943 | " claw = setup(scheme=scheme)\n", 944 | " claw.verbosity = 0\n", 945 | " claw.run()\n", 946 | " results.append(claw.frames)\n", 947 | " \n", 948 | "def animate(results,ymin=-0.1):\n", 949 | " fig = plt.figure(figsize=(width,8))\n", 950 | "\n", 951 | " N = len(results)\n", 952 | " n = int(np.ceil(np.sqrt(N)))\n", 953 | " axes = []\n", 954 | " gs1 = matplotlib.gridspec.GridSpec(n, n)\n", 955 | " gs1.update(wspace=0.,hspace=0.)\n", 956 | "\n", 957 | " for i in range(n):\n", 958 | " for j in range(n):\n", 959 | " k = n*i + j\n", 960 | " if k0:\n", 965 | " axes[-1].yaxis.set_ticklabels(())\n", 966 | "\n", 967 | " lines = [0]*len(schemes)\n", 968 | " for i in range(len(lines)):\n", 969 | " lines[i], = axes[i].plot([], [], lw=2)\n", 970 | "\n", 971 | " xc = results[0][0].p_centers[0]\n", 972 | "\n", 973 | " for i,ax in enumerate(axes):\n", 974 | " ax.set_xlim(0,1); ax.set_ylim(ymin,1.3)\n", 975 | " #ax.grid()\n", 976 | " ax.set_title(schemes[i], x = 0.5, y=0.85 )\n", 977 | " ax.plot(xc,results[i][0].q[0,:],color='k',alpha=0.3)\n", 978 | " \n", 979 | "\n", 980 | " def fplot(frame_number):\n", 981 | " fig.suptitle('Solution after %s cycles' % frame_number, fontsize=20)\n", 982 | " for i, line in enumerate(lines):\n", 983 | " line.set_data(xc,results[i][frame_number].q[0,:])\n", 984 | " return lines,\n", 985 | "\n", 986 | " return matplotlib.animation.FuncAnimation(fig, fplot, frames=len(claw.frames), interval=30)\n", 987 | "\n", 988 | "animate(results)" 989 | ] 990 | }, 991 | { 992 | "cell_type": "markdown", 993 | "metadata": {}, 994 | "source": [ 995 | "In the plot above, the solution advects across the full domain once between each frame of the animation (the boundary is periodic). By stepping through the animation, you can see how each limiter modifies the shape of the solution over time. The Lax-Wendroff method is based on a centered-difference approximation with no limiting; notice that it creates oscillations and is also less accurate than the limiter-based methods. For the advection equation, oscillations and overshoots are not a serious problem, but in the context of fluid dynamics or water wave simulations, they can be catastrophic." 996 | ] 997 | }, 998 | { 999 | "cell_type": "code", 1000 | "execution_count": null, 1001 | "metadata": {}, 1002 | "outputs": [], 1003 | "source": [] 1004 | } 1005 | ], 1006 | "metadata": { 1007 | "kernelspec": { 1008 | "display_name": "3.9", 1009 | "language": "python", 1010 | "name": "3.9" 1011 | }, 1012 | "language_info": { 1013 | "codemirror_mode": { 1014 | "name": "ipython", 1015 | "version": 3 1016 | }, 1017 | "file_extension": ".py", 1018 | "mimetype": "text/x-python", 1019 | "name": "python", 1020 | "nbconvert_exporter": "python", 1021 | "pygments_lexer": "ipython3", 1022 | "version": "3.9.10" 1023 | }, 1024 | "toc": { 1025 | "base_numbering": 1, 1026 | "nav_menu": {}, 1027 | "number_sections": true, 1028 | "sideBar": true, 1029 | "skip_h1_title": false, 1030 | "title_cell": "Table of Contents", 1031 | "title_sidebar": "Contents", 1032 | "toc_cell": false, 1033 | "toc_position": {}, 1034 | "toc_section_display": true, 1035 | "toc_window_display": false 1036 | } 1037 | }, 1038 | "nbformat": 4, 1039 | "nbformat_minor": 1 1040 | } 1041 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Content provided under a Creative Commons Attribution license, CC-BY 4.0; code under MIT License. (c)2014 David I. Ketcheson 2 | 3 | You are welcome to use and adapt these materials provided that you give attribution. 4 | -------------------------------------------------------------------------------- /Lesson_00_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:ad0d0df1d0c10070519526eea1a39d399e365a0e3712671f346a064ce27fc1af" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "heading", 13 | "level": 6, 14 | "metadata": {}, 15 | "source": [ 16 | "Content provided under a Creative Commons Attribution license, CC-BY 4.0; code under MIT License. (c)2014 [David I. Ketcheson](http://davidketcheson.info)" 17 | ] 18 | }, 19 | { 20 | "cell_type": "heading", 21 | "level": 5, 22 | "metadata": {}, 23 | "source": [ 24 | "version 0.1 - May 2014" 25 | ] 26 | }, 27 | { 28 | "cell_type": "heading", 29 | "level": 1, 30 | "metadata": {}, 31 | "source": [ 32 | "A Brief Introduction to Python" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "This lesson is a preamble to the HyperPython short course, in which you will learn some basic theory of hyperbolic conservation laws and how to solve them numerically.\n", 40 | "\n", 41 | "In this lesson, we'll learn about the [Python programming language](https://www.python.org/) and the [IPython notebook](http://ipython.org/notebook.html), as well as the Python packages [numpy](http://www.numpy.org/) and [matplotlib](http://matplotlib.org/). If you're already familiar with these, you may skip to [Lesson 1](Lesson_01_Advection.ipynb), where the real fun begins.\n", 42 | "\n", 43 | "This isn't a very thorough introduction -- we'll just learn the things that are essential for the course. Some resources for learning more about Python and its use in scientific computing are linked [at the end of this notebook](#Further-resources)." 44 | ] 45 | }, 46 | { 47 | "cell_type": "heading", 48 | "level": 2, 49 | "metadata": {}, 50 | "source": [ 51 | "Prerequisites" 52 | ] 53 | }, 54 | { 55 | "cell_type": "heading", 56 | "level": 3, 57 | "metadata": {}, 58 | "source": [ 59 | "Knowledge" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "To get through the course, you need not have used Python or studied hyperbolic PDEs before. But it is assumed that you have programmed in *some* language and that you are familiar with basic differential equations and numerical methods." 67 | ] 68 | }, 69 | { 70 | "cell_type": "heading", 71 | "level": 3, 72 | "metadata": {}, 73 | "source": [ 74 | "Software" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "To run the code in this course, you'll need an installation of Python, numpy, matplotlib, and Clawpack. The easiest way to get them all is to use [SageMathCloud](http://cloud.sagemath.org) -- just create a free account, start a new project, open a terminal, and type\n", 82 | " \n", 83 | " git clone git@github.com:ketch/HyperPython.git\n", 84 | " \n", 85 | "Open this notebook there and you're off.\n", 86 | "\n", 87 | "You can also use [Wakari](http://wakari.io), or install everything locally on your own machine. For local installation, [Anaconda](https://store.continuum.io/cshop/anaconda/) is convenient, or you can just use pip. All of these are free." 88 | ] 89 | }, 90 | { 91 | "cell_type": "heading", 92 | "level": 2, 93 | "metadata": {}, 94 | "source": [ 95 | "Python and the IPython Notebook" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "The code for this course is written in Python, which is a programming language designed to promote code that is easy to read and write. Python has become one of the most important languages in scientific computing. It is high-level like MATLAB, but unlike MATLAB it is free and is intended as a general-purpose language." 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "[IPython](http://www.ipython.org) is a collection of tools for interactive programming in Python. Most importantly for us, IPython includes an interactive shell and a browser-based notebook. The notebook (which you are using now) allows you to run Python code in your web browser; just click on a cell with code and hit shift+enter. Try it with the cell below." 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "collapsed": false, 115 | "input": [ 116 | "print \"The best conservation laws are hyperbolic!\"" 117 | ], 118 | "language": "python", 119 | "metadata": {}, 120 | "outputs": [] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "Try changing the message in the box and running it again.\n", 127 | "\n", 128 | "The box of code above is called a *cell*. You can use the menu and toolbar near the top of your browser to insert or delete cells or save the notebook. The text you're reading is also in a cell, which you can edit (just double-click on this text). You'll also see math in these cells; the math is written using LaTeX. For example:\n", 129 | "\n", 130 | "$$e^{i \\pi} = -1.$$\n", 131 | "\n", 132 | "The menu bar above also has buttons to run cells or to stop code running." 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "You can find a huge collection of interesting IPython notebooks on a wide range of topics [here](https://github.com/ipython/ipython/wiki/A-gallery-of-interesting-IPython-Notebooks#table-of-contents). Since notebooks combine text, mathematics, and executable code, they're a great way to learn." 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "Running the code cell below will reformat the notebook to make things a bit easier to read, and will also help you keep track of running code later on. The same cell appears at the top of each of the later lessons in the course." 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "collapsed": false, 152 | "input": [ 153 | "from IPython.core.display import HTML\n", 154 | "css_file = './custom.css'\n", 155 | "HTML(open(css_file, \"r\").read())" 156 | ], 157 | "language": "python", 158 | "metadata": {}, 159 | "outputs": [] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "metadata": {}, 164 | "source": [ 165 | "Now we'll get into the meat of Python. For this first lesson, I recommend that you open an IPython session in a terminal and type the code yourself, rather than just executing the code here in the notebook." 166 | ] 167 | }, 168 | { 169 | "cell_type": "heading", 170 | "level": 3, 171 | "metadata": {}, 172 | "source": [ 173 | "Python basics" 174 | ] 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "metadata": {}, 179 | "source": [ 180 | "Python has many built-in functions, like `print`, but most functions are inside *modules*, which aren't loaded unless you `import` them:" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "collapsed": false, 186 | "input": [ 187 | "from math import sqrt\n", 188 | "print sqrt(2)" 189 | ], 190 | "language": "python", 191 | "metadata": {}, 192 | "outputs": [] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": {}, 197 | "source": [ 198 | "We can also import a whole module:" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "collapsed": false, 204 | "input": [ 205 | "import math\n", 206 | "print math.sqrt(2)" 207 | ], 208 | "language": "python", 209 | "metadata": {}, 210 | "outputs": [] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "metadata": {}, 215 | "source": [ 216 | "What else is in the `math` module? You can find out using IPython's tab completion. Just put your cursor at the end of the line below and hit `tab`:" 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "collapsed": false, 222 | "input": [ 223 | "math." 224 | ], 225 | "language": "python", 226 | "metadata": {}, 227 | "outputs": [] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "What does `math.atan2` do, for instance? You can find out with IPython's magic help function, invoked by using the question mark after a function name. When you're done reading the help, you can close the pager below by clicking on the \"x\"." 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "collapsed": false, 239 | "input": [ 240 | "math.atan2?" 241 | ], 242 | "language": "python", 243 | "metadata": {}, 244 | "outputs": [] 245 | }, 246 | { 247 | "cell_type": "heading", 248 | "level": 3, 249 | "metadata": {}, 250 | "source": [ 251 | "Lists" 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "metadata": {}, 257 | "source": [ 258 | "A *list* is an ordered collection of values or objects." 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "collapsed": false, 264 | "input": [ 265 | "x = [1,2,3,4]" 266 | ], 267 | "language": "python", 268 | "metadata": {}, 269 | "outputs": [] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "metadata": {}, 274 | "source": [ 275 | "You can ask for one or more items from a list as follows:" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "collapsed": false, 281 | "input": [ 282 | "print x[0]" 283 | ], 284 | "language": "python", 285 | "metadata": {}, 286 | "outputs": [] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "collapsed": false, 291 | "input": [ 292 | "print x[1:3]" 293 | ], 294 | "language": "python", 295 | "metadata": {}, 296 | "outputs": [] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "metadata": {}, 301 | "source": [ 302 | "Two important things to remember:\n", 303 | "1. Python lists are indexed starting from zero.\n", 304 | "2. When you ask for a range of things, you don't get the last one." 305 | ] 306 | }, 307 | { 308 | "cell_type": "markdown", 309 | "metadata": {}, 310 | "source": [ 311 | "You can quickly make lists of numbers using the `range` function:" 312 | ] 313 | }, 314 | { 315 | "cell_type": "code", 316 | "collapsed": false, 317 | "input": [ 318 | "y = range(1,5)\n", 319 | "print y" 320 | ], 321 | "language": "python", 322 | "metadata": {}, 323 | "outputs": [] 324 | }, 325 | { 326 | "cell_type": "heading", 327 | "level": 3, 328 | "metadata": {}, 329 | "source": [ 330 | "Loops" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "collapsed": false, 336 | "input": [ 337 | "for i in range(5):\n", 338 | " print i\n", 339 | " print 'in the loop'\n", 340 | " \n", 341 | "print 'finished'" 342 | ], 343 | "language": "python", 344 | "metadata": {}, 345 | "outputs": [] 346 | }, 347 | { 348 | "cell_type": "markdown", 349 | "metadata": {}, 350 | "source": [ 351 | "A *for loop* just iterates over a list. Notice how the contents of the loop are indented. Python knows the loop has ended when it finds a line that is not indented. \n", 352 | "\n", 353 | "To nest loops, indent again:" 354 | ] 355 | }, 356 | { 357 | "cell_type": "code", 358 | "collapsed": false, 359 | "input": [ 360 | "for i in range(5):\n", 361 | " print i\n", 362 | " for j in range(2):\n", 363 | " print 'in the inner loop'\n", 364 | " \n", 365 | "print 'finished'" 366 | ], 367 | "language": "python", 368 | "metadata": {}, 369 | "outputs": [] 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "metadata": {}, 374 | "source": [ 375 | "Don't forget the colon!" 376 | ] 377 | }, 378 | { 379 | "cell_type": "heading", 380 | "level": 3, 381 | "metadata": {}, 382 | "source": [ 383 | "Functions" 384 | ] 385 | }, 386 | { 387 | "cell_type": "code", 388 | "collapsed": false, 389 | "input": [ 390 | "def square(x):\n", 391 | " return x**2\n", 392 | "\n", 393 | "print square(5)" 394 | ], 395 | "language": "python", 396 | "metadata": {}, 397 | "outputs": [] 398 | }, 399 | { 400 | "cell_type": "markdown", 401 | "metadata": {}, 402 | "source": [ 403 | "Notice how the contents of the function are indented (just like the for loop). Python knows the function has ended when it finds a line that is not indented." 404 | ] 405 | }, 406 | { 407 | "cell_type": "markdown", 408 | "metadata": {}, 409 | "source": [ 410 | "## Numpy\n", 411 | "Python includes a package for numerical computation called numpy, which will be an essential tool in this course. To get started, we import the numpy module. We will also tell Python that we want to refer to numpy by the short abbreviation \"np\":" 412 | ] 413 | }, 414 | { 415 | "cell_type": "code", 416 | "collapsed": false, 417 | "input": [ 418 | "import numpy as np" 419 | ], 420 | "language": "python", 421 | "metadata": {}, 422 | "outputs": [] 423 | }, 424 | { 425 | "cell_type": "markdown", 426 | "metadata": {}, 427 | "source": [ 428 | "### Arrays\n", 429 | "\n", 430 | "The most important Numpy class is the `array`. You can make arrays just like you would in MATLAB:" 431 | ] 432 | }, 433 | { 434 | "cell_type": "code", 435 | "collapsed": false, 436 | "input": [ 437 | "x = np.linspace(0, 1, 5)\n", 438 | "print x" 439 | ], 440 | "language": "python", 441 | "metadata": {}, 442 | "outputs": [] 443 | }, 444 | { 445 | "cell_type": "code", 446 | "collapsed": false, 447 | "input": [ 448 | "y = np.arange(0, 1, 0.2)\n", 449 | "print y" 450 | ], 451 | "language": "python", 452 | "metadata": {}, 453 | "outputs": [] 454 | }, 455 | { 456 | "cell_type": "markdown", 457 | "metadata": {}, 458 | "source": [ 459 | "Like `range`, `arange` omits the final value." 460 | ] 461 | }, 462 | { 463 | "cell_type": "markdown", 464 | "metadata": {}, 465 | "source": [ 466 | "Arrays are like lists, except that you can perform math with them in an easier and faster way:" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "collapsed": false, 472 | "input": [ 473 | "print x+y" 474 | ], 475 | "language": "python", 476 | "metadata": {}, 477 | "outputs": [] 478 | }, 479 | { 480 | "cell_type": "code", 481 | "collapsed": false, 482 | "input": [ 483 | "print x*y" 484 | ], 485 | "language": "python", 486 | "metadata": {}, 487 | "outputs": [] 488 | }, 489 | { 490 | "cell_type": "markdown", 491 | "metadata": {}, 492 | "source": [ 493 | "The syntax for creating a multidimensional array in numpy is also similar to Matlab:" 494 | ] 495 | }, 496 | { 497 | "cell_type": "code", 498 | "collapsed": false, 499 | "input": [ 500 | "A = np.array([[1,2,3],[4,5,6],[7,8,9]])\n", 501 | "print A" 502 | ], 503 | "language": "python", 504 | "metadata": {}, 505 | "outputs": [] 506 | }, 507 | { 508 | "cell_type": "heading", 509 | "level": 3, 510 | "metadata": {}, 511 | "source": [ 512 | "Indexing" 513 | ] 514 | }, 515 | { 516 | "cell_type": "markdown", 517 | "metadata": {}, 518 | "source": [ 519 | "You can slice numpy arrays just as in Fortran 90 or Matlab, but (as with lists) the arrays are indexed from zero and you don't get the last element of a slice." 520 | ] 521 | }, 522 | { 523 | "cell_type": "code", 524 | "collapsed": false, 525 | "input": [ 526 | "print A[0,0]" 527 | ], 528 | "language": "python", 529 | "metadata": {}, 530 | "outputs": [] 531 | }, 532 | { 533 | "cell_type": "code", 534 | "collapsed": false, 535 | "input": [ 536 | "print A[1:3,0:2]" 537 | ], 538 | "language": "python", 539 | "metadata": {}, 540 | "outputs": [] 541 | }, 542 | { 543 | "cell_type": "markdown", 544 | "metadata": {}, 545 | "source": [ 546 | "You can index in some slightly fancier ways, too:" 547 | ] 548 | }, 549 | { 550 | "cell_type": "code", 551 | "collapsed": false, 552 | "input": [ 553 | "print A[:,1:]" 554 | ], 555 | "language": "python", 556 | "metadata": {}, 557 | "outputs": [] 558 | }, 559 | { 560 | "cell_type": "code", 561 | "collapsed": false, 562 | "input": [ 563 | "print A[-1,:]" 564 | ], 565 | "language": "python", 566 | "metadata": {}, 567 | "outputs": [] 568 | }, 569 | { 570 | "cell_type": "markdown", 571 | "metadata": {}, 572 | "source": [ 573 | "You can do lots of other things with arrays. Type \"A.\" (notice the period!) and press `tab` to see some of them:" 574 | ] 575 | }, 576 | { 577 | "cell_type": "code", 578 | "collapsed": false, 579 | "input": [], 580 | "language": "python", 581 | "metadata": {}, 582 | "outputs": [] 583 | }, 584 | { 585 | "cell_type": "heading", 586 | "level": 2, 587 | "metadata": {}, 588 | "source": [ 589 | "Matplotlib" 590 | ] 591 | }, 592 | { 593 | "cell_type": "markdown", 594 | "metadata": {}, 595 | "source": [ 596 | "For plotting we will use the [matplotlib](http://matplotlib.org/) package. It works a lot like MATLAB's plotting functions." 597 | ] 598 | }, 599 | { 600 | "cell_type": "code", 601 | "collapsed": false, 602 | "input": [ 603 | "%matplotlib inline\n", 604 | "import matplotlib.pyplot as plt" 605 | ], 606 | "language": "python", 607 | "metadata": {}, 608 | "outputs": [] 609 | }, 610 | { 611 | "cell_type": "markdown", 612 | "metadata": {}, 613 | "source": [ 614 | "The line beginning with a \"%\" is referred to as a magic function. It makes plots appear in the browser, rather than in a separate window. If you want to know about all of IPython's magic functions, just type \"%magic\".\n", 615 | "\n", 616 | "Now for a very simple example. Suppose we want to plot the function $\\sin(\\exp(x))$ on the interval $x\\in(0,4)$. We'll use the numpy versions of the sine and exponential functions, which operate on arrays (the math module versions operate only on scalars): " 617 | ] 618 | }, 619 | { 620 | "cell_type": "code", 621 | "collapsed": false, 622 | "input": [ 623 | "x=np.linspace(0,4,1000)\n", 624 | "f=np.sin(np.exp(x))\n", 625 | "plt.plot(x,f)" 626 | ], 627 | "language": "python", 628 | "metadata": {}, 629 | "outputs": [] 630 | }, 631 | { 632 | "cell_type": "markdown", 633 | "metadata": {}, 634 | "source": [ 635 | "We'll also see later how to make animations with Matplotlib and IPython." 636 | ] 637 | }, 638 | { 639 | "cell_type": "markdown", 640 | "metadata": {}, 641 | "source": [ 642 | "### A note about speed\n", 643 | "\n", 644 | "Like MATLAB, Python is relatively slow -- especially when using **loops** with many iterations, nested loops, or deeply nested function calls. For the exercises in this course, Python will be sufficiently fast, but you should use numpy slicing whenever possible.\n", 645 | "\n", 646 | "We will often need to compute the difference of each pair of successive entries of an array. Here are two ways to do it. Which is faster? We can find out using another \"magic\" function." 647 | ] 648 | }, 649 | { 650 | "cell_type": "code", 651 | "collapsed": false, 652 | "input": [ 653 | "N = 1000000\n", 654 | "a = np.random.rand(N)\n", 655 | "b = np.zeros(N-1)" 656 | ], 657 | "language": "python", 658 | "metadata": {}, 659 | "outputs": [] 660 | }, 661 | { 662 | "cell_type": "code", 663 | "collapsed": false, 664 | "input": [ 665 | "%%timeit\n", 666 | "for i in range(len(b)):\n", 667 | " b[i] = a[i+1]-a[i]" 668 | ], 669 | "language": "python", 670 | "metadata": {}, 671 | "outputs": [] 672 | }, 673 | { 674 | "cell_type": "code", 675 | "collapsed": false, 676 | "input": [ 677 | "%%timeit\n", 678 | "b = a[1:]-a[:-1]" 679 | ], 680 | "language": "python", 681 | "metadata": {}, 682 | "outputs": [] 683 | }, 684 | { 685 | "cell_type": "markdown", 686 | "metadata": {}, 687 | "source": [ 688 | "For large-scale computational problems, you shouldn't use Python for any code that needs to be fast. Instead, you can write/generate code in C or Fortran and use weave, [cython](http://cython.org/), [f2py](http://www.f2py.com/), or other similar packages to automatically incorporate that code into your Python program. \n", 689 | "\n", 690 | "Toward the end of the course, we'll use the PyClaw package, which includes Fortran code and is built using f2py." 691 | ] 692 | }, 693 | { 694 | "cell_type": "heading", 695 | "level": 3, 696 | "metadata": {}, 697 | "source": [ 698 | "A warning about division" 699 | ] 700 | }, 701 | { 702 | "cell_type": "markdown", 703 | "metadata": {}, 704 | "source": [ 705 | "Be careful when dividing numbers in Python. If you divide with integers, Python may not do what you expect. In scientific computing, you usually want to use floating point numbers." 706 | ] 707 | }, 708 | { 709 | "cell_type": "code", 710 | "collapsed": false, 711 | "input": [ 712 | "print 1/2\n", 713 | "print 1.0/2.0" 714 | ], 715 | "language": "python", 716 | "metadata": {}, 717 | "outputs": [] 718 | }, 719 | { 720 | "cell_type": "heading", 721 | "level": 2, 722 | "metadata": {}, 723 | "source": [ 724 | "Why use Python?" 725 | ] 726 | }, 727 | { 728 | "cell_type": "markdown", 729 | "metadata": {}, 730 | "source": [ 731 | "Hopefully this brief adventure has convinced you that Python is a worthwhile programming language. Some of the reasons I chose Python as the language for this course (and other courses I teach) are:\n", 732 | "- Compared to lower-level languages like C, C++, and Fortran, Python code is easier to write and read (meaning fewer bugs!)\n", 733 | "- A huge number of available libraries means it is easy to get things done in Python without writing a lot of code yourself\n", 734 | "- Python is a general-purpose programming language, with features (like optional arguments with defaults) that lead to more elegant, maintainable code than what you can write with specialized languages like MATLAB.\n", 735 | "- Python is free and comes installed on most systems. You can run this whole course with only a web browser and access to the internet.\n", 736 | "- It's easy to incorporate code written in other languages into your Python programs.\n", 737 | "- It's often easy to [parallelize existing scientific codes](http://numerics.kaust.edu.sa/papers/pyclaw-sisc/pyclaw-sisc.html) using Python.\n", 738 | "\n", 739 | "For a longer discussion with links to some scientific studies of the value of Python, see [this blog post by Lorena Barba](http://lorenabarba.com/blog/why-i-push-for-python/)." 740 | ] 741 | }, 742 | { 743 | "cell_type": "heading", 744 | "level": 2, 745 | "metadata": {}, 746 | "source": [ 747 | "Further resources" 748 | ] 749 | }, 750 | { 751 | "cell_type": "heading", 752 | "level": 3, 753 | "metadata": {}, 754 | "source": [ 755 | "Python, numpy, and matplotlib" 756 | ] 757 | }, 758 | { 759 | "cell_type": "markdown", 760 | "metadata": {}, 761 | "source": [ 762 | "We've only scratched the surface of Python here. You'll learn more in the rest of the course -- and it's great to learn by doing. But if you want a really solid foundation, you may want to take a look at these:\n", 763 | "\n", 764 | "- http://www.learnpython.org/ (free)\n", 765 | "- http://www.codecademy.com/tracks/python (free)\n", 766 | "- http://www.diveintopython.net/ (free)\n", 767 | "- [A Primer on Scientific Programming with Python](http://www.amazon.com/Scientific-Programming-Computational-Science-Engineering/dp/3642302920) (book; not free)\n", 768 | "- [Matplotlib tutorial](http://www.loria.fr/~rougier/teaching/matplotlib/) (free)\n", 769 | "- [Numpy tutorial](http://wiki.scipy.org/Tentative_NumPy_Tutorial) (free)" 770 | ] 771 | }, 772 | { 773 | "cell_type": "heading", 774 | "level": 3, 775 | "metadata": {}, 776 | "source": [ 777 | "Other Python packages for science" 778 | ] 779 | }, 780 | { 781 | "cell_type": "markdown", 782 | "metadata": {}, 783 | "source": [ 784 | "Besides numpy and matplotlib, there are many other useful Python packages for scientific computing. Here is a short list: \n", 785 | "\n", 786 | "- [scipy](http://www.scipy.org/) - optimization, ODEs, sparse linear algebra, etc.\n", 787 | "- [sympy](http://sympy.org/) - symbolic computation\n", 788 | "- Visualization: [yt](http://yt-project.org/), [vispy](http://vispy.org/), [Bokeh](http://bokeh.pydata.org/)\n", 789 | "- [pandas](http://pandas.pydata.org/) - data analysis\n", 790 | "- [mpi4py](http://mpi4py.scipy.org/) - parallel computing\n", 791 | "- [petsc4py](http://code.google.com/p/petsc4py/), [pytrilinos](http://trilinos.sandia.gov/packages/pytrilinos/) - Python bindings for the \"big 2\" parallel scientific libraries\n", 792 | "- [pyCUDA](http://mathema.tician.de/software/pycuda), [pyOpenCL](http://mathema.tician.de/software/pyopencl) - GPGPU computing\n", 793 | "- [FENiCS](http://fenicsproject.org/), [FiPy](http://www.ctcms.nist.gov/fipy/), [PyClaw](http://clawpack.github.io/doc/pyclaw/) - solve complicated PDEs with very sophisticated numerical methods\n", 794 | "- [networkX](http://networkx.github.com/), [pygraphviz](http://networkx.lanl.gov/pygraphviz/) - graphs\n", 795 | "- [astropy](http://www.astropy.org/), [biopython](http://biopython.org/wiki/Main_Page), [pychem](http://pychem.sourceforge.net/) - discipline-specific tools" 796 | ] 797 | }, 798 | { 799 | "cell_type": "code", 800 | "collapsed": false, 801 | "input": [], 802 | "language": "python", 803 | "metadata": {}, 804 | "outputs": [] 805 | } 806 | ], 807 | "metadata": {} 808 | } 809 | ] 810 | } -------------------------------------------------------------------------------- /Lesson_04_Fluid_dynamics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:70f483d0cdc9b19b25cb4d7712103b68b352d934bdc7ec6ef6d2c6b8e126a633" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "code", 13 | "collapsed": false, 14 | "input": [ 15 | "from IPython.core.display import HTML\n", 16 | "css_file = './custom.css'\n", 17 | "HTML(open(css_file, \"r\").read())" 18 | ], 19 | "language": "python", 20 | "metadata": {}, 21 | "outputs": [ 22 | { 23 | "html": [ 24 | "\n", 25 | "\n", 26 | "\n", 27 | "\n", 28 | "\n", 29 | "\n" 134 | ], 135 | "metadata": {}, 136 | "output_type": "pyout", 137 | "prompt_number": 1, 138 | "text": [ 139 | "" 140 | ] 141 | } 142 | ], 143 | "prompt_number": 1 144 | }, 145 | { 146 | "cell_type": "heading", 147 | "level": 6, 148 | "metadata": {}, 149 | "source": [ 150 | "Content provided under a Creative Commons Attribution license, CC-BY 4.0; code under MIT License. (c)2014 [David I. Ketcheson](http://davidketcheson.info)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "heading", 155 | "level": 5, 156 | "metadata": {}, 157 | "source": [ 158 | "version 0.1 - April 2014" 159 | ] 160 | }, 161 | { 162 | "cell_type": "heading", 163 | "level": 1, 164 | "metadata": {}, 165 | "source": [ 166 | "Fluid dynamics" 167 | ] 168 | }, 169 | { 170 | "cell_type": "markdown", 171 | "metadata": {}, 172 | "source": [ 173 | "In this lesson we will look at the system of hyperbolic PDEs that governs the motions of fluids in the absence of viscosity. These consist of conservation laws for **mass, momentum**, and **energy**. Together, they are referred to as the **compressible Euler equations**, or simply the Euler equations." 174 | ] 175 | }, 176 | { 177 | "cell_type": "heading", 178 | "level": 2, 179 | "metadata": {}, 180 | "source": [ 181 | "Mass conservation" 182 | ] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "metadata": {}, 187 | "source": [ 188 | "We will use $\\rho(x,t)$ to denote the fluid density and $u(x,t)$ for its velocity. Then the equation for conservation of mass is just the **continuity equation** we discussed in [Lesson 1](Lesson_01_Advection.ipynb):\n", 189 | "\n", 190 | "$$\\rho_t + (\\rho u)_x = 0.$$" 191 | ] 192 | }, 193 | { 194 | "cell_type": "heading", 195 | "level": 2, 196 | "metadata": {}, 197 | "source": [ 198 | "Momentum conservation" 199 | ] 200 | }, 201 | { 202 | "cell_type": "markdown", 203 | "metadata": {}, 204 | "source": [ 205 | "The momentum is given by the product of density and velocity, i.e. $\\rho u$. The momentum flux has two components. First, the momentum is transported in the same way that the density is; this flux is given by the momentum times the density; i.e. $\\rho u^2$.\n", 206 | "\n", 207 | "To understand the second term in the momentum flux, we must realize that a fluid is made up of many tiny molecules. The density and velocity we are modeling are average values over some small region of space. The individual molecules in that region are not all moving with exactly velocity $u$; that's just their average. Each molecule also has some additional random velocity component. These random velocities are what accounts for the **pressure** of the fluid, which we'll denote by $p$. These velocity components also lead to a net flux of momentum. Thus the momentum conservation equation is\n", 208 | "\n", 209 | "$$(\\rho u)_t + (\\rho u^2 + p)_x = 0.$$" 210 | ] 211 | }, 212 | { 213 | "cell_type": "heading", 214 | "level": 2, 215 | "metadata": {}, 216 | "source": [ 217 | "Energy conservation" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "The energy has two components: internal energy $\\rho e$ and kinetic energy $\\rho u^2/2$:\n", 225 | "\n", 226 | "$$E = \\rho e + \\frac{1}{2}\\rho u^2.$$\n", 227 | "\n", 228 | "Like the momentum flux, the energy flux involves both bulk transport ($Eu$) and transport due to pressure ($pu$):\n", 229 | "\n", 230 | "$$E_t + (u(E+p))_x = 0.$$" 231 | ] 232 | }, 233 | { 234 | "cell_type": "heading", 235 | "level": 2, 236 | "metadata": {}, 237 | "source": [ 238 | "Equation of state" 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "metadata": {}, 244 | "source": [ 245 | "You may have noticed that we have 4 unknowns (density, momentum, energy, and pressure) but only 3 conservation laws. We need one more relation to close the system. That relation, known as the equation of state, expresses how the pressure is related to the other quantities. We'll focus on the case of an ideal gas, for which\n", 246 | "\n", 247 | "$$p = \\rho e (\\gamma-1).$$\n", 248 | "\n", 249 | "Here $\\gamma$ is the ratio of specific heats, which for air is approximately 1.4." 250 | ] 251 | }, 252 | { 253 | "cell_type": "heading", 254 | "level": 2, 255 | "metadata": {}, 256 | "source": [ 257 | "The Euler equations" 258 | ] 259 | }, 260 | { 261 | "cell_type": "markdown", 262 | "metadata": {}, 263 | "source": [ 264 | "We can write the three conservation laws as a single system $q_t + f(q)_x = 0$ by defining\n", 265 | "\\begin{align}\n", 266 | "q & = \\begin{pmatrix} \\rho \\\\ \\rho u \\\\ E\\end{pmatrix}, & \n", 267 | "f(q) & = \\begin{pmatrix} \\rho u \\\\ \\rho u^2 + p \\\\ u(E+p)\\end{pmatrix}.\n", 268 | "\\end{align}" 269 | ] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "metadata": {}, 274 | "source": [ 275 | "In three dimensions, the equations are similar. We have two additional velocity components $v, w$, and their corresponding fluxes. Additionally, we have to account for fluxes in the $y$ and $z$ directions. We can write the full system as\n", 276 | "\n", 277 | "$$ q_t + f(q)_x + g(q)_y + h(q)_z = 0$$\n", 278 | "\n", 279 | "with\n", 280 | "\n", 281 | "\\begin{align}\n", 282 | "q & = \\begin{pmatrix} \\rho \\\\ \\rho u \\\\ \\rho v \\\\ \\rho w \\\\ E\\end{pmatrix}, &\n", 283 | "f(q) & = \\begin{pmatrix} \\rho u \\\\ \\rho u^2 + p \\\\ \\rho u v \\\\ \\rho u w \\\\ u(E+p)\\end{pmatrix} &\n", 284 | "g(q) & = \\begin{pmatrix} \\rho v \\\\ \\rho uv \\\\ \\rho v^2 + p \\\\ \\rho v w \\\\ v(E+p)\\end{pmatrix} &\n", 285 | "h(q) & = \\begin{pmatrix} \\rho w \\\\ \\rho uw \\\\ \\rho vw \\\\ \\rho w^2 + p \\\\ w(E+p)\\end{pmatrix}.\n", 286 | "\\end{align}" 287 | ] 288 | }, 289 | { 290 | "cell_type": "heading", 291 | "level": 2, 292 | "metadata": {}, 293 | "source": [ 294 | "Solving the Euler equations" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "metadata": {}, 300 | "source": [ 301 | "These equations can be solved in a manner similar to what we used for advection and traffic flow. As you might guess, computing the flux gets significantly more complicated since we now have 3 (or 5) equations and more complicated flux expressions." 302 | ] 303 | }, 304 | { 305 | "cell_type": "heading", 306 | "level": 2, 307 | "metadata": {}, 308 | "source": [ 309 | "PyClaw" 310 | ] 311 | }, 312 | { 313 | "cell_type": "markdown", 314 | "metadata": {}, 315 | "source": [ 316 | "Implementing a solver for the Euler equations from scratch would be a lot of fun, but to save some time we'll use a package called [PyClaw](http://clawpack.github.io/doc/pyclaw/), which is part of the [Clawpack](http://clawpack.github.io/) software (Clawpack stands for Conservation LAWs PACKage). PyClaw allows us to quickly and easily set up and solve problems modeled by hyperbolic PDEs." 317 | ] 318 | }, 319 | { 320 | "cell_type": "markdown", 321 | "metadata": {}, 322 | "source": [ 323 | "Now let's get started. First, import the parts of Clawpack that we'll use:" 324 | ] 325 | }, 326 | { 327 | "cell_type": "code", 328 | "collapsed": false, 329 | "input": [ 330 | "from clawpack import pyclaw\n", 331 | "from clawpack import riemann" 332 | ], 333 | "language": "python", 334 | "metadata": {}, 335 | "outputs": [] 336 | }, 337 | { 338 | "cell_type": "markdown", 339 | "metadata": {}, 340 | "source": [ 341 | "### Setting up a problem\n", 342 | "To solve a problem, we'll need to create the following:\n", 343 | "\n", 344 | "- A **domain** over which to solve the problem\n", 345 | "- A **solution**, where we will provide the initial data. After running, the solution will contain -- you guessed it! -- the solution.\n", 346 | "- A **solver**, which is responsible for actually evolving the solution in time. Here we'll need to specify the equations to be solved and the boundary conditions.\n", 347 | "- A **controller**, which handles the running, output, and can be used for plotting\n", 348 | "\n", 349 | "This might sound complicated at first, but stick with me." 350 | ] 351 | }, 352 | { 353 | "cell_type": "markdown", 354 | "metadata": {}, 355 | "source": [ 356 | "Let's start by creating a controller and specifying the simulation end time:" 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "collapsed": false, 362 | "input": [ 363 | "claw = pyclaw.Controller()\n", 364 | "claw.tfinal = 0.1 # Simulation end time\n", 365 | "\n", 366 | "claw.keep_copy = True # Keep solution data in memory for plotting\n", 367 | "claw.output_format = None # Don't write solution data to file\n", 368 | "claw.num_output_times = 50 # Write 50 output frames" 369 | ], 370 | "language": "python", 371 | "metadata": {}, 372 | "outputs": [] 373 | }, 374 | { 375 | "cell_type": "markdown", 376 | "metadata": {}, 377 | "source": [ 378 | "### Riemann solvers\n", 379 | "\n", 380 | "The method used to compute the flux between each pair of cells is referred to as a *Riemann solver*. By specifying a Riemann solver, we will specify the system of PDEs that we want to solve. So far we have only used very simple approximate Riemann solvers. Clawpack includes much more sophisticated Riemann solvers for many hyperbolic systems.\n", 381 | "\n", 382 | "Place your cursor at the end of the line in the box below and hit 'Tab' (for autocompletion). You'll see a dropdown list of all the Riemann solvers currently available in PyClaw. The ones with 'py' at the end of the name are written in pure Python; the others are written in Fortran and wrapped with f2py." 383 | ] 384 | }, 385 | { 386 | "cell_type": "code", 387 | "collapsed": false, 388 | "input": [ 389 | "riemann." 390 | ], 391 | "language": "python", 392 | "metadata": {}, 393 | "outputs": [] 394 | }, 395 | { 396 | "cell_type": "markdown", 397 | "metadata": {}, 398 | "source": [ 399 | "We'll start with a simple 1D problem, using the Riemann solver `riemann.euler_with_efix_1D`:" 400 | ] 401 | }, 402 | { 403 | "cell_type": "code", 404 | "collapsed": false, 405 | "input": [ 406 | "riemann_solver = riemann.euler_with_efix_1D\n", 407 | "claw.solver = pyclaw.ClawSolver1D(riemann_solver)" 408 | ], 409 | "language": "python", 410 | "metadata": {}, 411 | "outputs": [] 412 | }, 413 | { 414 | "cell_type": "markdown", 415 | "metadata": {}, 416 | "source": [ 417 | "We also need to specify boundary conditions. We'll use extrapolation boundary conditions, so that waves simply pass out of the domain:" 418 | ] 419 | }, 420 | { 421 | "cell_type": "code", 422 | "collapsed": false, 423 | "input": [ 424 | "claw.solver.all_bcs = pyclaw.BC.extrap" 425 | ], 426 | "language": "python", 427 | "metadata": {}, 428 | "outputs": [] 429 | }, 430 | { 431 | "cell_type": "markdown", 432 | "metadata": {}, 433 | "source": [ 434 | "### The problem domain\n", 435 | "Next we need to specify the domain and the grid. We'll solve on the unit line $[0,1]$ using 100 grid cells. Note that each argument to the Domain constructor must be a tuple:" 436 | ] 437 | }, 438 | { 439 | "cell_type": "code", 440 | "collapsed": false, 441 | "input": [ 442 | "domain = pyclaw.Domain( (0.,), (1.,), (100,))" 443 | ], 444 | "language": "python", 445 | "metadata": {}, 446 | "outputs": [] 447 | }, 448 | { 449 | "cell_type": "markdown", 450 | "metadata": {}, 451 | "source": [ 452 | "### The initial solution\n", 453 | "Next we create a solution object that belongs to the controller and extends over the domain we specified:" 454 | ] 455 | }, 456 | { 457 | "cell_type": "code", 458 | "collapsed": false, 459 | "input": [ 460 | "claw.solution = pyclaw.Solution(claw.solver.num_eqn,domain)" 461 | ], 462 | "language": "python", 463 | "metadata": {}, 464 | "outputs": [] 465 | }, 466 | { 467 | "cell_type": "markdown", 468 | "metadata": {}, 469 | "source": [ 470 | "The initial data is specified in an array named `solution.q`. The density is contained in `q[0,:]`, the momentum in `q[1,:]`, and the energy in `q[2,:]`." 471 | ] 472 | }, 473 | { 474 | "cell_type": "code", 475 | "collapsed": false, 476 | "input": [ 477 | "x=domain.grid.x.centers # grid cell centers\n", 478 | "gam = 1.4 # ratio of specific heats\n", 479 | "\n", 480 | "rho_left = 1.0; rho_right = 0.125\n", 481 | "p_left = 1.0; p_right = 0.1\n", 482 | "\n", 483 | "claw.solution.q[0,:] = (x<0.5)*rho_left + (x>=0.5)*rho_right\n", 484 | "claw.solution.q[1,:] = 0.\n", 485 | "claw.solution.q[2,:] = ((x<0.5)*p_left + (x>=0.5)*p_right)/(gam-1.0)\n", 486 | "\n", 487 | "%matplotlib inline\n", 488 | "import matplotlib.pyplot as plt\n", 489 | "plt.plot(x, claw.solution.q[0,:],'-o')" 490 | ], 491 | "language": "python", 492 | "metadata": {}, 493 | "outputs": [] 494 | }, 495 | { 496 | "cell_type": "markdown", 497 | "metadata": {}, 498 | "source": [ 499 | "This problem is known as the **Sod shock-tube**. It amounts to setting up a tube with a thin separator between a high-pressure, high-density region and a low-pressure, low-density region, then suddenly removing the separator." 500 | ] 501 | }, 502 | { 503 | "cell_type": "markdown", 504 | "metadata": {}, 505 | "source": [ 506 | "Next we need to specify the value of $\\gamma$, the ratio of specific heats." 507 | ] 508 | }, 509 | { 510 | "cell_type": "code", 511 | "collapsed": false, 512 | "input": [ 513 | "problem_data = claw.solution.problem_data\n", 514 | "problem_data['gamma'] = 1.4\n", 515 | "problem_data['gamma1'] = 0.4" 516 | ], 517 | "language": "python", 518 | "metadata": {}, 519 | "outputs": [] 520 | }, 521 | { 522 | "cell_type": "markdown", 523 | "metadata": {}, 524 | "source": [ 525 | "Finally, let's run the simulation." 526 | ] 527 | }, 528 | { 529 | "cell_type": "code", 530 | "collapsed": false, 531 | "input": [ 532 | "claw.run()" 533 | ], 534 | "language": "python", 535 | "metadata": {}, 536 | "outputs": [] 537 | }, 538 | { 539 | "cell_type": "markdown", 540 | "metadata": {}, 541 | "source": [ 542 | "### Plotting\n", 543 | "Now we'll plot the results, which are contained in a list called `claw.frames`. It's simple to plot a single frame with matplotlib:" 544 | ] 545 | }, 546 | { 547 | "cell_type": "code", 548 | "collapsed": false, 549 | "input": [ 550 | "from matplotlib import animation\n", 551 | "import matplotlib.pyplot as plt\n", 552 | "from clawpack.visclaw.JSAnimation import IPython_display\n", 553 | "import numpy as np\n", 554 | "\n", 555 | "fig = plt.figure()\n", 556 | "ax = plt.axes(xlim=(0, 1), ylim=(-0.2, 1.2))\n", 557 | "\n", 558 | "frame = claw.frames[0]\n", 559 | "pressure = frame.q[0,:]\n", 560 | "line, = ax.plot([], [], 'o-', lw=2)\n", 561 | "\n", 562 | "def fplot(frame_number):\n", 563 | " frame = claw.frames[frame_number]\n", 564 | " pressure = frame.q[0,:]\n", 565 | " line.set_data(x,pressure)\n", 566 | " return line,\n", 567 | "\n", 568 | "animation.FuncAnimation(fig, fplot, frames=len(claw.frames), interval=30)" 569 | ], 570 | "language": "python", 571 | "metadata": {}, 572 | "outputs": [] 573 | }, 574 | { 575 | "cell_type": "heading", 576 | "level": 3, 577 | "metadata": {}, 578 | "source": [ 579 | "Waves" 580 | ] 581 | }, 582 | { 583 | "cell_type": "markdown", 584 | "metadata": {}, 585 | "source": [ 586 | "In the solution, 3 waves are visible:\n", 587 | "1. A **shock wave** moving rapidly to the right as the low-density fluid is compressed.\n", 588 | "2. A **rarefaction** wave moving to the left as the high-density fluid expands.\n", 589 | "3. A **contact discontinuity** moving more slowly to the right. This discontinuity in the density separates the region containing fluid that started in the high-pressure region and fluid that started in the low-pressure region." 590 | ] 591 | }, 592 | { 593 | "cell_type": "markdown", 594 | "metadata": {}, 595 | "source": [ 596 | "In fact, the solution of any Riemann problem consists of some combination of these three types of waves. In the Euler equations, one of the waves is always a contact discontinuity, but each of the other two waves may be a shock or a rarefaction, depending on the left and right states." 597 | ] 598 | }, 599 | { 600 | "cell_type": "heading", 601 | "level": 3, 602 | "metadata": {}, 603 | "source": [ 604 | "Putting it all together" 605 | ] 606 | }, 607 | { 608 | "cell_type": "markdown", 609 | "metadata": {}, 610 | "source": [ 611 | "For convenience, all of the code from the cells above to set up and run the shocktube problem is pasted together below. Play around with the code. You might:\n", 612 | "- Increase the number of grid points to see what the solution converges to. Notice that the code still runs pretty fast even for larger grids. This is because the bottom layer of code in PyClaw is compiled Fortran, not Python.\n", 613 | "- Change the initial left and right states, or set up a completely different initial condition. See if you can generate a solution with two shock waves, or two rarefaction waves (some physical intuition is helpful here).\n", 614 | "- Change the ratio of specific heats\n", 615 | "- Make the boundaries periodic, so that there is a second shock wave moving left from $x=1$." 616 | ] 617 | }, 618 | { 619 | "cell_type": "code", 620 | "collapsed": false, 621 | "input": [ 622 | "%matplotlib inline\n", 623 | "import matplotlib.pyplot as plt\n", 624 | "from clawpack import pyclaw\n", 625 | "from clawpack import riemann\n", 626 | "\n", 627 | "claw = pyclaw.Controller()\n", 628 | "claw.tfinal = 0.1\n", 629 | "\n", 630 | "claw.keep_copy = True # Keep solution data in memory for plotting\n", 631 | "claw.output_format = None # Don't write solution data to file\n", 632 | "claw.num_output_times = 50 # Write 50 output frames\n", 633 | "\n", 634 | "riemann_solver = riemann.euler_with_efix_1D\n", 635 | "claw.solver = pyclaw.ClawSolver1D(riemann_solver)\n", 636 | "\n", 637 | "claw.solver.all_bcs = pyclaw.BC.extrap\n", 638 | "\n", 639 | "domain = pyclaw.Domain( (0.,), (1.,), (100,))\n", 640 | "x=domain.grid.x.centers # grid cell centers\n", 641 | "\n", 642 | "claw.solution = pyclaw.Solution(claw.solver.num_eqn,domain)\n", 643 | "\n", 644 | "gam = 1.4 # ratio of specific heats\n", 645 | "claw.solution.problem_data['gamma'] = gam\n", 646 | "claw.solution.problem_data['gamma1'] = gam-1.0\n", 647 | "\n", 648 | "rho_left = 1.0; rho_right = 0.125\n", 649 | "p_left = 1.0; p_right = 0.1\n", 650 | "\n", 651 | "claw.solution.q[0,:] = (x<0.5)*rho_left + (x>=0.5)*rho_right\n", 652 | "claw.solution.q[1,:] = 0.\n", 653 | "claw.solution.q[2,:] = ((x<0.5)*p_left + (x>=0.5)*p_right)/(gam-1.0)\n", 654 | "\n", 655 | "status = claw.run()" 656 | ], 657 | "language": "python", 658 | "metadata": {}, 659 | "outputs": [] 660 | } 661 | ], 662 | "metadata": {} 663 | } 664 | ] 665 | } -------------------------------------------------------------------------------- /Lesson_05_PyClaw.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:a6012fdd078ee5922bad6e7a84c31a42690e4803260228bcf9ac2e37d98e9742" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "code", 13 | "collapsed": false, 14 | "input": [ 15 | "from IPython.core.display import HTML\n", 16 | "css_file = './custom.css'\n", 17 | "HTML(open(css_file, \"r\").read())" 18 | ], 19 | "language": "python", 20 | "metadata": {}, 21 | "outputs": [ 22 | { 23 | "html": [ 24 | "\n", 25 | "\n", 26 | "\n", 27 | "\n", 28 | "\n", 29 | "\n" 134 | ], 135 | "metadata": {}, 136 | "output_type": "pyout", 137 | "prompt_number": 1, 138 | "text": [ 139 | "" 140 | ] 141 | } 142 | ], 143 | "prompt_number": 1 144 | }, 145 | { 146 | "cell_type": "heading", 147 | "level": 6, 148 | "metadata": {}, 149 | "source": [ 150 | "Content provided under a Creative Commons Attribution license, CC-BY 4.0; code under MIT License. (c)2014 [David I. Ketcheson](http://davidketcheson.info)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "heading", 155 | "level": 5, 156 | "metadata": {}, 157 | "source": [ 158 | "version 0.1 - April 2014" 159 | ] 160 | }, 161 | { 162 | "cell_type": "heading", 163 | "level": 1, 164 | "metadata": {}, 165 | "source": [ 166 | "Solving problems with PyClaw" 167 | ] 168 | }, 169 | { 170 | "cell_type": "heading", 171 | "level": 2, 172 | "metadata": {}, 173 | "source": [ 174 | "Variable coefficients" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "Many hyperbolic problems involve coefficients that vary in space and/or time. The simplest example is our old friend the advection equation, but where the velocity may vary:\n", 182 | "\n", 183 | "$$q_t + (a(x,t) q)_x = 0.$$\n", 184 | "\n", 185 | "Such problems can be solved with PyClaw by storing the variable coefficients the `state.aux` array. For coefficients that vary in time, you can provide a function that updates the `aux` array at each time step." 186 | ] 187 | }, 188 | { 189 | "cell_type": "heading", 190 | "level": 2, 191 | "metadata": {}, 192 | "source": [ 193 | "Non-hyperbolic terms" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "Many physical models include both hyperbolic terms that represent transport and other terms that may represent reactions, diffusion, external forces, etc. It is common to refer to such terms as source terms, denoted by $S(q)$ and to the resulting equation as a *balance law*:\n", 201 | "\n", 202 | "$$q_t + f(q)_x = S(q).$$\n", 203 | "\n", 204 | "Such problems can be solved with PyClaw, but you must provide a routine that integrates the source term over one time step. Alternatively, the source terms may be incorporated into the Riemann solver by using an *f-wave solver*; this is particularly useful in situations where $f(q)_x$ and $S(q)$ are nearly equal." 205 | ] 206 | }, 207 | { 208 | "cell_type": "heading", 209 | "level": 2, 210 | "metadata": {}, 211 | "source": [ 212 | "Boundary conditions" 213 | ] 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "metadata": {}, 218 | "source": [ 219 | "PyClaw has built-in functions for the following kinds of boundary conditions:\n", 220 | "- Periodic\n", 221 | "- Non-reflecting (zero-order extrapolation)\n", 222 | "- Reflecting\n", 223 | "\n", 224 | "For other types of boundary conditions, you can provide a function that fills the ghost cells at the boundary of the domain." 225 | ] 226 | }, 227 | { 228 | "cell_type": "heading", 229 | "level": 2, 230 | "metadata": {}, 231 | "source": [ 232 | "Example: Shock-bubble interaction" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": {}, 238 | "source": [ 239 | "Let's look at an example that includes variable coefficients, source terms, and a custom boundary condition. This example appeared in a [2013 paper in SISC](http://epubs.siam.org/doi/abs/10.1137/110856976). It models the interaction of a shock wave with a low-density bubble. The problem is 3-dimensional, but we'll assume the shock is planar and the bubble is a sphere, which allows us to reduce the problem to two dimensions using cylindrical symmetry. The resulting equations are:\n", 240 | "\n", 241 | "$$\n", 242 | "\\rho_t + (\\rho u)_z + (\\rho v)_r = - \\frac{\\rho v}{r} \\\\\n", 243 | "(\\rho u)_t + (\\rho u^2 + p)_z + (\\rho u v)_r = - \\frac{\\rho u v}{r} \\\\\n", 244 | "(\\rho v)_t + (\\rho u v)_z + (\\rho v^2 + p)_r = -\\frac{\\rho v^2}{r} \\\\\n", 245 | "(\\rho E)_t + ((E+p)u)_z + ((E+p)v)_r = - \\frac{(E+p)v}{r}.\n", 246 | "$$\n", 247 | "\n", 248 | "The left hand side of these equations is identical to the 2D Euler equations in cartesian geometry; the right hand side terms arise because of the difference between $\\nabla$ in cartesian and cylindrical coordinates. We refer to those as *geometric source terms*. Here is a function that integrates just those source terms using a second-order Runge-Kutta method." 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "collapsed": false, 254 | "input": [ 255 | "gamma = 1.4\n", 256 | "gamma1 = gamma - 1.\n", 257 | "\n", 258 | "def step_Euler_radial(solver,state,dt):\n", 259 | " \"\"\"\n", 260 | " Geometric source terms for Euler equations with radial symmetry.\n", 261 | " Integrated using a 2-stage, 2nd-order Runge-Kutta method.\n", 262 | " This is a Clawpack-style source term routine.\n", 263 | " \"\"\"\n", 264 | " \n", 265 | " dt2 = dt/2.\n", 266 | " ndim = 2\n", 267 | "\n", 268 | " aux=state.aux\n", 269 | " q = state.q\n", 270 | "\n", 271 | " rad = aux[0,:,:]\n", 272 | "\n", 273 | " rho = q[0,:,:]\n", 274 | " u = q[1,:,:]/rho\n", 275 | " v = q[2,:,:]/rho\n", 276 | " press = gamma1 * (q[3,:,:] - 0.5*rho*(u**2 + v**2))\n", 277 | "\n", 278 | " qstar = np.empty(q.shape)\n", 279 | "\n", 280 | " qstar[0,:,:] = q[0,:,:] - dt2*(ndim-1)/rad * q[2,:,:]\n", 281 | " qstar[1,:,:] = q[1,:,:] - dt2*(ndim-1)/rad * rho*u*v\n", 282 | " qstar[2,:,:] = q[2,:,:] - dt2*(ndim-1)/rad * rho*v*v\n", 283 | " qstar[3,:,:] = q[3,:,:] - dt2*(ndim-1)/rad * v * (q[3,:,:] + press)\n", 284 | "\n", 285 | " rho = qstar[0,:,:]\n", 286 | " u = qstar[1,:,:]/rho\n", 287 | " v = qstar[2,:,:]/rho\n", 288 | " press = gamma1 * (qstar[3,:,:] - 0.5*rho*(u**2 + v**2))\n", 289 | "\n", 290 | " q[0,:,:] = q[0,:,:] - dt*(ndim-1)/rad * qstar[2,:,:]\n", 291 | " q[1,:,:] = q[1,:,:] - dt*(ndim-1)/rad * rho*u*v\n", 292 | " q[2,:,:] = q[2,:,:] - dt*(ndim-1)/rad * rho*v*v\n", 293 | " q[3,:,:] = q[3,:,:] - dt*(ndim-1)/rad * v * (qstar[3,:,:] + press)" 294 | ], 295 | "language": "python", 296 | "metadata": {}, 297 | "outputs": [] 298 | }, 299 | { 300 | "cell_type": "heading", 301 | "level": 3, 302 | "metadata": {}, 303 | "source": [ 304 | "Initial condition" 305 | ] 306 | }, 307 | { 308 | "cell_type": "markdown", 309 | "metadata": {}, 310 | "source": [ 311 | "Here is the code to set up the initial condition. Note that we have simplified it a bit: cells that lie on the boundary of the bubble should properly be initialized with a weighted average of states, but here we just initialize based on whether the cell center is inside the bubble or not. For the full code that does weighted averaging, see the corresponding example in the PyClaw examples directory." 312 | ] 313 | }, 314 | { 315 | "cell_type": "code", 316 | "collapsed": false, 317 | "input": [ 318 | "def qinit(state,z0=0.5,r0=0.,bubble_radius=0.2,rhoin=0.1,pinf=5.):\n", 319 | " grid = state.grid\n", 320 | "\n", 321 | " # Background state\n", 322 | " rhoout = 1.\n", 323 | " pout = 1.\n", 324 | " \n", 325 | " # Bubble state\n", 326 | " pin = 1.\n", 327 | "\n", 328 | " # Shock state\n", 329 | " zshock = 0.2\n", 330 | " rinf = (gamma1 + pinf*(gamma+1.))/ ((gamma+1.) + gamma1*pinf)\n", 331 | " vinf = 1./np.sqrt(gamma) * (pinf - 1.) / np.sqrt(0.5*((gamma+1.)/gamma) * pinf+0.5*gamma1/gamma)\n", 332 | " einf = 0.5*rinf*vinf**2 + pinf/gamma1\n", 333 | " \n", 334 | " z = grid.z.centers\n", 335 | " r = grid.r.centers\n", 336 | " R,Z = np.meshgrid(r,z)\n", 337 | " rad = np.sqrt((Z-z0)**2 + (R-r0)**2)\n", 338 | "\n", 339 | " state.q[0,:,:] = rinf*(Zbubble_radius)*(Z>=zshock)\n", 340 | " state.q[1,:,:] = rinf*vinf*(Zbubble_radius)*(Z>=zshock))/gamma1\n", 343 | " state.q[4,:,:] = 1.*(rad<=bubble_radius)" 344 | ], 345 | "language": "python", 346 | "metadata": {}, 347 | "outputs": [] 348 | }, 349 | { 350 | "cell_type": "heading", 351 | "level": 3, 352 | "metadata": {}, 353 | "source": [ 354 | "Boundary conditions" 355 | ] 356 | }, 357 | { 358 | "cell_type": "markdown", 359 | "metadata": {}, 360 | "source": [ 361 | "Symmetry dictates that we use a reflecting boundary condition at the bottom boundary, $r=0$. We will impose outflow boundary conditions at the top and right boundaries.\n", 362 | "\n", 363 | "Next we will write the function for the boundary condition at the left of the domain, where a shock is entering. This function operates on an array called `qbc` (for *q with boundary conditions*) and must fill in the values of the ghost cells at the left boundary of the grid." 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "collapsed": false, 369 | "input": [ 370 | "def shockbc(state,dim,t,qbc,num_ghost):\n", 371 | " \"\"\"\n", 372 | " Incoming shock at left boundary.\n", 373 | " \"\"\"\n", 374 | " p = 5.\n", 375 | " rho = (gamma1 + p*(gamma+1.))/ ((gamma+1.) + gamma1*p)\n", 376 | " v = 1./np.sqrt(gamma) * (p - 1.) / np.sqrt(0.5*((gamma+1.)/gamma) * p+0.5*gamma1/gamma)\n", 377 | " e = 0.5*rho*v**2 + p/gamma1\n", 378 | "\n", 379 | " for i in xrange(num_ghost):\n", 380 | " qbc[0,i,...] = rho\n", 381 | " qbc[1,i,...] = rho*v\n", 382 | " qbc[2,i,...] = 0.\n", 383 | " qbc[3,i,...] = e\n", 384 | " qbc[4,i,...] = 0." 385 | ], 386 | "language": "python", 387 | "metadata": {}, 388 | "outputs": [] 389 | }, 390 | { 391 | "cell_type": "markdown", 392 | "metadata": {}, 393 | "source": [ 394 | "Now the main code to set up the problem:" 395 | ] 396 | }, 397 | { 398 | "cell_type": "code", 399 | "collapsed": false, 400 | "input": [ 401 | "from clawpack import riemann\n", 402 | "from clawpack import pyclaw\n", 403 | "import numpy as np\n", 404 | "\n", 405 | "solver = pyclaw.ClawSolver2D(riemann.euler_5wave_2D)\n", 406 | "solver.step_source=step_Euler_radial\n", 407 | "\n", 408 | "# Boundary conditions\n", 409 | "solver.bc_lower[0]=pyclaw.BC.custom\n", 410 | "solver.bc_upper[0]=pyclaw.BC.extrap\n", 411 | "solver.bc_lower[1]=pyclaw.BC.wall\n", 412 | "solver.bc_upper[1]=pyclaw.BC.extrap\n", 413 | "solver.user_bc_lower=shockbc\n", 414 | "\n", 415 | "# We must also set boundary conditions for the auxiliary variable (r)\n", 416 | "# But in this case the values don't matter\n", 417 | "solver.aux_bc_lower[0]=pyclaw.BC.extrap\n", 418 | "solver.aux_bc_upper[0]=pyclaw.BC.extrap\n", 419 | "solver.aux_bc_lower[1]=pyclaw.BC.extrap\n", 420 | "solver.aux_bc_upper[1]=pyclaw.BC.extrap\n", 421 | "\n", 422 | "# Initialize domain\n", 423 | "mx=320; my=80\n", 424 | "z = pyclaw.Dimension('z',0.0,2.0,mx)\n", 425 | "r = pyclaw.Dimension('r',0.0,0.5,my)\n", 426 | "domain = pyclaw.Domain([z,r])\n", 427 | "num_aux=1\n", 428 | "state = pyclaw.State(domain,solver.num_eqn,num_aux)\n", 429 | "\n", 430 | "state.problem_data['gamma']= gamma\n", 431 | "state.problem_data['gamma1']= gamma1\n", 432 | "\n", 433 | "qinit(state)\n", 434 | "\n", 435 | "rc = state.grid.r.centers\n", 436 | "for j,rcoord in enumerate(rc):\n", 437 | " state.aux[0,:,j] = rcoord\n", 438 | "\n", 439 | "solver.cfl_max = 0.5\n", 440 | "solver.cfl_desired = 0.45\n", 441 | "solver.source_split = 1 # Use first-order splitting for the source term\n", 442 | "\n", 443 | "claw = pyclaw.Controller()\n", 444 | "claw.keep_copy = True\n", 445 | "claw.output_format = None\n", 446 | "claw.tfinal = 0.6\n", 447 | "claw.solution = pyclaw.Solution(state,domain)\n", 448 | "claw.solver = solver\n", 449 | "claw.num_output_times = 60" 450 | ], 451 | "language": "python", 452 | "metadata": {}, 453 | "outputs": [] 454 | }, 455 | { 456 | "cell_type": "code", 457 | "collapsed": false, 458 | "input": [ 459 | "claw.run()" 460 | ], 461 | "language": "python", 462 | "metadata": {}, 463 | "outputs": [] 464 | }, 465 | { 466 | "cell_type": "code", 467 | "collapsed": false, 468 | "input": [ 469 | "from matplotlib import animation\n", 470 | "import matplotlib.pyplot as plt\n", 471 | "from clawpack.visclaw.JSAnimation import IPython_display\n", 472 | "import numpy as np\n", 473 | "\n", 474 | "fig = plt.figure(figsize=[8,4])\n", 475 | "\n", 476 | "frame = claw.frames[0]\n", 477 | "rho = frame.q[0,:,:]\n", 478 | "\n", 479 | "x, y = frame.state.grid.c_centers \n", 480 | "\n", 481 | "# This essentially does a pcolor plot, but it returns the appropriate object\n", 482 | "# for use in animation. See http://matplotlib.org/examples/pylab_examples/pcolor_demo.html.\n", 483 | "# Note that it's necessary to transpose the data array because of the way imshow works.\n", 484 | "im = plt.imshow(rho.T, cmap='Greys', vmin=rho.min(), vmax=rho.max(),\n", 485 | " extent=[x.min(), x.max(), y.min(), y.max()],\n", 486 | " interpolation='nearest', origin='lower')\n", 487 | "\n", 488 | "def fplot(frame_number):\n", 489 | " frame = claw.frames[frame_number]\n", 490 | " rho = frame.q[0,:,:]\n", 491 | " im.set_data(rho.T)\n", 492 | " return im," 493 | ], 494 | "language": "python", 495 | "metadata": {}, 496 | "outputs": [] 497 | }, 498 | { 499 | "cell_type": "code", 500 | "collapsed": false, 501 | "input": [ 502 | "animation.FuncAnimation(fig, fplot, frames=len(claw.frames), interval=20)" 503 | ], 504 | "language": "python", 505 | "metadata": {}, 506 | "outputs": [] 507 | }, 508 | { 509 | "cell_type": "heading", 510 | "level": 2, 511 | "metadata": {}, 512 | "source": [ 513 | "Mapped grids" 514 | ] 515 | }, 516 | { 517 | "cell_type": "markdown", 518 | "metadata": {}, 519 | "source": [ 520 | "So far we have solved problems on uniform cartesian grids. For many problems, the natural domain is not cartesian; in other problems, there may be internal interfaces not aligned with a cartesian grid. PyClaw can be used on any domain for which there exists a mapping to a cartesian domain. Such mappings are surprisingly flexible, and can be used to solve in disks and spheres, as well as many more complicated geometries. Solving on a mapped grid requires the use of a Riemann solver that takes the geometry into account." 521 | ] 522 | }, 523 | { 524 | "cell_type": "heading", 525 | "level": 2, 526 | "metadata": {}, 527 | "source": [ 528 | "Other systems of equations" 529 | ] 530 | }, 531 | { 532 | "cell_type": "markdown", 533 | "metadata": {}, 534 | "source": [ 535 | "Clawpack includes Riemann solvers for the following systems of equations:\n", 536 | "- Acoustics in 1, 2, and 3 dimensions\n", 537 | "- Constant and variable-coefficient advection in 1 and 2 dimensions\n", 538 | "- Burgers equation\n", 539 | "- The Euler equations of compressible fluid dynamics in 1, 2, and 3 dimensions\n", 540 | "- The reactive Euler equations in 1 dimension\n", 541 | "- Nonlinear elasticity in 1 dimension\n", 542 | "- Linear elasticity in 2 dimensions\n", 543 | "- The shallow water equations, in 1 and 2 dimensions, including variable bathymetry\n", 544 | "- Traffic flow in 1 dimension\n", 545 | "\n", 546 | "Additional solvers are being developed by users all the time; if you don't see the system that interests you in this list, it's worth asking on the [Clawpack Users Google group](claw-users@groups.google.com) to see if someone has developed a solver already. You can also write your own Riemann solver, but that is beyond the scope of this course." 547 | ] 548 | }, 549 | { 550 | "cell_type": "heading", 551 | "level": 2, 552 | "metadata": {}, 553 | "source": [ 554 | "More examples" 555 | ] 556 | }, 557 | { 558 | "cell_type": "markdown", 559 | "metadata": {}, 560 | "source": [ 561 | "I think the simplest way to learn more about using PyClaw is through examples. Here are a few more:\n", 562 | "\n", 563 | "- [Acoustics](http://nbviewer.ipython.org/8332861) - An introductory example similar to Lesson 4 of this course, but using the acoustics equations.\n", 564 | "- [Quadrants](http://nbviewer.ipython.org/8333043) - Another 2-dimensional Euler equations example.\n", 565 | "- [Traffic](http://nbviewer.ipython.org/348ea7a9ae8b4a558c5b) - Solving the LWR traffic model with PyClaw.\n", 566 | "- [Stegotons](http://nbviewer.ipython.org/8554686) - Illustration of a curious class of solitary waves that arise in periodic nonlinear wave problems (using a nonlinear elasticity model). This example includes a custom boundary condition and a demonstration of how to use a `before_step` function.\n", 567 | "- [Wave runup on a beach](http://nbviewer.ipython.org/github/ketch/shallow_water_periodic_bathymetry/blob/master/pyclaw/beach.ipynb) - using the 1D shallow water equations\n", 568 | "- [Shallow water solitary waves](http://nbviewer.ipython.org/9250942) - in 2D, with periodic bathymetry" 569 | ] 570 | }, 571 | { 572 | "cell_type": "markdown", 573 | "metadata": {}, 574 | "source": [ 575 | "Many additional examples are included in the PyClaw examples module." 576 | ] 577 | }, 578 | { 579 | "cell_type": "code", 580 | "collapsed": false, 581 | "input": [ 582 | "from clawpack.pyclaw import examples" 583 | ], 584 | "language": "python", 585 | "metadata": {}, 586 | "outputs": [] 587 | }, 588 | { 589 | "cell_type": "code", 590 | "collapsed": false, 591 | "input": [ 592 | "examples." 593 | ], 594 | "language": "python", 595 | "metadata": {}, 596 | "outputs": [] 597 | } 598 | ], 599 | "metadata": {} 600 | } 601 | ] 602 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HyperPython 2 | 3 | 4 | A brief and practical introduction to the solution of hyperbolic conservation laws. 5 | This set of IPython notebooks was originally prepared for a 1-day tutorial that I 6 | taught in Gyor, Hungary in May 2014 as part of the 7 | [Workshop on Design, Simulation, Optimization and Control of Green Vehicles and Transportation](http://jkk.sze.hu/en_GB/program). 8 | 9 | ## Installation 10 | 11 | The easiest way to run these is to create a free account on [SageMathCloud](cloud.sagemath.org). Then create a new project, click "new file", type https://github.com/ketch/HyperPython.git into the box and hit enter. That's it! 12 | 13 | To run the notebooks on your own computer, you'll need: 14 | 15 | - Python >=2.7 16 | - IPython >=1.2.0 17 | - Numpy 18 | - Matplotlib 19 | - Clawpack >=5.1 20 | 21 | The last four can all be installed using [pip](http://pip.readthedocs.org/en/latest/installing.html): 22 | 23 | pip install ipython 24 | pip install numpy 25 | pip install matplotlib 26 | pip install clawpack 27 | 28 | Then clone or download the repository. 29 | 30 | ## Running the notebooks 31 | 32 | To start the course, do 33 | 34 | git clone https://github.com/ketch/HyperPython.git 35 | cd HyperPython 36 | ipython notebook 37 | 38 | and click on Lesson 0. 39 | 40 | ## Acknowledgment 41 | 42 | The design of the notebooks and their organization was inspired by [Lorena Barba](http://lorenabarba.com/)'s excellent 43 | [AeroPython course](https://github.com/barbagroup/AeroPython). 44 | 45 | ## Contributing 46 | 47 | If you spot any errors or would like to make improvements, pull requests are welcome! 48 | 49 | Content provided under a Creative Commons Attribution license, CC-BY 4.0; code under MIT License. 50 | 51 | (c)2014 David I. Ketcheson 52 | -------------------------------------------------------------------------------- /custom.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 111 | -------------------------------------------------------------------------------- /figures/FV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/FV.png -------------------------------------------------------------------------------- /figures/LWR-Velocity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/LWR-Velocity.png -------------------------------------------------------------------------------- /figures/entropy_condition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/entropy_condition.png -------------------------------------------------------------------------------- /figures/entropy_condition_rarefaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/entropy_condition_rarefaction.png -------------------------------------------------------------------------------- /figures/entropy_condition_shock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/entropy_condition_shock.png -------------------------------------------------------------------------------- /figures/finite_volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/finite_volume.png -------------------------------------------------------------------------------- /figures/ghost-cell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/ghost-cell.png -------------------------------------------------------------------------------- /figures/linear_reconstruction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/linear_reconstruction.png -------------------------------------------------------------------------------- /figures/shock_diagram_traffic_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/shock_diagram_traffic_a.png -------------------------------------------------------------------------------- /figures/shock_diagram_traffic_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ketch/HyperPython/174e0933ed9e468860daade44ddc1e5b69119657/figures/shock_diagram_traffic_b.png -------------------------------------------------------------------------------- /util/ianimate.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from matplotlib import animation 3 | from clawpack.visclaw.JSAnimation import IPython_display 4 | 5 | def ianimate(xc,q,bounds=(0,1,-0.1,1.1)): 6 | fig = plt.figure(figsize=(8,4)) 7 | ax = plt.axes() 8 | im, = ax.plot([], [],linewidth=2) 9 | plt.axis(bounds) 10 | 11 | def fplot(i): 12 | im.set_data(xc, q[i]) 13 | return im, 14 | 15 | return animation.FuncAnimation(fig, fplot, frames=len(q)) 16 | -------------------------------------------------------------------------------- /util/lxf_rk2.py: -------------------------------------------------------------------------------- 1 | # Solution to exercise in Lesson 3. 2 | 3 | 4 | import sys 5 | sys.path.append('./util') 6 | from ianimate import ianimate 7 | import numpy as np 8 | 9 | def f(q): 10 | return q*(1.0-q) 11 | 12 | def minmod(a,b): 13 | return 0.5*(np.sign(a)+np.sign(b))*np.minimum(np.abs(a),np.abs(b)) 14 | 15 | m = 100 # of points 16 | dx = 1./m # Size of 1 grid cell 17 | x = np.arange(-3*dx/2, 1.+5*dx/2, dx) 18 | 19 | t = 0. # Initial time 20 | T = 0.5 # Final time 21 | dt = 0.4 * dx # Time step 22 | 23 | Q = 0.9*np.exp(-100*(x-0.5)**2) 24 | Qnew = np.zeros(m+4) 25 | Qstar = np.zeros(m+4) 26 | sigma = np.zeros(m+4) 27 | 28 | dQplus = np.zeros(m+4) 29 | dQminus = np.zeros(m+4) 30 | F = np.zeros(m+4) 31 | QQ = [Q] 32 | 33 | while t < T: 34 | 35 | dQplus[:-1] = Q[1:]-Q[:-1] 36 | dQminus[1:] = Q[1:]-Q[:-1] 37 | sigma = minmod(dQplus,dQminus)/dx 38 | 39 | qplus = Q[1:-1] - sigma[1:-1] * dx/2.0 # q^+_{i-1/2} 40 | qminus = Q[:-2] + sigma[:-2] * dx/2.0 # q^-_{i-1/2} 41 | alpha = np.maximum(np.abs(1.-2.*qplus),np.abs(1.-2.*qminus)) 42 | F[1:-1] = 0.5*(f(qplus)+f(qminus) - alpha*dx/dt*(qplus-qminus) )# F_{i-1/2} 43 | Qstar[2:-2] = Q[2:-2] - dt/dx*(F[3:-1]-F[2:-2]) 44 | 45 | Qstar[0:2] = Qstar[2] 46 | Qstar[-2:] = Qstar[-3] 47 | 48 | dQplus[:-1] = Qstar[1:]-Qstar[:-1] 49 | dQminus[1:] = Qstar[1:]-Qstar[:-1] 50 | sigma = minmod(dQplus,dQminus)/dx 51 | 52 | qplus = Qstar[1:-1] - sigma[1:-1] * dx/2.0 # q^+_{i-1/2} 53 | qminus = Qstar[:-2] + sigma[:-2] * dx/2.0 # q^-_{i-1/2} 54 | alpha = np.maximum(np.abs(1.-2.*qplus),np.abs(1.-2.*qminus)) 55 | F[1:-1] = 0.5*(f(qplus)+f(qminus) - alpha*dx/dt*(qplus-qminus) )# F_{i-1/2} 56 | 57 | Qnew[2:-2] = 0.5*Q[2:-2] + 0.5*(Qstar[2:-2] - dt/dx*(F[3:-1]-F[2:-2])) 58 | 59 | 60 | Q = Qnew.copy() 61 | Q[0:2] = Q[2] 62 | Q[-2:] = Q[-3] 63 | t = t + dt 64 | QQ.append(Q) 65 | 66 | ianimate(x,QQ) 67 | --------------------------------------------------------------------------------