{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "%matplotlib inline\n", "import numpy as np\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Simple Harmonic Motion\n", "## Lecture 6" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Examples of oscillatory motion.\n", "\n", "First year: the pendulum." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## The Simple Pendulum" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Review of theory..." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "$\\Omega = \\sqrt{g/L}$" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This is a very simple model." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Simple numerical approach" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "As we did with the both exponential growth/decay, and the projectile motion problems, we'll start with the basic equations and solve them numerically. This way we can compare with the analytical solution from theory. Later, we'll deal with slightly more complicated oscillatory problems for which exact results are not available." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Equations of motion:\n", "\n", "\\begin{align}\n", "\\frac{d\\omega}{dt} &= - \\frac{g}{L} \\theta\\;, \\\\\n", "\\frac{d\\theta}{dt} &= \\omega\\;,\n", "\\end{align}\n", "\n", "where $\\omega$ is the angular velocity of the pendulum. You should recognize the approach of going for a second-order differential equation into a pair of first-order differential equations. We did the same thing when dealing with projectile motion." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "We can proceed to solve this problem with Euler's method as we have now down several times already.\n", "\n", "\\begin{align}\n", "\\omega_{i+1} &= \\omega_i - \\frac{g}{L} \\theta_i \\Delta t \\\\\n", "\\theta_{i+1} &= \\theta_i + \\omega_i \\Delta t\n", "\\end{align}" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "# parameters and constants\n", "\n", "L = 1 # m\n", "g = 9.81 # m/s^2\n", "\n", "Ω = np.sqrt(g/L)\n", "\n", "ω0 = 0.0\n", "θ0 = 0.2" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def calculate_euler(dt=0.04, tmax=10):\n", " \"\"\"\n", " Solve the simple pendulum equations using Euler's method.\n", " Takes time step dt and maximum time tmax as arguments.\n", " \n", " Returns arrays for t, ω, θ\n", " \"\"\"\n", " \n", " # allocate space\n", " num_steps = round(tmax/dt)\n", " t = np.zeros(num_steps)\n", " ω = np.zeros(num_steps)\n", " θ = np.zeros(num_steps)\n", "\n", " # initial values\n", " t[0] = 0\n", " ω[0] = ω0\n", " θ[0] = θ0\n", "\n", " # apply Euler's method\n", " for i in range(num_steps-1):\n", " t[i+1] = t[i] + dt\n", " ω[i+1] = ω[i] - g/L*θ[i]*dt\n", " θ[i+1] = θ[i] + ω[i]*dt\n", " \n", " return t, ω, θ" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def plot(t, θ, label=''):\n", " \"\"\"\n", " Plot position vs time\n", " \"\"\"\n", " plt.xlabel('time (s)')\n", " plt.ylabel(r'$\\theta$ (rad)')\n", " plt.plot(t, θ, linewidth=2, label=label)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "# main driver\n", "fig, axes = plt.subplots()\n", "\n", "dt = 0.1\n", "tmax = 10\n", "t, ω, θ = calculate_euler(dt=dt, tmax=tmax)\n", "\n", "θ_theory = θ0 * np.cos( Ω*t )\n", "plot(t, θ, label='Numerical, dt={:.2f}'.format(dt))\n", "plot(t, θ_theory, label='Theoretical')\n", "plt.legend(loc='upper left')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Exercise:\n", "> The exact solution and the numerical answer do not agree. Let's explore this by rerunning the \n", "> 1. Change the time step to `dt=0.01`. Does that improve the problem?\n", "> 2. Keeping `dt = 0.01`, change `tmax` to be 100. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Turns out that there is no *small enough* value of `dt` that will help us in this case. While the motion is necessarily periodic, Euler's method always leads to the solution growing in amplitude with time." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The issue is that the Euler method is an approximation to the differential equation. The key word here is *approximate*. While for some problems reduce `dt` can reduce the error in this approximation, for this problem the energy will always grow in time for *any* nonzero value of $\\Delta t$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Euler's Method of Solving ODE's" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Notes..." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Energy" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The model doesn't contain a source of energy nor any form of friction so the total energy should remain constant." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "For a simple harmonic oscillator, Euler's method an example of a numerical method that is inherently unstable. To understand this instability, we should look at the total energy of the pendulum system." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The energy, $E$, is given by\n", "\\begin{align}\n", "E = \\frac{1}{2} m L^2 \\omega^2 + m g L ( 1 - cos(\\theta) )\n", "\\end{align}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "The first term is the kinetic energy\n", "\\begin{align}\n", "\\frac{1}{2} m v^2 = \\frac{1}{2} m (\\omega L)^2\n", "\\end{align}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The second term is the gravitational potential energy\n", "\n", "\\begin{align}\n", "m g h = m g (L (1-cos\\theta) )\n", "\\end{align}\n", "\n", "where $h$ is the height above the lowest point. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "If we assume $\\theta$ is small then then the energy can be approximated as\n", "\\begin{align}\n", "E &= \\frac{1}{2} m L^2 \\omega^2 + m g L \\frac{1}{2} \\theta^2 \\\\\n", "&= \\frac{1}{2} m L^2 \\left( \\omega^2 + \\frac{g}{L} \\theta^2 \\right)\n", "\\end{align}" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def plot_energy(t, ω, θ):\n", " # calculate the total energy per unit mass\n", " E = 0.5 * L**2 *(ω**2 + g/L*θ**2)\n", " \n", " fig, axes = plt.subplots(2,1, figsize=(8,6))\n", " \n", " # plot solution\n", " plt.sca(axes[0]) # SetCurrentAxes\n", " plot(t, θ)\n", " \n", " # plot energy\n", " plt.sca(axes[1]) # SetCurrentAxes\n", " plt.plot(t, E)\n", " plt.xlabel('t (s)')\n", " plt.ylabel('E/m (J/kg)') " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "plot_energy(t, ω, θ)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Substitute in Euler's method... and show energy grows with time." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "\\begin{align}\n", "E_{i+1} &= \\frac{1}{2} m L^2 \\left( \\omega^2 + \\frac{g}{L} \\theta^2 \\right) \\\\\n", " &= \\frac{1}{2} m L^2 \\left( \\left(\\omega_i - \\frac{g}{L} \\theta_i \\Delta t \\right)^2 + \\frac{g}{L} \\left(\\theta_i + \\omega_i \\Delta t \\right)^2 \\right) \\\\\n", " & = \\frac{1}{2} m L^2 \\left( \\left(\\omega_i^2 - 2 \\frac{g}{L} \\theta_i \\omega_i \\Delta t + (\\frac{g}{L} \\theta_i \\Delta t)^2\\right) + \\left( \\frac{g}{L}\\theta_i^2 + 2 \\frac{g}{L}\\theta_i \\omega_i \\Delta t + \\frac{g}{L}(\\omega_i \\Delta t)^2 \\right) \\right) \\\\\n", " & = \\frac{1}{2} m L^2 \\left( \\omega_i^2 + (\\frac{g}{L} \\theta_i \\Delta t)^2 + \\frac{g}{L}\\theta_i^2 + \\frac{g}{L}(\\omega_i \\Delta t)^2 \\right) \\\\\n", " & = \\frac{1}{2} m L^2 \\left( \\omega_i^2 + \\frac{g}{L}\\theta_i^2 \\right) + \\frac{1}{2} m L^2 \\left( (\\frac{g}{L} \\theta_i \\Delta t)^2 + \\frac{g}{L}(\\omega_i \\Delta t)^2 \\right) \\\\\n", " & = \\frac{1}{2} m L^2 \\left( \\omega_i^2 + \\frac{g}{L}\\theta_i^2 \\right) + \\frac{1}{2} m g L \\left( \\frac{g}{L}\\theta_i^2 + \\omega_i^2 \\right)(\\Delta t)^2 \\\\\n", "E_{i+1} &= E_i + \\frac{1}{2} m g L \\left( \\frac{g}{L}\\theta_i^2 + \\omega_i^2 \\right)(\\Delta t)^2 \n", "\\end{align}\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Euler's method itself add energy every time step." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Turns out we can avoid this issue of energy increasing with time by making one small change and using a modified Euler's method:\n", "\\begin{align}\n", "\\omega_{i+1} &= \\omega_i - \\frac{g}{L} \\theta_i \\Delta t \\\\\n", "\\theta_{i+1} &= \\theta_i + \\omega_{i+1} \\Delta t\n", "\\end{align}\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Exercise\n", "> Can you spot the difference between this and the original Euler's method?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "This modified method is call the Euler-Cromer method. While the value from the previous time step is used to predict the velocity in the next time step, the position is updated using the velocity from the current time step. It is also known as the semi-implicit Euler method.\n", "\n", "This one small change gives a significant advantage. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def calculate_euler_cromer(dt=0.04, tmax=10):\n", " \"\"\"\n", " Solve the simple pendulum equations using Euler-Cromer method.\n", " Takes time step dt and maximum time tmax as arguments.\n", " \n", " Returns arrays for t, ω, θ\n", " \"\"\"\n", " \n", " # allocate space\n", " num_steps = round(tmax/dt)\n", " t = np.zeros(num_steps)\n", " ω = np.zeros(num_steps)\n", " θ = np.zeros(num_steps)\n", "\n", " # initial values\n", " t[0] = 0\n", " ω[0] = ω0\n", " θ[0] = θ0\n", "\n", " # apply Euler-Crommer method\n", " for i in range(num_steps-1):\n", " t[i+1] = t[i] + dt\n", " ω[i+1] = ω[i] - g/L*θ[i]*dt\n", " θ[i+1] = θ[i] + ω[i+1]*dt\n", " \n", " return t, ω, θ" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "fig, axes = plt.subplots(figsize=(8, 6))\n", "\n", "dt = 0.04\n", "tmax = 10\n", "t, ω, θ = calculate_euler_cromer(dt=dt, tmax=tmax)\n", "\n", "θ_theory = θ0 * np.cos(Ω*t)\n", "plot(t, θ, label='Numerical, dt={:.2f}'.format(dt))\n", "plot(t, θ_theory, label='Theoretical')\n", "plt.legend(loc='upper left')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "plot_energy(t, ω, θ)\n", "plt.ylim(0, 0.5)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "In the Euler-Cromer method, energy (when averaged over one period of an oscillation) is conserved. This energy conservation is a key property of the numerical stability of this method." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.4" } }, "nbformat": 4, "nbformat_minor": 1 }