{ "metadata": { "name": "" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Evaluating Burgers Equation with different CFD Schemes\n", "===\n", "\n", "We've already examined Burgers equation in both 1D and 2D ([Step 4](http://nbviewer.ipython.org/urls/github.com/barbagroup/CFDPython/blob/master/lessons/05_Step_4.ipynb) and [Step 8](http://nbviewer.ipython.org/urls/github.com/barbagroup/CFDPython/blob/master/lessons/10_Step_8.ipynb), respectively). Here we are going to restrict ourselves to the 1D Burgers equation and examine the role that different schemes play in discretizing non-linear 1st order hyperbolic equations. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Consider the 1D Burgers equation:\n", "\n", "$$\\frac{\\partial u}{\\partial t} = -u \\frac{\\partial u}{\\partial x}$$\n", "\n", "We want to represent this in conservative form so that we can better deal with potential shocks, which gives us:\n", "\n", "$$\\frac{\\partial u}{\\partial t} = - \\frac{\\partial }{\\partial x}(\\frac{u^2}{2})$$\n", "\n", "We can also write this as:\n", "\n", "$$\\frac{\\partial u}{\\partial t} = - \\frac{\\partial E}{\\partial x}$$\n", "\n", "if we take $E = \\frac{u^2}{2}$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Initial Conditions\n", "-----\n", "For each scheme, the initial conditions are \n", "\n", "$$u(x,0) = \\left\\{ \\begin{array}{cc}\n", "1 & 0 \\leq x < 2 \\\\\n", "0 & 2 \\leq x \\leq 4 \\\\ \\end{array} \\right.$$\n", "\n", "Assignment\n", "----\n", "First investigate the behavior of the Burgers function with $\\Delta t = \\Delta x = 1$, that is, with a Courant number of $\\frac{\\Delta t}{\\Delta x}=1$, then change the Courant number to $\\frac{\\Delta t}{\\Delta x}=0.5$. \n", "\n", "Also experiment with different mesh sizes to see what kind of behavior each scheme exhibits under different circumstances. \n", "\n", "Helper Code\n", "---\n", "Start by importing `numpy` and `matplotlib.pyplot`. The initial conditions are also defined below, along with a few helper functions to return values that are frequently required. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "%matplotlib inline\n", "import numpy\n", "from matplotlib import pyplot\n", "\n", "\n", "#Basic initial condition parameters\n", "#defining grid size, time steps, CFL condition, etc...\n", "nx = 81\n", "nt = 70\n", "sigma = 1\n", "dx = 4.0/nx\n", "dt = sigma*dx\n", "\n", "#Define a quick function to set up the initial square wave condition\n", "def u_ic():\n", " u = numpy.ones(nx)\n", " u[(nx-1)/2:]=0\n", " return u\n", "\n", "#Define two lambda functions to help with common operations encountered\n", "#with the Euler equations\n", "utoE = lambda u: (u/2)**2\n", "utoA = lambda u: u**2" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "##Lax-Friedrichs\n", "\n", "Lax-Friedrichs is an explicit, 1st order scheme, using forward-difference in time and central-difference in space. However, notice that the $u^n_i$ value is calculated as the average of its adjacent cells. \n", "\n", "$$\\frac{u_i^{n+1}-\\frac{1}{2}(u^n_{i+1}+u^n_{i-1})}{\\Delta t} = -\\frac{E^n_{i+1}-E^n_{i-1}}{2 \\Delta x}$$\n", "\n", "Transposing this to solve for $u^{n+1}_i$ yields:\n", "\n", "$$u_i^{n+1} = \\frac{1}{2}(u^n_{i+1}+u^n_{i-1}) - \\frac{\\Delta t}{2 \\Delta x}(E^n_{i+1}-E^n_{i-1})$$\n", "\n", "This scheme is implemented in the function below. All the schemes in this notebook are wrapped in their own functions to help with displaying animations of the results. This is also good coding practice!\n", "\n", "In order to display our animations, we're going to hold the results of each timestep in `u` in a 2D array. So our array `un` will have `nt` rows and `nx` columns." ] }, { "cell_type": "code", "collapsed": false, "input": [ "def laxfriedrichs(u, nt, dt, dx):\n", " #initialize our results array with dimensions nt by nx\n", " un = numpy.zeros((nt,len(u))) \n", " #copy the initial u array into each row of our new array\n", " un[:,:] = u.copy() \n", " \n", " '''\n", " Now, for each timestep, we're going to calculate u^n+1, \n", " then set the value of u equal to u^n+1 so we can calculate \n", " the next iteration. For every timestep, the entire vector\n", " u^n is saved in a single row of our results array un.\n", " '''\n", " for i in range(1,nt):\n", " E = utoE(u)\n", " un[i,1:-1] = .5*(u[2:]+u[:-2]) - dt/(2*dx)*(E[2:]-E[:-2])\n", " un[i,0] = 1\n", " u = un[i].copy()\n", " \n", " return un" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above cell just defines the function which can execute the Lax-Friedrichs scheme, now it needs to be called. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##Lax Friedrichs Test 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's try to display the results of our Lax Friedrichs scheme. First, we need to generate the results by calling our function `laxfriedrics`. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "u = u_ic() #make sure that u is set to our expected initial conditions\n", "un = laxfriedrichs(u,nt,dt,dx)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can ask NumPy to tell us the shape of our new array `un` and see if we have the dimensions we're expecting. We should have `nt` rows and `nx` columns. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "un.shape" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 4, "text": [ "(70, 81)" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Looks good! Now in order to view the results in an animation, we'll import a few extra libraries. The `JSAnimation` library is not part of the standard IPython install (yet!) but you can click [here](http://dummy) for instructions on how to install it." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from matplotlib import animation\n", "from JSAnimation.IPython_display import display_animation" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 5 }, { "cell_type": "markdown", "metadata": {}, "source": [ "To view a nice interactive animation of our results, we'll need to define a few parameters.\n", "\n", "* First, we need a `figure`, which we'll also make a little larger than the default size for easier viewing. \n", "* We also want to define an axis in the figure with some plotting limits. \n", "* Then, we want to initialize a `line` object that will be a part of our plot, but we don't need to give it any data yet, so we'll just assign it empty lists for its `x` and `y` coordinates.\n", "\n", "Now we need a functions to \"draw\" each frame of our animation:\n", "\n", "* `animate` takes a single row of our results array `un` and then plots a line.\n", "\n", "Now to set up the `animation` object, we pass is a few values:\n", "\n", "* `fig` is the figure we've already defined and we're telling `FuncAnimation` to use that to draw in\n", "* `animate` is the function we're using to actually plot our results\n", "* `frames=un` tells `FuncAnimation` that it should use each row of our array `un` to generate sequential frames of the animation.\n", "* `interval=50` sets the default amount of time in milliseconds between frames\n", "\n", "You'll notice a set of controls below the animation -- you can use the **+** and **-** buttons to speed up or slow down the animation." ] }, { "cell_type": "code", "collapsed": false, "input": [ "fig = pyplot.figure();\n", "ax = pyplot.axes(xlim=(0,4),ylim=(-.5,2));\n", "line, = ax.plot([],[],lw=2);\n", "\n", "def animate(data):\n", " x = numpy.linspace(0,4,nx)\n", " y = data\n", " line.set_data(x,y)\n", " return line,\n", "\n", "anim = animation.FuncAnimation(fig, animate, frames=un, interval=50)\n", "display_animation(anim, default_mode='loop')" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n", "\n", "
\n", " \n", "
\n", " \n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", " Once \n", " Loop \n", " Reflect \n", "
\n", "
\n", "\n", "\n", "\n" ], "metadata": {}, "output_type": "pyout", "prompt_number": 6, "text": [ "" ] } ], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "##Lax Friedrichs Test 2\n", "\n", "Now we can run the same scheme but we'll change our CFL number so that $\\frac{\\Delta t}{\\Delta x}=0.5$" ] }, { "cell_type": "code", "collapsed": false, "input": [ "dt = .5*dx\n", "u = u_ic() ##Reset our initial conditions (the square wave)\n", "un = laxfriedrichs(u,nt,dt,dx)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have our new data set, we can set up our next animation. We'll use all the same code from above, except now our `animate` function is already defined, so we can just set up a figure as before, then call `FuncAnimation`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "fig = pyplot.figure();\n", "ax = pyplot.axes(xlim=(0,4),ylim=(-.5,2));\n", "line, = ax.plot([],[],lw=2);\n", "\n", "anim = animation.FuncAnimation(fig, animate, frames=un, interval=50)\n", "display_animation(anim, default_mode='once')" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n", "\n", "
\n", " \n", "
\n", " \n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", " Once \n", " Loop \n", " Reflect \n", "
\n", "
\n", "\n", "\n", "\n" ], "metadata": {}, "output_type": "pyout", "prompt_number": 8, "text": [ "" ] } ], "prompt_number": 8 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice the strange \"step\" behavior on the leading edge of the wave? Check out a separate notebook [here](http://nbviewer.ipython.org/github/barbagroup/CFDPython/blob/master/lessons/19_Odd_Even_Decoupling.ipynb?create=1) to learn about \"Odd-Even Decoupling.\"\n", "\n", "Also take a look at how the change in CFL number affected the wave." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##Lax-Wendroff Scheme\n", "\n", "The Lax-Wendroff scheme starts with taking the Taylor series expansion about $u^{n+1}$:\n", "\n", "$$u^{n+1} = u^n + u_t \\Delta t + \\frac{(\\Delta t)^2}{2}u_{tt} + ...$$\n", "\n", "Now substitute spatial derivatives for the time derivatives\n", "\n", "$$E_t = -AE_x$$ \n", "\n", "$$u_t = -E_x$$\n", "\n", "where $A = \\frac{\\partial E}{\\partial u} = u$ is the Jacobian for Burgers equation.\n", "\n", "This, when plugged into the full Burgers equation, should yield\n", "\n", "$$\\frac{u_i^{n+1} - u_i^n}{\\Delta t} = \\frac{-E^n_{i+1}-E^n_{i-1}}{2 \\Delta x} + \\frac{\\Delta t}{2} \\left(\\frac{(A \\frac{\\partial E}{\\partial x})^n_{i+\\frac{1}{2}}-(A \\frac{\\partial E}{\\partial x})^n_{i-\\frac{1}{2}}}{\\Delta x}\\right)$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Approximate the last term in the above equation as:\n", "$$\\left(\\frac{(A \\frac{\\partial E}{\\partial x})^n_{i+\\frac{1}{2}}-(A \\frac{\\partial E}{\\partial x})^n_{i-\\frac{1}{2}}}{\\Delta x}\\right) \\approx \\frac{A^n_{i+\\frac{1}{2}}\\left(\\frac{E^n_{i+1}-E^n_{i}}{\\Delta x}\\right)-A^n_{i-\\frac{1}{2}}\\left(\\frac{E^n_i-E^n_{i-1}}{\\Delta x}\\right)}{\\Delta x}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And evaluate the Jacobian at the midpoints:\n", "\n", "$$\\frac{\\frac{1}{2 \\Delta x}(A^n_{i+1}+A^n_i)(E^n_{i+1}-E^n_i)-\\frac{1}{2 \\Delta x}(A^n_i+A^n_{i-1})(E^n_i-E^n_{i-1})}{\\Delta x}$$\n", "\n", "So our equation now reads:\n", "\n", "$$\\frac{u_i^{n+1} - u_i^n}{\\Delta t} = \\frac{\\frac{1}{2 \\Delta x}(A^n_{i+1}+A^n_i)(E^n_{i+1}-E^n_i)-\\frac{1}{2 \\Delta x}(A^n_i+A^n_{i-1})(E^n_i-E^n_{i-1})}{\\Delta x}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We substitute $A = u$ and then solve for $u^{n+1}_i$, the Lax-Wendroff Scheme results:\n", "\n", "\n", "$$u^{n+1}_i = u^n_i - \\frac{\\Delta t}{2 \\Delta x}(E^n_{i+1}-E^n_{i-1})+$$\n", "$$\\frac{\\Delta t^2}{4 \\Delta x^2}[(u^n_{i+1}+u^n_i)(E^n_{i+1}-E^n_i)-(u^n_i+u^n_{i-1})(E^n_i-E^n_{i-1})]$$\n", "\n", "\n", "Lax-Wendroff is a little bit long. Remember that you can use \\ slashes to split up a statement across several lines. This can help make code easier to parse (and also easier to debug!). " ] }, { "cell_type": "code", "collapsed": false, "input": [ "def laxwendroff(u, nt, dt, dx):\n", " un = numpy.zeros((nt,len(u)))\n", " un[:] = u.copy()\n", " \n", " for i in range(1,nt):\n", " E = utoE(u) \n", " un[i,1:-1] = u[1:-1] - dt/(2*dx) * (E[2:]-E[:-2]) + dt**2/(4*dx**2) *\\\n", " ((u[2:]+u[1:-1])*(E[2:]-E[1:-1]) -\\\n", " (u[1:-1]+u[:-2])*(E[1:-1]-E[:-2]))\n", " un[i,0]=1\n", " \n", " u = un[i].copy() \n", " \n", " return un" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 9 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that's we've defined a function for the Lax Wendroff scheme, we can use the seem procedure as above to animate and view our results. \n", "\n", "###With CFL = 1" ] }, { "cell_type": "code", "collapsed": false, "input": [ "u = u_ic()\n", "sigma = 1\n", "dt = sigma*dx\n", "un = laxwendroff(u,nt,dt,dx)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 10 }, { "cell_type": "code", "collapsed": false, "input": [ "fig = pyplot.figure();\n", "ax = pyplot.axes(xlim=(0,4),ylim=(-.5,2));\n", "line, = ax.plot([],[],lw=2);\n", "\n", "anim = animation.FuncAnimation(fig, animate, frames=un, interval=50)\n", "display_animation(anim, default_mode='once')" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n", "\n", "
\n", " \n", "
\n", " \n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", " Once \n", " Loop \n", " Reflect \n", "
\n", "
\n", "\n", "\n", "\n" ], "metadata": {}, "output_type": "pyout", "prompt_number": 11, "text": [ "" ] } ], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### With CFL = 0.5" ] }, { "cell_type": "code", "collapsed": false, "input": [ "u = u_ic()\n", "sigma = .5\n", "dt = sigma*dx\n", "un = laxwendroff(u,nt,dt,dx)\n", "\n", "fig = pyplot.figure();\n", "ax = pyplot.axes(xlim=(0,4),ylim=(-.5,2));\n", "line, = ax.plot([],[],lw=2);\n", "\n", "anim = animation.FuncAnimation(fig, animate, frames=un, interval=50)\n", "display_animation(anim, default_mode='once')" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n", "\n", "
\n", " \n", "
\n", " \n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", " Once \n", " Loop \n", " Reflect \n", "
\n", "
\n", "\n", "\n", "\n" ], "metadata": {}, "output_type": "pyout", "prompt_number": 12, "text": [ "" ] } ], "prompt_number": 12 }, { "cell_type": "markdown", "metadata": {}, "source": [ "How do the oscillations at the shock front vary with changes to the CFL condition?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##MacCormack Scheme\n", "\n", "$$u^*_i = u^n_i - \\frac{\\Delta t}{\\Delta x} (E^n_{i+1}-E^n_{i}) \\ \\ \\ \\ \\ \\ (predictor)$$\n", "\n", "$$u^{n+1}_i = \\frac{1}{2} (u^n_i + u^*_i - \\frac{\\Delta t}{\\Delta x} (E^*_i - E^{*}_{i-1})) \\ \\ \\ \\ \\ \\ (corrector)$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The MacCormack scheme is a 'predictor-corrector' method. It first calculates a rough estimate of the next timestep and then smooths the predicted value in the second step. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "def maccormack(u, nt, dt, dx):\n", " un = numpy.zeros((nt,len(u)))\n", " ustar = numpy.empty_like(u)\n", " un[:] = u.copy()\n", " ustar = u.copy()\n", " \n", " for i in range(1,nt):\n", " E = utoE(u)\n", " ustar[:-1] = u[:-1] - dt/dx * (E[1:]-E[:-1])\n", " Estar = utoE(ustar)\n", " un[i,1:] = .5 * (u[1:]+ustar[1:] - dt/dx * (Estar[1:] - Estar[:-1]))\n", " u = un[i].copy()\n", " \n", " return un" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 13 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### CFL = 1" ] }, { "cell_type": "code", "collapsed": false, "input": [ "u = u_ic()\n", "sigma = 1\n", "dt = sigma*dx\n", "\n", "un = maccormack(u,nt,dt,dx)\n", "\n", "fig = pyplot.figure();\n", "ax = pyplot.axes(xlim=(0,4),ylim=(-.5,2));\n", "line, = ax.plot([],[],lw=2);\n", "\n", "anim = animation.FuncAnimation(fig, animate, frames=un, interval=50)\n", "display_animation(anim, default_mode='once')" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n", "\n", "
\n", " \n", "
\n", " \n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", " Once \n", " Loop \n", " Reflect \n", "
\n", "
\n", "\n", "\n", "\n" ], "metadata": {}, "output_type": "pyout", "prompt_number": 14, "text": [ "" ] } ], "prompt_number": 14 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### CFL = 0.5" ] }, { "cell_type": "code", "collapsed": false, "input": [ "u = u_ic()\n", "sigma = 0.5\n", "dt = sigma*dx\n", "\n", "un = maccormack(u,nt,dt,dx)\n", "\n", "fig = pyplot.figure();\n", "ax = pyplot.axes(xlim=(0,4),ylim=(-.5,2));\n", "line, = ax.plot([],[],lw=2);\n", "\n", "anim = animation.FuncAnimation(fig, animate, frames=un, interval=50)\n", "display_animation(anim, default_mode='once')" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n", "\n", "
\n", " \n", "
\n", " \n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", " Once \n", " Loop \n", " Reflect \n", "
\n", "
\n", "\n", "\n", "\n" ], "metadata": {}, "output_type": "pyout", "prompt_number": 15, "text": [ "" ] } ], "prompt_number": 15 }, { "cell_type": "markdown", "metadata": {}, "source": [ "##Beam & Warming Implicit\n", "\n", "This is our first look at an implicit scheme. In the previous schemes used, it is possible to cleanly factor out some $u^{n+1}_i$ and solve for that in terms of $u^n_i$. When each term used is from the previous time step, that is an explicit method. \n", "\n", "The Beam & Warming scheme does not cleanly separate out all of the $u^{n+1}$ terms and it is computed in a different way from the other examples. \n", "\n", "Taking a Taylor series expansion of $u^{n+1}_i$:\n", "\n", "\n", "$$u^{n+1}_i = u^n_i + \\frac{1}{2} \\left[\\left. \\frac{\\partial u}{\\partial t} \\right|^{n}_i + \\left. \\frac{\\partial u}{\\partial t} \\right|^{n+1}_i \\right] \\Delta t + O(\\Delta t^3)$$\n", "\n", "For Burgers, $\\frac{\\partial u}{\\partial t} = -\\frac{ \\partial E}{\\partial x}$ , so:\n", "\n", "$$\\frac{u^{n+1}_i - u^n_i}{\\Delta t} = - \\frac{1}{2} \\left[ \\left.\\frac{\\partial E}{\\partial x} \\right|^n_i + \\left. \\frac{\\partial E}{\\partial x}\\right|^{n+1}_i \\right] + O(\\Delta t^2)$$\n", "\n", "$$\\therefore \\frac{u^{n+1}_i - u^n_i}{\\Delta t} = -\\frac{1}{2} \\left( \\left.\\frac{\\partial E}{\\partial x}\\right|^n_i + \\left.\\frac{\\partial E}{\\partial x}\\right|^n_i + \\frac{\\partial}{\\partial x} \\left[ A(u^{n+1}_i - u^n_i)\\right] \\right)$$\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using 2nd order central difference for the Jacobian A yields:\n", "\n", "$$\\frac{\\partial}{\\partial x} \\left[A(u^{n+1}_i-u^n_i)\\right] = $$\n", "\n", "$$\\frac{1}{2 \\Delta x} \\left(A^n_{i+1} u^{n+1}_{i+1} - A^n_{i-1} u^{n+1}_{i-1} \\right) - \\frac{1}{2 \\Delta x} \\left(A^n_{i+1}u^n_{i+1} - A^n_{i-1}u^n_{i-1} \\right)$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "which results in a tri-diagonal system:\n", "\n", "$$- \\frac{\\Delta t}{4 \\Delta x} \\left( A^n_{i-1} u^{n+1}_{i-1}\\right) + u^{n+1}_i + \\frac{\\Delta t}{4 \\Delta x} \\left( A^n_{i+1} u^{n+1}_{i+1} \\right) = $$\n", "\n", "$$ = u^n_i - \\frac{1}{2} \\frac{\\Delta t}{\\Delta x} \\left( E^n_{i+1} - E^n_{i-1} \\right) + \\frac{\\Delta t}{4 \\Delta x} \\left( A^n_{i+1} u^n_{i+1} - A^n_{i-1} u^n_{i-1} \\right) $$\n", "\n", "If you are unfamiliar with tri-diagonal systems, check out the [Wikipedia page](http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm) for a brief explanation. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "from scipy import linalg\n", "\n", "def beamwarming(u, nt, dt, dx):\n", " ##Tridiagonal setup##\n", " a = numpy.zeros_like(u)\n", " b = numpy.ones_like(u)\n", " c = numpy.zeros_like(u)\n", " d = numpy.zeros_like(u)\n", " \n", " un = numpy.zeros((nt,len(u)))\n", " un[:]=u.copy()\n", " \n", " for n in range(1,nt): \n", " u[0] = 1\n", " E = utoE(u)\n", " au = utoA(u)\n", " \n", " a[0] = -dt/(4*dx)*u[0]\n", " a[1:] = -dt/(4*dx)*u[0:-1]\n", " a[-1] = -dt/(4*dx)*u[-1]\n", " \n", " #b is all ones\n", " \n", " c[:-1] = dt/(4*dx)*u[1:]\n", " \n", " d[1:-1] = u[1:-1]-.5*dt/dx*(E[2:]-E[0:-2])+dt/(4*dx)*(au[2:]-au[:-2])\n", " \n", " ###subtract a[0]*LHS B.C to 'fix' thomas algorithm\n", " d[0] = u[0] - .5*dt/dx*(E[1]-E[0])+dt/(4*dx)*(au[1]-au[0]) - a[0] \n", " \n", " ab = numpy.matrix([c,b,a])\n", " u = linalg.solve_banded((1,1), ab, d)\n", " u[0]=1\n", " un[n] = u.copy() \n", " return un" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 16 }, { "cell_type": "markdown", "metadata": {}, "source": [ "###Why does the Thomas algorithm need to be 'fixed'?\n", "\n", "If you examine the [Wikipedia page](http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm), it explains that tridiagonal systems of equations have the form\n", "\n", "$$a_i x_{i-1} + b_i x_i + c_i x_{i+1} = d_i$$\n", "\n", "where $a_1 = 0$ and $c_n = 0$ so that the system of equations can be written out as follows\n", "\n", "$$\\begin{bmatrix}\n", "b_1 & c_1 & & & 0\\\\\n", "a_2 & b_2 & c_2 & & \\\\\n", " & a_3 & b_3 & \\ddots & \\\\\n", " & & \\ddots & \\ddots & c_{n-1} \\\\\n", "0 & & & a_n & b_n \n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "x_1 \\\\\n", "x_2 \\\\\n", "x_3 \\\\\n", "\\vdots \\\\\n", "x_n\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "d_1 \\\\\n", "d_2 \\\\\n", "d_3 \\\\\n", "\\vdots \\\\\n", "d_n\n", "\\end{bmatrix}\n", "$$\n", "\n", "** But ** what if $a_1 \\neq 0$? And what about boundary conditions?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "###Modified Thomas Algorithm Matrix\n", "\n", "To make use of the Thomas Algorithm, we want to apply the algorithm only to the internal elements of our problem and not to the boundary elements. Take the answer to be a vector spanning from $u_0$ to $u_{n+1}$\n", "\n", "$$u_0\\ \\ \\ u_1\\ \\ \\ u_2\\ \\ \\ \\dots \\ \\ \\ u_n \\ \\ \\ u_{n+1}$$\n", "\n", "where $u_0 = B_0$ and $u_{n+1} = B_{n+1}$ and where $B_0$ and $B_{n+1}$ are general boundary conditions. Then $[u_1 \\ \\ \\dots\\ \\ u_n]$ correspond to $[x_1 \\ \\ \\dots \\ \\ x_n]$\n", "\n", "Now we can write down a modified tri-diagonal system which accounts for $a_1 \\neq 0$ and boundary conditions\n", "\n", "$$\\begin{bmatrix}\n", "1 & 0 & 0 & \\dots & &0 \\\\\n", "a_1 & b_1 & c_1 & & & \\vdots\\\\\n", "0 & a_2 & b_2 & c_2 & & \\\\\n", "\\vdots& & a_3 & b_3 & \\ddots & \\\\\n", "& & & \\ddots & \\ddots & c_n\\\\\n", "0 & &\\dots & &0 & 1\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "u_0 \\\\\n", "u_1 \\\\\n", "u_2 \\\\\n", "u_3 \\\\\n", "\\vdots \\\\\n", "u_n \\\\\n", "u_{n+1}\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "B_0 \\\\\n", "d_1 \\\\\n", "d_2 \\\\\n", "d_3 \\\\\n", "\\vdots \\\\\n", "d_n \\\\\n", "B_{n+1}\n", "\\end{bmatrix}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Examining this system, you should note that the boundary conditions are properly observed, as $u_0 = B_0$ and $u_{n+1} = B_{n+1}$. \n", "All of the internal equations in the system are the same as the Wikipedia example, except the first and last equations. If we take $a_1 \\neq 0$ and $c_n \\neq 0$ then the corresponding equations are\n", "\n", "$$a_1 u_0 + b_1 u_1 + c_1 u_2 = d_1$$\n", "$$a_n u_{n-1} + b_n u_n + c_n u_{n+1} = d_n $$\n", "\n", "Taking the $a_1$ and $c_n$ terms to the right hand sides of their respective equations yields a slightly modified tridiagonal matrix that can be solved using existing tridiagonal solvers.\n", "\n", "$$\\begin{bmatrix}\n", "b_1 & c_1 & & & 0\\\\\n", "a_2 & b_2 & c_2 & & \\\\\n", " & a_3 & b_3 & \\ddots & \\\\\n", " & & \\ddots & \\ddots & c_{n-1} \\\\\n", "0 & & & a_n & b_n \n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "x_1 \\\\\n", "x_2 \\\\\n", "x_3 \\\\\n", "\\vdots \\\\\n", "x_n\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "d_1 - a_1 u_0\\\\\n", "d_2 \\\\\n", "d_3 \\\\\n", "\\vdots \\\\\n", "d_n - c_n u_{n+1}\n", "\\end{bmatrix}\n", "$$\n", "\n", "$u_0$ and $u_{n+1}$ are just the boundary conditions specified. For the Burger's problem, $u_0 = B_0 = 1$ and $u_{n+1} = B_{n+1} = 0$ so the final matrix is \n", "\n", "$$\\begin{bmatrix}\n", "b_1 & c_1 & & & 0\\\\\n", "a_2 & b_2 & c_2 & & \\\\\n", " & a_3 & b_3 & \\ddots & \\\\\n", " & & \\ddots & \\ddots & c_{n-1} \\\\\n", "0 & & & a_n & b_n \n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "x_1 \\\\\n", "x_2 \\\\\n", "x_3 \\\\\n", "\\vdots \\\\\n", "x_n\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "d_1 -a_1\\\\\n", "d_2 \\\\\n", "d_3 \\\\\n", "\\vdots \\\\\n", "d_n\n", "\\end{bmatrix}\n", "$$\n", "\n", "So for the problem at hand, we have to make a change to the first element of the `d` vector to correct for the usual assumptions made when using the Thomas algorithm. \n", "\n", "In the function above, this is accomplished with the line\n", "\n", " d[0] = u[0] - .5*dt/dx*(E[1]-E[0])+dt/(4*dx)*(au[1]-au[0]) - a[0]" ] }, { "cell_type": "code", "collapsed": false, "input": [ "u = u_ic()\n", "sigma = .5\n", "dt = sigma*dx\n", "nt=60\n", "\n", "un = beamwarming(u,nt,dt,dx)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 17 }, { "cell_type": "code", "collapsed": false, "input": [ "fig = pyplot.figure();\n", "ax = pyplot.axes(xlim=(0,4),ylim=(-.5,2));\n", "line, = ax.plot([],[],lw=2);\n", "\n", "anim = animation.FuncAnimation(fig, animate, frames=un, interval=50)\n", "display_animation(anim, default_mode='once')" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n", "\n", "
\n", " \n", "
\n", " \n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", " Once \n", " Loop \n", " Reflect \n", "
\n", "
\n", "\n", "\n", "\n" ], "metadata": {}, "output_type": "pyout", "prompt_number": 18, "text": [ "" ] } ], "prompt_number": 18 }, { "cell_type": "markdown", "metadata": {}, "source": [ "###Yikes.\n", "\n", "You may have noticed that we've made `nt` smaller than it has been for the other schemes. \n", "With our CFL number equal to 0.5, this implicit method can handle about 60 iterations before it blows up to numbers that Python can't handle (which then get replaced with `Inf`, which then breaks the banded matrix solver).\n", "\n", "So we need to calm things down a little bit and prevent it from going nuclear so quickly.\n", "\n", "##Beam-Warming with damping" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Implicit methods can work well in many situations, but this isn't one of them. As you can see above, the undamped Beam-Warming quickly turns into nonsense. We can add damping to help keep things under control. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Add a damping term 'D' where:\n", "\n", "$$D = -\\epsilon_e (u^n_{i+2} - 4u^n_{i+1} + 6u^n_i - 4^n_{i-1} + u^n_{i-2})$$\n", "\n", "Note below that while we can easily include the damping term for most values of the `d` vector, `d[0]` and `d[1]` require a little extra attention. There are no periodic boundary conditions being used, so any value of $u_i$ where $i < 0$ is set equal to 1." ] }, { "cell_type": "code", "collapsed": false, "input": [ "def dampit(u,eps,dt,dx):\n", "\td = u[2]-.5*dt/dx*(u[3]**2/2-u[1]**2/2)+dt/(4*dx)*(u[3]**2-u[1]**2)\\\n", "\t\t-eps*(u[4]-4*u[3]+6*u[2]-4*u[1]+u[0])\n", "\treturn d\n", "\n", "def beamwarming_damp(u, nt, dt, dx):\n", " ##Tridiagonal setup##\n", " a = numpy.zeros_like(u)\n", " b = numpy.ones_like(u)\n", " c = numpy.zeros_like(u)\n", " d = numpy.zeros_like(u)\n", " \n", " un = numpy.zeros((nt,len(u)))\n", " un[:] = u.copy()\n", " \n", " eps = .125\n", "\n", " for n in range(1,nt): \n", " u[0] = 1\n", " E = utoE(u)\n", " au = utoA(u)\n", " \n", " a[0] = -dt/(4*dx)*u[0]\n", " a[1:] = -dt/(4*dx)*u[0:-1]\n", " a[-1] = -dt/(4*dx)*u[-1]\n", " \n", " #b is all ones\n", " \n", " c[:-1] = dt/(4*dx)*u[1:]\n", " \n", " ###Calculate the damping factor for MOST of our u_vector\n", " d[2:-2] = u[2:-2]-.5*dt/dx*(E[3:-1]-E[1:-3])+dt/(4*dx)\\\n", " *(au[3:-1]-au[1:-3])\\\n", " -eps*(u[4:]-4*u[3:-1]+6*u[2:-2]-4*u[1:-3]+u[:-4])\n", " \n", "\n", " ###Calculate the damping factor for d[0] and d[1]\n", " damp = numpy.concatenate((np.ones(2), u[:3]))\t\n", " d[0] = dampit(damp,eps,dt,dx)\n", " damp = numpy.concatenate((np.ones(1), u[:4]))\n", " d[1] = dampit(damp,eps,dt,dx)\n", " \n", " ###subtract a[0]*LHS B.C to 'fix' thomas algorithm\n", " d[0] = d[0] - u[0] * a[0] \n", " \n", " ab = numpy.matrix([c,b,a])\n", " u = linalg.solve_banded((1,1), ab, d)\n", " u[0]=1\n", " un[n] = u.copy()\n", " return un" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 19 }, { "cell_type": "code", "collapsed": false, "input": [ "u = u_ic()\n", "sigma = 0.5\n", "dt = sigma*dx\n", "nt = 120\n", "un = beamwarming_damp(u,nt,dt,dx)\n", "\n", "fig = pyplot.figure();\n", "ax = pyplot.axes(xlim=(0,4),ylim=(-.5,2));\n", "line, = ax.plot([],[],lw=2);\n", "\n", "anim = animation.FuncAnimation(fig, animate, frames=un, interval=50)\n", "display_animation(anim, default_mode='once')" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n", "\n", "
\n", " \n", "
\n", " \n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", " Once \n", " Loop \n", " Reflect \n", "
\n", "
\n", "\n", "\n", "\n" ], "metadata": {}, "output_type": "pyout", "prompt_number": 20, "text": [ "" ] } ], "prompt_number": 20 }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's certainly more stable than what we had before. It certainly doesn't behave as nicely as some of our other schemes, but it is remaining finite, at least. This animation has a damping factor $\\epsilon = .125$. Try playing around with different values for the damping factor and see what happens." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from IPython.core.display import HTML\n", "def css_styling():\n", " styles = open(\"../styles/custom.css\", \"r\").read()\n", " return HTML(styles)\n", "css_styling()" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n", "\n", "\n", "\n" ], "metadata": {}, "output_type": "pyout", "prompt_number": 21, "text": [ "" ] } ], "prompt_number": 21 } ], "metadata": {} } ] }