From 5586d14a2f2294fee20bd84d20b2efa05bede2d6 Mon Sep 17 00:00:00 2001 From: rbds Date: Wed, 25 Nov 2015 20:33:42 -0500 Subject: [PATCH 01/61] start project --- Double_Pendulum_Problem.ipynb | 275 ++++++++++++++++++++++++++++++++++ figures/diagram.png | Bin 0 -> 9380 bytes numericalmoocstyle.css | 142 ++++++++++++++++++ 3 files changed, 417 insertions(+) create mode 100644 Double_Pendulum_Problem.ipynb create mode 100644 figures/diagram.png create mode 100644 numericalmoocstyle.css diff --git a/Double_Pendulum_Problem.ipynb b/Double_Pendulum_Problem.ipynb new file mode 100644 index 0000000..8185832 --- /dev/null +++ b/Double_Pendulum_Problem.ipynb @@ -0,0 +1,275 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dynamics of a Double Pendulum" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this module, we will study the dynamics of a double pendulum. We first derive the equations of motion from the Lagrangian. Next, we take a look at three different numerical integration schemes: Euler's method, Runge-Kutta, and a symplectic integrator. We will investigate how the initial conditions affect the behavior of the system, as well as some of the sources of error in these simulations. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Background\n", + "\n", + "The double pendulum problem is a simple system which can produce surprisingly complex movements. It is a chaotic system, meaning it is unpredictable, and there is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", + "\n", + "In this notebook we will be treating the two rods as massless and ignoring the effects of friction. For a treatment of this problem without these assumptions, see: \n", + "\n", + "See the image below for the definition of constants." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Image](figures/diagram.png)\n", + "#### Figure 1. Diagram of Double Pendulum System" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Equations of Motion\n", + "\n", + "To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", + "\n", + "$$\\begin{equation}\n", + "L = T- U\n", + "\\end{equation}$$\n", + "\n", + "where T is the total Kinetic Energy in the system, and U is the total Potential Energy. The equations of motion are then found using the Lagrange Equation:\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", + "\\end{equation}$$\n", + "\n", + "\n", + "So the equations of motion for the system become:\n", + "$$\\begin{eqnarray}\n", + "L &=& C_L S \\times \\frac{1}{2} \\rho v^2 \\\\\n", + "D &=& C_D S \\times \\frac{1}{2} \\rho v^2\n", + "\\end{eqnarray}$$\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This cell loads the style of the notebook, which is taken from the \n", + "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", + "\n", + "from IPython.core.display import HTML\n", + "css_file = 'numericalmoocstyle.css'\n", + "HTML(open(css_file, \"r\").read())" + ] + } + ], + "metadata": { + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/figures/diagram.png b/figures/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..a6130423b5a7dfdd7f34973887d9e8cc9609d567 GIT binary patch literal 9380 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 literal 0 HcmV?d00001 diff --git a/numericalmoocstyle.css b/numericalmoocstyle.css new file mode 100644 index 0000000..16a3c5c --- /dev/null +++ b/numericalmoocstyle.css @@ -0,0 +1,142 @@ + + + + + + + From 19910bea0e07bb1506ba5b05c92053935163092c Mon Sep 17 00:00:00 2001 From: rbds Date: Sat, 28 Nov 2015 21:01:30 +0000 Subject: [PATCH 02/61] update derivation of EoM --- Double_Pendulum_Problem.ipynb | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Double_Pendulum_Problem.ipynb b/Double_Pendulum_Problem.ipynb index 8185832..9632af2 100644 --- a/Double_Pendulum_Problem.ipynb +++ b/Double_Pendulum_Problem.ipynb @@ -47,7 +47,19 @@ "L = T- U\n", "\\end{equation}$$\n", "\n", - "where T is the total Kinetic Energy in the system, and U is the total Potential Energy. The equations of motion are then found using the Lagrange Equation:\n", + "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. To calculate potential energy:\n", + "\n", + "$$\\begin{equation}\n", + "U = m_1g(-y_1) + m_2g(-y_2)\n", + "\\end{equation}$$\n", + "\n", + "$$\\begin{equation}\n", + "U = m_1g(l_1cos(\\theta_1) + m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", + "\\end{equation}$$\n", + "\n", + "\n", + "\n", + "The equations of motion are then found using the Lagrange Equation:\n", "\n", "$$\\begin{equation}\n", "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", @@ -55,10 +67,7 @@ "\n", "\n", "So the equations of motion for the system become:\n", - "$$\\begin{eqnarray}\n", - "L &=& C_L S \\times \\frac{1}{2} \\rho v^2 \\\\\n", - "D &=& C_D S \\times \\frac{1}{2} \\rho v^2\n", - "\\end{eqnarray}$$\n" + "|" ] }, { @@ -267,7 +276,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.3" + "version": "3.5.0" } }, "nbformat": 4, From 3a1d4c84dc778be2b0037e17fe6a86c9fea5967d Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 29 Nov 2015 19:18:20 -0500 Subject: [PATCH 03/61] derive EoM --- Double_Pendulum_Problem.ipynb | 126 +++++++++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 16 deletions(-) diff --git a/Double_Pendulum_Problem.ipynb b/Double_Pendulum_Problem.ipynb index 9632af2..f7a2e9f 100644 --- a/Double_Pendulum_Problem.ipynb +++ b/Double_Pendulum_Problem.ipynb @@ -41,52 +41,146 @@ "source": [ "## Equations of Motion\n", "\n", - "To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", + "You are encouraged to follow this derivation ON PAPER on your own. The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", "\n", "$$\\begin{equation}\n", "L = T- U\n", "\\end{equation}$$\n", "\n", - "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. To calculate potential energy:\n", + "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", "\n", "$$\\begin{equation}\n", "U = m_1g(-y_1) + m_2g(-y_2)\n", "\\end{equation}$$\n", "\n", "$$\\begin{equation}\n", - "U = m_1g(l_1cos(\\theta_1) + m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", + "U = -m_1g(l_1cos(\\theta_1) - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", "\\end{equation}$$\n", "\n", + "Kinetic Energy is calculated as:\n", "\n", + "$$\\begin{equation}\n", + "T = \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2\n", + "\\end{equation}$$\n", "\n", - "The equations of motion are then found using the Lagrange Equation:\n", + "$$\\begin{equation}\n", + "T = \\frac{1}{2}m_1(l_1\\dot\\theta_1)^2 + \\frac{1}{2}m_2(l_1\\dot\\theta_1 + l_2\\dot\\theta_2)^2\n", + "\\end{equation}$$\n", "\n", "$$\\begin{equation}\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", + "T = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\n", "\\end{equation}$$\n", "\n", "\n", - "So the equations of motion for the system become:\n", - "|" + "So the Lagrangian quantity becomes:\n", + "$$\\begin{equation}\n", + "L = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+(m_1+m_2)l_1gcos\\theta_1 + m_2l_2gcos\\theta_2\n", + "\\end{equation}$$\n" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, - "outputs": [], - "source": [] + "source": [ + "The equations of motion are then found using the Lagrange Equation:\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", + "\\end{equation}$$\n", + "\n", + "\n", + "So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} = (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1)\n", + "\\end{equation}$$\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{\\theta_1}}\\right) = (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)\n", + "\\end{equation}$$\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{\\partial L}{\\partial \\theta_1} = m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", + "\\end{equation}$$\n", + "\n", + "Some of these terms will cancel out, and we are left with a final equation of motion:\n", + "\n", + "$$\\begin{equation}\n", + "0 = (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", + "\\end{equation}$$\n", + "\n", + "We will also need the equation of motion for $\\theta_2$. This can be derived using the same process. It should work out to the following:\n", + "\n", + "$$\\begin{equation}\n", + "0 = m_2l_2^2\\ddot\\theta_2+m_2l_1l_2\\ddot\\theta_1cos(\\theta_2-\\theta_1) - m_2l_2l_1\\dot\\theta_1^2sin(\\theta_2-\\theta_1)+ l_2m_2gsin\\theta_2\n", + "\\end{equation}$$" + ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problem setup\n", + "\n", + "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. So our state becomes:\n", + "\n", + "$$\\begin{eqnarray}\n", + "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\dot\\theta_1 \\\\ \\dot\\theta_2 \\end{pmatrix}\n", + "\\end{eqnarray}$$\n", + "\n", + "The equations of motion can be rewritten as a system of equations:\n", + "\n", + "$$ A\\dot x=f(x)$$\n", + "\n", + "where \n", + "\n", + "\\begin{equation}\n", + "A = \n", + "\\left[\\begin{array}{c}\n", + "1 &0 &0 &0 \\\\\n", + "0 &1 &0 &0 \\\\\n", + "0 &0 &(m_1+m_2)l_1^2 &m_2l_1l_2cos(x_2-x_1) \\\\\n", + "0 &0 &m_2l_1l_2cos(x_2-x_1) &m_2l_2^2\n", + "\\end{array}\\right]\n", + "\\end{equation}\n", + "\n", + "and\n", + "\n", + "$$\\begin{eqnarray}\n", + "f(\\vec{x}(t)) = \\begin{pmatrix} x_3\\\\ x_4\\\\ m_2l_1l_2x_4^2sin(x_2-x_1) -(m_1+m_2)gl_1sin(x_1)\\\\ m_2l_1l_2x_3^2sin(x_2-x_1)-l_2m_2gsinx_2 \\end{pmatrix}\n", + "\\end{eqnarray}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Integration\n", + "\n", + "#### Euler's Method\n", + "\n", + "The first method we will use is Euler integration, covered in lesson 1 of the Numerical Methods course. This is the simplest form of numerical integration, but it can still be effective. Using our equations for $\\dot x$ and $f(x)$:\n", + "\n", + "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", + "\n", + "where $h$ is the timestep.\n", + "\n", + "#### Runge-Kutta Method (RK4)\n", + "\n" + ] + }, + { + "cell_type": "markdown", "metadata": { "collapsed": true }, - "outputs": [], - "source": [] + "source": [ + "## Solve!\n", + "\n", + "Here we define functions for each of the integration methods, set our parameters and initial conditions, and then numerically integrate the motion of the pendulums. The plots below compare the different integration techniques." + ] }, { "cell_type": "code", @@ -276,7 +370,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.0" + "version": "3.4.3" } }, "nbformat": 4, From 0bd568b2913385ec0c7379f42e7822d443d21d6a Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 30 Nov 2015 22:20:52 +0000 Subject: [PATCH 04/61] start simple pendulum section --- Double_Pendulum_Problem.ipynb | 125 +++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 2 deletions(-) diff --git a/Double_Pendulum_Problem.ipynb b/Double_Pendulum_Problem.ipynb index f7a2e9f..3927841 100644 --- a/Double_Pendulum_Problem.ipynb +++ b/Double_Pendulum_Problem.ipynb @@ -4,7 +4,128 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Dynamics of a Double Pendulum" + "# Integration of Mechanical Systems\n", + "\n", + "In this module, we will be exploring numerical integrators and how they perform for different systems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple Harmonic Oscillator\n", + "\n", + "The simple harmonic oscillator is a simple physical system described by the second-order differential equation below. Despite its simplicity, this is a system that shows up in a similar form in many different fields of engineering. The same equation describes the behavior of a spring-mass-damper system, RLC circuit, or the motion of a (small-angle) pendulum.\n", + "\n", + "(Insert diagram for spring mass damper)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The differential equation describing the motion of a simple harmonic oscillator is:\n", + "\n", + "$$\\begin{equation}\n", + "0 = m\\ddot{x} + kx\n", + "\\end{equation}$$\n", + "\n", + "We can describe the state of the system by its position $x$ and its velocity $\\dot{x}$. We will call this state vector $q$, as:\n", + "\n", + "$$\\begin{eqnarray}\n", + "\\vec{q} = \\begin{bmatrix} x\\\\ \\dot{x} \\end{bmatrix}\n", + "\\end{eqnarray}$$\n", + "\n", + "This system can then be broken into two first order differential equations:\n", + "\n", + "$$\\begin{eqnarray}\n", + "\\vec{\\dot{q}} = \\begin{bmatrix} q_2\\\\ -\\frac{k}{m}q_1 \\end{bmatrix}\n", + "\\end{eqnarray}$$\n", + "\n", + "To integrate this system numerically, we can start with some initial conditions for $q$, then use a method such as Euler's method to integrate forward in time. This \n", + "\n", + "This system is a nice example for numerical integration, because it has an analytical form which we can compare to. The analytical solution is\n", + "\n", + "$$\\begin{equation}\n", + "x(t) = Acos(\\omega t + \\phi)\n", + "\n", + "\\end{equation}$$\n", + "\n", + "where $\\omega$ is the natural frequency of the system given by $\\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "### Adding a damping term" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "### Adding a " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dynamics of a Double Pendulum" ] }, { @@ -370,7 +491,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.3" + "version": "3.5.0" } }, "nbformat": 4, From 2ff46c0b39f7aabdb6864b9bd13774a8f4894da9 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 30 Nov 2015 22:55:37 +0000 Subject: [PATCH 05/61] equations for Euler, RK4 --- Double_Pendulum_Problem.ipynb | 59 +++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/Double_Pendulum_Problem.ipynb b/Double_Pendulum_Problem.ipynb index 3927841..46eed7c 100644 --- a/Double_Pendulum_Problem.ipynb +++ b/Double_Pendulum_Problem.ipynb @@ -42,13 +42,66 @@ "\\vec{\\dot{q}} = \\begin{bmatrix} q_2\\\\ -\\frac{k}{m}q_1 \\end{bmatrix}\n", "\\end{eqnarray}$$\n", "\n", - "To integrate this system numerically, we can start with some initial conditions for $q$, then use a method such as Euler's method to integrate forward in time. This \n", + "To look at the behavior of this system, we can start with some initial conditions for $q$, then use a numerical integration method such as Euler's method to integrate forward in time. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Euler's method\n", + "\n", + "Euler's method is discussed in lesson 1 of the Numerical Methods course. The method goes as follows:\n", + "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", + "where $h$ is the timestep." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Runge-Kutta Integration (RK4)\n", "\n", + "Depending on the application, Euler's method may provide enough accuracy with a small timestep, especially a simple harmonic oscillator. However, Euler is a first-order method and we can do better. One option is a Runge-Kutta scheme. This is a popular numerical integration method which is easily extended to higher orders. Here we will use a fourth order Runge-Kutta, also known as RK4:\n", + "\n", + "$$\\begin{equation}\n", + "q_{n+1} = q_n + \\frac{h}{6}\\left(k_1+2k_2+2k_3+k_4\\right)\n", + "\\end{equation}$$\n", + "where\n", + "$$\\begin{eqnarray}\n", + "k_1 = f(t_n, x_n)\\\\\n", + "k_2 = f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_1)\\\\\n", + "k_3 = f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_2)\\\\\n", + "k_4 = f(t_n+h, y_n+hk_3)\\\\\n", + "t_{n+1} = t_n + h\n", + "\\end{eqnarray}$$\n", + "\n", + "For more information on the Runge-Kutta method, including the derivation and explanation of coefficients, see (cite)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "This system is a nice example for numerical integration, because it has an analytical form which we can compare to. The analytical solution is\n", "\n", "$$\\begin{equation}\n", "x(t) = Acos(\\omega t + \\phi)\n", - "\n", "\\end{equation}$$\n", "\n", "where $\\omega$ is the natural frequency of the system given by $\\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " @@ -56,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "collapsed": true }, From 0d5761245f3e51cdb52467b8a5117ecd33b4a9ef Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 1 Dec 2015 00:52:51 +0000 Subject: [PATCH 06/61] update text --- Double_Pendulum_Problem.ipynb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Double_Pendulum_Problem.ipynb b/Double_Pendulum_Problem.ipynb index 46eed7c..f1ad2ff 100644 --- a/Double_Pendulum_Problem.ipynb +++ b/Double_Pendulum_Problem.ipynb @@ -39,7 +39,7 @@ "This system can then be broken into two first order differential equations:\n", "\n", "$$\\begin{eqnarray}\n", - "\\vec{\\dot{q}} = \\begin{bmatrix} q_2\\\\ -\\frac{k}{m}q_1 \\end{bmatrix}\n", + "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{k}{m}q_1 \\end{bmatrix}\n", "\\end{eqnarray}$$\n", "\n", "To look at the behavior of this system, we can start with some initial conditions for $q$, then use a numerical integration method such as Euler's method to integrate forward in time. " @@ -69,11 +69,11 @@ "\\end{equation}$$\n", "where\n", "$$\\begin{eqnarray}\n", - "k_1 = f(t_n, x_n)\\\\\n", - "k_2 = f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_1)\\\\\n", - "k_3 = f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_2)\\\\\n", - "k_4 = f(t_n+h, y_n+hk_3)\\\\\n", - "t_{n+1} = t_n + h\n", + "k_1 &=& f(t_n, x_n)\\\\\n", + "k_2 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_1)\\\\\n", + "k_3 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_2)\\\\\n", + "k_4 &=& f(t_n+h, y_n+hk_3)\\\\\n", + "t_{n+1} &=& t_n + h\n", "\\end{eqnarray}$$\n", "\n", "For more information on the Runge-Kutta method, including the derivation and explanation of coefficients, see (cite)\n" @@ -82,7 +82,11 @@ { "cell_type": "markdown", "metadata": {}, - "source": [] + "source": [ + "### Verlet method\n", + "\n", + "Symplectic integrator" + ] }, { "cell_type": "markdown", From cad05767b2074deb1daac1b2b715ddb80d02d226 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 30 Nov 2015 22:15:56 -0500 Subject: [PATCH 07/61] add symplectic integrators --- Double_Pendulum_Problem.ipynb | 156 ++++++++++++++++++++++++++-------- 1 file changed, 120 insertions(+), 36 deletions(-) diff --git a/Double_Pendulum_Problem.ipynb b/Double_Pendulum_Problem.ipynb index f1ad2ff..a10ae5f 100644 --- a/Double_Pendulum_Problem.ipynb +++ b/Double_Pendulum_Problem.ipynb @@ -6,7 +6,7 @@ "source": [ "# Integration of Mechanical Systems\n", "\n", - "In this module, we will be exploring numerical integrators and how they perform for different systems." + "In this module, we will be exploring numerical integrators and how they perform for different mechanical systems." ] }, { @@ -45,6 +45,11 @@ "To look at the behavior of this system, we can start with some initial conditions for $q$, then use a numerical integration method such as Euler's method to integrate forward in time. " ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, { "cell_type": "markdown", "metadata": {}, @@ -53,7 +58,8 @@ "\n", "Euler's method is discussed in lesson 1 of the Numerical Methods course. The method goes as follows:\n", "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", - "where $h$ is the timestep." + "\n", + "where $h$ is the timestep. This isn't a complicated scheme, so for details see the code below." ] }, { @@ -83,15 +89,48 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Verlet method\n", + "### Symplectic Integrators\n", + "\n", + "Symplectic integrators are similar to the methods discussed above, but they use equations of motion derived from Hamiltonian mechanics. According to [Berkeley source], syplectic integrators preserve the conserved Hamiltonian quantities. In practical terms, this works out to mean the methods reflect conservation of energy, down to a truncation error.\n", "\n", - "Symplectic integrator" + "The Hamiltonian equations of motion can be derived from the total energy in the system, much like the Lagrangian. The coordinates we use are position ($q$) and momentum ($p = m\\dot x$). The Hamiltonian is:\n", + "$$ H = T+V$$\n", + "where $T$ is the kinetic energy in the system, and $V$ is the potential energy in the system. So, we get:\n", + "$$T = \\frac{1}{2}m\\dot{x}^2 = \\frac{p^2}{2m}$$\n", + "and\n", + "$$V = mgq$$\n", + "\n", + "The Hamilton's equations are:\n", + "$$\\begin{eqnarray*}\n", + "\\frac{dp}{dt} &=& -\\frac{\\partial H}{\\partial q}\\\\\n", + "\\frac{dq}{dt} &=& \\frac{\\partial H}{\\partial p}\n", + "\\end{eqnarray*}$$\n", + "\n", + "So our equations of motion become:\n", + "$$\\begin{equation*}\n", + "\\begin{bmatrix} \\dot q \\\\ \\dot p \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q}\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -mg \\\\ \\frac{p}{m} \\end{bmatrix}\n", + "\\end{equation*}$$" ] }, { "cell_type": "markdown", "metadata": {}, - "source": [] + "source": [ + "Let's now take a look at symplectic integration methods. We'll start with a first order method - symplectic Euler's method. This uses the equations:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "p_{n+1} = p_n - h \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1},q_n}\\\\\n", + "q_{n+1} = q_n + \\frac{h}{2} \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1},q_n}\n", + "\\end{eqnarray*}$$\n", + "\n", + "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method, $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "p_{n+1/2} &=& p_n - \\frac{h}{2} \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1/2},q_n}\\\\\n", + "q_{n+1} &=& q_n + \\frac{h}{2} \\left(\\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_n} + \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_{n+1/2}} \\right) \\\\\n", + "p_{n+1} &=& p_{n+1/2} - \\frac{h}{2} \\frac{\\partial H}{\\partial q}\\Bigr|_{p_{n+1/2},q_{n+1/2}}\n", + "\\end{eqnarray*}$$" + ] }, { "cell_type": "markdown", @@ -102,24 +141,44 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This system is a nice example for numerical integration, because it has an analytical form which we can compare to. The analytical solution is\n", + "### Analytical Solution\n", + "The simple harmonic oscillator is a nice example for numerical integration, because it has an analytical form which we can compare to. The analytical solution is\n", "\n", "$$\\begin{equation}\n", - "x(t) = Acos(\\omega t + \\phi)\n", + "x(t) = Acos(\\omega_n t + \\phi)\n", "\\end{equation}$$\n", "\n", - "where $\\omega$ is the natural frequency of the system given by $\\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " + "where $\\omega_n$ is the natural frequency of the system given by $\\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " ] }, { - "cell_type": "code", - "execution_count": 3, + "cell_type": "markdown", "metadata": { "collapsed": true }, - "outputs": [], "source": [ - "### Adding a damping term" + "### Adding a damping term\n", + "\n", + "A more realistic oscillator also has a damping term. This might correspond to friction, or to some other non-conservative force. For now, we will assume that the damping is linear, and is proportional to the velocity (this is called the -- model of friction). We get the following equation of motion:\n", + "\n", + "$$\\begin{equation*}\n", + "0 = m\\ddot{x} + c\\dot{x} + kx\n", + "\\end{equation*}$$\n", + "\n", + "This gives us the differential equations:\n", + "$$\\begin{eqnarray*}\n", + "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{c}{m}\\dot{q_1} -\\frac{k}{m}q_1 \\end{bmatrix}\n", + "\\end{eqnarray*}$$\n", + "\n", + "We also have a new analytical solution. Assuming the system is underdamped (add explanation of damping ratio), the solution becomes:\n", + "\n", + "\n", + "$$\\begin{equation}\n", + "x(t) = Acos(\\omega_d t + \\phi)\n", + "\\end{equation}$$\n", + "\n", + "where $\\omega_d = \\omega_n\\sqrt{1 - \\zeta^2}$ is called the damped natural frequency.\n", + "\n" ] }, { @@ -227,27 +286,18 @@ "\n", "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", "\n", - "$$\\begin{equation}\n", - "U = m_1g(-y_1) + m_2g(-y_2)\n", - "\\end{equation}$$\n", - "\n", - "$$\\begin{equation}\n", - "U = -m_1g(l_1cos(\\theta_1) - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", - "\\end{equation}$$\n", + "$$\\begin{eqnarray}\n", + "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", + " &=& -m_1g(l_1cos(\\theta_1) - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", + "\\end{eqnarray}$$\n", "\n", "Kinetic Energy is calculated as:\n", "\n", - "$$\\begin{equation}\n", - "T = \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2\n", - "\\end{equation}$$\n", - "\n", - "$$\\begin{equation}\n", - "T = \\frac{1}{2}m_1(l_1\\dot\\theta_1)^2 + \\frac{1}{2}m_2(l_1\\dot\\theta_1 + l_2\\dot\\theta_2)^2\n", - "\\end{equation}$$\n", - "\n", - "$$\\begin{equation}\n", - "T = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\n", - "\\end{equation}$$\n", + "$$\\begin{eqnarray*}\n", + "T &=& \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2 \\\\\n", + "&=& \\frac{1}{2}m_1(l_1\\dot\\theta_1)^2 + \\frac{1}{2}m_2(l_1\\dot\\theta_1 + l_2\\dot\\theta_2)^2 \\\\\n", + "&=& \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\n", + "\\end{eqnarray*}$$\n", "\n", "\n", "So the Lagrangian quantity becomes:\n", @@ -328,7 +378,13 @@ "\n", "$$\\begin{eqnarray}\n", "f(\\vec{x}(t)) = \\begin{pmatrix} x_3\\\\ x_4\\\\ m_2l_1l_2x_4^2sin(x_2-x_1) -(m_1+m_2)gl_1sin(x_1)\\\\ m_2l_1l_2x_3^2sin(x_2-x_1)-l_2m_2gsinx_2 \\end{pmatrix}\n", - "\\end{eqnarray}$$" + "\\end{eqnarray}$$\n", + "\n", + "In order to solve this, we can evaluate matrix A and vector $f(x)$ at each time step, then invert A and premultiply the result to each side of the equation. This gives us an explicit system of equations of motion:\n", + "\n", + "$$ \\dot x=A^{-1}f(x)$$\n", + "\n", + "You may notice that these operations require a significant computational load at each time step. In order to reduce this, we can analytically invert the matrix A, then simply fill in the necessary values at each time step and multiply by $f(x)$. This will significantly speed up running time, as inverting a matrix is an expensive operation.\n" ] }, { @@ -337,7 +393,7 @@ "source": [ "## Integration\n", "\n", - "#### Euler's Method\n", + "### Euler's Method\n", "\n", "The first method we will use is Euler integration, covered in lesson 1 of the Numerical Methods course. This is the simplest form of numerical integration, but it can still be effective. Using our equations for $\\dot x$ and $f(x)$:\n", "\n", @@ -345,7 +401,7 @@ "\n", "where $h$ is the timestep.\n", "\n", - "#### Runge-Kutta Method (RK4)\n", + "### Runge-Kutta Method (RK4)\n", "\n" ] }, @@ -360,9 +416,28 @@ "Here we define functions for each of the integration methods, set our parameters and initial conditions, and then numerically integrate the motion of the pendulums. The plots below compare the different integration techniques." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sources: \n", + "\n", + "http://scienceworld.wolfram.com/physics/DoublePendulum.html\n", + "\n", + "http://www.phy.uct.ac.za/courses/opencontent/phylab2/worksheet9_09.pdf\n", + "\n", + "http://www.phys.lsu.edu/faculty/gonzalez/Teaching/Phys7221/DoublePendulum.pdf\n", + "\n", + "http://www.iontrap.wabash.edu/adlab/papers/F2011_foster_groninger_tang_chaos.pdf\n", + "\n", + "https://math.berkeley.edu/~alanw/242papers99/markiewicz.pdf\n", + "\n", + "http://www.unige.ch/~hairer/poly_geoint/week2.pdf" + ] + }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -517,7 +592,7 @@ "" ] }, - "execution_count": 4, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -530,6 +605,15 @@ "css_file = 'numericalmoocstyle.css'\n", "HTML(open(css_file, \"r\").read())" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -548,7 +632,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.0" + "version": "3.4.3" } }, "nbformat": 4, From 7d1581e259aad843e6e5fe82ed3ad18615dd240f Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 1 Dec 2015 18:38:19 -0500 Subject: [PATCH 08/61] move files around --- .../Double_Pendulum_Problem.ipynb | 25 ++++++++---------- {figures => randy_schur/figures}/diagram.png | Bin .../numericalmoocstyle.css | 0 3 files changed, 11 insertions(+), 14 deletions(-) rename Double_Pendulum_Problem.ipynb => randy_schur/Double_Pendulum_Problem.ipynb (95%) rename {figures => randy_schur/figures}/diagram.png (100%) rename numericalmoocstyle.css => randy_schur/numericalmoocstyle.css (100%) diff --git a/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb similarity index 95% rename from Double_Pendulum_Problem.ipynb rename to randy_schur/Double_Pendulum_Problem.ipynb index a10ae5f..3fcaa39 100644 --- a/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -321,25 +321,19 @@ "\n", "So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", "\n", - "$$\\begin{equation}\n", - "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} = (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1)\n", - "\\end{equation}$$\n", - "\n", - "$$\\begin{equation}\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{\\theta_1}}\\right) = (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)\n", - "\\end{equation}$$\n", - "\n", - "$$\\begin{equation}\n", - "\\frac{\\partial L}{\\partial \\theta_1} = m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", - "\\end{equation}$$\n", + "$$\\begin{eqnarray*}\n", + "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} &=& (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1) \\\\\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{\\theta_1}}\\right) &=& (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1) \\\\\n", + "-\\frac{\\partial L}{\\partial \\theta_1} &=& -m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)-(m_1+m_2)gl_1sin\\theta_1\n", + "\\end{eqnarray*}$$\n", "\n", - "Some of these terms will cancel out, and we are left with a final equation of motion:\n", + "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", "\n", "$$\\begin{equation}\n", "0 = (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", "\\end{equation}$$\n", "\n", - "We will also need the equation of motion for $\\theta_2$. This can be derived using the same process. It should work out to the following:\n", + "We will also need the equation of motion for $\\theta_2$. This can be derived using the same process. It should work out to the following (check this by yourself!):\n", "\n", "$$\\begin{equation}\n", "0 = m_2l_2^2\\ddot\\theta_2+m_2l_1l_2\\ddot\\theta_1cos(\\theta_2-\\theta_1) - m_2l_2l_1\\dot\\theta_1^2sin(\\theta_2-\\theta_1)+ l_2m_2gsin\\theta_2\n", @@ -432,7 +426,10 @@ "\n", "https://math.berkeley.edu/~alanw/242papers99/markiewicz.pdf\n", "\n", - "http://www.unige.ch/~hairer/poly_geoint/week2.pdf" + "http://www.unige.ch/~hairer/poly_geoint/week2.pdf\n", + "\n", + "http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1994CeMDA..60..409T&defaultprint=YES&filetype=.pdf\n", + "\n" ] }, { diff --git a/figures/diagram.png b/randy_schur/figures/diagram.png similarity index 100% rename from figures/diagram.png rename to randy_schur/figures/diagram.png diff --git a/numericalmoocstyle.css b/randy_schur/numericalmoocstyle.css similarity index 100% rename from numericalmoocstyle.css rename to randy_schur/numericalmoocstyle.css From 398cf3cf5e475ff41947fa4c2c52bb5c384f0fea Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 1 Dec 2015 19:34:00 -0500 Subject: [PATCH 09/61] update damped Hamiltonian derivation --- Double_Pendulum_Problem.ipynb | 706 ++++++++++++++++++++++++++++++++++ 1 file changed, 706 insertions(+) create mode 100644 Double_Pendulum_Problem.ipynb diff --git a/Double_Pendulum_Problem.ipynb b/Double_Pendulum_Problem.ipynb new file mode 100644 index 0000000..59df853 --- /dev/null +++ b/Double_Pendulum_Problem.ipynb @@ -0,0 +1,706 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Integration of Mechanical Systems\n", + "\n", + "In this module, we will be exploring numerical integrators and how they perform for different mechanical systems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple Harmonic Oscillator\n", + "\n", + "The simple harmonic oscillator is a simple physical system described by the second-order differential equation below. Despite its simplicity, this is a system that shows up in a similar form in many different fields of engineering. The same equation describes the behavior of a spring-mass-damper system, RLC circuit, or the motion of a (small-angle) pendulum.\n", + "\n", + "(Insert diagram for spring mass damper)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The differential equation describing the motion of a simple harmonic oscillator is:\n", + "\n", + "$$\\begin{equation}\n", + "0 = m\\ddot{x} + kx\n", + "\\end{equation}$$\n", + "\n", + "We can describe the state of the system by its position $x$ and its velocity $\\dot{x}$. We will call this state vector $q$, as:\n", + "\n", + "$$\\begin{eqnarray}\n", + "\\vec{q} = \\begin{bmatrix} x\\\\ \\dot{x} \\end{bmatrix}\n", + "\\end{eqnarray}$$\n", + "\n", + "This system can then be broken into two first order differential equations:\n", + "\n", + "$$\\begin{eqnarray}\n", + "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{k}{m}q_1 \\end{bmatrix}\n", + "\\end{eqnarray}$$\n", + "\n", + "To look at the behavior of this system, we can start with some initial conditions for $q$, then use a numerical integration method such as Euler's method to integrate forward in time. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Equations of Motion from Lagrangian\n", + "\n", + "You are encouraged to follow this derivation ON PAPER on your own. We gave you the equations of motion for a simple harmonic oscillator, but what if we don't have this equation? The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", + "\n", + "$$\\begin{equation}\n", + "L = T- U\n", + "\\end{equation}$$\n", + "\n", + "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "U &=& \\frac{1}{2}kx^2\\\\\n", + "\\end{eqnarray*}$$\n", + "\n", + "Kinetic Energy is calculated as:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "T &=& \\frac{1}{2}mv^2 \\\\\n", + "&=& \\frac{1}{2}m(\\dot x)^2 \n", + "\\end{eqnarray*}$$\n", + "\n", + "\n", + "So the Lagrangian quantity becomes:\n", + "$$\\begin{equation}\n", + "L = \\frac{1}{2}m(\\dot x)^2 - \\frac{1}{2}kx^2\\\\\n", + "\\end{equation}$$\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The equations of motion are then found using the Lagrange Equation:\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", + "\\end{equation}$$\n", + "\n", + "Since we have only one state (position), we need only one equation of motion. We'll calculate this in steps:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "\\frac{\\partial L}{\\partial \\dot{x}} &=& m\\dot{x} \\\\\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x}}\\right) &=& m\\ddot{x}\\\\\n", + "-\\frac{\\partial L}{\\partial x} &=& + kx\n", + "\\end{eqnarray*}$$\n", + "\n", + "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", + "\n", + "$$\\begin{equation}\n", + "0 = m\\ddot{x} + kx\n", + "\\end{equation}$$\n", + "\n", + "This is the same equation we have before! So, we can be relatively confident that this derivation is correct." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Integration Methods\n", + "\n", + "### Euler's method\n", + "\n", + "Euler's method is discussed in lesson 1 of the Numerical Methods course. The method goes as follows:\n", + "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", + "\n", + "where $h$ is the timestep. This isn't a complicated scheme, so for details see the code below." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Runge-Kutta Integration (RK4)\n", + "\n", + "Depending on the application, Euler's method may provide enough accuracy with a small timestep, especially a simple harmonic oscillator. However, Euler is a first-order method and we can do better. One option is a Runge-Kutta scheme. This is a popular numerical integration method which is easily extended to higher orders. Here we will use a fourth order Runge-Kutta, also known as RK4:\n", + "\n", + "$$\\begin{equation}\n", + "q_{n+1} = q_n + \\frac{h}{6}\\left(k_1+2k_2+2k_3+k_4\\right)\n", + "\\end{equation}$$\n", + "where\n", + "$$\\begin{eqnarray}\n", + "k_1 &=& f(t_n, x_n)\\\\\n", + "k_2 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_1)\\\\\n", + "k_3 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_2)\\\\\n", + "k_4 &=& f(t_n+h, y_n+hk_3)\\\\\n", + "t_{n+1} &=& t_n + h\n", + "\\end{eqnarray}$$\n", + "\n", + "For more information on the Runge-Kutta method, including the derivation and explanation of coefficients, see (cite)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Symplectic Integrators\n", + "\n", + "Symplectic integrators are similar to the methods discussed above, but they use equations of motion derived from Hamiltonian mechanics. According to [Berkeley source], syplectic integrators preserve the conserved Hamiltonian quantities. In practical terms, this works out to mean the methods reflect conservation of energy, down to a truncation error.\n", + "\n", + "The Hamiltonian equations of motion can be derived from the total energy in the system, much like the Lagrangian. The coordinates we use are position ($q$) and momentum ($p = m\\dot x$). The Hamiltonian is:\n", + "$$ H = T+V$$\n", + "where $T$ is the kinetic energy in the system, and $V$ is the potential energy in the system. So, we get:\n", + "$$\\begin{eqnarray*}\n", + "T &=& \\frac{1}{2}m\\dot{x}^2 = \\frac{p^2}{2m}\\\\\n", + "V &=& mgq\\\\\n", + "H &=& \\frac{p^2}{2m} + mgq\n", + "\\end{eqnarray*}$$\n", + "\n", + "\n", + "The Hamilton's equations are:\n", + "$$\\begin{eqnarray*}\n", + "\\frac{dp}{dt} &=& -\\frac{\\partial H}{\\partial q}\\\\\n", + "\\frac{dq}{dt} &=& \\frac{\\partial H}{\\partial p}\n", + "\\end{eqnarray*}$$\n", + "\n", + "So our equations of motion become:\n", + "$$\\begin{equation*}\n", + "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q}\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -mg \\\\ \\frac{p}{m} \\end{bmatrix}\n", + "\\end{equation*}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now take a look at symplectic integration methods. We'll start with a first order method - symplectic Euler's method. This uses the equations:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "p_{n+1} = p_n - h \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1},q_n}\\\\\n", + "q_{n+1} = q_n + \\frac{h}{2} \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1},q_n}\n", + "\\end{eqnarray*}$$\n", + "\n", + "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method, $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "p_{n+1/2} &=& p_n - \\frac{h}{2} \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1/2},q_n}\\\\\n", + "q_{n+1} &=& q_n + \\frac{h}{2} \\left(\\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_n} + \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_{n+1/2}} \\right) \\\\\n", + "p_{n+1} &=& p_{n+1/2} - \\frac{h}{2} \\frac{\\partial H}{\\partial q}\\Bigr|_{p_{n+1/2},q_{n+1/2}}\n", + "\\end{eqnarray*}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Analytical Solution\n", + "The simple harmonic oscillator is a nice example for numerical integration, because it has an analytical form which we can compare to. The analytical solution is\n", + "\n", + "$$\\begin{equation}\n", + "x(t) = Acos(\\omega_n t + \\phi)\n", + "\\end{equation}$$\n", + "\n", + "where $\\omega_n$ is the natural frequency of the system given by $\\omega_n = \\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Code section?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "### Adding a damping term\n", + "\n", + "A more realistic oscillator also has a damping term. This might correspond to friction, or to some other non-conservative force. For now, we will assume that the damping is linear, and is proportional to the velocity (this is called the -- model of friction). We get the following equation of motion:\n", + "\n", + "$$\\begin{equation*}\n", + "0 = m\\ddot{x} + c\\dot{x} + kx\n", + "\\end{equation*}$$\n", + "\n", + "This gives us the differential equations:\n", + "$$\\begin{eqnarray*}\n", + "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{c}{m}\\dot{q_1} -\\frac{k}{m}q_1 \\end{bmatrix}\n", + "\\end{eqnarray*}$$\n", + "\n", + "The Euler's method and Runge-Kutta integration work the same way with this new equation. We also have a new analytical solution. Assuming the system is underdamped (add explanation of damping ratio), the solution becomes:\n", + "\n", + "\n", + "$$\\begin{equation}\n", + "x(t) = Acos(\\omega_d t + \\phi)\n", + "\\end{equation}$$\n", + "\n", + "where $\\omega_d = \\omega_n\\sqrt{1 - \\zeta^2}$ is called the damped natural frequency.\n", + "\n", + "The big change comes in the Hamiltonian equations. If these are dependent on convservation of energy, how do we deal with a system where energy is not conserved?\n", + "\n", + "Our first step is to re-examine the Lagrangian. Since losses due to friction are neither potential nor kinetic energy, we'll need a new term. The Lagrangian equation of motion with non-conservative forces is:\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = Q\n", + "\\end{equation}$$\n", + "\n", + "where $Q$ is the sum of all non-conservative forces. In this case, \n", + "$$Q = c\\frac{\\partial x}{\\partial t}$$\n", + "\n", + "Now that we have this, we can add a similar term to the Hamiltonian equations of motion to deal with the same problem. For more information on the derivation of the Hamiltonian from the Lagrangian, see Tveter (cite). The Hamiltonian equations of motion become:\n", + "\n", + "$$\\begin{equation*}\n", + "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q} + Q\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -mg + \\frac{c}{m}q \\\\ \\frac{p}{m} \\end{bmatrix}\n", + "\\end{equation*}$$\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dynamics of a Double Pendulum" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this module, we will study the dynamics of a double pendulum. We first derive the equations of motion from the Lagrangian. Next, we take a look at three different numerical integration schemes: Euler's method, Runge-Kutta, and a symplectic integrator. We will investigate how the initial conditions affect the behavior of the system, as well as some of the sources of error in these simulations. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Background\n", + "\n", + "The double pendulum problem is a simple system which can produce surprisingly complex movements. It is a chaotic system, meaning it is unpredictable, and there is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", + "\n", + "In this notebook we will be treating the two rods as massless and ignoring the effects of friction. For a treatment of this problem without these assumptions, see: \n", + "\n", + "See the image below for the definition of constants." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Image](figures/diagram.png)\n", + "#### Figure 1. Diagram of Double Pendulum System" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Equations of Motion \n", + "\n", + "You are encouraged to follow this derivation ON PAPER on your own. The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", + "\n", + "$$\\begin{equation}\n", + "L = T- U\n", + "\\end{equation}$$\n", + "\n", + "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", + "\n", + "$$\\begin{eqnarray}\n", + "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", + " &=& -m_1g(l_1cos(\\theta_1) - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", + "\\end{eqnarray}$$\n", + "\n", + "Kinetic Energy is calculated as:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "T &=& \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2 \\\\\n", + "&=& \\frac{1}{2}m_1(l_1\\dot\\theta_1)^2 + \\frac{1}{2}m_2(l_1\\dot\\theta_1 + l_2\\dot\\theta_2)^2 \\\\\n", + "&=& \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\n", + "\\end{eqnarray*}$$\n", + "\n", + "\n", + "So the Lagrangian quantity becomes:\n", + "$$\\begin{equation}\n", + "L = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+(m_1+m_2)l_1gcos\\theta_1 + m_2l_2gcos\\theta_2\n", + "\\end{equation}$$\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "The equations of motion are then found using the Lagrange Equation:\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", + "\\end{equation}$$\n", + "\n", + "\n", + "So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} &=& (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1) \\\\\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{\\theta_1}}\\right) &=& (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1) \\\\\n", + "-\\frac{\\partial L}{\\partial \\theta_1} &=& -m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)-(m_1+m_2)gl_1sin\\theta_1\n", + "\\end{eqnarray*}$$\n", + "\n", + "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", + "\n", + "$$\\begin{equation}\n", + "0 = (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", + "\\end{equation}$$\n", + "\n", + "We will also need the equation of motion for $\\theta_2$. This can be derived using the same process. It should work out to the following (check this by yourself!):\n", + "\n", + "$$\\begin{equation}\n", + "0 = m_2l_2^2\\ddot\\theta_2+m_2l_1l_2\\ddot\\theta_1cos(\\theta_2-\\theta_1) - m_2l_2l_1\\dot\\theta_1^2sin(\\theta_2-\\theta_1)+ l_2m_2gsin\\theta_2\n", + "\\end{equation}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problem setup\n", + "\n", + "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. So our state becomes:\n", + "\n", + "$$\\begin{eqnarray}\n", + "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\dot\\theta_1 \\\\ \\dot\\theta_2 \\end{pmatrix}\n", + "\\end{eqnarray}$$\n", + "\n", + "The equations of motion can be rewritten as a system of equations:\n", + "\n", + "$$ A\\dot x=f(x)$$\n", + "\n", + "where \n", + "\n", + "\\begin{equation}\n", + "A = \n", + "\\left[\\begin{array}{c}\n", + "1 &0 &0 &0 \\\\\n", + "0 &1 &0 &0 \\\\\n", + "0 &0 &(m_1+m_2)l_1^2 &m_2l_1l_2cos(x_2-x_1) \\\\\n", + "0 &0 &m_2l_1l_2cos(x_2-x_1) &m_2l_2^2\n", + "\\end{array}\\right]\n", + "\\end{equation}\n", + "\n", + "and\n", + "\n", + "$$\\begin{eqnarray}\n", + "f(\\vec{x}(t)) = \\begin{pmatrix} x_3\\\\ x_4\\\\ m_2l_1l_2x_4^2sin(x_2-x_1) -(m_1+m_2)gl_1sin(x_1)\\\\ m_2l_1l_2x_3^2sin(x_2-x_1)-l_2m_2gsinx_2 \\end{pmatrix}\n", + "\\end{eqnarray}$$\n", + "\n", + "In order to solve this, we can evaluate matrix A and vector $f(x)$ at each time step, then invert A and premultiply the result to each side of the equation. This gives us an explicit system of equations of motion:\n", + "\n", + "$$ \\dot x=A^{-1}f(x)$$\n", + "\n", + "You may notice that these operations require a significant computational load at each time step. In order to reduce this, we can analytically invert the matrix A, then simply fill in the necessary values at each time step and multiply by $f(x)$. This will significantly speed up running time, as inverting a matrix is an expensive operation.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Integration\n", + "\n", + "### Euler's Method\n", + "\n", + "The first method we will use is Euler integration, covered in lesson 1 of the Numerical Methods course. This is the simplest form of numerical integration, but it can still be effective. Using our equations for $\\dot x$ and $f(x)$:\n", + "\n", + "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", + "\n", + "where $h$ is the timestep.\n", + "\n", + "### Runge-Kutta Method (RK4)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "## Solve!\n", + "\n", + "Here we define functions for each of the integration methods, set our parameters and initial conditions, and then numerically integrate the motion of the pendulums. The plots below compare the different integration techniques." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sources: \n", + "\n", + "http://scienceworld.wolfram.com/physics/DoublePendulum.html\n", + "\n", + "http://www.phy.uct.ac.za/courses/opencontent/phylab2/worksheet9_09.pdf\n", + "\n", + "http://www.phys.lsu.edu/faculty/gonzalez/Teaching/Phys7221/DoublePendulum.pdf\n", + "\n", + "http://www.iontrap.wabash.edu/adlab/papers/F2011_foster_groninger_tang_chaos.pdf\n", + "\n", + "https://math.berkeley.edu/~alanw/242papers99/markiewicz.pdf\n", + "\n", + "http://www.unige.ch/~hairer/poly_geoint/week2.pdf\n", + "\n", + "http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1994CeMDA..60..409T&defaultprint=YES&filetype=.pdf\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This cell loads the style of the notebook, which is taken from the \n", + "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", + "\n", + "from IPython.core.display import HTML\n", + "css_file = './randy_schur/numericalmoocstyle.css'\n", + "HTML(open(css_file, \"r\").read())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From 94457d64208b7d9d41e4693b153869cad265ea25 Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 1 Dec 2015 20:26:57 -0500 Subject: [PATCH 10/61] in class changes 12/01 --- Double_Pendulum_Problem.ipynb | 706 --------------------- randy_schur/Double_Pendulum_Problem.ipynb | 204 ++++-- randy_schur/figures/spring-mass-damper.png | Bin 0 -> 11939 bytes 3 files changed, 137 insertions(+), 773 deletions(-) delete mode 100644 Double_Pendulum_Problem.ipynb create mode 100644 randy_schur/figures/spring-mass-damper.png diff --git a/Double_Pendulum_Problem.ipynb b/Double_Pendulum_Problem.ipynb deleted file mode 100644 index 59df853..0000000 --- a/Double_Pendulum_Problem.ipynb +++ /dev/null @@ -1,706 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Integration of Mechanical Systems\n", - "\n", - "In this module, we will be exploring numerical integrators and how they perform for different mechanical systems." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simple Harmonic Oscillator\n", - "\n", - "The simple harmonic oscillator is a simple physical system described by the second-order differential equation below. Despite its simplicity, this is a system that shows up in a similar form in many different fields of engineering. The same equation describes the behavior of a spring-mass-damper system, RLC circuit, or the motion of a (small-angle) pendulum.\n", - "\n", - "(Insert diagram for spring mass damper)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The differential equation describing the motion of a simple harmonic oscillator is:\n", - "\n", - "$$\\begin{equation}\n", - "0 = m\\ddot{x} + kx\n", - "\\end{equation}$$\n", - "\n", - "We can describe the state of the system by its position $x$ and its velocity $\\dot{x}$. We will call this state vector $q$, as:\n", - "\n", - "$$\\begin{eqnarray}\n", - "\\vec{q} = \\begin{bmatrix} x\\\\ \\dot{x} \\end{bmatrix}\n", - "\\end{eqnarray}$$\n", - "\n", - "This system can then be broken into two first order differential equations:\n", - "\n", - "$$\\begin{eqnarray}\n", - "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{k}{m}q_1 \\end{bmatrix}\n", - "\\end{eqnarray}$$\n", - "\n", - "To look at the behavior of this system, we can start with some initial conditions for $q$, then use a numerical integration method such as Euler's method to integrate forward in time. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Equations of Motion from Lagrangian\n", - "\n", - "You are encouraged to follow this derivation ON PAPER on your own. We gave you the equations of motion for a simple harmonic oscillator, but what if we don't have this equation? The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", - "\n", - "$$\\begin{equation}\n", - "L = T- U\n", - "\\end{equation}$$\n", - "\n", - "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "U &=& \\frac{1}{2}kx^2\\\\\n", - "\\end{eqnarray*}$$\n", - "\n", - "Kinetic Energy is calculated as:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}mv^2 \\\\\n", - "&=& \\frac{1}{2}m(\\dot x)^2 \n", - "\\end{eqnarray*}$$\n", - "\n", - "\n", - "So the Lagrangian quantity becomes:\n", - "$$\\begin{equation}\n", - "L = \\frac{1}{2}m(\\dot x)^2 - \\frac{1}{2}kx^2\\\\\n", - "\\end{equation}$$\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The equations of motion are then found using the Lagrange Equation:\n", - "\n", - "$$\\begin{equation}\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", - "\\end{equation}$$\n", - "\n", - "Since we have only one state (position), we need only one equation of motion. We'll calculate this in steps:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "\\frac{\\partial L}{\\partial \\dot{x}} &=& m\\dot{x} \\\\\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x}}\\right) &=& m\\ddot{x}\\\\\n", - "-\\frac{\\partial L}{\\partial x} &=& + kx\n", - "\\end{eqnarray*}$$\n", - "\n", - "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", - "\n", - "$$\\begin{equation}\n", - "0 = m\\ddot{x} + kx\n", - "\\end{equation}$$\n", - "\n", - "This is the same equation we have before! So, we can be relatively confident that this derivation is correct." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Integration Methods\n", - "\n", - "### Euler's method\n", - "\n", - "Euler's method is discussed in lesson 1 of the Numerical Methods course. The method goes as follows:\n", - "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", - "\n", - "where $h$ is the timestep. This isn't a complicated scheme, so for details see the code below." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Runge-Kutta Integration (RK4)\n", - "\n", - "Depending on the application, Euler's method may provide enough accuracy with a small timestep, especially a simple harmonic oscillator. However, Euler is a first-order method and we can do better. One option is a Runge-Kutta scheme. This is a popular numerical integration method which is easily extended to higher orders. Here we will use a fourth order Runge-Kutta, also known as RK4:\n", - "\n", - "$$\\begin{equation}\n", - "q_{n+1} = q_n + \\frac{h}{6}\\left(k_1+2k_2+2k_3+k_4\\right)\n", - "\\end{equation}$$\n", - "where\n", - "$$\\begin{eqnarray}\n", - "k_1 &=& f(t_n, x_n)\\\\\n", - "k_2 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_1)\\\\\n", - "k_3 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_2)\\\\\n", - "k_4 &=& f(t_n+h, y_n+hk_3)\\\\\n", - "t_{n+1} &=& t_n + h\n", - "\\end{eqnarray}$$\n", - "\n", - "For more information on the Runge-Kutta method, including the derivation and explanation of coefficients, see (cite)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Symplectic Integrators\n", - "\n", - "Symplectic integrators are similar to the methods discussed above, but they use equations of motion derived from Hamiltonian mechanics. According to [Berkeley source], syplectic integrators preserve the conserved Hamiltonian quantities. In practical terms, this works out to mean the methods reflect conservation of energy, down to a truncation error.\n", - "\n", - "The Hamiltonian equations of motion can be derived from the total energy in the system, much like the Lagrangian. The coordinates we use are position ($q$) and momentum ($p = m\\dot x$). The Hamiltonian is:\n", - "$$ H = T+V$$\n", - "where $T$ is the kinetic energy in the system, and $V$ is the potential energy in the system. So, we get:\n", - "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}m\\dot{x}^2 = \\frac{p^2}{2m}\\\\\n", - "V &=& mgq\\\\\n", - "H &=& \\frac{p^2}{2m} + mgq\n", - "\\end{eqnarray*}$$\n", - "\n", - "\n", - "The Hamilton's equations are:\n", - "$$\\begin{eqnarray*}\n", - "\\frac{dp}{dt} &=& -\\frac{\\partial H}{\\partial q}\\\\\n", - "\\frac{dq}{dt} &=& \\frac{\\partial H}{\\partial p}\n", - "\\end{eqnarray*}$$\n", - "\n", - "So our equations of motion become:\n", - "$$\\begin{equation*}\n", - "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q}\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -mg \\\\ \\frac{p}{m} \\end{bmatrix}\n", - "\\end{equation*}$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's now take a look at symplectic integration methods. We'll start with a first order method - symplectic Euler's method. This uses the equations:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "p_{n+1} = p_n - h \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1},q_n}\\\\\n", - "q_{n+1} = q_n + \\frac{h}{2} \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1},q_n}\n", - "\\end{eqnarray*}$$\n", - "\n", - "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method, $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "p_{n+1/2} &=& p_n - \\frac{h}{2} \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1/2},q_n}\\\\\n", - "q_{n+1} &=& q_n + \\frac{h}{2} \\left(\\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_n} + \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_{n+1/2}} \\right) \\\\\n", - "p_{n+1} &=& p_{n+1/2} - \\frac{h}{2} \\frac{\\partial H}{\\partial q}\\Bigr|_{p_{n+1/2},q_{n+1/2}}\n", - "\\end{eqnarray*}$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Analytical Solution\n", - "The simple harmonic oscillator is a nice example for numerical integration, because it has an analytical form which we can compare to. The analytical solution is\n", - "\n", - "$$\\begin{equation}\n", - "x(t) = Acos(\\omega_n t + \\phi)\n", - "\\end{equation}$$\n", - "\n", - "where $\\omega_n$ is the natural frequency of the system given by $\\omega_n = \\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Code section?" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "### Adding a damping term\n", - "\n", - "A more realistic oscillator also has a damping term. This might correspond to friction, or to some other non-conservative force. For now, we will assume that the damping is linear, and is proportional to the velocity (this is called the -- model of friction). We get the following equation of motion:\n", - "\n", - "$$\\begin{equation*}\n", - "0 = m\\ddot{x} + c\\dot{x} + kx\n", - "\\end{equation*}$$\n", - "\n", - "This gives us the differential equations:\n", - "$$\\begin{eqnarray*}\n", - "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{c}{m}\\dot{q_1} -\\frac{k}{m}q_1 \\end{bmatrix}\n", - "\\end{eqnarray*}$$\n", - "\n", - "The Euler's method and Runge-Kutta integration work the same way with this new equation. We also have a new analytical solution. Assuming the system is underdamped (add explanation of damping ratio), the solution becomes:\n", - "\n", - "\n", - "$$\\begin{equation}\n", - "x(t) = Acos(\\omega_d t + \\phi)\n", - "\\end{equation}$$\n", - "\n", - "where $\\omega_d = \\omega_n\\sqrt{1 - \\zeta^2}$ is called the damped natural frequency.\n", - "\n", - "The big change comes in the Hamiltonian equations. If these are dependent on convservation of energy, how do we deal with a system where energy is not conserved?\n", - "\n", - "Our first step is to re-examine the Lagrangian. Since losses due to friction are neither potential nor kinetic energy, we'll need a new term. The Lagrangian equation of motion with non-conservative forces is:\n", - "\n", - "$$\\begin{equation}\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = Q\n", - "\\end{equation}$$\n", - "\n", - "where $Q$ is the sum of all non-conservative forces. In this case, \n", - "$$Q = c\\frac{\\partial x}{\\partial t}$$\n", - "\n", - "Now that we have this, we can add a similar term to the Hamiltonian equations of motion to deal with the same problem. For more information on the derivation of the Hamiltonian from the Lagrangian, see Tveter (cite). The Hamiltonian equations of motion become:\n", - "\n", - "$$\\begin{equation*}\n", - "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q} + Q\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -mg + \\frac{c}{m}q \\\\ \\frac{p}{m} \\end{bmatrix}\n", - "\\end{equation*}$$\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Dynamics of a Double Pendulum" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this module, we will study the dynamics of a double pendulum. We first derive the equations of motion from the Lagrangian. Next, we take a look at three different numerical integration schemes: Euler's method, Runge-Kutta, and a symplectic integrator. We will investigate how the initial conditions affect the behavior of the system, as well as some of the sources of error in these simulations. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Background\n", - "\n", - "The double pendulum problem is a simple system which can produce surprisingly complex movements. It is a chaotic system, meaning it is unpredictable, and there is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", - "\n", - "In this notebook we will be treating the two rods as massless and ignoring the effects of friction. For a treatment of this problem without these assumptions, see: \n", - "\n", - "See the image below for the definition of constants." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Image](figures/diagram.png)\n", - "#### Figure 1. Diagram of Double Pendulum System" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Equations of Motion \n", - "\n", - "You are encouraged to follow this derivation ON PAPER on your own. The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", - "\n", - "$$\\begin{equation}\n", - "L = T- U\n", - "\\end{equation}$$\n", - "\n", - "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", - "\n", - "$$\\begin{eqnarray}\n", - "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", - " &=& -m_1g(l_1cos(\\theta_1) - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", - "\\end{eqnarray}$$\n", - "\n", - "Kinetic Energy is calculated as:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2 \\\\\n", - "&=& \\frac{1}{2}m_1(l_1\\dot\\theta_1)^2 + \\frac{1}{2}m_2(l_1\\dot\\theta_1 + l_2\\dot\\theta_2)^2 \\\\\n", - "&=& \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\n", - "\\end{eqnarray*}$$\n", - "\n", - "\n", - "So the Lagrangian quantity becomes:\n", - "$$\\begin{equation}\n", - "L = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+(m_1+m_2)l_1gcos\\theta_1 + m_2l_2gcos\\theta_2\n", - "\\end{equation}$$\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "The equations of motion are then found using the Lagrange Equation:\n", - "\n", - "$$\\begin{equation}\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", - "\\end{equation}$$\n", - "\n", - "\n", - "So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} &=& (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1) \\\\\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{\\theta_1}}\\right) &=& (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1) \\\\\n", - "-\\frac{\\partial L}{\\partial \\theta_1} &=& -m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)-(m_1+m_2)gl_1sin\\theta_1\n", - "\\end{eqnarray*}$$\n", - "\n", - "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", - "\n", - "$$\\begin{equation}\n", - "0 = (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", - "\\end{equation}$$\n", - "\n", - "We will also need the equation of motion for $\\theta_2$. This can be derived using the same process. It should work out to the following (check this by yourself!):\n", - "\n", - "$$\\begin{equation}\n", - "0 = m_2l_2^2\\ddot\\theta_2+m_2l_1l_2\\ddot\\theta_1cos(\\theta_2-\\theta_1) - m_2l_2l_1\\dot\\theta_1^2sin(\\theta_2-\\theta_1)+ l_2m_2gsin\\theta_2\n", - "\\end{equation}$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Problem setup\n", - "\n", - "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. So our state becomes:\n", - "\n", - "$$\\begin{eqnarray}\n", - "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\dot\\theta_1 \\\\ \\dot\\theta_2 \\end{pmatrix}\n", - "\\end{eqnarray}$$\n", - "\n", - "The equations of motion can be rewritten as a system of equations:\n", - "\n", - "$$ A\\dot x=f(x)$$\n", - "\n", - "where \n", - "\n", - "\\begin{equation}\n", - "A = \n", - "\\left[\\begin{array}{c}\n", - "1 &0 &0 &0 \\\\\n", - "0 &1 &0 &0 \\\\\n", - "0 &0 &(m_1+m_2)l_1^2 &m_2l_1l_2cos(x_2-x_1) \\\\\n", - "0 &0 &m_2l_1l_2cos(x_2-x_1) &m_2l_2^2\n", - "\\end{array}\\right]\n", - "\\end{equation}\n", - "\n", - "and\n", - "\n", - "$$\\begin{eqnarray}\n", - "f(\\vec{x}(t)) = \\begin{pmatrix} x_3\\\\ x_4\\\\ m_2l_1l_2x_4^2sin(x_2-x_1) -(m_1+m_2)gl_1sin(x_1)\\\\ m_2l_1l_2x_3^2sin(x_2-x_1)-l_2m_2gsinx_2 \\end{pmatrix}\n", - "\\end{eqnarray}$$\n", - "\n", - "In order to solve this, we can evaluate matrix A and vector $f(x)$ at each time step, then invert A and premultiply the result to each side of the equation. This gives us an explicit system of equations of motion:\n", - "\n", - "$$ \\dot x=A^{-1}f(x)$$\n", - "\n", - "You may notice that these operations require a significant computational load at each time step. In order to reduce this, we can analytically invert the matrix A, then simply fill in the necessary values at each time step and multiply by $f(x)$. This will significantly speed up running time, as inverting a matrix is an expensive operation.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Integration\n", - "\n", - "### Euler's Method\n", - "\n", - "The first method we will use is Euler integration, covered in lesson 1 of the Numerical Methods course. This is the simplest form of numerical integration, but it can still be effective. Using our equations for $\\dot x$ and $f(x)$:\n", - "\n", - "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", - "\n", - "where $h$ is the timestep.\n", - "\n", - "### Runge-Kutta Method (RK4)\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "## Solve!\n", - "\n", - "Here we define functions for each of the integration methods, set our parameters and initial conditions, and then numerically integrate the motion of the pendulums. The plots below compare the different integration techniques." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sources: \n", - "\n", - "http://scienceworld.wolfram.com/physics/DoublePendulum.html\n", - "\n", - "http://www.phy.uct.ac.za/courses/opencontent/phylab2/worksheet9_09.pdf\n", - "\n", - "http://www.phys.lsu.edu/faculty/gonzalez/Teaching/Phys7221/DoublePendulum.pdf\n", - "\n", - "http://www.iontrap.wabash.edu/adlab/papers/F2011_foster_groninger_tang_chaos.pdf\n", - "\n", - "https://math.berkeley.edu/~alanw/242papers99/markiewicz.pdf\n", - "\n", - "http://www.unige.ch/~hairer/poly_geoint/week2.pdf\n", - "\n", - "http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1994CeMDA..60..409T&defaultprint=YES&filetype=.pdf\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# This cell loads the style of the notebook, which is taken from the \n", - "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", - "\n", - "from IPython.core.display import HTML\n", - "css_file = './randy_schur/numericalmoocstyle.css'\n", - "HTML(open(css_file, \"r\").read())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "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.4.3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 3fcaa39..125771d 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -15,9 +15,16 @@ "source": [ "## Simple Harmonic Oscillator\n", "\n", - "The simple harmonic oscillator is a simple physical system described by the second-order differential equation below. Despite its simplicity, this is a system that shows up in a similar form in many different fields of engineering. The same equation describes the behavior of a spring-mass-damper system, RLC circuit, or the motion of a (small-angle) pendulum.\n", + "The simple harmonic oscillator is a simple physical system described by the second-order differential equation below. Despite its simplicity, this is a system that shows up in a similar form in many different fields of engineering. The same equation describes the behavior of a spring-mass-damper system, RLC circuit, or the motion of a (small-angle) pendulum.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Image](figures/spring-mass-damper.png)\n", "\n", - "(Insert diagram for spring mass damper)" + "##### Figure 1. Diagram of Spring-Mass-Damper system (from https://en.wikipedia.org/wiki/Harmonic_oscillator)" ] }, { @@ -48,12 +55,70 @@ { "cell_type": "markdown", "metadata": {}, - "source": [] + "source": [ + "### Equations of Motion from Lagrangian\n", + "\n", + "You are encouraged to follow this derivation ON PAPER on your own. We gave you the equations of motion for a simple harmonic oscillator, but what if we don't have this equation? The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", + "\n", + "$$\\begin{equation}\n", + "L = T- U\n", + "\\end{equation}$$\n", + "\n", + "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "U &=& \\frac{1}{2}kx^2\\\\\n", + "\\end{eqnarray*}$$\n", + "\n", + "Kinetic Energy is calculated as:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "T &=& \\frac{1}{2}mv^2 \\\\\n", + "&=& \\frac{1}{2}m(\\dot x)^2 \n", + "\\end{eqnarray*}$$\n", + "\n", + "\n", + "So the Lagrangian quantity becomes:\n", + "$$\\begin{equation}\n", + "L = \\frac{1}{2}m(\\dot x)^2 - \\frac{1}{2}kx^2\\\\\n", + "\\end{equation}$$\n", + "\n", + "\n" + ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "The equations of motion are then found using the Lagrange Equation:\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", + "\\end{equation}$$\n", + "\n", + "Since we have only one generalized coordinate (position), we need only one equation of motion. We'll calculate this in steps:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "\\frac{\\partial L}{\\partial \\dot{x}} &=& m\\dot{x} \\\\\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x}}\\right) &=& m\\ddot{x}\\\\\n", + "-\\frac{\\partial L}{\\partial x} &=& + kx\n", + "\\end{eqnarray*}$$\n", + "\n", + "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", + "\n", + "$$\\begin{equation}\n", + "0 = m\\ddot{x} + kx\n", + "\\end{equation}$$\n", + "\n", + "This is the same equation we have before! So, we can be relatively confident that this derivation is correct." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Integration Methods\n", + "\n", "### Euler's method\n", "\n", "Euler's method is discussed in lesson 1 of the Numerical Methods course. The method goes as follows:\n", @@ -96,9 +161,12 @@ "The Hamiltonian equations of motion can be derived from the total energy in the system, much like the Lagrangian. The coordinates we use are position ($q$) and momentum ($p = m\\dot x$). The Hamiltonian is:\n", "$$ H = T+V$$\n", "where $T$ is the kinetic energy in the system, and $V$ is the potential energy in the system. So, we get:\n", - "$$T = \\frac{1}{2}m\\dot{x}^2 = \\frac{p^2}{2m}$$\n", - "and\n", - "$$V = mgq$$\n", + "$$\\begin{eqnarray*}\n", + "T &=& \\frac{1}{2}m\\dot{x}^2 = \\frac{p^2}{2m}\\\\\n", + "V &=& mgq\\\\\n", + "H &=& \\frac{p^2}{2m} + mgq\n", + "\\end{eqnarray*}$$\n", + "\n", "\n", "The Hamilton's equations are:\n", "$$\\begin{eqnarray*}\n", @@ -108,7 +176,7 @@ "\n", "So our equations of motion become:\n", "$$\\begin{equation*}\n", - "\\begin{bmatrix} \\dot q \\\\ \\dot p \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q}\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -mg \\\\ \\frac{p}{m} \\end{bmatrix}\n", + "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q}\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -mg \\\\ \\frac{p}{m} \\end{bmatrix}\n", "\\end{equation*}$$" ] }, @@ -123,7 +191,7 @@ "q_{n+1} = q_n + \\frac{h}{2} \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1},q_n}\n", "\\end{eqnarray*}$$\n", "\n", - "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method, $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations:\n", + "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method, $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations:\n", "\n", "$$\\begin{eqnarray*}\n", "p_{n+1/2} &=& p_n - \\frac{h}{2} \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1/2},q_n}\\\\\n", @@ -132,11 +200,6 @@ "\\end{eqnarray*}$$" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, { "cell_type": "markdown", "metadata": {}, @@ -148,7 +211,14 @@ "x(t) = Acos(\\omega_n t + \\phi)\n", "\\end{equation}$$\n", "\n", - "where $\\omega_n$ is the natural frequency of the system given by $\\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " + "where $\\omega_n$ is the natural frequency of the system given by $\\omega_n = \\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Code section?" ] }, { @@ -170,7 +240,7 @@ "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{c}{m}\\dot{q_1} -\\frac{k}{m}q_1 \\end{bmatrix}\n", "\\end{eqnarray*}$$\n", "\n", - "We also have a new analytical solution. Assuming the system is underdamped (add explanation of damping ratio), the solution becomes:\n", + "The Euler's method and Runge-Kutta integration work the same way with this new equation. We also have a new analytical solution. Assuming the system is underdamped (add explanation of damping ratio), the solution becomes:\n", "\n", "\n", "$$\\begin{equation}\n", @@ -178,18 +248,34 @@ "\\end{equation}$$\n", "\n", "where $\\omega_d = \\omega_n\\sqrt{1 - \\zeta^2}$ is called the damped natural frequency.\n", - "\n" + "\n", + "The big change comes in the Hamiltonian equations. If these are dependent on convservation of energy, how do we deal with a system where energy is not conserved?\n", + "\n", + "Our first step is to re-examine the Lagrangian. Since losses due to friction are neither potential nor kinetic energy, we'll need a new term. The Lagrangian equation of motion with non-conservative forces is:\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = Q\n", + "\\end{equation}$$\n", + "\n", + "where $Q$ is the sum of all non-conservative forces. In this case, \n", + "$$Q = c\\frac{\\partial x}{\\partial t}$$\n", + "\n", + "Now that we have this, we can add a similar term to the Hamiltonian equations of motion to deal with the same problem. For more information on the derivation of the Hamiltonian from the Lagrangian, see Tveter (cite). The Hamiltonian equations of motion become:\n", + "\n", + "$$\\begin{equation*}\n", + "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q} + Q\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -mg + \\frac{c}{m}q \\\\ \\frac{p}{m} \\end{bmatrix}\n", + "\\end{equation*}$$\n", + "\n", + "From here, the we can use the same Verlet integration method as before." ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, - "outputs": [], "source": [ - "### Adding a " + "### Duffing equation (Ading a non-linear damping term)" ] }, { @@ -228,15 +314,6 @@ "outputs": [], "source": [] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "metadata": {}, @@ -248,7 +325,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this module, we will study the dynamics of a double pendulum. We first derive the equations of motion from the Lagrangian. Next, we take a look at three different numerical integration schemes: Euler's method, Runge-Kutta, and a symplectic integrator. We will investigate how the initial conditions affect the behavior of the system, as well as some of the sources of error in these simulations. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." + "In this module, we will study the dynamics of a double pendulum. Like before, we'll derive the equations of motion from the Lagrangian and the Hamiltonian. We can use the same methods of integration as before, but this time there is no analytical solution to compare to. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." ] }, { @@ -259,7 +336,7 @@ "\n", "The double pendulum problem is a simple system which can produce surprisingly complex movements. It is a chaotic system, meaning it is unpredictable, and there is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", "\n", - "In this notebook we will be treating the two rods as massless and ignoring the effects of friction. For a treatment of this problem without these assumptions, see: \n", + "In this notebook we will be treating the two rods as massless and ignoring the effects of friction. For a treatment of this problem without these assumptions, see (cite).\n", "\n", "See the image below for the definition of constants." ] @@ -276,20 +353,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Equations of Motion\n", - "\n", - "You are encouraged to follow this derivation ON PAPER on your own. The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", + "## Equations of Motion \n", "\n", - "$$\\begin{equation}\n", + "As a reminder, the Lagrangian is\n", + "$$\\begin{equation*}\n", "L = T- U\n", - "\\end{equation}$$\n", - "\n", + "\\end{equation*}$$\n", "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", "\n", - "$$\\begin{eqnarray}\n", + "$$\\begin{eqnarray*}\n", "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", " &=& -m_1g(l_1cos(\\theta_1) - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", - "\\end{eqnarray}$$\n", + "\\end{eqnarray*}$$\n", "\n", "Kinetic Energy is calculated as:\n", "\n", @@ -301,9 +376,9 @@ "\n", "\n", "So the Lagrangian quantity becomes:\n", - "$$\\begin{equation}\n", + "$$\\begin{equation*}\n", "L = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+(m_1+m_2)l_1gcos\\theta_1 + m_2l_2gcos\\theta_2\n", - "\\end{equation}$$\n" + "\\end{equation*}$$\n" ] }, { @@ -314,12 +389,12 @@ "source": [ "The equations of motion are then found using the Lagrange Equation:\n", "\n", - "$$\\begin{equation}\n", + "$$\\begin{equation*}\n", "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", - "\\end{equation}$$\n", + "\\end{equation*}$$\n", "\n", "\n", - "So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", + "There are two generalized coordinates ($\\theta_1 and \\theta_2$. So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", "\n", "$$\\begin{eqnarray*}\n", "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} &=& (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1) \\\\\n", @@ -329,15 +404,15 @@ "\n", "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", "\n", - "$$\\begin{equation}\n", + "$$\\begin{equation*}\n", "0 = (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", - "\\end{equation}$$\n", + "\\end{equation*}$$\n", "\n", "We will also need the equation of motion for $\\theta_2$. This can be derived using the same process. It should work out to the following (check this by yourself!):\n", "\n", - "$$\\begin{equation}\n", + "$$\\begin{equation*}\n", "0 = m_2l_2^2\\ddot\\theta_2+m_2l_1l_2\\ddot\\theta_1cos(\\theta_2-\\theta_1) - m_2l_2l_1\\dot\\theta_1^2sin(\\theta_2-\\theta_1)+ l_2m_2gsin\\theta_2\n", - "\\end{equation}$$" + "\\end{equation*}$$" ] }, { @@ -349,16 +424,16 @@ "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. So our state becomes:\n", "\n", "$$\\begin{eqnarray}\n", - "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\dot\\theta_1 \\\\ \\dot\\theta_2 \\end{pmatrix}\n", + "\\vec{q}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\dot\\theta_1 \\\\ \\dot\\theta_2 \\end{pmatrix}\n", "\\end{eqnarray}$$\n", "\n", "The equations of motion can be rewritten as a system of equations:\n", "\n", - "$$ A\\dot x=f(x)$$\n", + "$$ A\\dot q=f(q)$$\n", "\n", "where \n", "\n", - "\\begin{equation}\n", + "\\begin{equation*}\n", "A = \n", "\\left[\\begin{array}{c}\n", "1 &0 &0 &0 \\\\\n", @@ -366,19 +441,19 @@ "0 &0 &(m_1+m_2)l_1^2 &m_2l_1l_2cos(x_2-x_1) \\\\\n", "0 &0 &m_2l_1l_2cos(x_2-x_1) &m_2l_2^2\n", "\\end{array}\\right]\n", - "\\end{equation}\n", + "\\end{equation*}\n", "\n", "and\n", "\n", - "$$\\begin{eqnarray}\n", - "f(\\vec{x}(t)) = \\begin{pmatrix} x_3\\\\ x_4\\\\ m_2l_1l_2x_4^2sin(x_2-x_1) -(m_1+m_2)gl_1sin(x_1)\\\\ m_2l_1l_2x_3^2sin(x_2-x_1)-l_2m_2gsinx_2 \\end{pmatrix}\n", - "\\end{eqnarray}$$\n", + "$$\\begin{eqnarray*}\n", + "f(\\vec{q}(t)) = \\begin{pmatrix} q_3\\\\ q_4\\\\ m_2l_1l_2x_4^2sin(q_2-q_1) -(m_1+m_2)gl_1sin(q_1)\\\\ m_2l_1l_2q_3^2sin(q_2-q_1)-l_2m_2gsinq_2 \\end{pmatrix}\n", + "\\end{eqnarray*}$$\n", "\n", "In order to solve this, we can evaluate matrix A and vector $f(x)$ at each time step, then invert A and premultiply the result to each side of the equation. This gives us an explicit system of equations of motion:\n", "\n", - "$$ \\dot x=A^{-1}f(x)$$\n", + "$$ \\dot q=A^{-1}f(q)$$\n", "\n", - "You may notice that these operations require a significant computational load at each time step. In order to reduce this, we can analytically invert the matrix A, then simply fill in the necessary values at each time step and multiply by $f(x)$. This will significantly speed up running time, as inverting a matrix is an expensive operation.\n" + "You may notice that these operations require a significant computational load at each time step. In order to reduce this, we can analytically invert the matrix A, then simply fill in the necessary values at each time step and multiply by $f(q)$. This will significantly speed up running time, as inverting a matrix is an expensive operation.\n" ] }, { @@ -387,15 +462,10 @@ "source": [ "## Integration\n", "\n", - "### Euler's Method\n", - "\n", - "The first method we will use is Euler integration, covered in lesson 1 of the Numerical Methods course. This is the simplest form of numerical integration, but it can still be effective. Using our equations for $\\dot x$ and $f(x)$:\n", - "\n", - "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", + "For Euler and Runge-Kutta, we can use the equation above to integrate our system. The results will be VERY dependent on initial conditions; this is why we call it a chaotic system.\n", "\n", - "where $h$ is the timestep.\n", + "For the symplectic integrator, we'll need the equations of motion from the Hamiltonian. If you want good practice, you can derive these on your own. The Hamiltonian equations become:\n", "\n", - "### Runge-Kutta Method (RK4)\n", "\n" ] }, @@ -434,7 +504,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": { "collapsed": false }, @@ -589,7 +659,7 @@ "" ] }, - "execution_count": 2, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } @@ -599,7 +669,7 @@ "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", "\n", "from IPython.core.display import HTML\n", - "css_file = 'numericalmoocstyle.css'\n", + "css_file = './randy_schur/numericalmoocstyle.css'\n", "HTML(open(css_file, \"r\").read())" ] }, diff --git a/randy_schur/figures/spring-mass-damper.png b/randy_schur/figures/spring-mass-damper.png new file mode 100644 index 0000000000000000000000000000000000000000..6d473b7743810a379b1b68dd02bfcfb5bde94bb0 GIT binary patch literal 11939 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 DQ=vp- literal 0 HcmV?d00001 From 6c7ef12ee3ba553cfd168b314ce11c3f27f8d499 Mon Sep 17 00:00:00 2001 From: rbds Date: Fri, 4 Dec 2015 18:33:39 -0500 Subject: [PATCH 11/61] bad job starting programming --- randy_schur/Double_Pendulum_Problem.ipynb | 118 ++++++++++++++++++++-- 1 file changed, 112 insertions(+), 6 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 125771d..6696c5c 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -218,9 +218,115 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Code section?" + "### Let's Get to Programming" ] }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy\n", + "from scipy.linalg import solve\n", + "from math import pi\n", + "\n", + "from matplotlib import pyplot\n", + "%matplotlib notebook\n", + "from matplotlib import rcParams, cm\n", + "rcParams['font.family'] = 'serif'\n", + "rcParams['font.size'] = 16" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "#Set up parameters:\n", + "\n", + "m = 3\n", + "k = 17;\n", + "\n", + "T = 2.5; #[seconds]\n", + "dt = .01; #\n", + "N = int(T/dt)+1\n", + "t = numpy.linspace(0.0, T, N)\n", + "\n", + "#Initial Conditions\n", + "x0 = numpy.array([pi/2, 0])\n", + "xdot0 = numpy.zeros_like(x0)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def euler(u, f, dt):\n", + " \"\"\" Euler's method for integrating a system of differential equations.\n", + " \n", + " Parameters:\n", + " IC - \n", + " x -\n", + " dt-\n", + " \n", + " Returns: \n", + " x - array of values at time T.\n", + " \"\"\"\n", + " \n", + " return u + dt*f(u)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def f(u):\n", + " \"\"\"Returns RHS of harmonic oscillator EOM\n", + " \n", + " \n", + " \"\"\"\n", + " pos = u[0]\n", + " vel = u[1]\n", + " \n", + " return numpy.array([vel, k/m*vel])\n", + " \n", + " \n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "metadata": { @@ -685,21 +791,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.3" + "pygments_lexer": "ipython2", + "version": "2.7.10" } }, "nbformat": 4, From bb0a2b05918f50b68a073dc16a66ff8beb501705 Mon Sep 17 00:00:00 2001 From: rbds Date: Sat, 5 Dec 2015 16:25:54 -0500 Subject: [PATCH 12/61] better start to programming --- randy_schur/Double_Pendulum_Problem.ipynb | 855 +++++++++++++++++++++- 1 file changed, 830 insertions(+), 25 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 6696c5c..d2b7906 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -223,7 +223,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 60, "metadata": { "collapsed": false }, @@ -231,7 +231,7 @@ "source": [ "import numpy\n", "from scipy.linalg import solve\n", - "from math import pi\n", + "from math import pi, cos, sin, sqrt\n", "\n", "from matplotlib import pyplot\n", "%matplotlib notebook\n", @@ -242,30 +242,32 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 61, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ "#Set up parameters:\n", "\n", "m = 3\n", - "k = 17;\n", + "k = 12;\n", "\n", - "T = 2.5; #[seconds]\n", + "T = 5; #[seconds]\n", "dt = .01; #\n", "N = int(T/dt)+1\n", "t = numpy.linspace(0.0, T, N)\n", "\n", "#Initial Conditions\n", - "x0 = numpy.array([pi/2, 0])\n", - "xdot0 = numpy.zeros_like(x0)\n" + "x0 = 2\n", + "xdot0 = 0\n", + "\n", + "x_init = numpy.array([x0, xdot0])" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 62, "metadata": { "collapsed": true }, @@ -275,45 +277,848 @@ " \"\"\" Euler's method for integrating a system of differential equations.\n", " \n", " Parameters:\n", - " IC - \n", - " x -\n", - " dt-\n", + " u - state at current step \n", + " f - RHS of equation\n", + " dt- time step size\n", " \n", " Returns: \n", " x - array of values at time T.\n", " \"\"\"\n", " \n", - " return u + dt*f(u)\n", + " return u + dt*f(u) " + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def RK4(u, f, dt):\n", + " k1 = f(u)\n", + " k2 = f(u) + 0.5*dt*k1\n", + " k3 = f(u) + 0.5*dt*k2\n", + " k4 = f(u) + dt*k3\n", + " \n", + " return u + dt/6*(k1+2*k2+2*k3+k4)\n", " " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 64, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def f(u):\n", + "def f_harmonic_oscillator(q):\n", " \"\"\"Returns RHS of harmonic oscillator EOM\n", " \n", + " Parameters:\n", + " q - initial state\n", + " \n", + " Returns:\n", + " f - RHS of harmonic oscillator eqn.\n", " \n", " \"\"\"\n", - " pos = u[0]\n", - " vel = u[1]\n", + " pos = q[0]\n", + " vel = q[1]\n", " \n", - " return numpy.array([vel, k/m*vel])\n", + " return numpy.array([vel, -k/m*pos])" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#Euler\n", + "q1 = numpy.zeros((N,2)) \n", + "q1[0] = x_init.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " q1[n+1] = euler(q1[n], f_damped_SHM, dt)\n", + " #print(q1[n])\n", + " \n", + "#Runge-Kutta \n", + "q2 = numpy.zeros((N,2))\n", + "q2[0] = x_init.copy()\n", + "for n in range(N-1):\n", + " q2[n+1] = RK4(q2[n], f_damped_SHM, dt)\n", + "\n", + "#Symplectic\n", + "q3 = numpy.zeros((N,2))\n", + "q3[0] = x_init_H.copy()\n", + "for n in range(N-1):\n", + " q3[n+1] = verlet_SHM(q3[n], dt)\n", + "\n", + "#Analytical Solution \n", + "A = x0 #with no forcing, the max amplitude is equal to initial amplitude\n", + "phi = 0\n", + "q_analytical = A*numpy.cos(numpy.sqrt(k/m)*t + phi)\n", + " \n", + "pyplot.figure(figsize=(12,8));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.title('Harmonic oscillator position');\n", + "pyplot.plot(t, q1[:,0], lw=2, label='Euler');\n", + "pyplot.plot(t, q2[:,0], 'r-', lw=2, label='RK4');\n", + "pyplot.plot(t, q3[:,0], 'g', lw=2, label='Verlet');\n", + "pyplot.plot(t, q_analytical, 'k--', lw=2, label='analytical');\n", + "pyplot.legend();\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "metadata": { From 23dacc591ee4d9a1422d63b4fe97407a006fb9e5 Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 6 Dec 2015 09:08:10 -0500 Subject: [PATCH 17/61] need to calculate analytical velocity for SHM --- randy_schur/Double_Pendulum_Problem.ipynb | 1262 ++++++--------------- 1 file changed, 333 insertions(+), 929 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index c16acba..54e4c6a 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -223,11 +223,20 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 1, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/randy/anaconda3/lib/python3.4/site-packages/IPython/kernel/__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", + " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" + ] + } + ], "source": [ "import numpy\n", "from scipy.linalg import solve\n", @@ -242,7 +251,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -255,14 +264,14 @@ "#g = 9.8 #[m/s**2]\n", "\n", "T = 25; #[seconds]\n", - "dt = .01; #\n", + "dt = .02; #\n", "N = int(T/dt)+1\n", "t = numpy.linspace(0.0, T, N)" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -285,7 +294,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -311,13 +320,13 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 5, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def f_harmonic_oscillator(q):\n", + "def f_harmonic_oscillator(u):\n", " \"\"\"Returns RHS of harmonic oscillator EOM\n", " \n", " Parameters:\n", @@ -327,15 +336,15 @@ " f - RHS of harmonic oscillator eqn.\n", " \n", " \"\"\"\n", - " pos = q[0]\n", - " vel = q[1]\n", + " pos = u[0]\n", + " vel = u[1]\n", " \n", " return numpy.array([vel, -k/m*pos])" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -355,909 +364,15 @@ " p_half = pos - dt/2*mom/m\n", " q_half = mom + dt/2*(k*pos)\n", " \n", - " q = mom + dt/2*(2*k*p_half)\n", - " p = p_half - dt/2*q/m\n", - " \n", - " return numpy.array([p, q]) " - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "#Initial Conditions\n", - "x0 = 0.5 #[m]\n", - "xdot0 = 0 #[m/s]\n", - "\n", - "p0 = x0\n", - "q0 = m*xdot0\n", - "\n", - "x_init = numpy.array([x0, xdot0])\n", - "x_init_H = numpy.array([p0, q0])" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "metadata": { - "collapsed": false, - "scrolled": true - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pyplot.figure(figsize=(10,6))\n", + "pyplot.tick_params(axis='both', labelsize=14)\n", + "pyplot.grid(True)\n", + "pyplot.xlabel('$\\Delta t$', fontsize=16)\n", + "pyplot.ylabel('Error', fontsize=16)\n", + "pyplot.loglog(dt_values, Euler_error_values, 'bo-', label='Euler')\n", + "pyplot.loglog(dt_values, RK_error_values, 'ro-', label='RK4')\n", + "pyplot.loglog(dt_values, Verlet_error_values, 'go-', label='Symplectic')\n", + "pyplot.axis('equal')\n", + "pyplot.legend();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. But ordinarily we don't see a second order scheme (Verlet) outperforming a fourth order scheme (RK4) by three orders of magnitude. What is going on?\n", + "\n", + "One thing we notice in the plots of position is that for Euler and Runge-Kutta, the spring is stretched farther and farther each oscillation. This means it has more and more potential energy. With no outside forces adding energy to the system and no friction to remove energy, Conservation of Energy says that value should be constant! Let's take a look at total energy in the system. Since we derived equations of motion from the Lagrangian, this should be easy! All we need to do is calculate kinetic and potential energy at each time step, and we already have the information we need to do this. " + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def get_Energy(u):\n", + " \"\"\" Calculates total energu in the system at given time step for a simple harmonic oscillator\n", + " Parameters:\n", + " u - state of system [pos, vel]\n", + " Returns:\n", + " total energy in system.\n", + " \"\"\"\n", + " pos = u[0]\n", + " vel = u[1]\n", + " T = 0.5*m*vel**2\n", + " V = 0.5*k*pos**2\n", + " \n", + " return T+V" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#Euler total energy\n", + "Euler_energy = numpy.zeros_like(t)\n", + "for i in range(N):\n", + " Euler_energy[i] = get_Energy(q1[i,:])\n", + " \n", + "\n", + "#RK4 total energy\n", + "RK4_energy = numpy.zeros_like(t)\n", + "for i in range(N):\n", + " RK4_energy[i] = get_Energy(q2[i,:])\n", + "\n", + "#Verlet total energy\n", + "#Here we will convert from momentum to velocity so we can use the same get_energy function.\n", + "#Momemtum (q) is mass*velocity.\n", + "Verlet_energy = numpy.zeros_like(t)\n", + "Verlet_vel = q3[:,1]/m\n", + "for i in range(N):\n", + " Verlet_energy[i] = get_Energy(numpy.array([q3[i,0], Verlet_vel[i]]))\n", + "\n", + "\n", + "#analytic total energy\n", + "analytical_energy = numpy.zeros_like(t)\n", + "anayltical_state = numpy.array([q_analytical, vel_analytical]).T\n", + "for i in range(N):\n", + " analytical_energy[i] = get_Energy(anayltical_state[i,:])" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#Euler\n", + "q1 = numpy.zeros((N,2)) \n", + "q1[0] = x_init.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " q1[n+1] = euler(q1[n], f_damped_HM, dt)\n", + " #print(q1[n])\n", + " \n", + "#Runge-Kutta \n", + "q2 = numpy.zeros((N,2))\n", + "q2[0] = x_init.copy()\n", + "for n in range(N-1):\n", + " q2[n+1] = RK4(q2[n], f_damped_HM, dt)\n", + "\n", + "#Symplectic\n", + "q3 = numpy.zeros((N,2))\n", + "q3[0] = x_init_H.copy()\n", + "for n in range(N-1):\n", + " q3[n+1] = Verlet_damped_HM(q3[n], dt)\n", "\n", "#Analytical Solution \n", "A = x0 #with no forcing, the max amplitude is equal to initial amplitude\n", From ee9438f9745b15395a7523c921e7fc7725925834 Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 6 Dec 2015 13:50:02 -0500 Subject: [PATCH 20/61] damped harmonic oscillations --- randy_schur/Double_Pendulum_Problem.ipynb | 265 ++++++++++++++++++---- randy_schur/numericalmoocstyle.css | 5 +- 2 files changed, 221 insertions(+), 49 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index b1a1efb..fb997f8 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -223,11 +223,20 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 4, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/randy/anaconda3/lib/python3.4/site-packages/IPython/kernel/__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", + " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" + ] + } + ], "source": [ "import numpy\n", "from scipy.linalg import solve\n", @@ -242,7 +251,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -262,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -285,7 +294,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -311,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 8, "metadata": { "collapsed": true }, @@ -335,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -363,7 +372,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 10, "metadata": { "collapsed": true }, @@ -382,7 +391,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 11, "metadata": { "collapsed": false, "scrolled": true @@ -1189,7 +1198,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 12, "metadata": { "collapsed": true }, @@ -1212,7 +1221,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -1233,7 +1242,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -2011,7 +2020,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -2034,7 +2043,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -2069,7 +2078,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -2893,7 +2902,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 18, "metadata": { "collapsed": true }, @@ -2917,7 +2926,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 46, "metadata": { "collapsed": true }, @@ -2934,11 +2943,11 @@ " pos = u[0]\n", " mom = u[1]\n", " \n", - " p_half = pos - dt/2*(mom/m - c/m)\n", - " q_half = mom + dt/2*(k*pos)\n", + " p_half = pos - dt/2*(mom/m)\n", + " #q_half = mom + dt/2*(k*pos)\n", " \n", - " q = mom + dt/2*(2*k*p_half)\n", - " p = p_half - dt/2*(q/m - c/m)\n", + " q = mom + dt*(k*p_half - c/m*mom)\n", + " p = p_half - dt/2*(q/m)\n", " \n", " return numpy.array([p, q]) \n", " " @@ -2946,7 +2955,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 47, "metadata": { "collapsed": true }, @@ -2970,12 +2979,14 @@ "x_init = numpy.array([x0, xdot0])\n", "x_init_H = numpy.array([p0, q0])\n", "zeta = 0.1 #[N/(m/s)]\n", - "c = zeta*2*m*sqrt(k/m)" + "wn = sqrt(k/m)\n", + "wd = wn*sqrt(1-zeta**2)\n", + "c = zeta*2*m*wn" ] }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 48, "metadata": { "collapsed": false }, @@ -3719,7 +3730,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3731,49 +3742,53 @@ ], "source": [ "#Euler\n", - "q1 = numpy.zeros((N,2)) \n", - "q1[0] = x_init.copy() #set initial conditions\n", + "q1_d = numpy.zeros((N,2)) \n", + "q1_d[0] = x_init.copy() #set initial conditions\n", "for n in range(N-1): #integrate with Euler\n", - " q1[n+1] = euler(q1[n], f_damped_HM, dt)\n", + " q1_d[n+1] = euler(q1_d[n], f_damped_HM, dt)\n", " #print(q1[n])\n", " \n", "#Runge-Kutta \n", - "q2 = numpy.zeros((N,2))\n", - "q2[0] = x_init.copy()\n", + "q2_d = numpy.zeros((N,2))\n", + "q2_d[0] = x_init.copy()\n", "for n in range(N-1):\n", - " q2[n+1] = RK4(q2[n], f_damped_HM, dt)\n", + " q2_d[n+1] = RK4(q2_d[n], f_damped_HM, dt)\n", "\n", "#Symplectic\n", - "q3 = numpy.zeros((N,2))\n", - "q3[0] = x_init_H.copy()\n", + "q3_d = numpy.zeros((N,2))\n", + "q3_d[0] = x_init_H.copy()\n", "for n in range(N-1):\n", - " q3[n+1] = Verlet_damped_HM(q3[n], dt)\n", + " q3_d[n+1] = Verlet_damped_HM(q3_d[n], dt)\n", "\n", "#Analytical Solution \n", "A = x0 #with no forcing, the max amplitude is equal to initial amplitude\n", "phi = 0\n", - "q_analytical = A*numpy.cos(numpy.sqrt(k/m)*t + phi)\n", + "q_d_analytical = A*numpy.exp(-zeta*wn*t)*numpy.cos(wd*t + phi)\n", " \n", - "pyplot.figure(figsize=(12,8));\n", + "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Harmonic oscillator position');\n", - "pyplot.plot(t, q1[:,0], lw=2, label='Euler');\n", - "pyplot.plot(t, q2[:,0], 'r-', lw=2, label='RK4');\n", - "pyplot.plot(t, q3[:,0], 'g', lw=2, label='Verlet');\n", - "pyplot.plot(t, q_analytical, 'k--', lw=2, label='analytical');\n", + "pyplot.plot(t, q1_d[:,0], lw=2, label='Euler');\n", + "pyplot.plot(t, q2_d[:,0], 'r-', lw=2, label='RK4');\n", + "pyplot.plot(t, q3_d[:,0], 'g', lw=2, label='Verlet');\n", + "pyplot.plot(t, q_d_analytical, 'k--', lw=2, label='analytical');\n", "pyplot.legend();\n" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, - "outputs": [], - "source": [] + "source": [ + "How does each integrator perform compared to the case of simple harmonic motion? From the eye test, it seems like the error is actually better here. Why is that? \n", + "\n", + "The effect of the damper is to remove energy from the system. So, in this case the damping term is helping to remove some energy from the system that the integration adds. As time goes to infinity, these solutions will converge, and the Euler and Runge-Kutta methods are stable. \n", + "\n", + "Another way to investigate stability is with a phase plot. Instead of position vs. time, we can plot velocity vs. position. This plot is in phase space (a subset of state space). For any given state (position and velocity), we should be able to determine what the next state will be. This gives us an entire direction field, over which we can plot the actual behavior of the system to see if they match up. If the phase plot becomes repetitive, we can call it stable" + ] }, { "cell_type": "markdown", @@ -4010,17 +4025,173 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# This cell loads the style of the notebook, which is taken from the \n", "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", "\n", "from IPython.core.display import HTML\n", - "css_file = './randy_schur/numericalmoocstyle.css'\n", + "css_file = './numericalmoocstyle.css'\n", "HTML(open(css_file, \"r\").read())" ] }, diff --git a/randy_schur/numericalmoocstyle.css b/randy_schur/numericalmoocstyle.css index 16a3c5c..aca3fcb 100644 --- a/randy_schur/numericalmoocstyle.css +++ b/randy_schur/numericalmoocstyle.css @@ -15,12 +15,13 @@ } div.cell { /* set cell width */ - width: 750px; + width: 1000px; } div #notebook { /* centre the content */ background: #fff; /* white background for content */ - width: 1000px; + width: 125 +0px; margin: auto; padding-left: 0em; } From 942444c759646482ca5cbc52dc8a24dd29760f04 Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 6 Dec 2015 13:55:20 -0500 Subject: [PATCH 21/61] adjust formatting css --- randy_schur/Double_Pendulum_Problem.ipynb | 38 ++++++++++++++++++++++- randy_schur/numericalmoocstyle.css | 7 ++--- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index fb997f8..45c8aed 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -3787,9 +3787,45 @@ "\n", "The effect of the damper is to remove energy from the system. So, in this case the damping term is helping to remove some energy from the system that the integration adds. As time goes to infinity, these solutions will converge, and the Euler and Runge-Kutta methods are stable. \n", "\n", - "Another way to investigate stability is with a phase plot. Instead of position vs. time, we can plot velocity vs. position. This plot is in phase space (a subset of state space). For any given state (position and velocity), we should be able to determine what the next state will be. This gives us an entire direction field, over which we can plot the actual behavior of the system to see if they match up. If the phase plot becomes repetitive, we can call it stable" + "Another way to investigate stability is with a phase plot. Instead of position vs. time, we can plot velocity vs. position. This plot is in phase space (a subset of state space). For any given state (position and velocity), we should be able to determine what the next state will be. This gives us an entire direction field, over which we can plot the actual behavior of the system to see if they match up. If the phase plot becomes repetitive, we can call it stable, and if the phase plot goes to the origin, we can call it asymptotically stable." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "metadata": { diff --git a/randy_schur/numericalmoocstyle.css b/randy_schur/numericalmoocstyle.css index aca3fcb..e6a8035 100644 --- a/randy_schur/numericalmoocstyle.css +++ b/randy_schur/numericalmoocstyle.css @@ -20,8 +20,7 @@ div.cell { /* set cell width */ div #notebook { /* centre the content */ background: #fff; /* white background for content */ - width: 125 -0px; + width: 1250px; margin: auto; padding-left: 0em; } @@ -49,7 +48,7 @@ div.text_cell_render{ line-height: 140%; font-size: 125%; font-weight: 400; - width:600px; + width:800px; margin-left:auto; margin-right:auto; } @@ -129,7 +128,7 @@ div.text_cell_render{ MathJax.Hub.Config({ TeX: { extensions: ["AMSmath.js"], - equationNumbers: { autoNumber: "AMS", useLabelIds: true} + equationNumbers: { autoNumber: "AMS", useLabelIds: false} }, tex2jax: { inlineMath: [ ['$','$'], ["\\(","\\)"] ], From c22093e9d85da3ccc71b01f643bc2861c36442ed Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 6 Dec 2015 14:24:18 -0500 Subject: [PATCH 22/61] phase portrait --- randy_schur/Double_Pendulum_Problem.ipynb | 866 +++++++++++++++++++++- 1 file changed, 837 insertions(+), 29 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 45c8aed..e377cd8 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -223,7 +223,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "metadata": { "collapsed": false }, @@ -243,6 +243,7 @@ "from math import pi, cos, sin, sqrt\n", "\n", "from matplotlib import pyplot\n", + "from matplotlib.pyplot import quiver\n", "%matplotlib notebook\n", "from matplotlib import rcParams, cm\n", "rcParams['font.family'] = 'serif'\n", @@ -251,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -271,7 +272,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -294,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -320,7 +321,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -344,7 +345,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -372,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -391,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "metadata": { "collapsed": false, "scrolled": true @@ -1198,7 +1199,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -1221,7 +1222,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -1242,7 +1243,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -2020,7 +2021,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 12, "metadata": { "collapsed": true }, @@ -2043,7 +2044,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -2078,7 +2079,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -2902,7 +2903,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -2926,7 +2927,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 16, "metadata": { "collapsed": true }, @@ -2955,7 +2956,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 17, "metadata": { "collapsed": true }, @@ -2986,7 +2987,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 29, "metadata": { "collapsed": false }, @@ -3764,6 +3765,7 @@ "A = x0 #with no forcing, the max amplitude is equal to initial amplitude\n", "phi = 0\n", "q_d_analytical = A*numpy.exp(-zeta*wn*t)*numpy.cos(wd*t + phi)\n", + "vel_d_analytical = -A*numpy.exp(-zeta*wn*t)*wd*numpy.sin(wd*t+phi)\n", " \n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", @@ -3792,12 +3794,819 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 32, "metadata": { - "collapsed": true + "collapsed": false }, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.5\n", + "-0.366458550519\n", + "0.778366222918\n", + "-1.06721090077\n" + ] + } + ], + "source": [ + "print(max(q_d_analytical))\n", + "print(min(q_d_analytical))\n", + "print(max(vel_d_analytical))\n", + "print(min(vel_d_analytical))" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#create a phase plot\n", + "NP = 15\n", + "pos = numpy.linspace(-x0, x0, NP)\n", + "vel = numpy.linspace(-2*x0, 2*x0, NP)\n", + "\n", + "X1, X2 = numpy.meshgrid(pos, vel)\n", + "u = numpy.zeros_like(X1)\n", + "v = numpy.zeros_like(X2)\n", + "\n", + "for i in range(NP):\n", + " for j in range(NP):\n", + " x = X1[i, j]\n", + " y = X2[i, j]\n", + " yprime = f_damped_HM([x, y])\n", + " u[i,j] = yprime[0]\n", + " v[i,j] = yprime[1]\n", + "\n", + "\n", + "pyplot.figure(figsize=(10,8));\n", + "pyplot.quiver(X1, X2, u, v, pivot='mid', color='k', label='direction field');\n", + "pyplot.plot(q_d_analytical, vel_d_analytical, 'k-', label='analytical');\n", + "pyplot.plot(q1_d[:,0], q1_d[:,1],'r-', label='Euler');\n", + "pyplot.plot(q2_d[:,0], q2_d[:,1], 'b-', label='RK4');\n", + "pyplot.plot(q3_d[:,0], -Verlet_vel_d, 'g-', label='Verlet');\n", + "pyplot.xlabel('Postion ($x_1$)')\n", + "pyplot.ylabel('Velocity ($x_2$)')\n", + "pyplot.legend();\n", + "pyplot.title('Phase portrait for damped harmonic oscillator');\n" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "pyplot.close(\"all\") #This line is for cleanup - There are a lot of plots in this notebook. Comment it out \n", + " #if you want to keep all of the plots open." + ] }, { "cell_type": "code", @@ -4691,7 +5479,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this module, we will study the dynamics of a double pendulum. Like before, we'll derive the equations of motion from the Lagrangian and the Hamiltonian. We can use the same methods of integration as before, but this time there is no analytical solution to compare to. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." + "In this section, we will study the dynamics of a double pendulum. Like before, we'll derive the equations of motion from the Lagrangian and the Hamiltonian. We can use the same methods of integration as before, but this time there is no analytical solution to compare to. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." ] }, { @@ -4700,7 +5488,7 @@ "source": [ "## Background\n", "\n", - "The double pendulum problem is a simple system which can produce surprisingly complex movements. It is a chaotic system, meaning it is unpredictable, and there is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", + "The double pendulum problem is a relatively simple system which can produce surprisingly complex movements. It is a chaotic system, meaning it is unpredictable and small changes in initial conditions lead to large changes in motion. There is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", "\n", "In this notebook we will be treating the two rods as massless and ignoring the effects of friction. For a treatment of this problem without these assumptions, see (cite).\n", "\n", @@ -4870,7 +5658,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -5025,7 +5813,7 @@ "" ] }, - "execution_count": 20, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } From b1173a800901bc3163e9e17591bd6d7ad284652d Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 6 Dec 2015 22:14:56 -0500 Subject: [PATCH 24/61] derive double pendulum from Hamiltonian --- randy_schur/Double_Pendulum_Problem.ipynb | 323 +++++++++++++++++----- 1 file changed, 260 insertions(+), 63 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index ee97532..54f8b7c 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -223,23 +223,15 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 26, "metadata": { "collapsed": false }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/randy/anaconda3/lib/python3.4/site-packages/IPython/kernel/__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", - " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" - ] - } - ], + "outputs": [], "source": [ "import numpy\n", "from scipy.linalg import solve\n", + "from numpy.linalg import det\n", "from math import pi, cos, sin, sqrt\n", "\n", "from matplotlib import pyplot\n", @@ -252,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -272,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 14, "metadata": { "collapsed": true }, @@ -295,7 +287,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -321,7 +313,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 16, "metadata": { "collapsed": true }, @@ -345,7 +337,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 17, "metadata": { "collapsed": true }, @@ -373,7 +365,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 18, "metadata": { "collapsed": true }, @@ -392,7 +384,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 19, "metadata": { "collapsed": false, "scrolled": true @@ -1193,13 +1185,13 @@ "source": [ "If we look closely at the plot, we can see that Euler and Runge-Kutta integration are close to each other, but begin to diverge as time goes on. This should be the behavior we expect for the integration of a non-linear system. But how close are these methods to the analytical solution? Does Verlet do any better? In this case it is obvious that Euler and Runge-Kutta are not correct (and possible unstable). Let's do an error analysis.\n", "\n", - "#### Error Analysis\n", + "### Error Analysis\n", "The error analysis code below is based on the numerical methods MOOC notebook on phugoid oscillation. " ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 20, "metadata": { "collapsed": true }, @@ -1222,7 +1214,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -1243,7 +1235,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -2021,7 +2013,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 23, "metadata": { "collapsed": true }, @@ -2044,7 +2036,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 24, "metadata": { "collapsed": false }, @@ -2079,7 +2071,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 25, "metadata": { "collapsed": false }, @@ -2903,7 +2895,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 27, "metadata": { "collapsed": true }, @@ -2927,7 +2919,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 28, "metadata": { "collapsed": true }, @@ -2956,7 +2948,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 29, "metadata": { "collapsed": true }, @@ -2987,7 +2979,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 30, "metadata": { "collapsed": false }, @@ -3793,7 +3785,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 31, "metadata": { "collapsed": false }, @@ -4600,19 +4592,11 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 32, "metadata": { "collapsed": false }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/randy/anaconda3/lib/python3.4/site-packages/matplotlib/pyplot.py:424: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_open_warning`).\n", - " max_open_warning, RuntimeWarning)\n" - ] - }, { "data": { "application/javascript": [ @@ -5395,7 +5379,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 33, "metadata": { "collapsed": false }, @@ -5517,7 +5501,7 @@ "\n", "$$\\begin{eqnarray*}\n", "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", - " &=& -m_1g(l_1cos(\\theta_1) - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", + " &=& -m_1gl_1cos\\theta_1 - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", "\\end{eqnarray*}$$\n", "\n", "Kinetic Energy is calculated as:\n", @@ -5577,13 +5561,13 @@ "\n", "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. So our state becomes:\n", "\n", - "$$\\begin{eqnarray}\n", - "\\vec{q}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\dot\\theta_1 \\\\ \\dot\\theta_2 \\end{pmatrix}\n", - "\\end{eqnarray}$$\n", + "$$\\begin{eqnarray*}\n", + "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\dot\\theta_1 \\\\ \\dot\\theta_2 \\end{pmatrix}\n", + "\\end{eqnarray*}$$\n", "\n", "The equations of motion can be rewritten as a system of equations:\n", "\n", - "$$ A\\dot q=f(q)$$\n", + "$$ A\\dot x=f(x)$$\n", "\n", "where \n", "\n", @@ -5600,65 +5584,278 @@ "and\n", "\n", "$$\\begin{eqnarray*}\n", - "f(\\vec{q}(t)) = \\begin{pmatrix} q_3\\\\ q_4\\\\ m_2l_1l_2x_4^2sin(q_2-q_1) -(m_1+m_2)gl_1sin(q_1)\\\\ m_2l_1l_2q_3^2sin(q_2-q_1)-l_2m_2gsinq_2 \\end{pmatrix}\n", + "f(\\vec{x}(t)) = \\begin{pmatrix} x_3\\\\ x_4\\\\ m_2l_1l_2x_4^2sin(x_2-x_1) -(m_1+m_2)gl_1sin(x_1)\\\\ m_2l_1l_2x_3^2sin(x_2-x_1)-l_2m_2gsinx_2 \\end{pmatrix}\n", "\\end{eqnarray*}$$\n", "\n", "In order to solve this, we can evaluate matrix A and vector $f(x)$ at each time step, then invert A and premultiply the result to each side of the equation. This gives us an explicit system of equations of motion:\n", "\n", - "$$ \\dot q=A^{-1}f(q)$$\n", + "$$ \\dot x=A^{-1}f(x)$$\n", "\n", - "You may notice that these operations require a significant computational load at each time step. In order to reduce this, we can analytically invert the matrix A, then simply fill in the necessary values at each time step and multiply by $f(q)$. This will significantly speed up running time, as inverting a matrix is an expensive operation.\n" + "You may notice that these operations require a significant computational load at each time step. In order to reduce this, we can analytically invert the matrix A, then simply fill in the necessary values at each time step and multiply by $f(q)$. This will significantly speed up running time, as inverting a matrix is a computationally expensive operation.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Integration\n", + "### Equations of Motion from Hamiltonian\n", "\n", - "For Euler and Runge-Kutta, we can use the equation above to integrate our system. The results will be VERY dependent on initial conditions; this is why we call it a chaotic system.\n", + "Here we derive the equations of motion from the Hamiltonian. If you want good practice, you can derive these on your own. We start with the same potential and kinetic energy, but we'll need to use postion ($\\theta$) and momentum ($I\\omega$) to give us:\n", "\n", - "For the symplectic integrator, we'll need the equations of motion from the Hamiltonian. If you want good practice, you can derive these on your own. The Hamiltonian equations become:\n", + "$$\\begin{eqnarray*}\n", + "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", + " &=& -m_1g(l_1cos\\theta_1 - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", + "\\end{eqnarray*}$$\n", "\n", - "\n" + "Kinetic Energy is calculated as:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "T &=& \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2 \\\\\n", + "&=& \\frac{q_1^2}{2I_1} + \\frac{q_2^2}{2I_2}\n", + "\\end{eqnarray*}$$\n", + "\n", + "where $I_i = m_il_i^2$. So our Hamiltonian quantity becomes:\n", + "$$\\begin{equation*}\n", + "H = \\frac{1}{2}\\left(\\frac{q_1^2}{I_1} + \\frac{q_2^2}{I_2}\\right) - (m_1+m_2)gl_1cos\\theta_1 - m_2gl_2cos\\theta_2\n", + "\\end{equation*}$$\n", + "\n", + "Since we have two degrees of freedom, we will have four equations of motion. These will be the following:\n", + "$$\\begin{eqnarray*}\n", + "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = \\frac{q_1}{I_1} \\\\\n", + "\\dot{q_1} &=& \\frac{\\partial H}{\\partial p_1} = (m_1+m_2)gl_1sinp_1 \\\\\n", + "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = \\frac{q_2}{I_2} \\\\\n", + "\\dot{q_2} &=& \\frac{\\partial H}{\\partial p_2} = m_2gl_2sinp_2 \\\\\n", + "\\end{eqnarray*}$$\n" ] }, { "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Integration\n", + "\n", + "For Euler and Runge-Kutta, we can use the matrix equation above to integrate our system. The results will be VERY dependent on initial conditions; this is why we call it a chaotic system. For the symplectic integrator, we'll use the Hamiltonian equations of motion above. " + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def A_inv(A):\n", + " \"\"\"Returns the inverse of matrix A (from EOM analysis)\n", + " \n", + " Parameters: \n", + " A - Matrix of coefficients\n", + " Returns:\n", + " A^-1 - Inverse of matrix A\n", + " \"\"\"\n", + " d = det(A)\n", + " Ai = 1/d*([[A[2,2]*A[3,3]-A[2,3]*A[3,2], 0, 0, 0], [0, A[2,2]*A[3,3]-A[2,3]*A[3,2], 0, 0]\\\n", + " , [0, 0, A[3,3], -A[2,3]], [0, 0, -A[3,2], A[2,2]]])\n", + " \n", + " return Ai " + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def f_double_pendulum(u):\n", + " \"\"\"Returns RHS of double pendulum EOM\n", + " \n", + " Parameters:\n", + " q - initial state\n", + " \n", + " Returns:\n", + " RHS - RHS of harmonic oscillator eqn.\n", + " \n", + " \"\"\"\n", + " x1 = u[0]\n", + " x2 = u[1]\n", + " x3 = u[2]\n", + " x4 = u[3]\n", + " \n", + " f1 = x3\n", + " f2 = x4\n", + " f3 = m2*l1*l2*x4**2*sin(x2 - x1) - (m1+m2)*g*l1*sin(x1)\n", + " f4 = m2*l1*l2*x3**2*sin(x2 - x1) - l2*m2*g*sin(x2)\n", + " \n", + " A = numpy.matrix([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, (m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[0, 0, m2*l1*l2*cos(x2-x1), m2*l1**2]])\n", + " print(A)\n", + " Ai = A_inv(A)\n", + " f = numpy.array([f1, f2, f3, f4])\n", + " print(f)\n", + " RHS = Ai*f.T\n", + " \n", + " return numpy.array([f1, f2, f3, f4])" + ] + }, + { + "cell_type": "code", + "execution_count": 102, "metadata": { "collapsed": true }, + "outputs": [], "source": [ - "## Solve!\n", + "#Set up parameters:\n", + "g = 9.8 #[m/s^2]\n", + "m1 = 2\n", + "m2 = 1.5;\n", + "l1 = 1\n", + "l2 = 1.5\n", + "\n", + "T = 25; #[seconds]\n", + "dt = .02; #\n", + "N = int(T/dt)+1\n", + "t = numpy.linspace(0.0, T, N)\n", + "\n", + "#Initial Conditions\n", + "theta1_0 = 2 #[radians]\n", + "theta2_0 = 1\n", + "theta1_dot_0 = 0 #[rad/s]\n", + "theta2_dot_0 = 0 #\n", "\n", - "Here we define functions for each of the integration methods, set our parameters and initial conditions, and then numerically integrate the motion of the pendulums. The plots below compare the different integration techniques." + "#p0 = x0\n", + "#q0 = m*xdot0\n", + "x_init_dp = numpy.array([theta1_0, theta2_0, theta1_dot_0, theta2_dot_0])\n", + "#x_init_H = numpy.array([p0, q0])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 1. 0. 0. 0. ]\n", + " [ 0. 1. 0. 0. ]\n", + " [ 0. 0. 3.5 1.21568019]\n", + " [ 0. 0. 1.21568019 1.5 ]]\n", + "[ 0. 0. -31.18890174 -18.55443522]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/randy/anaconda3/lib/python3.4/site-packages/ipykernel/__main__.py:10: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future\n" + ] + }, + { + "ename": "ValueError", + "evalue": "operands could not be broadcast together with shapes (0,) (4,) ", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mq1_dp\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mx_init_dp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m#set initial conditions\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mn\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mN\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;31m#integrate with Euler\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mq1_dp\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mn\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0meuler\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mq1_dp\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mn\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf_double_pendulum\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[1;31m#print(q1[n])\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m\u001b[0m in \u001b[0;36meuler\u001b[1;34m(u, f, dt)\u001b[0m\n\u001b[0;32m 11\u001b[0m \"\"\"\n\u001b[0;32m 12\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 13\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mu\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mdt\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mu\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m\u001b[0m in \u001b[0;36mf_double_pendulum\u001b[1;34m(u)\u001b[0m\n\u001b[0;32m 24\u001b[0m \u001b[0mf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnumpy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0marray\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mf1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf3\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf4\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 25\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 26\u001b[1;33m \u001b[0mRHS\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mAi\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mT\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 27\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 28\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mnumpy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0marray\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mf1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf3\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf4\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mValueError\u001b[0m: operands could not be broadcast together with shapes (0,) (4,) " + ] + } + ], + "source": [ + "#Euler\n", + "q1_dp = numpy.zeros((N,4)) \n", + "q1_dp[0] = x_init_dp.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " q1_dp[n+1,:] = euler(q1_dp[n,:], f_double_pendulum, dt)\n", + " #print(q1[n])\n", + " \n", + "pyplot.figure(figsize=(10,8));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.title('Harmonic oscillator position');\n", + "pyplot.plot(t, q1_d[:,0], lw=2, label='Euler');\n", + "pyplot.legend();" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "source": [ + "a = numpy.matrix([[1,2],[3,4]])\n", + "print(n)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sources: \n", "\n", - "http://scienceworld.wolfram.com/physics/DoublePendulum.html\n", + "[1] http://scienceworld.wolfram.com/physics/DoublePendulum.html\n", "\n", - "http://www.phy.uct.ac.za/courses/opencontent/phylab2/worksheet9_09.pdf\n", + "[2] http://www.phy.uct.ac.za/courses/opencontent/phylab2/worksheet9_09.pdf\n", "\n", - "http://www.phys.lsu.edu/faculty/gonzalez/Teaching/Phys7221/DoublePendulum.pdf\n", + "[3] http://www.phys.lsu.edu/faculty/gonzalez/Teaching/Phys7221/DoublePendulum.pdf\n", "\n", - "http://www.iontrap.wabash.edu/adlab/papers/F2011_foster_groninger_tang_chaos.pdf\n", + "[4] http://www.iontrap.wabash.edu/adlab/papers/F2011_foster_groninger_tang_chaos.pdf\n", "\n", - "https://math.berkeley.edu/~alanw/242papers99/markiewicz.pdf\n", + "[5] https://math.berkeley.edu/~alanw/242papers99/markiewicz.pdf\n", "\n", - "http://www.unige.ch/~hairer/poly_geoint/week2.pdf\n", + "[6] http://www.unige.ch/~hairer/poly_geoint/week2.pdf\n", "\n", - "http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1994CeMDA..60..409T&defaultprint=YES&filetype=.pdf\n", + "[7] http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1994CeMDA..60..409T&defaultprint=YES&filetype=.pdf\n", "\n" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 1, "metadata": { "collapsed": false }, @@ -5813,7 +6010,7 @@ "" ] }, - "execution_count": 21, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } From b1f00cd9b75f71dfbed721ae78c35880762fe434 Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 6 Dec 2015 22:41:33 -0500 Subject: [PATCH 25/61] evening commit --- randy_schur/Double_Pendulum_Problem.ipynb | 1220 ++++++++++++++++----- 1 file changed, 918 insertions(+), 302 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 54f8b7c..421339e 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -223,11 +223,20 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 1, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/randy/anaconda3/lib/python3.4/site-packages/IPython/kernel/__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", + " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" + ] + } + ], "source": [ "import numpy\n", "from scipy.linalg import solve\n", @@ -244,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -264,7 +273,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -287,7 +296,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -313,7 +322,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -337,7 +346,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -365,7 +374,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -384,7 +393,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 8, "metadata": { "collapsed": false, "scrolled": true @@ -1129,7 +1138,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1191,7 +1200,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -1214,7 +1223,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -1235,7 +1244,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -1979,7 +1988,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2013,7 +2022,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 12, "metadata": { "collapsed": true }, @@ -2036,7 +2045,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -2071,7 +2080,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -2815,7 +2824,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2895,7 +2904,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -2919,7 +2928,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 16, "metadata": { "collapsed": true }, @@ -2948,7 +2957,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 17, "metadata": { "collapsed": true }, @@ -2979,7 +2988,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -3723,7 +3732,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3785,7 +3794,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -4529,7 +4538,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4592,7 +4601,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -5336,7 +5345,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5379,7 +5388,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -5639,7 +5648,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 90, "metadata": { "collapsed": true }, @@ -5654,7 +5663,7 @@ " A^-1 - Inverse of matrix A\n", " \"\"\"\n", " d = det(A)\n", - " Ai = 1/d*([[A[2,2]*A[3,3]-A[2,3]*A[3,2], 0, 0, 0], [0, A[2,2]*A[3,3]-A[2,3]*A[3,2], 0, 0]\\\n", + " Ai = 1/d*numpy.array([[A[2,2]*A[3,3]-A[2,3]*A[3,2], 0, 0, 0], [0, A[2,2]*A[3,3]-A[2,3]*A[3,2], 0, 0]\\\n", " , [0, 0, A[3,3], -A[2,3]], [0, 0, -A[3,2], A[2,2]]])\n", " \n", " return Ai " @@ -5662,7 +5671,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 91, "metadata": { "collapsed": false }, @@ -5688,21 +5697,23 @@ " f3 = m2*l1*l2*x4**2*sin(x2 - x1) - (m1+m2)*g*l1*sin(x1)\n", " f4 = m2*l1*l2*x3**2*sin(x2 - x1) - l2*m2*g*sin(x2)\n", " \n", - " A = numpy.matrix([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, (m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[0, 0, m2*l1*l2*cos(x2-x1), m2*l1**2]])\n", - " print(A)\n", + " A = numpy.array([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, (m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[0, 0, m2*l1*l2*cos(x2-x1), m2*l1**2]])\n", + " #print(numpy.shape(A))\n", + " #print(type(A))\n", " Ai = A_inv(A)\n", - " f = numpy.array([f1, f2, f3, f4])\n", - " print(f)\n", - " RHS = Ai*f.T\n", + " f = numpy.array([[f1, f2, f3, f4]])\n", + " #print(numpy.shape(f.T))\n", + " #print(type(f))\n", + " RHS = Ai.dot(f.T)\n", " \n", - " return numpy.array([f1, f2, f3, f4])" + " return RHS" ] }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 92, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ @@ -5727,294 +5738,899 @@ "#p0 = x0\n", "#q0 = m*xdot0\n", "x_init_dp = numpy.array([theta1_0, theta2_0, theta1_dot_0, theta2_dot_0])\n", - "#x_init_H = numpy.array([p0, q0])\n" + "#x_init_H = numpy.array([p0, q0])\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 93, "metadata": { - "collapsed": false + "collapsed": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[ 1. 0. 0. 0. ]\n", - " [ 0. 1. 0. 0. ]\n", - " [ 0. 0. 3.5 1.21568019]\n", - " [ 0. 0. 1.21568019 1.5 ]]\n", - "[ 0. 0. -31.18890174 -18.55443522]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/randy/anaconda3/lib/python3.4/site-packages/ipykernel/__main__.py:10: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future\n" - ] - }, - { - "ename": "ValueError", - "evalue": "operands could not be broadcast together with shapes (0,) (4,) ", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mq1_dp\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mx_init_dp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m#set initial conditions\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mn\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mN\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;31m#integrate with Euler\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mq1_dp\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mn\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0meuler\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mq1_dp\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mn\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf_double_pendulum\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[1;31m#print(q1[n])\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m\u001b[0m in \u001b[0;36meuler\u001b[1;34m(u, f, dt)\u001b[0m\n\u001b[0;32m 11\u001b[0m \"\"\"\n\u001b[0;32m 12\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 13\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mu\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mdt\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mu\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m\u001b[0m in \u001b[0;36mf_double_pendulum\u001b[1;34m(u)\u001b[0m\n\u001b[0;32m 24\u001b[0m \u001b[0mf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnumpy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0marray\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mf1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf3\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf4\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 25\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 26\u001b[1;33m \u001b[0mRHS\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mAi\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mT\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 27\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 28\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mnumpy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0marray\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mf1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf3\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf4\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mValueError\u001b[0m: operands could not be broadcast together with shapes (0,) (4,) " - ] - } - ], + "outputs": [], "source": [ - "#Euler\n", - "q1_dp = numpy.zeros((N,4)) \n", - "q1_dp[0] = x_init_dp.copy() #set initial conditions\n", - "for n in range(N-1): #integrate with Euler\n", - " q1_dp[n+1,:] = euler(q1_dp[n,:], f_double_pendulum, dt)\n", - " #print(q1[n])\n", + "def euler_DP(u, f, dt):\n", + " \"\"\" Euler's method for integrating a system of differential equations.\n", " \n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", - "pyplot.title('Harmonic oscillator position');\n", - "pyplot.plot(t, q1_d[:,0], lw=2, label='Euler');\n", - "pyplot.legend();" + " Parameters:\n", + " u - state at current step \n", + " f - RHS of equation\n", + " dt- time step size\n", + " \n", + " Returns: \n", + " x - array of values at next time step.\n", + " \"\"\"\n", + " #print(f(u))\n", + " #print(u)\n", + " \n", + " return u + dt*f(u).T " ] }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 101, "metadata": { "collapsed": false }, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "0\n" + "/home/randy/anaconda3/lib/python3.4/site-packages/ipykernel/__main__.py:18: RuntimeWarning: overflow encountered in double_scalars\n", + "/home/randy/anaconda3/lib/python3.4/site-packages/ipykernel/__main__.py:19: RuntimeWarning: overflow encountered in double_scalars\n", + "/home/randy/anaconda3/lib/python3.4/site-packages/numpy/linalg/linalg.py:1729: RuntimeWarning: invalid value encountered in det\n", + " return _umath_linalg.det(a, signature=signature).astype(result_t)\n", + "/home/randy/anaconda3/lib/python3.4/site-packages/ipykernel/__main__.py:10: RuntimeWarning: divide by zero encountered in double_scalars\n", + "/home/randy/anaconda3/lib/python3.4/site-packages/ipykernel/__main__.py:10: RuntimeWarning: invalid value encountered in multiply\n" ] - } - ], - "source": [ - "a = numpy.matrix([[1,2],[3,4]])\n", - "print(n)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sources: \n", - "\n", - "[1] http://scienceworld.wolfram.com/physics/DoublePendulum.html\n", - "\n", - "[2] http://www.phy.uct.ac.za/courses/opencontent/phylab2/worksheet9_09.pdf\n", - "\n", - "[3] http://www.phys.lsu.edu/faculty/gonzalez/Teaching/Phys7221/DoublePendulum.pdf\n", - "\n", - "[4] http://www.iontrap.wabash.edu/adlab/papers/F2011_foster_groninger_tang_chaos.pdf\n", - "\n", - "[5] https://math.berkeley.edu/~alanw/242papers99/markiewicz.pdf\n", - "\n", - "[6] http://www.unige.ch/~hairer/poly_geoint/week2.pdf\n", - "\n", - "[7] http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1994CeMDA..60..409T&defaultprint=YES&filetype=.pdf\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [ + }, { "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#Euler\n", + "q1_dp = numpy.zeros((N,4)) \n", + "q1_dp[0,:] = x_init_dp.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " q1_dp[n+1,:] = euler_DP(q1_dp[n,:], f_double_pendulum, dt)\n", + " #print(q1[n])\n", + " \n", + "#Runge-Kutta\n", + "q2_dp = numpy.zeros((N,4)) \n", + "q2_dp[0,:] = x_init_dp.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " q2_dp[n+1,:] = RK4_DP(q2_dp[n,:], f_double_pendulum, dt)\n", + " \n", + "pyplot.figure(figsize=(10,8));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.title('Double Pendulum Euler');\n", + "pyplot.plot(t, q1_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, q1_dp[:,1], lw=2, label='Joint 2')\n", + "pyplot.legend();\n", + "\n", + "pyplot.figure(figsize=(10,8));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.title('Double Pendulum RK4');\n", + "pyplot.plot(t, q2_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, q2_dp[:,1], lw=2, label='Joint 2')\n", + "pyplot.legend();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] }, { "cell_type": "code", @@ -6652,21 +7428,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.3" + "pygments_lexer": "ipython2", + "version": "2.7.10" } }, "nbformat": 4, diff --git a/randy_schur/figures/POS_computer.png b/randy_schur/figures/POS_computer.png new file mode 100644 index 0000000000000000000000000000000000000000..131df2d4a15ea42251f9d3ab248c4b39ce98553a GIT binary patch literal 82838 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 literal 0 HcmV?d00001 diff --git a/randy_schur/figures/POS_computer2.png b/randy_schur/figures/POS_computer2.png new file mode 100644 index 0000000000000000000000000000000000000000..0f25ac8e84b9776c87df59641350933d01dd33d6 GIT binary patch literal 21996 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 My85}Sb4q9e0E?&BL;wH) literal 0 HcmV?d00001 From 1ba78db66df5947b147a9529b1d561d82ae8be59 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 7 Dec 2015 15:24:57 -0500 Subject: [PATCH 27/61] fix derivation for double pendulum EOM --- randy_schur/Double_Pendulum_Problem.ipynb | 86 +++++++++-------------- 1 file changed, 33 insertions(+), 53 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 456bdf6..08b4cb1 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -5568,39 +5568,51 @@ "source": [ "## Problem setup\n", "\n", - "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. So our state becomes:\n", + "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. Let's rewrite the equations of motion in a more readable form. We can use the standard form of this equation (which you'll be familiar with if you have studied mechanics) by separating out mass terms (anything multiplied by $\\ddot{x}$), centripetal terms (anything multiplied by $\\dot{x}$), and gravitational terms (anything multiplied by $x$). We get the following equations of state:\n", "\n", "$$\\begin{eqnarray*}\n", - "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\dot\\theta_1 \\\\ \\dot\\theta_2 \\end{pmatrix}\n", + "0 &=&\\textbf{M}\\vec{\\ddot{x}}(t) + \\textbf{V}\\dot{x} + \\textbf{G} \n", "\\end{eqnarray*}$$\n", "\n", - "The equations of motion can be rewritten as a system of equations:\n", + "The equations of motion can be solved for $\\ddot{x}$:\n", "\n", - "$$ A\\dot x=f(x)$$\n", + "$$ \\ddot{x} = \\textbf{M}^{-1}\\left[ \\textbf{V}\\dot x + \\textbf{G} \\right]$$\n", "\n", "where \n", "\n", + "\\begin{eqnarray*}\n", + "M = \n", + "\\left[\\begin{array}{c}\n", + "(m_1+m_2)l_1 & m_2l_1l_2cos(\\theta_2 - \\theta_1)\\\\\n", + " m_2l_1l_2cos(\\theta_2 - \\theta_1) &m_2l_2^2\n", + "\\end{array}\\right]\n", + "\\end{eqnarray*}\n", + "\n", + "\n", "\\begin{equation*}\n", - "A = \n", + "V = \n", "\\left[\\begin{array}{c}\n", - "1 &0 &0 &0 \\\\\n", - "0 &1 &0 &0 \\\\\n", - "0 &0 &(m_1+m_2)l_1^2 &m_2l_1l_2cos(x_2-x_1) \\\\\n", - "0 &0 &m_2l_1l_2cos(x_2-x_1) &m_2l_2^2\n", + "0 & -m_2l_1l_2sin(\\theta_2 - \\theta_1)\\\\\n", + " -m_2l_1l_2sin(\\theta_2 - \\theta_1) &0\n", "\\end{array}\\right]\n", "\\end{equation*}\n", "\n", + "\n", + "\\begin{eqnarray*}\n", + "G = \n", + "\\left[\\begin{array}{c}\n", + "(m_1+m_2)l_1gsin\\theta_1\\\\\n", + " m_2l_2gsin\\theta_2\n", + "\\end{array}\\right]\n", + "\\end{eqnarray*}\n", + "\n", "and\n", "\n", "$$\\begin{eqnarray*}\n", - "f(\\vec{x}(t)) = \\begin{pmatrix} x_3\\\\ x_4\\\\ m_2l_1l_2x_4^2sin(x_2-x_1) -(m_1+m_2)gl_1sin(x_1)\\\\ m_2l_1l_2x_3^2sin(x_2-x_1)-l_2m_2gsinx_2 \\end{pmatrix}\n", + "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\end{pmatrix}, \\vec{\\dot{x}(t)} = \\begin{pmatrix} \\dot \\theta_1 \\\\ \\dot \\theta_2 \\end{pmatrix}\n", "\\end{eqnarray*}$$\n", "\n", - "In order to solve this, we can evaluate matrix A and vector $f(x)$ at each time step, then invert A and premultiply the result to each side of the equation. This gives us an explicit system of equations of motion:\n", - "\n", - "$$ \\dot x=A^{-1}f(x)$$\n", - "\n", - "You may notice that these operations require a significant computational load at each time step. In order to reduce this, we can analytically invert the matrix A, then simply fill in the necessary values at each time step and multiply by $f(q)$. This will significantly speed up running time, as inverting a matrix is a computationally expensive operation.\n" + "We can now treat this system the same way we handled the simple harmonic motion; break the two second order ODEs into four first-order ODEs, then use Euler or Runge-Kutta on the results." ] }, { @@ -5653,27 +5665,11 @@ "collapsed": true }, "outputs": [], - "source": [ - "def A_inv(A):\n", - " \"\"\"Returns the inverse of matrix A (from EOM analysis)\n", - " \n", - " Parameters: \n", - " A - Matrix of coefficients\n", - " Returns:\n", - " A^-1 - Inverse of matrix A\n", - " \"\"\"\n", - " A2 = A[2:4, 2:4]\n", - " #print(A2)\n", - " d = det(A2)\n", - " Ai = numpy.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, A[3,3]/d, -A[2,3]/d], [0, 0, -A[3,2]/d, A[2,2]/d]])\n", - "\n", - " \n", - " return Ai " - ] + "source": [] }, { "cell_type": "code", - "execution_count": 173, + "execution_count": 191, "metadata": { "collapsed": false }, @@ -5694,22 +5690,6 @@ " x3 = u[2]\n", " x4 = u[3]\n", " \n", - " #f1 = x3\n", - " #f2 = x4\n", - " #f3 = m2*l1*l2*x4**2*sin(x2 - x1) - (m1+m2)*g*l1*sin(x1)\n", - " #f4 = m2*l1*l2*x3**2*sin(x2 - x1) - l2*m2*g*sin(x2)\n", - " \n", - " #A = numpy.array([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, (m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[0, 0, m2*l1*l2*cos(x2-x1), m2*l1**2]])\n", - " #print(numpy.shape(A))\n", - " \n", - " #i = A_inv(A)\n", - " #print(type(Ai))\n", - " #print(Ai)\n", - " #f = numpy.array([[f1, f2, f3, f4]])\n", - " #print(numpy.shape(f.T))\n", - " #print(type(f))\n", - " #RHS = \n", - " \n", " M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[m2*l1*l2*cos(x2-x1), m2*l2**2]])\n", " V = numpy.array([[0, -m2*l1*l2*sin(x2-x1)],[-m2*l1*l2*sin(x2-x1), 0]])\n", " G = numpy.array([[(m1+m2)*g*l1*sin(x1)],[m2*l2*g*sin(x2)]])\n", @@ -5725,7 +5705,7 @@ }, { "cell_type": "code", - "execution_count": 186, + "execution_count": 192, "metadata": { "collapsed": false }, @@ -5758,7 +5738,7 @@ }, { "cell_type": "code", - "execution_count": 187, + "execution_count": 193, "metadata": { "collapsed": true }, @@ -5783,7 +5763,7 @@ }, { "cell_type": "code", - "execution_count": 188, + "execution_count": 194, "metadata": { "collapsed": false }, @@ -5809,7 +5789,7 @@ }, { "cell_type": "code", - "execution_count": 190, + "execution_count": 195, "metadata": { "collapsed": false }, From c23801eb828c5b077064362acff2153a641e34ff Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 7 Dec 2015 15:34:07 -0500 Subject: [PATCH 28/61] split notebooks --- ...on Techniques for Mechanical Systems.ipynb | 5419 +++++++++++++++++ 1 file changed, 5419 insertions(+) create mode 100644 randy_schur/Integration Techniques for Mechanical Systems.ipynb diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb new file mode 100644 index 0000000..d66d98c --- /dev/null +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -0,0 +1,5419 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Integration of Mechanical Systems\n", + "\n", + "In this module, we will be exploring numerical integrators and how they perform for different mechanical systems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple Harmonic Oscillator\n", + "\n", + "The simple harmonic oscillator is a simple physical system described by the second-order differential equation below. Despite its simplicity, this is a system that shows up in a similar form in many different fields of engineering. The same equation describes the behavior of a spring-mass-damper system, RLC circuit, or the motion of a (small-angle) pendulum.\n", + "\n", + "![Image](figures/spring-mass-damper.png)\n", + "\n", + "##### Figure 1. Diagram of Spring-Mass-Damper system (from https://en.wikipedia.org/wiki/Harmonic_oscillator)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The differential equation describing the motion of a simple harmonic oscillator is:\n", + "\n", + "$$\\begin{equation}\n", + "0 = m\\ddot{x} + kx\n", + "\\end{equation}$$\n", + "\n", + "We can describe the state of the system by its position $x$ and its velocity $\\dot{x}$. We will call this state vector $q$, as:\n", + "\n", + "$$\\begin{eqnarray}\n", + "\\vec{q} = \\begin{bmatrix} x\\\\ \\dot{x} \\end{bmatrix}\n", + "\\end{eqnarray}$$\n", + "\n", + "This system can then be broken into two first order differential equations:\n", + "\n", + "$$\\begin{eqnarray}\n", + "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{k}{m}q_1 \\end{bmatrix}\n", + "\\end{eqnarray}$$\n", + "\n", + "To look at the behavior of this system, we can start with some initial conditions for $q$, then use a numerical integration method such as Euler's method to integrate forward in time. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Equations of Motion from Lagrangian\n", + "\n", + "You are encouraged to follow this derivation ON PAPER on your own. We gave you the equations of motion for a simple harmonic oscillator, but what if we don't have this equation? The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", + "\n", + "$$\\begin{equation}\n", + "L = T- U\n", + "\\end{equation}$$\n", + "\n", + "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy in this system comes from the stretching or compressing of the spring, and is calculated as:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "U &=& \\frac{1}{2}kx^2\\\\\n", + "\\end{eqnarray*}$$\n", + "\n", + "Kinetic Energy in this system is from the motion of the mass, and is calculated as:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "T &=& \\frac{1}{2}mv^2 \\\\\n", + "&=& \\frac{1}{2}m(\\dot x)^2 \n", + "\\end{eqnarray*}$$\n", + "\n", + "\n", + "So the Lagrangian quantity becomes:\n", + "$$\\begin{equation}\n", + "L = \\frac{1}{2}m(\\dot x)^2 - \\frac{1}{2}kx^2\\\\\n", + "\\end{equation}$$\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The equations of motion are then found using the Lagrange Equation:\n", + "\n", + "$$\\begin{equation}\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", + "\\end{equation}$$\n", + "\n", + "Since we have only one generalized coordinate (position), we need only one equation of motion. We'll calculate this in steps:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "\\frac{\\partial L}{\\partial \\dot{x}} &=& m\\dot{x} \\\\\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x}}\\right) &=& m\\ddot{x}\\\\\n", + "-\\frac{\\partial L}{\\partial x} &=& + kx\n", + "\\end{eqnarray*}$$\n", + "\n", + "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", + "\n", + "$$\\begin{equation}\n", + "0 = m\\ddot{x} + kx\n", + "\\end{equation}$$\n", + "\n", + "This is the same equation we have before! So, we can be relatively confident that this derivation is correct." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Integration Methods\n", + "\n", + "### Euler's method\n", + "\n", + "Euler's method is discussed in lesson 1 of the Numerical Methods course. The method goes as follows:\n", + "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", + "\n", + "where $h$ is the timestep. This isn't a complicated scheme, so for details see the code below." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Runge-Kutta Integration (RK4)\n", + "\n", + "Depending on the application, Euler's method may provide enough accuracy with a small timestep, especially a simple harmonic oscillator. However, Euler is a first-order method and we can do better. One option is a Runge-Kutta scheme. This is a popular numerical integration method which is easily extended to higher orders. Here we will use a fourth order Runge-Kutta, also known as RK4:\n", + "\n", + "$$\\begin{equation}\n", + "q_{n+1} = q_n + \\frac{h}{6}\\left(k_1+2k_2+2k_3+k_4\\right)\n", + "\\end{equation}$$\n", + "where\n", + "$$\\begin{eqnarray}\n", + "k_1 &=& f(t_n, x_n)\\\\\n", + "k_2 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_1)\\\\\n", + "k_3 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_2)\\\\\n", + "k_4 &=& f(t_n+h, y_n+hk_3)\\\\\n", + "t_{n+1} &=& t_n + h\n", + "\\end{eqnarray}$$\n", + "\n", + "For more information on the Runge-Kutta method, including the derivation and explanation of coefficients, see (cite)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Symplectic Integrators\n", + "\n", + "Symplectic integrators are similar to the methods discussed above, but they use equations of motion derived from Hamiltonian mechanics. According to [Berkeley source], syplectic integrators preserve the conserved Hamiltonian quantities. In practical terms, this works out to mean the methods reflect conservation of momentum, down to a truncation error.\n", + "\n", + "The Hamiltonian equations of motion can be derived from the total energy in the system, much like the Lagrangian. The coordinates we use are position ($q$) and momentum ($p = m\\dot x$). The Hamiltonian is:\n", + "$$ H = T+V$$\n", + "where $T$ is the kinetic energy in the system, and $V$ is the potential energy in the system. So, we get:\n", + "$$\\begin{eqnarray*}\n", + "T &=& \\frac{1}{2}m\\dot{x}^2 = \\frac{q^2}{2m}\\\\\n", + "V &=& \\frac{1}{2}k{x}^2 = \\frac{kp^2}{2} \\\\\n", + "H &=& \\frac{q^2}{2m} + \\frac{kp^2}{2}\n", + "\\end{eqnarray*}$$\n", + "\n", + "\n", + "The Hamilton's equations are:\n", + "$$\\begin{eqnarray*}\n", + "\\frac{dp}{dt} &=& -\\frac{\\partial H}{\\partial q}\\\\\n", + "\\frac{dq}{dt} &=& \\frac{\\partial H}{\\partial p}\n", + "\\end{eqnarray*}$$\n", + "\n", + "So our equations of motion become:\n", + "$$\\begin{equation*}\n", + "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q}\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -\\frac{q}{m} \\\\ kp\\end{bmatrix}\n", + "\\end{equation*}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now take a look at symplectic integration methods. We'll start with a first order method - symplectic Euler's method. This uses the equations:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "p_{n+1} = p_n - h \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1},q_n}\\\\\n", + "q_{n+1} = q_n + \\frac{h}{2} \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1},q_n}\n", + "\\end{eqnarray*}$$\n", + "\n", + "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method, $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "p_{n+1/2} &=& p_n - \\frac{h}{2} \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1/2},q_n}\\\\\n", + "q_{n+1} &=& q_n + \\frac{h}{2} \\left(\\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_n} + \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_{n+1/2}} \\right) \\\\\n", + "p_{n+1} &=& p_{n+1/2} - \\frac{h}{2} \\frac{\\partial H}{\\partial q}\\Bigr|_{p_{n+1/2},q_{n+1/2}}\n", + "\\end{eqnarray*}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Analytical Solution\n", + "The simple harmonic oscillator is a nice example for numerical integration, because it has an analytical form which we can compare to. The analytical solution is\n", + "\n", + "$$\\begin{equation}\n", + "x(t) = Acos(\\omega_n t + \\phi)\n", + "\\end{equation}$$\n", + "\n", + "where $\\omega_n$ is the natural frequency of the system given by $\\omega_n = \\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let's Get to Programming" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy\n", + "from scipy.linalg import solve\n", + "from numpy.linalg import det\n", + "from math import pi, cos, sin, sqrt\n", + "\n", + "from matplotlib import pyplot\n", + "from matplotlib.pyplot import quiver\n", + "%matplotlib notebook\n", + "from matplotlib import rcParams, cm\n", + "rcParams['font.family'] = 'serif'\n", + "rcParams['font.size'] = 16" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "#Set up parameters:\n", + "\n", + "m = 12\n", + "k = 150;\n", + "#g = 9.8 #[m/s**2]\n", + "\n", + "T = 25; #[seconds]\n", + "dt = .02; #\n", + "N = int(T/dt)+1\n", + "t = numpy.linspace(0.0, T, N)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def euler(u, f, dt):\n", + " \"\"\" Euler's method for integrating a system of differential equations.\n", + " \n", + " Parameters:\n", + " u - state at current step \n", + " f - RHS of equation\n", + " dt- time step size\n", + " \n", + " Returns: \n", + " x - array of values at next time step.\n", + " \"\"\"\n", + " \n", + " return u + dt*f(u) " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def RK4(u, f, dt):\n", + " \"\"\"Runge Kutta fourth order integration method\n", + " \n", + " Parameters:\n", + " u - state of the system at time t\n", + " f - function for RHS of state equations\n", + " dt - time step\n", + " \n", + " Returns: array of state values at next time step.\n", + " \"\"\"\n", + " k1 = f(u)\n", + " k2 = f(u) + 0.5*dt*k1\n", + " k3 = f(u) + 0.5*dt*k2\n", + " k4 = f(u) + dt*k3\n", + " \n", + " return u + dt/6*(k1+2*k2+2*k3+k4)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def f_harmonic_oscillator(u):\n", + " \"\"\"Returns RHS of harmonic oscillator EOM\n", + " \n", + " Parameters:\n", + " q - initial state\n", + " \n", + " Returns:\n", + " f - RHS of harmonic oscillator eqn.\n", + " \n", + " \"\"\"\n", + " pos = u[0]\n", + " vel = u[1]\n", + " \n", + " return numpy.array([vel, -k/m*pos])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def verlet_SHM(u, dt):\n", + " \"\"\" Verlet integration for integrating from Hamiltonian equations of motion\n", + " for a simple harmonic oscillator\n", + " Parameters:\n", + " u - state at current time step\n", + " dt - time step size\n", + " Returns: state at next time step.\n", + " \"\"\"\n", + " pos = u[0]\n", + " mom = u[1]\n", + " \n", + " p_half = pos - dt/2*mom/m\n", + " q_half = mom + dt/2*(k*pos)\n", + " \n", + " q = mom + dt/2*(2*k*p_half)\n", + " p = p_half - dt/2*q/m\n", + " \n", + " return numpy.array([p, q]) " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "#Initial Conditions\n", + "x0 = 0.5 #[m]\n", + "xdot0 = 0 #[m/s]\n", + "\n", + "p0 = x0\n", + "q0 = m*xdot0\n", + "\n", + "x_init = numpy.array([x0, xdot0])\n", + "x_init_H = numpy.array([p0, q0])" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pyplot.figure(figsize=(10,6))\n", + "pyplot.tick_params(axis='both', labelsize=14)\n", + "pyplot.grid(True)\n", + "pyplot.xlabel('$\\Delta t$', fontsize=16)\n", + "pyplot.ylabel('Error', fontsize=16)\n", + "pyplot.loglog(dt_values, Euler_error_values, 'bo-', label='Euler')\n", + "pyplot.loglog(dt_values, RK_error_values, 'ro-', label='RK4')\n", + "pyplot.loglog(dt_values, Verlet_error_values, 'go-', label='Symplectic')\n", + "pyplot.axis('equal')\n", + "pyplot.legend();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. But ordinarily we don't see a second order scheme (Verlet) outperforming a fourth order scheme (RK4) by three orders of magnitude. What is going on?\n", + "\n", + "One thing we notice in the plots of position is that for Euler and Runge-Kutta, the spring is stretched farther and farther each oscillation. This means it has more and more potential energy. With no outside forces adding energy to the system and no friction to remove energy, Conservation of Energy says that value should be constant! Let's take a look at total energy in the system. Since we derived equations of motion from the Lagrangian, this should be easy! All we need to do is calculate kinetic and potential energy at each time step, and we already have the information we need to do this. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def get_Energy(u):\n", + " \"\"\" Calculates total energu in the system at given time step for a simple harmonic oscillator\n", + " Parameters:\n", + " u - state of system [pos, vel]\n", + " Returns:\n", + " total energy in system.\n", + " \"\"\"\n", + " pos = u[0]\n", + " vel = u[1]\n", + " T = 0.5*m*vel**2\n", + " V = 0.5*k*pos**2\n", + " \n", + " return T+V" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "#Euler total energy\n", + "Euler_energy = numpy.zeros_like(t)\n", + "for i in range(N):\n", + " Euler_energy[i] = get_Energy(q1[i,:])\n", + " \n", + "\n", + "#RK4 total energy\n", + "RK4_energy = numpy.zeros_like(t)\n", + "for i in range(N):\n", + " RK4_energy[i] = get_Energy(q2[i,:])\n", + "\n", + "#Verlet total energy\n", + "#Here we will convert from momentum to velocity so we can use the same get_energy function.\n", + "#Momemtum (q) is mass*velocity.\n", + "Verlet_energy = numpy.zeros_like(t)\n", + "Verlet_vel = q3[:,1]/m\n", + "for i in range(N):\n", + " Verlet_energy[i] = get_Energy(numpy.array([q3[i,0], Verlet_vel[i]]))\n", + "\n", + "\n", + "#analytic total energy\n", + "analytical_energy = numpy.zeros_like(t)\n", + "anayltical_state = numpy.array([q_analytical, vel_analytical]).T\n", + "for i in range(N):\n", + " analytical_energy[i] = get_Energy(anayltical_state[i,:])" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#Euler\n", + "q1_d = numpy.zeros((N,2)) \n", + "q1_d[0] = x_init.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " q1_d[n+1] = euler(q1_d[n], f_damped_HM, dt)\n", + " #print(q1[n])\n", + " \n", + "#Runge-Kutta \n", + "q2_d = numpy.zeros((N,2))\n", + "q2_d[0] = x_init.copy()\n", + "for n in range(N-1):\n", + " q2_d[n+1] = RK4(q2_d[n], f_damped_HM, dt)\n", + "\n", + "#Symplectic\n", + "q3_d = numpy.zeros((N,2))\n", + "q3_d[0] = x_init_H.copy()\n", + "for n in range(N-1):\n", + " q3_d[n+1] = Verlet_damped_HM(q3_d[n], dt)\n", + "\n", + "#Analytical Solution \n", + "A = x0 #with no forcing, the max amplitude is equal to initial amplitude\n", + "phi = 0\n", + "q_d_analytical = A*numpy.exp(-zeta*wn*t)*numpy.cos(wd*t + phi)\n", + "vel_d_analytical = -A*numpy.exp(-zeta*wn*t)*wd*numpy.sin(wd*t+phi)\n", + " \n", + "pyplot.figure(figsize=(10,8));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.title('Harmonic oscillator position');\n", + "pyplot.plot(t, q1_d[:,0], lw=2, label='Euler');\n", + "pyplot.plot(t, q2_d[:,0], 'r-', lw=2, label='RK4');\n", + "pyplot.plot(t, q3_d[:,0], 'g', lw=2, label='Verlet');\n", + "pyplot.plot(t, q_d_analytical, 'k--', lw=2, label='analytical');\n", + "pyplot.legend();\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "How does each integrator perform compared to the case of simple harmonic motion? From the eye test, it seems like the error is actually better here. Why is that? \n", + "\n", + "The effect of the damper is to remove energy from the system. So, in this case the damping term is helping to remove some energy from the system that the integration adds. As time goes to infinity, these solutions will converge, and the Euler and Runge-Kutta methods are stable. We can again look at the energy in the system according to each method. \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#create a phase plot\n", + "NP = 15\n", + "pos = numpy.linspace(-x0, x0, NP)\n", + "vel = numpy.linspace(-2*x0, 2*x0, NP)\n", + "\n", + "X1, X2 = numpy.meshgrid(pos, vel)\n", + "u = numpy.zeros_like(X1)\n", + "v = numpy.zeros_like(X2)\n", + "\n", + "for i in range(NP):\n", + " for j in range(NP):\n", + " x = X1[i, j]\n", + " y = X2[i, j]\n", + " yprime = f_damped_HM([x, y])\n", + " u[i,j] = yprime[0]\n", + " v[i,j] = yprime[1]\n", + "\n", + "\n", + "pyplot.figure(figsize=(10,8));\n", + "pyplot.quiver(X1, X2, u, v, pivot='mid', color='k', label='direction field');\n", + "pyplot.plot(q_d_analytical, vel_d_analytical, 'k-', label='analytical');\n", + "pyplot.plot(q1_d[:,0], q1_d[:,1],'r-', label='Euler');\n", + "pyplot.plot(q2_d[:,0], q2_d[:,1], 'b-', label='RK4');\n", + "pyplot.plot(q3_d[:,0], -Verlet_vel_d, 'g-', label='Verlet');\n", + "pyplot.xlabel('Postion ($x_1$)')\n", + "pyplot.ylabel('Velocity ($x_2$)')\n", + "pyplot.legend();\n", + "pyplot.title('Phase portrait for damped harmonic oscillator');\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.10" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From 2b151d7e0d676cc58b9ee6ea9ea6e2251f3e6d36 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 7 Dec 2015 15:53:04 -0500 Subject: [PATCH 29/61] damping for double pendulum --- randy_schur/Double_Pendulum_Problem.ipynb | 4408 +---------------- ...on Techniques for Mechanical Systems.ipynb | 205 +- 2 files changed, 463 insertions(+), 4150 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 08b4cb1..ba51bfe 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -4,282 +4,286 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Integration of Mechanical Systems\n", - "\n", - "In this module, we will be exploring numerical integrators and how they perform for different mechanical systems." + "# Dynamics of a Double Pendulum" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Simple Harmonic Oscillator\n", - "\n", - "The simple harmonic oscillator is a simple physical system described by the second-order differential equation below. Despite its simplicity, this is a system that shows up in a similar form in many different fields of engineering. The same equation describes the behavior of a spring-mass-damper system, RLC circuit, or the motion of a (small-angle) pendulum.\n" + "In this section, we will study the dynamics of a double pendulum. Like before, we'll derive the equations of motion from the Lagrangian and the Hamiltonian. We can use the same methods of integration as before, but this time there is no analytical solution to compare to. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "![Image](figures/spring-mass-damper.png)\n", + "## Background\n", "\n", - "##### Figure 1. Diagram of Spring-Mass-Damper system (from https://en.wikipedia.org/wiki/Harmonic_oscillator)" + "The double pendulum problem is a relatively simple system which can produce surprisingly complex movements. It is a chaotic system, meaning it is unpredictable and small changes in initial conditions lead to large changes in motion. There is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", + "\n", + "In this notebook we will be treating the two rods as massless and we'll start out ignoring the effects of friction. For a treatment of this problem without these assumptions, see (cite).\n", + "\n", + "See the image below for the definition of constants." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The differential equation describing the motion of a simple harmonic oscillator is:\n", - "\n", - "$$\\begin{equation}\n", - "0 = m\\ddot{x} + kx\n", - "\\end{equation}$$\n", - "\n", - "We can describe the state of the system by its position $x$ and its velocity $\\dot{x}$. We will call this state vector $q$, as:\n", - "\n", - "$$\\begin{eqnarray}\n", - "\\vec{q} = \\begin{bmatrix} x\\\\ \\dot{x} \\end{bmatrix}\n", - "\\end{eqnarray}$$\n", - "\n", - "This system can then be broken into two first order differential equations:\n", - "\n", - "$$\\begin{eqnarray}\n", - "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{k}{m}q_1 \\end{bmatrix}\n", - "\\end{eqnarray}$$\n", - "\n", - "To look at the behavior of this system, we can start with some initial conditions for $q$, then use a numerical integration method such as Euler's method to integrate forward in time. " + "![Image](figures/diagram.png)\n", + "#### Figure 1. Diagram of Double Pendulum System" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Equations of Motion from Lagrangian\n", - "\n", - "You are encouraged to follow this derivation ON PAPER on your own. We gave you the equations of motion for a simple harmonic oscillator, but what if we don't have this equation? The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", + "## Equations of Motion \n", "\n", - "$$\\begin{equation}\n", + "As a reminder, the Lagrangian is\n", + "$$\\begin{equation*}\n", "L = T- U\n", - "\\end{equation}$$\n", - "\n", - "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy in this system comes from the stretching or compressing of the spring, and is calculated as:\n", + "\\end{equation*}$$\n", + "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", "\n", "$$\\begin{eqnarray*}\n", - "U &=& \\frac{1}{2}kx^2\\\\\n", + "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", + " &=& -m_1gl_1cos\\theta_1 - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", "\\end{eqnarray*}$$\n", "\n", - "Kinetic Energy in this system is from the motion of the mass, and is calculated as:\n", + "Kinetic Energy is calculated as:\n", "\n", "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}mv^2 \\\\\n", - "&=& \\frac{1}{2}m(\\dot x)^2 \n", + "T &=& \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2 \\\\\n", + "&=& \\frac{1}{2}m_1(l_1\\dot\\theta_1)^2 + \\frac{1}{2}m_2(l_1\\dot\\theta_1 + l_2\\dot\\theta_2)^2 \\\\\n", + "&=& \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\n", "\\end{eqnarray*}$$\n", "\n", "\n", "So the Lagrangian quantity becomes:\n", - "$$\\begin{equation}\n", - "L = \\frac{1}{2}m(\\dot x)^2 - \\frac{1}{2}kx^2\\\\\n", - "\\end{equation}$$\n", - "\n", - "\n" + "$$\\begin{equation*}\n", + "L = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+(m_1+m_2)l_1gcos\\theta_1 + m_2l_2gcos\\theta_2\n", + "\\end{equation*}$$\n" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": true + }, "source": [ "The equations of motion are then found using the Lagrange Equation:\n", "\n", - "$$\\begin{equation}\n", + "$$\\begin{equation*}\n", "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", - "\\end{equation}$$\n", + "\\end{equation*}$$\n", "\n", - "Since we have only one generalized coordinate (position), we need only one equation of motion. We'll calculate this in steps:\n", + "\n", + "There are two generalized coordinates ($\\theta_1 and \\theta_2$. So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", "\n", "$$\\begin{eqnarray*}\n", - "\\frac{\\partial L}{\\partial \\dot{x}} &=& m\\dot{x} \\\\\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x}}\\right) &=& m\\ddot{x}\\\\\n", - "-\\frac{\\partial L}{\\partial x} &=& + kx\n", + "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} &=& (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1) \\\\\n", + "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{\\theta_1}}\\right) &=& (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1) \\\\\n", + "-\\frac{\\partial L}{\\partial \\theta_1} &=& -m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)-(m_1+m_2)gl_1sin\\theta_1\n", "\\end{eqnarray*}$$\n", "\n", "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", "\n", - "$$\\begin{equation}\n", - "0 = m\\ddot{x} + kx\n", - "\\end{equation}$$\n", + "$$\\begin{equation*}\n", + "0 = (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", + "\\end{equation*}$$\n", + "\n", + "We will also need the equation of motion for $\\theta_2$. This can be derived using the same process. It should work out to the following (check this by yourself!):\n", "\n", - "This is the same equation we have before! So, we can be relatively confident that this derivation is correct." + "$$\\begin{equation*}\n", + "0 = m_2l_2^2\\ddot\\theta_2+m_2l_1l_2\\ddot\\theta_1cos(\\theta_2-\\theta_1) - m_2l_2l_1\\dot\\theta_1^2sin(\\theta_2-\\theta_1)+ l_2m_2gsin\\theta_2\n", + "\\end{equation*}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Integration Methods\n", + "## Problem setup\n", "\n", - "### Euler's method\n", + "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. Let's rewrite the equations of motion in a more readable form. We can use the standard form of this equation (which you'll be familiar with if you have studied mechanics) by separating out mass terms (anything multiplied by $\\ddot{x}$), centripetal terms (anything multiplied by $\\dot{x}$), and gravitational terms (anything multiplied by $x$). We get the following equations of state:\n", "\n", - "Euler's method is discussed in lesson 1 of the Numerical Methods course. The method goes as follows:\n", - "$$x_i^{n+1} = x_i^n + h\\dot{x}_i^n$$\n", + "$$\\begin{eqnarray*}\n", + "0 &=&\\textbf{M}\\vec{\\ddot{x}}(t) + \\textbf{V}\\dot{x} + \\textbf{G} \n", + "\\end{eqnarray*}$$\n", "\n", - "where $h$ is the timestep. This isn't a complicated scheme, so for details see the code below." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Runge-Kutta Integration (RK4)\n", + "The equations of motion can be solved for $\\ddot{x}$:\n", "\n", - "Depending on the application, Euler's method may provide enough accuracy with a small timestep, especially a simple harmonic oscillator. However, Euler is a first-order method and we can do better. One option is a Runge-Kutta scheme. This is a popular numerical integration method which is easily extended to higher orders. Here we will use a fourth order Runge-Kutta, also known as RK4:\n", + "$$ \\ddot{x} = \\textbf{M}^{-1}\\left[ \\textbf{V}\\dot x + \\textbf{G} \\right]$$\n", "\n", - "$$\\begin{equation}\n", - "q_{n+1} = q_n + \\frac{h}{6}\\left(k_1+2k_2+2k_3+k_4\\right)\n", - "\\end{equation}$$\n", - "where\n", - "$$\\begin{eqnarray}\n", - "k_1 &=& f(t_n, x_n)\\\\\n", - "k_2 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_1)\\\\\n", - "k_3 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_2)\\\\\n", - "k_4 &=& f(t_n+h, y_n+hk_3)\\\\\n", - "t_{n+1} &=& t_n + h\n", - "\\end{eqnarray}$$\n", + "where \n", "\n", - "For more information on the Runge-Kutta method, including the derivation and explanation of coefficients, see (cite)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Symplectic Integrators\n", + "\\begin{eqnarray*}\n", + "M = \n", + "\\left[\\begin{array}{c}\n", + "(m_1+m_2)l_1 & m_2l_1l_2cos(\\theta_2 - \\theta_1)\\\\\n", + " m_2l_1l_2cos(\\theta_2 - \\theta_1) &m_2l_2^2\n", + "\\end{array}\\right]\n", + "\\end{eqnarray*}\n", "\n", - "Symplectic integrators are similar to the methods discussed above, but they use equations of motion derived from Hamiltonian mechanics. According to [Berkeley source], syplectic integrators preserve the conserved Hamiltonian quantities. In practical terms, this works out to mean the methods reflect conservation of momentum, down to a truncation error.\n", "\n", - "The Hamiltonian equations of motion can be derived from the total energy in the system, much like the Lagrangian. The coordinates we use are position ($q$) and momentum ($p = m\\dot x$). The Hamiltonian is:\n", - "$$ H = T+V$$\n", - "where $T$ is the kinetic energy in the system, and $V$ is the potential energy in the system. So, we get:\n", - "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}m\\dot{x}^2 = \\frac{q^2}{2m}\\\\\n", - "V &=& \\frac{1}{2}k{x}^2 = \\frac{kp^2}{2} \\\\\n", - "H &=& \\frac{q^2}{2m} + \\frac{kp^2}{2}\n", - "\\end{eqnarray*}$$\n", + "\\begin{equation*}\n", + "V = \n", + "\\left[\\begin{array}{c}\n", + "0 & -m_2l_1l_2sin(\\theta_2 - \\theta_1)\\\\\n", + " -m_2l_1l_2sin(\\theta_2 - \\theta_1) &0\n", + "\\end{array}\\right]\n", + "\\end{equation*}\n", + "\n", + "\n", + "\\begin{eqnarray*}\n", + "G = \n", + "\\left[\\begin{array}{c}\n", + "(m_1+m_2)l_1gsin\\theta_1\\\\\n", + " m_2l_2gsin\\theta_2\n", + "\\end{array}\\right]\n", + "\\end{eqnarray*}\n", "\n", + "and\n", "\n", - "The Hamilton's equations are:\n", "$$\\begin{eqnarray*}\n", - "\\frac{dp}{dt} &=& -\\frac{\\partial H}{\\partial q}\\\\\n", - "\\frac{dq}{dt} &=& \\frac{\\partial H}{\\partial p}\n", + "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\end{pmatrix}, \\vec{\\dot{x}(t)} = \\begin{pmatrix} \\dot \\theta_1 \\\\ \\dot \\theta_2 \\end{pmatrix}\n", "\\end{eqnarray*}$$\n", "\n", - "So our equations of motion become:\n", - "$$\\begin{equation*}\n", - "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q}\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -\\frac{q}{m} \\\\ kp\\end{bmatrix}\n", - "\\end{equation*}$$" + "We can now treat this system the same way we handled the simple harmonic motion; break the two second order ODEs into four first-order ODEs, then use Euler or Runge-Kutta on the results." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's now take a look at symplectic integration methods. We'll start with a first order method - symplectic Euler's method. This uses the equations:\n", + "### Equations of Motion from Hamiltonian\n", + "\n", + "Here we derive the equations of motion from the Hamiltonian. If you want good practice, you can derive these on your own. We start with the same potential and kinetic energy, but we'll need to use postion ($\\theta$) and momentum ($I\\omega$) to give us:\n", + "\n", + "$$\\begin{eqnarray*}\n", + "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", + " &=& -m_1g(l_1cos\\theta_1 - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", + "\\end{eqnarray*}$$\n", + "\n", + "Kinetic Energy is calculated as:\n", "\n", "$$\\begin{eqnarray*}\n", - "p_{n+1} = p_n - h \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1},q_n}\\\\\n", - "q_{n+1} = q_n + \\frac{h}{2} \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1},q_n}\n", + "T &=& \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2 \\\\\n", + "&=& \\frac{q_1^2}{2I_1} + \\frac{q_2^2}{2I_2}\n", "\\end{eqnarray*}$$\n", "\n", - "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method, $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations:\n", + "where $I_i = m_il_i^2$. So our Hamiltonian quantity becomes:\n", + "$$\\begin{equation*}\n", + "H = \\frac{1}{2}\\left(\\frac{q_1^2}{I_1} + \\frac{q_2^2}{I_2}\\right) - (m_1+m_2)gl_1cos\\theta_1 - m_2gl_2cos\\theta_2\n", + "\\end{equation*}$$\n", "\n", + "Since we have two degrees of freedom, we will have four equations of motion. These will be the following:\n", "$$\\begin{eqnarray*}\n", - "p_{n+1/2} &=& p_n - \\frac{h}{2} \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1/2},q_n}\\\\\n", - "q_{n+1} &=& q_n + \\frac{h}{2} \\left(\\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_n} + \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1/2},q_{n+1/2}} \\right) \\\\\n", - "p_{n+1} &=& p_{n+1/2} - \\frac{h}{2} \\frac{\\partial H}{\\partial q}\\Bigr|_{p_{n+1/2},q_{n+1/2}}\n", - "\\end{eqnarray*}$$" + "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = \\frac{q_1}{I_1} \\\\\n", + "\\dot{q_1} &=& \\frac{\\partial H}{\\partial p_1} = (m_1+m_2)gl_1sinp_1 \\\\\n", + "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = \\frac{q_2}{I_2} \\\\\n", + "\\dot{q_2} &=& \\frac{\\partial H}{\\partial p_2} = m_2gl_2sinp_2 \\\\\n", + "\\end{eqnarray*}$$\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Analytical Solution\n", - "The simple harmonic oscillator is a nice example for numerical integration, because it has an analytical form which we can compare to. The analytical solution is\n", - "\n", - "$$\\begin{equation}\n", - "x(t) = Acos(\\omega_n t + \\phi)\n", - "\\end{equation}$$\n", + "## Integration\n", "\n", - "where $\\omega_n$ is the natural frequency of the system given by $\\omega_n = \\sqrt{\\frac{k}{m}}$ . The amplitude, $A$, and the phase, $\\phi$, of oscillation are determined from the initial conditions. " + "For Euler and Runge-Kutta, we can use the matrix equation above to integrate our system. The results will be VERY dependent on initial conditions; this is why we call it a chaotic system. For the symplectic integrator, we'll use the Hamiltonian equations of motion above. " ] }, { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Let's Get to Programming" - ] + "cell_type": "code", + "execution_count": 159, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 202, "metadata": { "collapsed": false }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\rschur\\Anaconda2\\lib\\site-packages\\IPython\\kernel\\__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", - " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" - ] - } - ], + "outputs": [], "source": [ - "import numpy\n", - "from scipy.linalg import solve\n", - "from numpy.linalg import det\n", - "from math import pi, cos, sin, sqrt\n", - "\n", - "from matplotlib import pyplot\n", - "from matplotlib.pyplot import quiver\n", - "%matplotlib notebook\n", - "from matplotlib import rcParams, cm\n", - "rcParams['font.family'] = 'serif'\n", - "rcParams['font.size'] = 16" + "def f_double_pendulum(u):\n", + " \"\"\"Returns RHS of double pendulum EOM\n", + " \n", + " Parameters:\n", + " q - initial state\n", + " \n", + " Returns:\n", + " RHS - RHS of harmonic oscillator eqn.\n", + " \n", + " \"\"\"\n", + " x1 = u[0]\n", + " x2 = u[1]\n", + " x3 = u[2]\n", + " x4 = u[3]\n", + " \n", + " M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[m2*l1*l2*cos(x2-x1), m2*l2**2]])\n", + " V = numpy.array([[0, -m2*l1*l2*sin(x2-x1)],[-m2*l1*l2*sin(x2-x1), 0]])\n", + " G = numpy.array([[(m1+m2)*g*l1*sin(x1)],[m2*l2*g*sin(x2)]])\n", + " qdd = numpy.linalg.inv(M).dot(V.dot(numpy.array([[x3],[x4]])) +G) #- 2*numpy.array([[x3],[x4]])) \n", + " #print(qdd)\n", + " #print(M)\n", + " #print(V.dot(numpy.array([[x3],[x4]])))\n", + " #print(G)\n", + " RHS = numpy.array([[x3, x4, qdd[0], qdd[1]]])\n", + " #print(RHS.T)\n", + " return RHS.T" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 203, "metadata": { "collapsed": false }, "outputs": [], "source": [ "#Set up parameters:\n", - "\n", - "m = 12\n", - "k = 150;\n", - "#g = 9.8 #[m/s**2]\n", + "g = -9.8 #[m/s^2]\n", + "m1 = 2\n", + "m2 = 1.5;\n", + "l1 = 1\n", + "l2 = 1.5\n", "\n", "T = 25; #[seconds]\n", "dt = .02; #\n", "N = int(T/dt)+1\n", - "t = numpy.linspace(0.0, T, N)" + "t = numpy.linspace(0.0, T, N)\n", + "\n", + "#Initial Conditions\n", + "theta1_0 = 1 #[radians]\n", + "theta2_0 = pi\n", + "theta1_dot_0 = 0 #[rad/s]\n", + "theta2_dot_0 = 0 #\n", + "\n", + "#p0 = x0\n", + "#q0 = m*xdot0\n", + "x_init_dp = numpy.array([theta1_0, theta2_0, theta1_dot_0, theta2_dot_0])\n", + "#x_init_H = numpy.array([p0, q0])\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 204, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def euler(u, f, dt):\n", + "def euler_DP(u, f, dt):\n", " \"\"\" Euler's method for integrating a system of differential equations.\n", " \n", " Parameters:\n", @@ -290,3511 +294,41 @@ " Returns: \n", " x - array of values at next time step.\n", " \"\"\"\n", + " #print(f(u))\n", + " #print(u)\n", " \n", - " return u + dt*f(u) " - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def RK4(u, f, dt):\n", - " \"\"\"Runge Kutta fourth order integration method\n", - " \n", - " Parameters:\n", - " u - state of the system at time t\n", - " f - function for RHS of state equations\n", - " dt - time step\n", - " \n", - " Returns: array of state values at next time step.\n", - " \"\"\"\n", - " k1 = f(u)\n", - " k2 = f(u) + 0.5*dt*k1\n", - " k3 = f(u) + 0.5*dt*k2\n", - " k4 = f(u) + dt*k3\n", - " \n", - " return u + dt/6*(k1+2*k2+2*k3+k4)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def f_harmonic_oscillator(u):\n", - " \"\"\"Returns RHS of harmonic oscillator EOM\n", - " \n", - " Parameters:\n", - " q - initial state\n", - " \n", - " Returns:\n", - " f - RHS of harmonic oscillator eqn.\n", - " \n", - " \"\"\"\n", - " pos = u[0]\n", - " vel = u[1]\n", - " \n", - " return numpy.array([vel, -k/m*pos])" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def verlet_SHM(u, dt):\n", - " \"\"\" Verlet integration for integrating from Hamiltonian equations of motion\n", - " for a simple harmonic oscillator\n", - " Parameters:\n", - " u - state at current time step\n", - " dt - time step size\n", - " Returns: state at next time step.\n", - " \"\"\"\n", - " pos = u[0]\n", - " mom = u[1]\n", - " \n", - " p_half = pos - dt/2*mom/m\n", - " q_half = mom + dt/2*(k*pos)\n", - " \n", - " q = mom + dt/2*(2*k*p_half)\n", - " p = p_half - dt/2*q/m\n", - " \n", - " return numpy.array([p, q]) " - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "#Initial Conditions\n", - "x0 = 0.5 #[m]\n", - "xdot0 = 0 #[m/s]\n", - "\n", - "p0 = x0\n", - "q0 = m*xdot0\n", - "\n", - "x_init = numpy.array([x0, xdot0])\n", - "x_init_H = numpy.array([p0, q0])" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false, - "scrolled": true - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "pyplot.figure(figsize=(10,6))\n", - "pyplot.tick_params(axis='both', labelsize=14)\n", - "pyplot.grid(True)\n", - "pyplot.xlabel('$\\Delta t$', fontsize=16)\n", - "pyplot.ylabel('Error', fontsize=16)\n", - "pyplot.loglog(dt_values, Euler_error_values, 'bo-', label='Euler')\n", - "pyplot.loglog(dt_values, RK_error_values, 'ro-', label='RK4')\n", - "pyplot.loglog(dt_values, Verlet_error_values, 'go-', label='Symplectic')\n", - "pyplot.axis('equal')\n", - "pyplot.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. But ordinarily we don't see a second order scheme (Verlet) outperforming a fourth order scheme (RK4) by three orders of magnitude. What is going on?\n", - "\n", - "One thing we notice in the plots of position is that for Euler and Runge-Kutta, the spring is stretched farther and farther each oscillation. This means it has more and more potential energy. With no outside forces adding energy to the system and no friction to remove energy, Conservation of Energy says that value should be constant! Let's take a look at total energy in the system. Since we derived equations of motion from the Lagrangian, this should be easy! All we need to do is calculate kinetic and potential energy at each time step, and we already have the information we need to do this. " - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def get_Energy(u):\n", - " \"\"\" Calculates total energu in the system at given time step for a simple harmonic oscillator\n", - " Parameters:\n", - " u - state of system [pos, vel]\n", - " Returns:\n", - " total energy in system.\n", - " \"\"\"\n", - " pos = u[0]\n", - " vel = u[1]\n", - " T = 0.5*m*vel**2\n", - " V = 0.5*k*pos**2\n", - " \n", - " return T+V" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "#Euler total energy\n", - "Euler_energy = numpy.zeros_like(t)\n", - "for i in range(N):\n", - " Euler_energy[i] = get_Energy(q1[i,:])\n", - " \n", - "\n", - "#RK4 total energy\n", - "RK4_energy = numpy.zeros_like(t)\n", - "for i in range(N):\n", - " RK4_energy[i] = get_Energy(q2[i,:])\n", - "\n", - "#Verlet total energy\n", - "#Here we will convert from momentum to velocity so we can use the same get_energy function.\n", - "#Momemtum (q) is mass*velocity.\n", - "Verlet_energy = numpy.zeros_like(t)\n", - "Verlet_vel = q3[:,1]/m\n", - "for i in range(N):\n", - " Verlet_energy[i] = get_Energy(numpy.array([q3[i,0], Verlet_vel[i]]))\n", - "\n", - "\n", - "#analytic total energy\n", - "analytical_energy = numpy.zeros_like(t)\n", - "anayltical_state = numpy.array([q_analytical, vel_analytical]).T\n", - "for i in range(N):\n", - " analytical_energy[i] = get_Energy(anayltical_state[i,:])" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "#Euler\n", - "q1_d = numpy.zeros((N,2)) \n", - "q1_d[0] = x_init.copy() #set initial conditions\n", - "for n in range(N-1): #integrate with Euler\n", - " q1_d[n+1] = euler(q1_d[n], f_damped_HM, dt)\n", - " #print(q1[n])\n", - " \n", - "#Runge-Kutta \n", - "q2_d = numpy.zeros((N,2))\n", - "q2_d[0] = x_init.copy()\n", - "for n in range(N-1):\n", - " q2_d[n+1] = RK4(q2_d[n], f_damped_HM, dt)\n", - "\n", - "#Symplectic\n", - "q3_d = numpy.zeros((N,2))\n", - "q3_d[0] = x_init_H.copy()\n", - "for n in range(N-1):\n", - " q3_d[n+1] = Verlet_damped_HM(q3_d[n], dt)\n", - "\n", - "#Analytical Solution \n", - "A = x0 #with no forcing, the max amplitude is equal to initial amplitude\n", - "phi = 0\n", - "q_d_analytical = A*numpy.exp(-zeta*wn*t)*numpy.cos(wd*t + phi)\n", - "vel_d_analytical = -A*numpy.exp(-zeta*wn*t)*wd*numpy.sin(wd*t+phi)\n", - " \n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", - "pyplot.title('Harmonic oscillator position');\n", - "pyplot.plot(t, q1_d[:,0], lw=2, label='Euler');\n", - "pyplot.plot(t, q2_d[:,0], 'r-', lw=2, label='RK4');\n", - "pyplot.plot(t, q3_d[:,0], 'g', lw=2, label='Verlet');\n", - "pyplot.plot(t, q_d_analytical, 'k--', lw=2, label='analytical');\n", - "pyplot.legend();\n" + " return u + dt*f(u).T " ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 205, "metadata": { - "collapsed": true + "collapsed": false }, + "outputs": [], "source": [ - "How does each integrator perform compared to the case of simple harmonic motion? From the eye test, it seems like the error is actually better here. Why is that? \n", - "\n", - "The effect of the damper is to remove energy from the system. So, in this case the damping term is helping to remove some energy from the system that the integration adds. As time goes to infinity, these solutions will converge, and the Euler and Runge-Kutta methods are stable. We can again look at the energy in the system according to each method. \n", - "\n" + "def RK4_DP(u,f, dt):\n", + " \"\"\"Runge Kutta fourth order integration method\n", + " \n", + " Parameters:\n", + " u - state of the system at time t\n", + " f - function for RHS of state equations\n", + " dt - time step\n", + " \n", + " Returns: array of state values at next time step.\n", + " \"\"\"\n", + " k1 = f(u).T\n", + " k2 = f(u).T + 0.5*dt*k1\n", + " k3 = f(u).T + 0.5*dt*k2\n", + " k4 = f(u).T + dt*k3 \n", + " \n", + " return u + dt/6*(k1+2*k2+2*k3+k4)" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 206, "metadata": { "collapsed": false }, @@ -4538,7 +1072,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4546,66 +1080,7 @@ }, "metadata": {}, "output_type": "display_data" - } - ], - "source": [ - "#Euler total energy\n", - "Euler_energy_d = numpy.zeros_like(t)\n", - "for i in range(N):\n", - " Euler_energy_d[i] = get_Energy(q1_d[i,:])\n", - " \n", - "\n", - "#RK4 total energy\n", - "RK4_energy_d = numpy.zeros_like(t)\n", - "for i in range(N):\n", - " RK4_energy_d[i] = get_Energy(q2_d[i,:])\n", - "\n", - "#Verlet total energy\n", - "#Here we will convert from momentum to velocity so we can use the same get_energy function.\n", - "#Momemtum (q) is mass*velocity.\n", - "Verlet_energy_d = numpy.zeros_like(t)\n", - "Verlet_vel_d = q3_d[:,1]/m\n", - "for i in range(N):\n", - " Verlet_energy_d[i] = get_Energy(numpy.array([q3_d[i,0], Verlet_vel_d[i]]))\n", - "\n", - "\n", - "#analytic total energy\n", - "analytical_energy_d = numpy.zeros_like(t)\n", - "anayltical_state_d = numpy.array([q_d_analytical, vel_d_analytical]).T\n", - "for i in range(N):\n", - " analytical_energy_d[i] = get_Energy(anayltical_state_d[i,:])\n", - " \n", - "pyplot.figure(figsize=(8,6));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'Total energy in system (J)', fontsize=18);\n", - "pyplot.title('Energy in Damped Harmonic Oscillator');\n", - "pyplot.plot(t, Euler_energy_d, lw=2, label='Euler');\n", - "pyplot.plot(t, RK4_energy_d, 'r-', lw=2, label='RK4');\n", - "pyplot.plot(t, Verlet_energy_d, 'g', lw=2, label='Verlet');\n", - "pyplot.plot(t, analytical_energy_d, 'k--', lw=2, label='analytical');\n", - "pyplot.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can see some oscillations in the total energy, which come from the discretization error. Overall, we see again that Verlet performs better here than Euler or Runge-Kutta (although not perfectly - it does have induced oscillations from the damping term), but just like the analytical solution the energy in the system asymptotically approaches zero.\n", - "\n", - "\n", - "### Phase Portrait\n", - "\n", - "Another way to investigate stability is with a phase plot. Instead of position vs. time, we can plot velocity vs. position. This plot is in phase space (a subset of state space). For any given state (position and velocity), we should be able to determine what the next state will be. This gives us an entire direction field, over which we can plot the actual behavior of the system to see if they match up. If the phase plot becomes repetitive, we can call it stable, and if the phase plot goes to the origin, we can call it asymptotically stable." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "collapsed": false - }, - "outputs": [ + }, { "data": { "application/javascript": [ @@ -5340,342 +1815,87 @@ ] }, "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "#create a phase plot\n", - "NP = 15\n", - "pos = numpy.linspace(-x0, x0, NP)\n", - "vel = numpy.linspace(-2*x0, 2*x0, NP)\n", - "\n", - "X1, X2 = numpy.meshgrid(pos, vel)\n", - "u = numpy.zeros_like(X1)\n", - "v = numpy.zeros_like(X2)\n", - "\n", - "for i in range(NP):\n", - " for j in range(NP):\n", - " x = X1[i, j]\n", - " y = X2[i, j]\n", - " yprime = f_damped_HM([x, y])\n", - " u[i,j] = yprime[0]\n", - " v[i,j] = yprime[1]\n", - "\n", - "\n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.quiver(X1, X2, u, v, pivot='mid', color='k', label='direction field');\n", - "pyplot.plot(q_d_analytical, vel_d_analytical, 'k-', label='analytical');\n", - "pyplot.plot(q1_d[:,0], q1_d[:,1],'r-', label='Euler');\n", - "pyplot.plot(q2_d[:,0], q2_d[:,1], 'b-', label='RK4');\n", - "pyplot.plot(q3_d[:,0], -Verlet_vel_d, 'g-', label='Verlet');\n", - "pyplot.xlabel('Postion ($x_1$)')\n", - "pyplot.ylabel('Velocity ($x_2$)')\n", - "pyplot.legend();\n", - "pyplot.title('Phase portrait for damped harmonic oscillator');\n" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "pyplot.close(\"all\") #This line is for cleanup - There are a lot of plots in this notebook. Comment it out \n", - " #if you want to keep all of the plots open." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "### Duffing equation (Adding a non-linear damping term)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Dynamics of a Double Pendulum" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this section, we will study the dynamics of a double pendulum. Like before, we'll derive the equations of motion from the Lagrangian and the Hamiltonian. We can use the same methods of integration as before, but this time there is no analytical solution to compare to. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Background\n", - "\n", - "The double pendulum problem is a relatively simple system which can produce surprisingly complex movements. It is a chaotic system, meaning it is unpredictable and small changes in initial conditions lead to large changes in motion. There is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", - "\n", - "In this notebook we will be treating the two rods as massless and ignoring the effects of friction. For a treatment of this problem without these assumptions, see (cite).\n", - "\n", - "See the image below for the definition of constants." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Image](figures/diagram.png)\n", - "#### Figure 1. Diagram of Double Pendulum System" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Equations of Motion \n", - "\n", - "As a reminder, the Lagrangian is\n", - "$$\\begin{equation*}\n", - "L = T- U\n", - "\\end{equation*}$$\n", - "where $T$ is the total Kinetic Energy in the system, and $U$ is the total Potential Energy. Potential energy is calculated as:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", - " &=& -m_1gl_1cos\\theta_1 - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", - "\\end{eqnarray*}$$\n", - "\n", - "Kinetic Energy is calculated as:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2 \\\\\n", - "&=& \\frac{1}{2}m_1(l_1\\dot\\theta_1)^2 + \\frac{1}{2}m_2(l_1\\dot\\theta_1 + l_2\\dot\\theta_2)^2 \\\\\n", - "&=& \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\n", - "\\end{eqnarray*}$$\n", - "\n", - "\n", - "So the Lagrangian quantity becomes:\n", - "$$\\begin{equation*}\n", - "L = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+(m_1+m_2)l_1gcos\\theta_1 + m_2l_2gcos\\theta_2\n", - "\\end{equation*}$$\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "The equations of motion are then found using the Lagrange Equation:\n", - "\n", - "$$\\begin{equation*}\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = 0\n", - "\\end{equation*}$$\n", - "\n", - "\n", - "There are two generalized coordinates ($\\theta_1 and \\theta_2$. So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} &=& (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1) \\\\\n", - "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{\\theta_1}}\\right) &=& (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1) \\\\\n", - "-\\frac{\\partial L}{\\partial \\theta_1} &=& -m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)-(m_1+m_2)gl_1sin\\theta_1\n", - "\\end{eqnarray*}$$\n", - "\n", - "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", - "\n", - "$$\\begin{equation*}\n", - "0 = (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", - "\\end{equation*}$$\n", - "\n", - "We will also need the equation of motion for $\\theta_2$. This can be derived using the same process. It should work out to the following (check this by yourself!):\n", - "\n", - "$$\\begin{equation*}\n", - "0 = m_2l_2^2\\ddot\\theta_2+m_2l_1l_2\\ddot\\theta_1cos(\\theta_2-\\theta_1) - m_2l_2l_1\\dot\\theta_1^2sin(\\theta_2-\\theta_1)+ l_2m_2gsin\\theta_2\n", - "\\end{equation*}$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "## Problem setup\n", - "\n", - "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. Let's rewrite the equations of motion in a more readable form. We can use the standard form of this equation (which you'll be familiar with if you have studied mechanics) by separating out mass terms (anything multiplied by $\\ddot{x}$), centripetal terms (anything multiplied by $\\dot{x}$), and gravitational terms (anything multiplied by $x$). We get the following equations of state:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "0 &=&\\textbf{M}\\vec{\\ddot{x}}(t) + \\textbf{V}\\dot{x} + \\textbf{G} \n", - "\\end{eqnarray*}$$\n", - "\n", - "The equations of motion can be solved for $\\ddot{x}$:\n", - "\n", - "$$ \\ddot{x} = \\textbf{M}^{-1}\\left[ \\textbf{V}\\dot x + \\textbf{G} \\right]$$\n", - "\n", - "where \n", - "\n", - "\\begin{eqnarray*}\n", - "M = \n", - "\\left[\\begin{array}{c}\n", - "(m_1+m_2)l_1 & m_2l_1l_2cos(\\theta_2 - \\theta_1)\\\\\n", - " m_2l_1l_2cos(\\theta_2 - \\theta_1) &m_2l_2^2\n", - "\\end{array}\\right]\n", - "\\end{eqnarray*}\n", - "\n", - "\n", - "\\begin{equation*}\n", - "V = \n", - "\\left[\\begin{array}{c}\n", - "0 & -m_2l_1l_2sin(\\theta_2 - \\theta_1)\\\\\n", - " -m_2l_1l_2sin(\\theta_2 - \\theta_1) &0\n", - "\\end{array}\\right]\n", - "\\end{equation*}\n", - "\n", - "\n", - "\\begin{eqnarray*}\n", - "G = \n", - "\\left[\\begin{array}{c}\n", - "(m_1+m_2)l_1gsin\\theta_1\\\\\n", - " m_2l_2gsin\\theta_2\n", - "\\end{array}\\right]\n", - "\\end{eqnarray*}\n", - "\n", - "and\n", - "\n", - "$$\\begin{eqnarray*}\n", - "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\end{pmatrix}, \\vec{\\dot{x}(t)} = \\begin{pmatrix} \\dot \\theta_1 \\\\ \\dot \\theta_2 \\end{pmatrix}\n", - "\\end{eqnarray*}$$\n", + "#Euler\n", + "q1_dp = numpy.zeros((N,4)) \n", + "q1_dp[0,:] = x_init_dp.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " q1_dp[n+1,:] = euler_DP(q1_dp[n,:], f_double_pendulum, dt)\n", + " #print(q1[n])\n", + " \n", + "#Runge-Kutta\n", + "q2_dp = numpy.zeros((N,4)) \n", + "q2_dp[0,:] = x_init_dp.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " q2_dp[n+1,:] = RK4_DP(q2_dp[n,:], f_double_pendulum, dt)\n", + " \n", + "pyplot.figure(figsize=(10,8));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.title('Double Pendulum Euler');\n", + "pyplot.plot(t, q1_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, q1_dp[:,1], lw=2, label='Joint 2')\n", + "pyplot.legend();\n", "\n", - "We can now treat this system the same way we handled the simple harmonic motion; break the two second order ODEs into four first-order ODEs, then use Euler or Runge-Kutta on the results." + "pyplot.figure(figsize=(10,8));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.title('Double Pendulum RK4');\n", + "pyplot.plot(t, q2_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, q2_dp[:,1], lw=2, label='Joint 2')\n", + "pyplot.legend();" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": false + }, "source": [ - "### Equations of Motion from Hamiltonian\n", - "\n", - "Here we derive the equations of motion from the Hamiltonian. If you want good practice, you can derive these on your own. We start with the same potential and kinetic energy, but we'll need to use postion ($\\theta$) and momentum ($I\\omega$) to give us:\n", + "Again, we can see that these integrators cause the system to go unstable. In fact, there is a physical interpretation for what is happening. In the RK4 plot, you can see the position of joint 1 oscillating betwee -pi and pi (corresponding to straight up in the air), until it finally reaches a value less than -pi. So, once joint 1 'flips around,' the system diverges.\n", "\n", - "$$\\begin{eqnarray*}\n", - "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", - " &=& -m_1g(l_1cos\\theta_1 - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", - "\\end{eqnarray*}$$\n", - "\n", - "Kinetic Energy is calculated as:\n", - "\n", - "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2 \\\\\n", - "&=& \\frac{q_1^2}{2I_1} + \\frac{q_2^2}{2I_2}\n", - "\\end{eqnarray*}$$\n", - "\n", - "where $I_i = m_il_i^2$. So our Hamiltonian quantity becomes:\n", - "$$\\begin{equation*}\n", - "H = \\frac{1}{2}\\left(\\frac{q_1^2}{I_1} + \\frac{q_2^2}{I_2}\\right) - (m_1+m_2)gl_1cos\\theta_1 - m_2gl_2cos\\theta_2\n", - "\\end{equation*}$$\n", + "Just like with simple harmonic motion, we can add in a damping term to help remove some of the energy that the integration scheme adds in. In the problem setup, we actually used an abbreviated form of a standard equation. The full equation is here:\n", "\n", - "Since we have two degrees of freedom, we will have four equations of motion. These will be the following:\n", - "$$\\begin{eqnarray*}\n", - "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = \\frac{q_1}{I_1} \\\\\n", - "\\dot{q_1} &=& \\frac{\\partial H}{\\partial p_1} = (m_1+m_2)gl_1sinp_1 \\\\\n", - "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = \\frac{q_2}{I_2} \\\\\n", - "\\dot{q_2} &=& \\frac{\\partial H}{\\partial p_2} = m_2gl_2sinp_2 \\\\\n", - "\\end{eqnarray*}$$\n" + "\\begin{equation}\n", + "\\textbf{M}(x)\\vec{\\ddot{x}}(t) + \\textbf{V}(x, \\dot x ) \\dot{x} + \\textbf{G(x)} + f(\\dot x) + \\tau_d(t) = \\tau(t)\n", + "\\end{equation}" ] }, { "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Integration\n", - "\n", - "For Euler and Runge-Kutta, we can use the matrix equation above to integrate our system. The results will be VERY dependent on initial conditions; this is why we call it a chaotic system. For the symplectic integrator, we'll use the Hamiltonian equations of motion above. " - ] - }, - { - "cell_type": "code", - "execution_count": 159, "metadata": { "collapsed": true }, - "outputs": [], - "source": [] + "source": [ + "where \\textbf{M, V}, and \\textbf{G} are defined as above, $f$ is frictional or damping terms, and the $\\tau$ terms represent added torque to the system. Since we aren't adding any torque, the $\\tau$ terms will remain 0. However, we can add in some linear damping by including the frictional term. This requires redefining our RHS function, but no other changes!" + ] }, { "cell_type": "code", - "execution_count": 191, + "execution_count": 207, "metadata": { "collapsed": false }, "outputs": [], "source": [ - "def f_double_pendulum(u):\n", + "def f_damped_double_pendulum(u):\n", " \"\"\"Returns RHS of double pendulum EOM\n", " \n", " Parameters:\n", @@ -5693,7 +1913,8 @@ " M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[m2*l1*l2*cos(x2-x1), m2*l2**2]])\n", " V = numpy.array([[0, -m2*l1*l2*sin(x2-x1)],[-m2*l1*l2*sin(x2-x1), 0]])\n", " G = numpy.array([[(m1+m2)*g*l1*sin(x1)],[m2*l2*g*sin(x2)]])\n", - " qdd = numpy.linalg.inv(M).dot(V.dot(numpy.array([[x3],[x4]])) +G) #- 2*numpy.array([[x3],[x4]])) \n", + " f = c*numpy.array([[x3],[x4]])\n", + " qdd = numpy.linalg.inv(M).dot(V.dot(numpy.array([[x3],[x4]]))+G -f) \n", " #print(qdd)\n", " #print(M)\n", " #print(V.dot(numpy.array([[x3],[x4]])))\n", @@ -5705,91 +1926,7 @@ }, { "cell_type": "code", - "execution_count": 192, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "#Set up parameters:\n", - "g = -9.8 #[m/s^2]\n", - "m1 = 2\n", - "m2 = 1.5;\n", - "l1 = 1\n", - "l2 = 1.5\n", - "\n", - "T = 25; #[seconds]\n", - "dt = .02; #\n", - "N = int(T/dt)+1\n", - "t = numpy.linspace(0.0, T, N)\n", - "\n", - "#Initial Conditions\n", - "theta1_0 = 0 #[radians]\n", - "theta2_0 = 1\n", - "theta1_dot_0 = 0 #[rad/s]\n", - "theta2_dot_0 = 0 #\n", - "\n", - "#p0 = x0\n", - "#q0 = m*xdot0\n", - "x_init_dp = numpy.array([theta1_0, theta2_0, theta1_dot_0, theta2_dot_0])\n", - "#x_init_H = numpy.array([p0, q0])\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 193, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def euler_DP(u, f, dt):\n", - " \"\"\" Euler's method for integrating a system of differential equations.\n", - " \n", - " Parameters:\n", - " u - state at current step \n", - " f - RHS of equation\n", - " dt- time step size\n", - " \n", - " Returns: \n", - " x - array of values at next time step.\n", - " \"\"\"\n", - " #print(f(u))\n", - " #print(u)\n", - " \n", - " return u + dt*f(u).T " - ] - }, - { - "cell_type": "code", - "execution_count": 194, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def RK4_DP(u,f, dt):\n", - " \"\"\"Runge Kutta fourth order integration method\n", - " \n", - " Parameters:\n", - " u - state of the system at time t\n", - " f - function for RHS of state equations\n", - " dt - time step\n", - " \n", - " Returns: array of state values at next time step.\n", - " \"\"\"\n", - " k1 = f(u).T\n", - " k2 = f(u).T + 0.5*dt*k1\n", - " k3 = f(u).T + 0.5*dt*k2\n", - " k4 = f(u).T + dt*k3 \n", - " \n", - " return u + dt/6*(k1+2*k2+2*k3+k4)" - ] - }, - { - "cell_type": "code", - "execution_count": 195, + "execution_count": 208, "metadata": { "collapsed": false }, @@ -6533,7 +2670,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -7281,7 +3418,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -7292,26 +3429,29 @@ } ], "source": [ + "#We use the same initial conditions as the example without damping, so they aren't redefined here.\n", + "c = 2 #[N/(rad/s)] define some damping\n", + "\n", "#Euler\n", - "q1_dp = numpy.zeros((N,4)) \n", - "q1_dp[0,:] = x_init_dp.copy() #set initial conditions\n", + "q1d_dp = numpy.zeros((N,4)) \n", + "q1d_dp[0,:] = x_init_dp.copy() #set initial conditions\n", "for n in range(N-1): #integrate with Euler\n", - " q1_dp[n+1,:] = euler_DP(q1_dp[n,:], f_double_pendulum, dt)\n", + " q1d_dp[n+1,:] = euler_DP(q1d_dp[n,:], f_damped_double_pendulum, dt)\n", " #print(q1[n])\n", " \n", "#Runge-Kutta\n", - "q2_dp = numpy.zeros((N,4)) \n", - "q2_dp[0,:] = x_init_dp.copy() #set initial conditions\n", + "q2d_dp = numpy.zeros((N,4)) \n", + "q2d_dp[0,:] = x_init_dp.copy() #set initial conditions\n", "for n in range(N-1): #integrate with Euler\n", - " q2_dp[n+1,:] = RK4_DP(q2_dp[n,:], f_double_pendulum, dt)\n", + " q2d_dp[n+1,:] = RK4_DP(q2d_dp[n,:], f_damped_double_pendulum, dt)\n", " \n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Double Pendulum Euler');\n", - "pyplot.plot(t, q1_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, q1_dp[:,1], lw=2, label='Joint 2')\n", + "pyplot.plot(t, q1d_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, q1d_dp[:,1], lw=2, label='Joint 2')\n", "pyplot.legend();\n", "\n", "pyplot.figure(figsize=(10,8));\n", @@ -7319,44 +3459,20 @@ "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Double Pendulum RK4');\n", - "pyplot.plot(t, q2_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, q2_dp[:,1], lw=2, label='Joint 2')\n", + "pyplot.plot(t, q2d_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, q2d_dp[:,1], lw=2, label='Joint 2')\n", "pyplot.legend();" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "metadata": { "collapsed": true }, - "source": [] + "source": [ + "### Investigating system behavior\n", + "\n" + ] }, { "cell_type": "markdown", @@ -7388,7 +3504,7 @@ }, "outputs": [], "source": [ - "# This cell loads the style of the notebook, which is taken from the \n", + "# This cell loads the style of the notebook, which is modified from the \n", "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", "\n", "from IPython.core.display import HTML\n", diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index d66d98c..4594f0f 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -5369,12 +5369,209 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": { - "collapsed": true + "collapsed": false }, - "outputs": [], - "source": [] + "outputs": [ + { + "ename": "SyntaxError", + "evalue": "invalid syntax (, line 1)", + "output_type": "error", + "traceback": [ + "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m1\u001b[0m\n\u001b[1;33m Sources:\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" + ] + } + ], + "source": [ + "Sources: \n", + "\n", + "[1] http://scienceworld.wolfram.com/physics/DoublePendulum.html\n", + "\n", + "[2] http://www.phy.uct.ac.za/courses/opencontent/phylab2/worksheet9_09.pdf\n", + "\n", + "[3] http://www.phys.lsu.edu/faculty/gonzalez/Teaching/Phys7221/DoublePendulum.pdf\n", + "\n", + "[4] http://www.iontrap.wabash.edu/adlab/papers/F2011_foster_groninger_tang_chaos.pdf\n", + "\n", + "[5] https://math.berkeley.edu/~alanw/242papers99/markiewicz.pdf\n", + "\n", + "[6] http://www.unige.ch/~hairer/poly_geoint/week2.pdf\n", + "\n", + "[7] http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1994CeMDA..60..409T&defaultprint=YES&filetype=.pdf\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This cell loads the style of the notebook, which is modified from the \n", + "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", + "\n", + "from IPython.core.display import HTML\n", + "css_file = './numericalmoocstyle.css'\n", + "HTML(open(css_file, \"r\").read())" + ] }, { "cell_type": "code", From 52478e3afbc66804e6a6fbf5e4a6daaa933281e0 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 7 Dec 2015 16:51:18 -0500 Subject: [PATCH 30/61] Delete 0e1p4ebd.wag.txt --- 0e1p4ebd.wag.txt | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 0e1p4ebd.wag.txt diff --git a/0e1p4ebd.wag.txt b/0e1p4ebd.wag.txt deleted file mode 100644 index d5e5a32..0000000 --- a/0e1p4ebd.wag.txt +++ /dev/null @@ -1,13 +0,0 @@ - -# Please enter the commit message for your changes. Lines starting -# with '#' will be ignored, and an empty message aborts the commit. -# On branch master -# Your branch is up-to-date with 'origin/master'. -# -# Changes to be committed: -# new file: figures/POS_computer.png -# -# Changes not staged for commit: -# modified: Double_Pendulum_Problem.ipynb -# - From c58c649ed88c610665ece81969b716319f360630 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 7 Dec 2015 19:03:59 -0500 Subject: [PATCH 31/61] plot Poincare section --- randy_schur/Double_Pendulum_Problem.ipynb | 897 +++++++++++++++++++++- 1 file changed, 874 insertions(+), 23 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index ba51bfe..080d2bd 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -197,15 +197,6 @@ "For Euler and Runge-Kutta, we can use the matrix equation above to integrate our system. The results will be VERY dependent on initial conditions; this is why we call it a chaotic system. For the symplectic integrator, we'll use the Hamiltonian equations of motion above. " ] }, - { - "cell_type": "code", - "execution_count": 159, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 202, @@ -244,7 +235,7 @@ }, { "cell_type": "code", - "execution_count": 203, + "execution_count": 287, "metadata": { "collapsed": false }, @@ -257,7 +248,7 @@ "l1 = 1\n", "l2 = 1.5\n", "\n", - "T = 25; #[seconds]\n", + "T = 50; #[seconds]\n", "dt = .02; #\n", "N = int(T/dt)+1\n", "t = numpy.linspace(0.0, T, N)\n", @@ -277,7 +268,7 @@ }, { "cell_type": "code", - "execution_count": 204, + "execution_count": 288, "metadata": { "collapsed": true }, @@ -302,7 +293,7 @@ }, { "cell_type": "code", - "execution_count": 205, + "execution_count": 289, "metadata": { "collapsed": false }, @@ -328,7 +319,7 @@ }, { "cell_type": "code", - "execution_count": 206, + "execution_count": 290, "metadata": { "collapsed": false }, @@ -1072,7 +1063,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1820,7 +1811,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1871,10 +1862,44 @@ "source": [ "Again, we can see that these integrators cause the system to go unstable. In fact, there is a physical interpretation for what is happening. In the RK4 plot, you can see the position of joint 1 oscillating betwee -pi and pi (corresponding to straight up in the air), until it finally reaches a value less than -pi. So, once joint 1 'flips around,' the system diverges.\n", "\n", + "With simple harmonic motion, the symplectic integrator was able to accurately represent the state of the system without adding energy. Let's see if it is successful with the double pendulum. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def euler_DP(u, f, dt):\n", + " \"\"\" Euler's method for integrating a system of differential equations.\n", + " \n", + " Parameters:\n", + " u - state at current step \n", + " f - RHS of equation\n", + " dt- time step size\n", + " \n", + " Returns: \n", + " x - array of values at next time step.\n", + " \"\"\"\n", + " #print(f(u))\n", + " #print(u)\n", + " \n", + " return u + dt*f(u).T " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Damped Double Pendulum\n", + "\n", "Just like with simple harmonic motion, we can add in a damping term to help remove some of the energy that the integration scheme adds in. In the problem setup, we actually used an abbreviated form of a standard equation. The full equation is here:\n", "\n", "\\begin{equation}\n", - "\\textbf{M}(x)\\vec{\\ddot{x}}(t) + \\textbf{V}(x, \\dot x ) \\dot{x} + \\textbf{G(x)} + f(\\dot x) + \\tau_d(t) = \\tau(t)\n", + "\\textbf{M}(x)\\vec{\\ddot{x}}(t) + \\textbf{V}(x, \\dot x ) \\vec{\\dot{x}} + \\textbf{G(x)} + f(\\dot x) + \\tau_d(t) = \\tau(t)\n", "\\end{equation}" ] }, @@ -1884,12 +1909,12 @@ "collapsed": true }, "source": [ - "where \\textbf{M, V}, and \\textbf{G} are defined as above, $f$ is frictional or damping terms, and the $\\tau$ terms represent added torque to the system. Since we aren't adding any torque, the $\\tau$ terms will remain 0. However, we can add in some linear damping by including the frictional term. This requires redefining our RHS function, but no other changes!" + "where $\\textbf{M, V}$, and $\\textbf{G}$ are defined as above, $f$ is frictional or damping terms, and the $\\tau$ terms represent added torque to the system. Since we aren't adding any torque, the $\\tau$ terms will remain 0. However, we can add in some linear damping by including the frictional term. This requires redefining our RHS function, but no other changes!" ] }, { "cell_type": "code", - "execution_count": 207, + "execution_count": 291, "metadata": { "collapsed": false }, @@ -1926,7 +1951,7 @@ }, { "cell_type": "code", - "execution_count": 208, + "execution_count": 292, "metadata": { "collapsed": false }, @@ -2670,7 +2695,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3418,7 +3443,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3471,7 +3496,833 @@ }, "source": [ "### Investigating system behavior\n", - "\n" + "\n", + "In order to investigate system behavior for damped harmonic motion, we used a phase portrait where we plotted position vs. velocity. In this system, we have 2 positions and 2 velocities, so a phase plot won't accurately capture the behavior of the system. Instead we are going to use something called a Poincare section (https://en.wikipedia.org/wiki/Poincar%C3%A9_map). The way these work is to capture the system behavior at a specific, repetetive point. For example, each time that $\\theta_1 = 0$ (mass 1 is hanging straight down) we can capture the angular position and velocity of mass 2. This way, we are taking a 'snapshot' of the system, and plotting the state each time. We can now investigate system behavior on a simple 2D plot, similar to the phase portrait." + ] + }, + { + "cell_type": "code", + "execution_count": 294, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def poincare(state, sf, val, s1, s2):\n", + " '''Creates a poincare section, capturing system state each time q[state]=val\n", + " \n", + " Parameters:\n", + " state - state vector\n", + " sf - the row in q to fix\n", + " val - the value to capture state at \n", + " s1 - state to capture 1\n", + " s2 - state to capture 2\n", + " \n", + " Returns:\n", + " v - array with information for poincare section\n", + " '''\n", + " v = numpy.zeros((2,1))\n", + " for i in range(len(state[:,sf])-1):\n", + " if (state[i,sf]>val):\n", + " if (state[i+1,sf] < val):\n", + " # print(v)\n", + " # print([[state[i, s1],[state[i, s2]]]])\n", + " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", + " \n", + " if (state[i,sf] < val):\n", + " if (state[i+1,sf] > val):\n", + " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", + " \n", + " return v\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 296, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#Verlet integration\n", + "q3_dp = numpy.zeros((N,4)) \n", + "q3_dp[0,:] = x_init_H_dp.copy() #set initial conditions\n", + "print(q3_dp[0,:])\n", + "for n in range(N-1): #integrate with Euler\n", + " q3_dp[n+1,:] = Verlet_DP(q3_dp[n,:], dt)\n", + " \n", + "pyplot.figure(figsize=(10,8));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.title('Double Pendulum Euler');\n", + "pyplot.plot(t, q3_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, q3_dp[:,2], lw=2, label='Joint 2')\n", + "pyplot.legend();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index 4594f0f..f4e1e02 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -379,7 +379,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 28, "metadata": { "collapsed": false }, @@ -1123,7 +1123,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1154,7 +1154,7 @@ " q3[n+1] = verlet_SHM(q3[n], dt)\n", "\n", "#Analytical Solution \n", - "A = x0 #with no forcing, the max amplitude is equal to initial amplitude\n", + "A = x0 #with no forcing and no initial velocity, the max amplitude is equal to initial amplitude\n", "phi = 0\n", "omega = sqrt(k/m)\n", "q_analytical = A*numpy.cos(omega*t + phi)\n", From f069866e87665f2a434c7a806c91c2cc9e5a9add Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 8 Dec 2015 00:51:23 +0000 Subject: [PATCH 33/61] add screenshots --- randy_schur/Double_Pendulum_Problem.ipynb | 4563 +---------------- ...on Techniques for Mechanical Systems.ipynb | 491 +- randy_schur/figures/Error_64bit.png | Bin 0 -> 24164 bytes randy_schur/figures/response_64bit.png | Bin 0 -> 69838 bytes 4 files changed, 285 insertions(+), 4769 deletions(-) create mode 100644 randy_schur/figures/Error_64bit.png create mode 100644 randy_schur/figures/response_64bit.png diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 1267ee8..b1c0004 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -199,7 +199,7 @@ }, { "cell_type": "code", - "execution_count": 202, + "execution_count": 1, "metadata": { "collapsed": false }, @@ -235,11 +235,23 @@ }, { "cell_type": "code", - "execution_count": 319, + "execution_count": 2, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'numpy' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[0mdt\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m.02\u001b[0m\u001b[1;33m;\u001b[0m \u001b[1;31m#\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 12\u001b[0m \u001b[0mN\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mT\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0mdt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 13\u001b[1;33m \u001b[0mt\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnumpy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlinspace\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0.0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mT\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mN\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 14\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 15\u001b[0m \u001b[1;31m#Initial Conditions\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mNameError\u001b[0m: name 'numpy' is not defined" + ] + } + ], "source": [ "#Set up parameters:\n", "g = -9.8 #[m/s^2]\n", @@ -273,7 +285,7 @@ }, { "cell_type": "code", - "execution_count": 320, + "execution_count": null, "metadata": { "collapsed": true }, @@ -298,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 321, + "execution_count": null, "metadata": { "collapsed": false }, @@ -324,1508 +336,11 @@ }, { "cell_type": "code", - "execution_count": 322, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "#Euler\n", "q1_dp = numpy.zeros((N,4)) \n", @@ -1872,7 +387,7 @@ }, { "cell_type": "code", - "execution_count": 327, + "execution_count": null, "metadata": { "collapsed": false }, @@ -1905,767 +420,11 @@ }, { "cell_type": "code", - "execution_count": 328, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ 1. 0. 3.14159265 0. ]\n" - ] - }, - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 296, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "v = poincare(q2d_dp, 0, 0, 2, 3) #use states from RK4 integration\n", "\n", @@ -5178,21 +681,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.10" + "pygments_lexer": "ipython3", + "version": "3.5.0" } }, "nbformat": 4, diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index f4e1e02..5997a83 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -218,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -239,7 +239,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 23, "metadata": { "collapsed": true }, @@ -259,7 +259,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 24, "metadata": { "collapsed": true }, @@ -282,7 +282,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 25, "metadata": { "collapsed": true }, @@ -308,7 +308,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 26, "metadata": { "collapsed": true }, @@ -332,7 +332,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 27, "metadata": { "collapsed": true }, @@ -360,7 +360,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 28, "metadata": { "collapsed": true }, @@ -379,7 +379,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "metadata": { "collapsed": false }, @@ -459,7 +459,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -790,11 +789,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -843,6 +844,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -856,7 +870,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -896,7 +911,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -913,7 +929,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -972,6 +988,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -979,8 +997,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -1035,14 +1057,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -1123,7 +1151,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1185,7 +1213,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 30, "metadata": { "collapsed": true }, @@ -1208,7 +1236,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 31, "metadata": { "collapsed": true }, @@ -1229,7 +1257,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 32, "metadata": { "collapsed": false }, @@ -1309,7 +1337,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -1640,11 +1667,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -1693,6 +1722,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -1706,7 +1748,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -1746,7 +1789,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -1763,7 +1807,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1822,6 +1866,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -1829,8 +1875,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -1885,14 +1935,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -1973,7 +2029,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2007,7 +2063,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 12, "metadata": { "collapsed": true }, @@ -2030,7 +2086,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 13, "metadata": { "collapsed": true }, @@ -2065,7 +2121,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -2145,7 +2201,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -2476,11 +2531,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -2529,6 +2586,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -2542,7 +2612,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -2582,7 +2653,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -2599,7 +2671,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -2658,6 +2730,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -2665,8 +2739,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -2721,14 +2799,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -2809,7 +2893,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2887,7 +2971,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -2911,7 +2995,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 16, "metadata": { "collapsed": true }, @@ -2940,7 +3024,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 17, "metadata": { "collapsed": true }, @@ -2971,7 +3055,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -3051,7 +3135,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -3382,11 +3465,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -3435,6 +3520,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -3448,7 +3546,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -3488,7 +3587,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -3505,7 +3605,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -3564,6 +3664,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -3571,8 +3673,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -3627,14 +3733,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -3715,7 +3827,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3775,7 +3887,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -3855,7 +3967,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -4186,11 +4297,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -4239,6 +4352,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -4252,7 +4378,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -4292,7 +4419,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -4309,7 +4437,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4368,6 +4496,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -4375,8 +4505,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -4431,14 +4565,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -4519,7 +4659,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4582,7 +4722,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -4662,7 +4802,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -4993,11 +5132,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -5046,6 +5187,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -5059,7 +5213,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -5099,7 +5254,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -5116,7 +5272,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -5175,6 +5331,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -5182,8 +5340,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -5238,14 +5400,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -5326,7 +5494,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5369,17 +5537,17 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "ename": "SyntaxError", - "evalue": "invalid syntax (, line 1)", + "evalue": "invalid syntax (, line 1)", "output_type": "error", "traceback": [ - "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m1\u001b[0m\n\u001b[1;33m Sources:\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" + "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m1\u001b[0m\n\u001b[1;33m Sources:\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" ] } ], @@ -5404,166 +5572,11 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# This cell loads the style of the notebook, which is modified from the \n", "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", @@ -5594,21 +5607,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.10" + "pygments_lexer": "ipython3", + "version": "3.5.0" } }, "nbformat": 4, diff --git a/randy_schur/figures/Error_64bit.png b/randy_schur/figures/Error_64bit.png new file mode 100644 index 0000000000000000000000000000000000000000..b4cae05a13a6e3f228b6bf89dad15aa2b901e2ea GIT binary patch literal 24164 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

ps&xN;2LzyQF=Z%CXlt%FhqHDaXzA#TR_^^` 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 literal 0 HcmV?d00001 diff --git a/randy_schur/figures/response_64bit.png b/randy_schur/figures/response_64bit.png new file mode 100644 index 0000000000000000000000000000000000000000..f47bc1d720eb6f14cb295d8c32546121af0803aa GIT binary patch literal 69838 zcmZ6zcRbba|39v&NQI`6S0~}fN>&_{j&;Z}vqxDOk-bSJR0v1*k$LPr$}D?JR%B$4 z$lmMsIJ{o(&-eCw{zDwk=k>g<$MtyJ$K%RZUQUvNjE0PagoHv`3av;&a?FZ^+smxkVNUJovp2*wf@WRr%6cu zA(2MkS9a{1jdsvdQr)ks;D3CB_QHqjEN?#>Q(FB_PP_O_Tbnhru&}$GP+%HU*Cm)* zSfR0MrJPZ?b-}nmBeCk}+4FyNSQ^hCS3X|(A-sQxf%%J0(2Hd0Q;W;)tE;Bge;Zm_ z)@`Ik+}rLBRV|{hFdn#2q*SjDzx%3095@MyBj)hmSQr&tzmZ1{zP=zFKlr+UJNQC^ zr#kpb{Hyn8L`6kYlvx$7hhX&e^}XUBKYmO_shDEBy*8Ib^A;`+?c(FdkB?1EbZ6bd z%F5b0I53E~eoji?+uIAM@VZIW{gZ%=F?I|M3k&lizpPVi+wgjyj^~CnnmgQlxYFxa z;j@m}{b6qgK_Q_S0jr}!j3n^fg}C6LAVg@Bo15ER|98H=flcGWE^8P3_jcEZ$h_iI zvvla{p8YC($PpSqFVy{7*wEQ|tITEn?Ynn>dO{}Q8Xq4hchh|MsKA2E3vu)2c=*sw zK|yDD-enFgu3=Hq7OC9Y8I+-^X{sz!Q)_E2|7nsp)n~4ge63G$I;Ijlq|;Sf%XIhd zm$I#PB%(>d~BV0&DMa4_(8W(72W`>6!8yb%P`9n)d z8O!(d%K7vEG|vP#7%;f41O@~IeEISPjqvsH2}3#Maw*-pd)I0DM zv&)nrI9zks?JBtX`UH7+c&=Z+zszkhR4!~ag4Wg5y>%;1!jFc6UT|f;V*ir=V3~`m z;-2Fn9Zq_yT-OXnc8mKYj*bbg}J#)YisUv z1wHn&KNXafm2G?)8yg>`tF@;n5l&Lj{Os+uo9Q&^N>v#jA0H73xF)PoXs!-%K~GPw zt)(SsH?y)>$52;Oc!P>Q3`r&Qf1 zTceFXaC2~2Y^_Yg3p!X^=N1%Dkda03TjDo5YO1TH7=vowU0y~pP*Q&53Be?e1kd*7 z5p*luTU%RUQjQ!sa>ux})L|a~e5HxA;sOUZAD=x$SG15rVL^c*yG9mf=S!w`37_R) z>5CU6w{G2Xa9D@gFfcTnM)hgjn4X;M$v08ryMx~N{j10lDbw^HjxghsZ84Yq@hB%n$GJH1cX9FYE-O>*pY9(sHl7T;A!+K)L-OLI$0%%` zV5Wx=Fzm3fD;bh5>wnn;r3#<@VzSto&1*|fPw&psjfsrho@$TZ*x2~+;k=||9WB2_ za#_@<02bSpot+KSFgY=il#q~b-ph?Zh`BBeS9xb?7IAQLazbpvE>KKaSs1R;uk+u9 zjq&d7Te)b#@#*R5v9Xok-;OPY@9!*Qg@j7%=lX6kGU7r*nVFdbuH1e(`Z=q73)9ao zC>SyN`TO_pplO`vvdNVk`y#F zA~eQo2qR7+yu2Z!pR=>Mtn{Bh&khJ^gOJwI(uxWVE%MlRhw1XC7y9_|qv=Uw2*3!2 zHJFY`0b(e@py6FA_V-GPii%1~wnJ{IRu9w$FmMJ+y*YE0+iIj*v&iD&<;!hRca;!< zo#!RizGxI#lsPVrL`6kO23&hKR6fwx*H>4^eB;K0$@Xp<>5Tgj7?mx}8`FfC=9U(C zp37Wc!P3&wE!E7GnXabF$|n%ZBNn1N3&-lLcDL8hojX@yH7fD=@$fm(9X%tX?gBGa z*BeLV??Aq|QbI^S>p#j9-96KlRm% z5xdcuqU04133WoCwX4f+{qKO-XwXf_FAXr=uRoTSqCN`9VzHNLAiOmT&5Df(Vqd9Y@yr(K@V5DOfc@ zhPmsn3E@Uf!QZwFdv8?0V&V66hKSQv`-V_$z_2VCOj3dzT zn&LiN)x5n(R|i=_cCy{w9UG3rRVz-^)YOQ@%QD&6*%cHPLIf2?5Nz%3Igm(78q%|L zEP+y;r2-`PDBimr)0;(0N;Va=r>Szbw00ytjD)Pwht99{qm`7DG!x#0U}ZpA_k!9Oo@4nKVKUozM0&;V~Z(hIVG3%b4Xo>Vb$8f1A&FvBl6Ox&LqavGOq;=-cSl>NQUE0ph&IzGnPm8P5~ zoymdm@i*F*VFPwN&bK?dU8)iPZMjs=LI&W_O%kS>`oJ56hmqY0(!o%Ta&x&qwy&(N zcA3h?e8$5r-|}TQ%?u6Cfa;dK`j|b!U22X-O+6~&nXS8f`9>6W{70%wNZ&ZU!1LeR z^A+yhmV?K-Q9`xOf_9?u~lfkzu4~GbXZVO<#MT5 zY;sGJ`k5GYpT1A({oc`0rxVhi{%QR5XS?t?5%=f`GBPsmzy`FifP(zYY_Rl8xH|m> zavN)F1fr#;C*6zQO^%M9zSnYxI@GaesgH*{q^t%~*Zl@87@k z^Lea_2xTw&@~lQ{aX8$wE=S2?5rV} z<)x+SRa+mHJ9qAgit4h4K*0&|xhCRz<>JMfvtLR|ApLGb(VQPB0bq1n$VpG{Q(j(P zH@RAcdzl>5(ZO=J$kERb*`f-P87JDj&Z{t^rKIYYbrT{c0i3nAwM`0SDiP!a-`m@v zR1zJI^?xKGQCh&E+OjkX8vFX3+vCJdy%7k6mX;Pu#St!N=ZG~Xqu=6hS}bKS7^=vC zKRsDP6&^!+JzN(30?f>Wsi~=z728+^j?n#$(Ez%-may9onCH3mYtP^(yI3DV&3#YH z{}zB*$p zfH+z{^W>_hs-$_Vm87}_rd<%LX@5|5Gs@)T@1Tsln?yGnXh!o0`qxz)cj!C^768zA5BPDn3|%Z^+>fZ z02Ps{LATj4Y}CD^-f!*gx4F50^!K|?eUF6-fAQkQhK2_H+V@|amM5UVh&^~>z7M|h{nyq;A1P(n0tva^RY7(f7R0s4>NHRChwWa)l9SnBkO^qdZ4;#|Wf z2q86PWm^~%q+c6AUVv7e_qgPu1ajb>9($XUrB2Jaxw&LyWP9rs`)=@qh%Ab;XN~Oh z5)#H>Ow3sP%JMR`*oBK1dEspUbU~dS^4Q;nLSWvR+zty|bF(GJDMAl$OQ^1U18Xec zXSZ)JPYezYrm1FiJLMNh$;rCnoG0!hp?RbuxgLPd z&c{TBoK|*`Tq{bl4;&Hwnvjs-OU=Bsv7x4+QCwV1%&v9*^d0H`DfYXjeo%DM)%5JF zewMj7#E7{4IgtZI+$s5ds`C*R#Fl`cX#Ql=P`}=U61{BkB54XNT7l-R%>C+t=xc0D zRnIeO)=lktk}g-x+)a>`lhY}8h2-(itzFnc)y>Va(UO{Gj8|cVf7;`{U%h%28Tl*h zw!XEswOV`Qj7U3VJb-X>K(qjS4G-Uj(v)Z1mU^*Nxulnewky@|D9JUCoSt zXJ_Zy>gv~CUhHbwzeV>q12&$b4=&icivgr0B;PN>ZE|vQkVqsTWS^5HFBInM%7ul6 zV-W{@8I|bX`^Cl>@F{^nsHoT{0_0y2#v~*QM-`i#;)wJ+7{SbJ4EKW_3IltH#X=2f zY6xb&j)MAh%$$54B7!#Kcu>hgTVmS`Fx)!2FUBy*VjMo|9l&jI4CV6vppIh3LJ*f!l2~!m*V17 zJEN9fz1A#qd@7^UDJNEtTJ&|C-r%zvh{rZ($tNO54DJdxlYJ|hi#$x4Q z9h3?In+e%__eg$K%27Wmx#D2zR$W&|+IL@sWDfq^&dWD_JJTYu9gqs(vjKpyh+><` zR$9wa1s$DoqZXP|=MLThyBP+|rLn3?A5sWiU53_|9~~Vw@cpd(^XCg#(OsZDuON5< zs@P5csQLD781VA$*6OQ;BuRPs^Djv@N%D_>EDS$5eq`y$Q__DJl5Yu^zy8X?!UF7HZO}Hcpr=r`XUr2Rw=RtxC^Zit)!?I}{U|)|+hu25Zfa`! zqmW94rwTi@xVZT3+c%$M@bt`Uo)n&*NKanDujtQ?i-^OQCh5EwhD|xXOuWx;Nz%?x zq2s)52kOs@(?|bL4*CZXch_d(pHm;s-4i-MOl>tQ(;bw@>q$S+C*L}H^k{-&3fe25 zHQlQ6^=a&+0wdL0pNQMmKb1r(bIQTN;c(_=-X}$RH7ZydT2DH@}fGY(SP#R|olo;u>lrt74Dp zfU1Y}et{JB`SWK$*8)O9c^`lNRP%v1zgO`7nlR1%7p^3IM@E@pYxDA|WGn(7jqvNT zS{$hXC*AXkPuvS#!%672j)A`eV)K!hi|tn)77Q0e{UC*$^EFrJZ`-Swcf_YhRBVx zbVU(MtE&T$jJ34-0n48}bLBf!8LNF5JnTJEQc?uXUF)$JkG)>t(fz70N6IoLCMh8y z7e|Nze&=>>Uc)cV3d=yeoQ_U0>ane@Er>I(2{3trsLj|1-WCJJpc33Qs6QXu4kaJ( z`jG2#3*fI%Hg&{bQO5;6BbKXS9Vwz*YZS7tzt~J74gx~&%#>Zu8i+qW$$&yk=O789 z+Un}+(hB%ZejI;$<>VOB)A!TbIi-V9Q1lNVg|+7`V^x2aN@xd$hDxFVGz4N2O}z#D zOCXPF#QXVC{6&B!i9hyCBQl4!ETe{5yr;K*z-1KQv-RnVjJtIUPp z;#~Yu{UPEe{4NAS-p1CStX}<(Ldtgnrwf|ufFYe%0NH3F7I11)DCdn%m>SgOz`($N z!-xGcr;b*h5rxa_%*@Q}Y!nWcAkXspEEKfV7AGC2@erey2<^|$hx@~wgub~{XO}kS2`?Xp+ zOgYx!jY}Nn?QLuT-u>hR%?kvZot+(+K~UywA$I{TNf|D;cvgw{+=5HLkvHU@4Vl3=;-L_UB z;586|J~B8WPy@g^Yh`c022koOgGf|h;A2ZmhV$pS9#_6z9IXS{0D+N_0c1#BQ3zrZ zRPQ^9Le-}%uRCH6l;Vx{BMZ1T^4fAw-wzq{eoP0Mo`eK@S{83vv3u!o(~!)Ny?49y zjf*HD?@Ybevi8=Sr6n#AN!ULy{|6UrZv>(*`|+`VlMzwj)yg;PIs5M|AdNb5ynOZQ zFwq>a5#ry$$K(l&OiYL2_isM(JVtu{ARm!@%DmTNbCX4s96l;4_^G`Ada~2mgXhVf z|9_NjN=kO!a=L>jc@|f;wPAsK#3Xxfk0Px2=t-)@ueL7I@5Ah02aW6>a0!)5laU%7uWg2snEZtwrBSE zaXWB|`g*Bz=K?^;0MWAWO)DA@*RKkXeL&ICB5r~#EK+|OVA0&5>U?C^M6@2xwWDWt zZtl#FMDn&}D=RBr+Y+~Jdw^#E+sw_)tKLu)tVv@q5kd}sL3;pg@y%CA#GfvYjE){o zJ(t`CvJ;d*Fdp>`1Fr?4kCx{tQ6Egt$S7DFc5;U#4)FZi9iua1rmqib9I?i%(Z7NG z8UD}zlj(%FJcELQ0Bu>*^^`bRX3)C>I0p>Ku3c;k%!xP37z7_cdERH~D<2-go_=x7 zX??)qU=-1(gQ6b}4sN-$1=p6s!^+CxroH$isA3XX;~R82jm}h?HDK;b=B1yTU$(wj4OY7K$yk~@W2zloCF|0Ch*sr!Cju^Lf5k* zeL?fS;|DM06#V3oIcTG1oH*0EZ`JVNyfTRx_u%)HNZ6w@GAkvr1 zk&9A6?5Il105MP{R}t+(6IY1en_W9g?OH7y(>d3zc5v0M((I zI$8}dj*bR2{0O>0Cg@HFD|!sjb-=;w+|wSOoMaBc%=dqJfQHPe5IOqP$O!BKkm`JV zK422@cs!Lay8kX8-^bOJg#{mfe<+-*ARq&FW(>h_Y8Nv`F*7l}dH4zCz9S$MiAF50 zuk-h2+fUt#7NVw%7Bb$s+*HOQw=WHA2F^#l0r&P+-ctsMN_X2AzC+hz+$8pQIm^~) z<_2US9jw&SR$Jw}INT5AsI?=H6#-d>gycoek4$>X_Cy(Ug8cWf2HZypwDKt5N4sOh ztSqmg@%G?dNVs@4ikJ|^Zjp_RaxqD1sv#|li;xkfy#H6dNo4(uT6}yQ@rsJ>yCFWw z4@j3*d24TZ5yKHe>EE21BvJLoz)S{hyKQgFL)re}I?wZeAOxJN8XL(DGnAg`b2{E< z81xctQELn{g6wQksLsoO4MePXM-m#hjpdFk>rW)Nnb#+IuPjgCTi1m9XOPl|n zUKAk-G>n)}!6I{Vc9uY+$HvBfPw+^W@2+Y7`?M5i48l~_!7s1xi?3s2XJ;os-9vFz zYk6vOc@nlN5WY#+>X4KZlakgzFZ=xY$~s{>$97urzac*{W-cnYfxtGdti2(0(n1mj z!Fedx5K?r6B$xybAj{vB`S_e}a%zewm4H;0rS*ksote1&GvonLG}H<p4%>H?WcbC}5tT%t==yz>_ z7(pj!Ti@ONh$)y}$bNQnhzA@=&}G7P_asNXQD>UVW87a&CnOfxugkKawRPW zxAHR~IM3p9{XaAqpbhl(Z&(;$d#~$Zj+2t!IDoM7-e9#so(k~cv*=HD;<<5S6p;6P z`F3WY)|}0j!m(hD5jk}0()M9B<#J=f;+vOupa24S>pc4m^jP2;P-HG?ToZQA2bTzD zrSIoY2w*^;=7Xi`0<57;y<;-IXl}=a-zzICuwcIi2BHgGcf?W{emU&#?N+uYC$spXbDz|Z;U|ZHl-i3GoWbQ-X-;6!_J$JL=h|_? ziLYKAD>1C`n?(v8m-Vf#Mjr&5v9mHO>m4%gl`Y-GI7L-e+H2STPE0&{^oY20RwW?T zlse@JNQcM73_+kk%z>hIgPD19ZSEHlm_oq^E2mCSDpAo(>|6Ku{t6Edz^9hr)xyVt z&QFewDOd3(Bqf0#Wd%zC7`v^lt-^X7bMM|)V&R0u-~IUOiz8%SBfzf51^&W1e{32j z^Ac-e2QBy_4mXKGch}Tv>qqjRhH?sS5%`Xup^Ex}pFdEn(!WtlYYC1i4tJs8PImhO zNODdS&Een`fYJUNz5$^1vmGk$Cb*`vR*xQa8^^dYl|BcK4{&F8arIA6D7z*T9XQdw z@O|$na3=Ul?k%!Co0p!>wzdI(2SDPEi`*= z_9rTuxVo0AJuO)0X8lRe4T1@NvZG_uf2GYiP9QFx^lUSr2H5oGebUb?uD|-$m9=@i zixMjFrAvi+-;Neey(i8i+_^RE_9(2zB^q!xwUw1|5fQ2OZ(hDU3e539M+)$`bPO2O zt%Cjb2A2ATJ&6jt%nsuumtL3Y8FPNi!82#hj0tcsF){J*;CRo{3+5}tOMnge1e=$e zTUuJ`!@|z)C#(mR+PE`W0SM2fOKS{N)4tha9E6&9X7bLIl(DB#p$7y|_0 zi4!M4Di&*l;_1FMO#qdPm;lX)3#+Q@wpy{jYYF#~aQBW9mZ=gN`tqP0jRqTg+%T%V zrup^$6V3dmlvGsKU?!@hJ<2K22B7vCm^m133w9?-NIvQO9{eioW>j20B<5`lu89*6 zUZP42w(A74qoV@^M=Aye&XvcTV#dbCIkAig7%l=MB_%Ip3?{wl=3jzbG$QWPr+;!{ zFSN3izj3bK-Nrr0Y-B!n?L%m&l@0+4z)Wt2S$Fzr3W{hLHbkbny!^tT%RIcB+sHm7^ra8xIkup zetvfLce#9h*}2Q#v+u-+dg%NKpC78wWtjJLSx$tW7s09p+!2B8Ys1(7*>Gyyd_x48eFkkyuUsy;yo38Zf_R6_7VpgcQGth<3E3e*z( zCXekoek&IZ4GrK5#7&t`)Pcr#UbeX5*Sl`9VbzU{XPpk2FUeFGgqPjl{_x37$VC}0 zlj)j8nXrB^y*hbD55a8%qX%w6l+VFb^O5=7sTD2pryWYe*47sB7cfc14)Z!7JcHE& z8qRqd8nB0yV`PTY7quFB z{q9{8q}D>11<+Gz@(7Ho`fW-PQf`ftq^rn3LLg>v;hz~C8GB_9-20xYgO4|

fB! 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 Date: Mon, 7 Dec 2015 20:04:00 -0500 Subject: [PATCH 34/61] POS computer screenshots --- ...on Techniques for Mechanical Systems.ipynb | 137 ++++++------------ randy_schur/figures/combined_response.png | Bin 0 -> 212034 bytes randy_schur/figures/error_combined.png | Bin 0 -> 94263 bytes 3 files changed, 46 insertions(+), 91 deletions(-) create mode 100644 randy_schur/figures/combined_response.png create mode 100644 randy_schur/figures/error_combined.png diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index 5997a83..f97971e 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -218,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -239,7 +239,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -259,7 +259,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -282,7 +282,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -308,7 +308,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -332,7 +332,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -360,7 +360,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 8, "metadata": { "collapsed": true }, @@ -379,7 +379,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -459,6 +459,7 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -789,13 +790,11 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -844,19 +843,6 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys (original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object')\n", - " obj[key] = original[key]\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -870,8 +856,7 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event)});\n", + " step: event.step});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -911,8 +896,7 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value,\n", - " guiEvent: simpleKeys(event)});\n", + " this.send_message(name, {key: value});\n", " return false;\n", "}\n", "\n", @@ -929,7 +913,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -988,8 +972,6 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", - " fig.root.unbind('remove')\n", - "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -997,12 +979,8 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.close_ws(fig, msg);\n", - "}\n", - "\n", - "mpl.figure.prototype.close_ws = function(fig, msg){\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", + " fig.send_message('closing', {});\n", + " fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -1057,20 +1035,14 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('

');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -1151,7 +1123,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1213,7 +1185,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 10, "metadata": { "collapsed": true }, @@ -1236,7 +1208,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 11, "metadata": { "collapsed": true }, @@ -1257,7 +1229,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -1337,6 +1309,7 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -1667,13 +1640,11 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -1722,19 +1693,6 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys (original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object')\n", - " obj[key] = original[key]\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -1748,8 +1706,7 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event)});\n", + " step: event.step});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -1789,8 +1746,7 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value,\n", - " guiEvent: simpleKeys(event)});\n", + " this.send_message(name, {key: value});\n", " return false;\n", "}\n", "\n", @@ -1807,7 +1763,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1866,8 +1822,6 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", - " fig.root.unbind('remove')\n", - "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -1875,12 +1829,8 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.close_ws(fig, msg);\n", - "}\n", - "\n", - "mpl.figure.prototype.close_ws = function(fig, msg){\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", + " fig.send_message('closing', {});\n", + " fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -1935,20 +1885,14 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -2029,7 +1973,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2056,7 +2000,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. But ordinarily we don't see a second order scheme (Verlet) outperforming a fourth order scheme (RK4) by three orders of magnitude. What is going on?\n", + "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. Don't believe that these are significant? Take a look at the following screenshot, which shows the plots above, built with identical code on two different computers. The responses on the left are from a 64-bit computer, and the responses on the right are from a 32-bit computer. Look especially at the phase shift in the system response. That's a huge difference! Even the precision of the machine can have a significant impact on numerical integration.\n", + "\n", + "![Image](figures/combined_response.png)\n", + "\n", + "![Image](figures/error_combined.png)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Despite the numerical errors, ordinarily we don't see a second order scheme (Verlet) outperforming a fourth order scheme (RK4) by three orders of magnitude. What is going on?\n", "\n", "One thing we notice in the plots of position is that for Euler and Runge-Kutta, the spring is stretched farther and farther each oscillation. This means it has more and more potential energy. With no outside forces adding energy to the system and no friction to remove energy, Conservation of Energy says that value should be constant! Let's take a look at total energy in the system. Since we derived equations of motion from the Lagrangian, this should be easy! All we need to do is calculate kinetic and potential energy at each time step, and we already have the information we need to do this. " ] @@ -5607,21 +5562,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.0" + "pygments_lexer": "ipython2", + "version": "2.7.11" } }, "nbformat": 4, diff --git a/randy_schur/figures/combined_response.png b/randy_schur/figures/combined_response.png new file mode 100644 index 0000000000000000000000000000000000000000..5befc1e97c740b9ae2a92cfb41eb03fd927dc84c GIT binary patch literal 212034 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

P-kdQL{s!kE20qU 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�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 literal 0 HcmV?d00001 diff --git a/randy_schur/figures/error_combined.png b/randy_schur/figures/error_combined.png new file mode 100644 index 0000000000000000000000000000000000000000..c78925f55ef1169c352cddbddcb1150fdb752bfb GIT binary patch literal 94263 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

KBv0ke_Mq#6kHW}F74W}2 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

)Wsf~N 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 WA|n$D&m3TJ!57XHZ&HFi6a7D!;kYmW literal 0 HcmV?d00001 From 7052c2cdb01cd0f1b203fd8bb456c4f85743c76a Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 7 Dec 2015 22:45:32 -0500 Subject: [PATCH 35/61] start fixing stupid derivation mistakes --- randy_schur/Double_Pendulum_Problem.ipynb | 4652 ++++++++++++++++- ...on Techniques for Mechanical Systems.ipynb | 512 +- 2 files changed, 4884 insertions(+), 280 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index b1c0004..2f8e1d9 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this section, we will study the dynamics of a double pendulum. Like before, we'll derive the equations of motion from the Lagrangian and the Hamiltonian. We can use the same methods of integration as before, but this time there is no analytical solution to compare to. Finally, we will look at the properties of the system in phase space using phase plots and Poincare sections." + "In this notebook, we will study the dynamics of a double pendulum. Like before, we'll derive the equations of motion from the Lagrangian and the Hamiltonian. We can use the same methods of integration as before, but this time there is no analytical solution to compare to. Finally, we will look at the properties of the system in phase space using Poincare sections." ] }, { @@ -20,7 +20,7 @@ "source": [ "## Background\n", "\n", - "The double pendulum problem is a relatively simple system which can produce surprisingly complex movements. It is a chaotic system, meaning it is unpredictable and small changes in initial conditions lead to large changes in motion. There is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", + "The double pendulum problem is a relatively simple system which can produce surprisingly complex motion. It is a chaotic system, meaning it is unpredictable and small changes in initial conditions lead to large changes in motion. There is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", "\n", "In this notebook we will be treating the two rods as massless and we'll start out ignoring the effects of friction. For a treatment of this problem without these assumptions, see (cite).\n", "\n", @@ -80,7 +80,7 @@ "\\end{equation*}$$\n", "\n", "\n", - "There are two generalized coordinates ($\\theta_1 and \\theta_2$. So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", + "There are two generalized coordinates ($\\theta_1$ and $\\theta_2$). So the equation of motion for $\\theta_1$ is calculated by the following steps:\n", "\n", "$$\\begin{eqnarray*}\n", "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} &=& (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1) \\\\\n", @@ -107,13 +107,13 @@ "source": [ "## Problem setup\n", "\n", - "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. We have talked about implicit equations in this class, and we solve them by forming a system of equations. In this case, we will need to solve the equations of motion at each time step. We will characterize our system in state space using the angle and angular velocity of each pendulum. Let's rewrite the equations of motion in a more readable form. We can use the standard form of this equation (which you'll be familiar with if you have studied mechanics) by separating out mass terms (anything multiplied by $\\ddot{x}$), centripetal terms (anything multiplied by $\\dot{x}$), and gravitational terms (anything multiplied by $x$). We get the following equations of state:\n", + "Notice that these equations of motion are implicit equations for $\\ddot\\theta_1$ and $\\ddot\\theta_2$. Let's rewrite the equations of motion in a more readable form. We can use the standard form of this equation (which you'll be familiar with if you have studied any dynamics) by separating out mass terms (anything multiplied by $\\ddot{x}$), centripetal terms (anything multiplied by $\\dot{x}$), and gravitational terms (anything multiplied by $x$). We get the following equation of motion:\n", "\n", "$$\\begin{eqnarray*}\n", "0 &=&\\textbf{M}\\vec{\\ddot{x}}(t) + \\textbf{V}\\dot{x} + \\textbf{G} \n", "\\end{eqnarray*}$$\n", "\n", - "The equations of motion can be solved for $\\ddot{x}$:\n", + "The equation of motion can be solved for $\\ddot{x}$:\n", "\n", "$$ \\ddot{x} = \\textbf{M}^{-1}\\left[ \\textbf{V}\\dot x + \\textbf{G} \\right]$$\n", "\n", @@ -148,7 +148,7 @@ "and\n", "\n", "$$\\begin{eqnarray*}\n", - "\\vec{x}(t) = \\begin{pmatrix} \\theta_1\\\\ \\theta_2\\\\ \\end{pmatrix}, \\vec{\\dot{x}(t)} = \\begin{pmatrix} \\dot \\theta_1 \\\\ \\dot \\theta_2 \\end{pmatrix}\n", + "\\vec{x}(t) = \\begin{bmatrix} \\theta_1\\\\ \\theta_2\\\\ \\end{bmatrix}, \\vec{\\dot{x}(t)} = \\begin{bmatrix} \\dot \\theta_1 \\\\ \\dot \\theta_2 \\end{bmatrix}\n", "\\end{eqnarray*}$$\n", "\n", "We can now treat this system the same way we handled the simple harmonic motion; break the two second order ODEs into four first-order ODEs, then use Euler or Runge-Kutta on the results." @@ -167,23 +167,22 @@ " &=& -m_1g(l_1cos\\theta_1 - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", "\\end{eqnarray*}$$\n", "\n", - "Kinetic Energy is calculated as:\n", + "Kinetic Energy for rotational motion calculated as:\n", "\n", "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}mv_1^2+\\frac{1}{2}mv_2^2 \\\\\n", - "&=& \\frac{q_1^2}{2I_1} + \\frac{q_2^2}{2I_2}\n", + "T &=& \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\n", "\\end{eqnarray*}$$\n", "\n", - "where $I_i = m_il_i^2$. So our Hamiltonian quantity becomes:\n", + "where $I_i$ is the angular moment of inertia and $\\omega$ is the angular velocity. So our Hamiltonian quantity becomes:\n", "$$\\begin{equation*}\n", "H = \\frac{1}{2}\\left(\\frac{q_1^2}{I_1} + \\frac{q_2^2}{I_2}\\right) - (m_1+m_2)gl_1cos\\theta_1 - m_2gl_2cos\\theta_2\n", "\\end{equation*}$$\n", "\n", "Since we have two degrees of freedom, we will have four equations of motion. These will be the following:\n", "$$\\begin{eqnarray*}\n", - "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = \\frac{q_1}{I_1} \\\\\n", + "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = -\\frac{q_1}{I_1} \\\\\n", "\\dot{q_1} &=& \\frac{\\partial H}{\\partial p_1} = (m_1+m_2)gl_1sin(p_1) \\\\\n", - "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = \\frac{q_2}{I_2} \\\\\n", + "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = -\\frac{q_2}{I_2} \\\\\n", "\\dot{q_2} &=& \\frac{\\partial H}{\\partial p_2} = m_2gl_2sin(p_2) \\\\\n", "\\end{eqnarray*}$$\n" ] @@ -203,6 +202,36 @@ "metadata": { "collapsed": false }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/randy/anaconda3/lib/python3.4/site-packages/IPython/kernel/__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", + " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" + ] + } + ], + "source": [ + "import numpy\n", + "from scipy.linalg import solve\n", + "from numpy.linalg import det\n", + "from math import pi, cos, sin, sqrt\n", + "\n", + "from matplotlib import pyplot\n", + "from matplotlib.pyplot import quiver\n", + "%matplotlib notebook\n", + "from matplotlib import rcParams, cm\n", + "rcParams['font.family'] = 'serif'\n", + "rcParams['font.size'] = 16" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "def f_double_pendulum(u):\n", @@ -235,23 +264,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { "collapsed": false }, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'numpy' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[0mdt\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m.02\u001b[0m\u001b[1;33m;\u001b[0m \u001b[1;31m#\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 12\u001b[0m \u001b[0mN\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mT\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0mdt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 13\u001b[1;33m \u001b[0mt\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnumpy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlinspace\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0.0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mT\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mN\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 14\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 15\u001b[0m \u001b[1;31m#Initial Conditions\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mNameError\u001b[0m: name 'numpy' is not defined" - ] - } - ], + "outputs": [], "source": [ "#Set up parameters:\n", "g = -9.8 #[m/s^2]\n", @@ -285,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -310,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -336,11 +353,1508 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('

');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "#Euler\n", "q1_dp = numpy.zeros((N,4)) \n", @@ -387,7 +1901,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -407,24 +1921,780 @@ " p2 = u[2]\n", " q2 = u[3]\n", " \n", - " p1_half = p1 - dt/2*(q1/I1)\n", - " q1 = q1 + dt*((m1+m2)*g*l1*sin(p1_half))\n", - " p1 = p1_half - dt/2*(q1/I1)\n", + " p1_half = p1 + dt/2*(q1/I1)\n", + " q1 = q1 - dt*((m1+m2)*g*l1*sin(p1_half))\n", + " p1 = p1_half + dt/2*(q1/I1)\n", " \n", - " p2_half = p2 - dt/2*(q2/I2)\n", - " q2 = q2 + dt*m2*g*l2*sin(p2_half)\n", - " p2 = p2_half - dt/2*(q2/I2)\n", + " p2_half = p2 + dt/2*(q2/I2)\n", + " q2 = q2 - dt*m2*g*l2*sin(p2_half)\n", + " p2 = p2_half + dt/2*(q2/I2)\n", " \n", " return numpy.array([p1, q1, p2, q2]) " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 1. 0. 3.14159265 0. ]\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "v = poincare(q2d_dp, 0, 0, 2, 3) #use states from RK4 integration\n", - "\n", + "print(numpy.shape(v))\n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r'$\\theta_2$', fontsize=18);\n", - "pyplot.ylabel(r'$\\dot{\\theta_2}$', fontsize=18);\n", - "pyplot.title('Poincare Section');\n", + "pyplot.ylabel(r'$\\dot{\\theta_2}$', fontsize=18);#pyplot.title('Poincare Section');\n", "pyplot.plot(v[0,:], v[1,:], 'bo', lw=2, );\n", - "#pyplot.plot(t, q2d_dp[:,1], lw=2, label='Joint 2')\n", - "#pyplot.legend();\n" + "\n", + "from matplotlib import animation\n", + "from JSAnimation.IPython_display import display_animation\n", + "def animate(data):\n", + " print(numpy.shape(data))\n", + " x = data[0,:]\n", + " y = data[1,:]\n", + " line.set_data(x, y)\n", + " return line," + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# #(try to animate plot)\n", + "#fig = pyplot.figure();\n", + "#ax = pyplot.axes(xlim=(-10, 10), ylim=(-10,10),xlabel=(r'$\\theta_2$'), ylabel=(r'$\\dot{\\theta_2}$'))\n", + "#line, = ax.plot([],[],'bo', lw=2);\n", + "\n", + "#anim = animation.FuncAnimation(fig, animate, frames= v[:,:], interval=10)\n", + "#display_animation(anim, default_mode='once')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "(try to animate plot)\n", - "\n", - "Other than a few outliers, the plot converges to 0,0. So, this system is stable! This would not be the case for the undamped double pendulum, as integrated by RK4. " + "Other than a few outliers, the plot converges to (0,0). So, this system is stable! This would not be the case for the undamped double pendulum, as integrated by RK4. " ] }, { @@ -695,7 +5239,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.0" + "version": "3.4.3" } }, "nbformat": 4, diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index f97971e..dec9b79 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -6,7 +6,7 @@ "source": [ "# Integration of Mechanical Systems\n", "\n", - "In this module, we will be exploring numerical integrators and how they perform for different mechanical systems." + "In this module, we will be exploring numerical integrators and how they perform on different mechanical systems." ] }, { @@ -32,7 +32,7 @@ "0 = m\\ddot{x} + kx\n", "\\end{equation}$$\n", "\n", - "We can describe the state of the system by its position $x$ and its velocity $\\dot{x}$. We will call this state vector $q$, as:\n", + "Since this is a second order system, we will need two states to accurately describe the system. We can use position $x$ and velocity $\\dot{x}$. This is called the state vector $q$, as:\n", "\n", "$$\\begin{eqnarray}\n", "\\vec{q} = \\begin{bmatrix} x\\\\ \\dot{x} \\end{bmatrix}\n", @@ -44,7 +44,7 @@ "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{k}{m}q_1 \\end{bmatrix}\n", "\\end{eqnarray}$$\n", "\n", - "To look at the behavior of this system, we can start with some initial conditions for $q$, then use a numerical integration method such as Euler's method to integrate forward in time. " + "To look at the behavior of this system, we can start with some initial conditions for $q$, then use a numerical integration method such as Euler's method to integrate forward in time. This is equivalent to pulling (or pushing) on the spring, then letting go at t=0." ] }, { @@ -53,7 +53,7 @@ "source": [ "### Equations of Motion from Lagrangian\n", "\n", - "You are encouraged to follow this derivation ON PAPER on your own. We gave you the equations of motion for a simple harmonic oscillator, but what if we don't have this equation? The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", + "You are encouraged to follow this derivation ON PAPER on your own. The equations of motion are given above for a simple harmonic oscillator, but what if we didn't have this equation ahead of time? The following is a method that will work in nearly any situation to find equations of motion, often working well where a force analysis becomes difficult. This is especially good practice for anyone studying mechanics. To derive the equations of motion, we will start with the Lagrangian. This is defined by:\n", "\n", "$$\\begin{equation}\n", "L = T- U\n", @@ -69,13 +69,13 @@ "\n", "$$\\begin{eqnarray*}\n", "T &=& \\frac{1}{2}mv^2 \\\\\n", - "&=& \\frac{1}{2}m(\\dot x)^2 \n", + "&=& \\frac{1}{2}m\\dot{x}^2 \n", "\\end{eqnarray*}$$\n", "\n", "\n", "So the Lagrangian quantity becomes:\n", "$$\\begin{equation}\n", - "L = \\frac{1}{2}m(\\dot x)^2 - \\frac{1}{2}kx^2\\\\\n", + "L = \\frac{1}{2}m\\dot{x}^2 - \\frac{1}{2}kx^2\\\\\n", "\\end{equation}$$\n", "\n", "\n" @@ -128,10 +128,10 @@ "source": [ "### Runge-Kutta Integration (RK4)\n", "\n", - "Depending on the application, Euler's method may provide enough accuracy with a small timestep, especially a simple harmonic oscillator. However, Euler is a first-order method and we can do better. One option is a Runge-Kutta scheme. This is a popular numerical integration method which is easily extended to higher orders. Here we will use a fourth order Runge-Kutta, also known as RK4:\n", + "Depending on the application, Euler's method may provide enough accuracy with a small timestep, especially a simple harmonic oscillator. However, Euler is a first-order method and we can do better. One option is a Runge-Kutta scheme. This is a popular numerical integration method which is easily extended to higher orders. Here we will use a fourth order Runge-Kutta, also called RK4:\n", "\n", "$$\\begin{equation}\n", - "q_{n+1} = q_n + \\frac{h}{6}\\left(k_1+2k_2+2k_3+k_4\\right)\n", + "q_i^{n+1} = q_i^n + \\frac{h}{6}\\left(k_1+2k_2+2k_3+k_4\\right)\n", "\\end{equation}$$\n", "where\n", "$$\\begin{eqnarray}\n", @@ -151,17 +151,21 @@ "source": [ "### Symplectic Integrators\n", "\n", - "Symplectic integrators are similar to the methods discussed above, but they use equations of motion derived from Hamiltonian mechanics. According to [Berkeley source], syplectic integrators preserve the conserved Hamiltonian quantities. In practical terms, this works out to mean the methods reflect conservation of momentum, down to a truncation error.\n", + "Symplectic integrators are similar to the methods discussed above, but they use equations of motion derived from Hamiltonian mechanics. According to [5], syplectic integrators preserve the conserved Hamiltonian quantities. In practical terms, this works out to mean the methods reflect conservation of energy, down to a truncation error.\n", "\n", "The Hamiltonian equations of motion can be derived from the total energy in the system, much like the Lagrangian. The coordinates we use are position ($q$) and momentum ($p = m\\dot x$). The Hamiltonian is:\n", "$$ H = T+V$$\n", "where $T$ is the kinetic energy in the system, and $V$ is the potential energy in the system. So, we get:\n", "$$\\begin{eqnarray*}\n", "T &=& \\frac{1}{2}m\\dot{x}^2 = \\frac{q^2}{2m}\\\\\n", - "V &=& \\frac{1}{2}k{x}^2 = \\frac{kp^2}{2} \\\\\n", - "H &=& \\frac{q^2}{2m} + \\frac{kp^2}{2}\n", + "V &=& \\frac{1}{2}k{x}^2 = \\frac{kp^2}{2} \n", "\\end{eqnarray*}$$\n", "\n", + "and \n", + "\n", + "$$H = \\frac{q^2}{2m} + \\frac{kp^2}{2}$$\n", + "\n", + "\n", "\n", "The Hamilton's equations are:\n", "$$\\begin{eqnarray*}\n", @@ -179,14 +183,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's now take a look at symplectic integration methods. We'll start with a first order method - symplectic Euler's method. This uses the equations:\n", + "Let's now take a look at symplectic integration methods [7]. We'll start with a first order method - symplectic Euler's method. This uses the equations:\n", "\n", "$$\\begin{eqnarray*}\n", "p_{n+1} = p_n - h \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1},q_n}\\\\\n", "q_{n+1} = q_n + \\frac{h}{2} \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1},q_n}\n", "\\end{eqnarray*}$$\n", "\n", - "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method, $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations:\n", + "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method (we'll investigate this later), $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations [6]:\n", "\n", "$$\\begin{eqnarray*}\n", "p_{n+1/2} &=& p_n - \\frac{h}{2} \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1/2},q_n}\\\\\n", @@ -222,7 +226,16 @@ "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/randy/anaconda3/lib/python3.4/site-packages/IPython/kernel/__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", + " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" + ] + } + ], "source": [ "import numpy\n", "from scipy.linalg import solve\n", @@ -239,27 +252,25 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 8, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ - "#Set up parameters:\n", - "\n", + "#Set up system parameters:\n", "m = 12\n", - "k = 150;\n", - "#g = 9.8 #[m/s**2]\n", + "k = 150\n", "\n", "T = 25; #[seconds]\n", - "dt = .02; #\n", - "N = int(T/dt)+1\n", - "t = numpy.linspace(0.0, T, N)" + "dt = .02; #time step size\n", + "N = int(T/dt)+1 #number of time steps\n", + "t = numpy.linspace(0.0, T, N) #array of time values" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -282,7 +293,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 10, "metadata": { "collapsed": true }, @@ -296,7 +307,8 @@ " f - function for RHS of state equations\n", " dt - time step\n", " \n", - " Returns: array of state values at next time step.\n", + " Returns: \n", + " state values at next time step.\n", " \"\"\"\n", " k1 = f(u)\n", " k2 = f(u) + 0.5*dt*k1\n", @@ -308,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "metadata": { "collapsed": true }, @@ -318,10 +330,10 @@ " \"\"\"Returns RHS of harmonic oscillator EOM\n", " \n", " Parameters:\n", - " q - initial state\n", + " u - initial state\n", " \n", " Returns:\n", - " f - RHS of harmonic oscillator eqn.\n", + " RHS of harmonic oscillator eqn.\n", " \n", " \"\"\"\n", " pos = u[0]\n", @@ -332,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 12, "metadata": { "collapsed": true }, @@ -344,7 +356,8 @@ " Parameters:\n", " u - state at current time step\n", " dt - time step size\n", - " Returns: state at next time step.\n", + " Returns: \n", + " state at next time step.\n", " \"\"\"\n", " pos = u[0]\n", " mom = u[1]\n", @@ -360,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 13, "metadata": { "collapsed": true }, @@ -370,6 +383,7 @@ "x0 = 0.5 #[m]\n", "xdot0 = 0 #[m/s]\n", "\n", + "#Equivalent initial conditions for the Hamiltonian system\n", "p0 = x0\n", "q0 = m*xdot0\n", "\n", @@ -379,7 +393,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -1123,7 +1137,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1134,20 +1148,20 @@ } ], "source": [ - "#Euler\n", - "q1 = numpy.zeros((N,2)) \n", + "#Euler Integration\n", + "q1 = numpy.zeros((N,2)) #initialize array\n", "q1[0] = x_init.copy() #set initial conditions\n", "for n in range(N-1): #integrate with Euler\n", " q1[n+1] = euler(q1[n], f_harmonic_oscillator, dt)\n", " #print(q1[n])\n", " \n", - "#Runge-Kutta \n", + "#Runge-Kutta Integration\n", "q2 = numpy.zeros((N,2))\n", "q2[0] = x_init.copy()\n", "for n in range(N-1):\n", " q2[n+1] = RK4(q2[n], f_harmonic_oscillator, dt)\n", "\n", - "#Symplectic\n", + "#Symplectic Integration\n", "q3 = numpy.zeros((N,2))\n", "q3[0] = x_init_H.copy()\n", "for n in range(N-1):\n", @@ -1177,7 +1191,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If we look closely at the plot, we can see that Euler and Runge-Kutta integration are close to each other, but begin to diverge as time goes on. This should be the behavior we expect for the integration of a non-linear system. But how close are these methods to the analytical solution? Does Verlet do any better? In this case it is obvious that Euler and Runge-Kutta are not correct (and possible unstable). Let's do an error analysis.\n", + "If we look closely at the plot, we can see that Euler and Runge-Kutta integration are close to each other, but begin to diverge as time goes on. This should be the behavior we expect for the integration of a non-linear system. But how close are these methods to the analytical solution? Does Verlet do any better? In this case it is obvious that Euler and Runge-Kutta are not correct (and possibly unstable). Let's do an error analysis.\n", "\n", "### Error Analysis\n", "The error analysis code below is based on the numerical methods MOOC notebook on phugoid oscillation. " @@ -1185,7 +1199,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -1208,7 +1222,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 16, "metadata": { "collapsed": true }, @@ -1229,7 +1243,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -1973,7 +1987,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2000,7 +2014,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. Don't believe that these are significant? Take a look at the following screenshot, which shows the plots above, built with identical code on two different computers. The responses on the left are from a 64-bit computer, and the responses on the right are from a 32-bit computer. Look especially at the phase shift in the system response. That's a huge difference! Even the precision of the machine can have a significant impact on numerical integration.\n", + "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. Don't believe that these are significant? Take a look at the following screenshot, which shows the plots above, built with identical code on two different computers. The responses on the left are from a 64-bit computer, and the responses on the right are from a 32-bit computer. Look closely at the phase shift in the system response. That's a huge difference! Even the precision of the machine can have a significant impact on numerical integration.\n", "\n", "![Image](figures/combined_response.png)\n", "\n", @@ -2018,7 +2032,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 18, "metadata": { "collapsed": true }, @@ -2041,7 +2055,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -2076,7 +2090,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -2156,6 +2170,7 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -2486,13 +2501,11 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -2541,19 +2554,6 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys (original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object')\n", - " obj[key] = original[key]\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -2567,8 +2567,7 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event)});\n", + " step: event.step});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -2608,8 +2607,7 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value,\n", - " guiEvent: simpleKeys(event)});\n", + " this.send_message(name, {key: value});\n", " return false;\n", "}\n", "\n", @@ -2626,7 +2624,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -2685,8 +2683,6 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", - " fig.root.unbind('remove')\n", - "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -2694,12 +2690,8 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.close_ws(fig, msg);\n", - "}\n", - "\n", - "mpl.figure.prototype.close_ws = function(fig, msg){\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", + " fig.send_message('closing', {});\n", + " fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -2754,20 +2746,14 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -2848,7 +2834,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2875,7 +2861,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It looks like Euler and Runge-Kutta integration are adding a significant amount of energy to the system. Our symplectic integrator is more accurate because it seems to preserve energy. Maybe all of that work deriving the equations of motion was worth it! We now know that the Hamiltonian equations do better with preserved quantities, and this can make a big difference even in a simple system." + "It looks like Euler and Runge-Kutta integration are adding a significant amount of energy to the system. Our symplectic integrator is more accurate because it seems to preserve energy. Maybe all of that work deriving the equations of motion was worth it! We now know that the Hamiltonian equations do better with Conservation of Energy, and this can make a big difference even in a simple system." ] }, { @@ -2884,7 +2870,7 @@ "source": [ "## Adding a damping term\n", "\n", - "A more realistic oscillator also has a damping term. This might correspond to friction, or to some other non-conservative force. For now, we will assume that the damping is linear, and is proportional to the velocity (this is called the -- model of friction). We get the following equation of motion:\n", + "A more realistic oscillator also has a damping term. This might correspond to friction, or to some other non-conservative force. For now, we will assume that the damping is linear, and is proportional to the velocity (this is called the viscous damping model of friction). We get the following equation of motion:\n", "\n", "$$\\begin{equation*}\n", "0 = m\\ddot{x} + c\\dot{x} + kx\n", @@ -2895,7 +2881,21 @@ "\\vec{\\dot{q}} = f(\\vec{q}) = \\begin{bmatrix} q_2\\\\ -\\frac{c}{m}\\dot{q_1} -\\frac{k}{m}q_1 \\end{bmatrix}\n", "\\end{eqnarray*}$$\n", "\n", - "The Euler's method and Runge-Kutta integration work the same way with this new equation. We also have a new analytical solution. Assuming the system is underdamped (add explanation of damping ratio), the solution becomes:\n", + "We can define a damping ratio, $\\zeta = \\frac{c}{2m\\omega_n} \\geq 0$, to express how large the damping is relative to the other system parameters. This differential equation has several types of solutions, depending on the damping ratio. These lead to a few different behaviors:\n", + "\n", + "1. Undamped $\\zeta =0$ - This is simple harmonic motion, which we have already examined\n", + "2. Underdamped $\\zeta < 1$ - The system will oscillate, but will lose energy over time. We will assume this case for the rest of this notebook, since it leads to the most interesting results.\n", + "3. Critically damped $\\zeta = 1$ - A special case which will lead to no oscillation (the system may overshoot its equilibrium position one time, but it won't oscillate) \n", + "4. Overdamped $\\zeta>1$ - This system will lead to no oscillation\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Euler's method and Runge-Kutta integration work the same way as in simple harmonic motion. We also have a new analytical solution. Assuming the system is underdamped (which we will do from here on out), the solution becomes:\n", "\n", "\n", "$$\\begin{equation}\n", @@ -2921,12 +2921,12 @@ "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q} + Q\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -\\frac{q}{m} - \\frac{c}{m}q \\\\ kp \\end{bmatrix}\n", "\\end{equation*}$$\n", "\n", - "From here, the we can use the same Verlet integration method as before." + "From here, the we can use the same Verlet integration method as before. If this explanation has too much hand-waving for you, please see [7]." ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 21, "metadata": { "collapsed": true }, @@ -2950,7 +2950,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 22, "metadata": { "collapsed": true }, @@ -2979,7 +2979,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 23, "metadata": { "collapsed": true }, @@ -3010,7 +3010,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 24, "metadata": { "collapsed": false }, @@ -3090,6 +3090,7 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -3420,13 +3421,11 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -3475,19 +3474,6 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys (original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object')\n", - " obj[key] = original[key]\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -3501,8 +3487,7 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event)});\n", + " step: event.step});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -3542,8 +3527,7 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value,\n", - " guiEvent: simpleKeys(event)});\n", + " this.send_message(name, {key: value});\n", " return false;\n", "}\n", "\n", @@ -3560,7 +3544,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -3619,8 +3603,6 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", - " fig.root.unbind('remove')\n", - "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -3628,12 +3610,8 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.close_ws(fig, msg);\n", - "}\n", - "\n", - "mpl.figure.prototype.close_ws = function(fig, msg){\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", + " fig.send_message('closing', {});\n", + " fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -3688,20 +3666,14 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -3782,7 +3754,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3834,7 +3806,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "How does each integrator perform compared to the case of simple harmonic motion? From the eye test, it seems like the error is actually better here. Why is that? \n", + "How does each integrator perform compared to the case of simple harmonic motion? Just by looking at the system response, it seems like the error is actually better here. Why is that? \n", "\n", "The effect of the damper is to remove energy from the system. So, in this case the damping term is helping to remove some energy from the system that the integration adds. As time goes to infinity, these solutions will converge, and the Euler and Runge-Kutta methods are stable. We can again look at the energy in the system according to each method. \n", "\n" @@ -3842,7 +3814,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 25, "metadata": { "collapsed": false }, @@ -3922,6 +3894,7 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -4252,13 +4225,11 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -4307,19 +4278,6 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys (original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object')\n", - " obj[key] = original[key]\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -4333,8 +4291,7 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event)});\n", + " step: event.step});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -4374,8 +4331,7 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value,\n", - " guiEvent: simpleKeys(event)});\n", + " this.send_message(name, {key: value});\n", " return false;\n", "}\n", "\n", @@ -4392,7 +4348,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4451,8 +4407,6 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", - " fig.root.unbind('remove')\n", - "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -4460,12 +4414,8 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.close_ws(fig, msg);\n", - "}\n", - "\n", - "mpl.figure.prototype.close_ws = function(fig, msg){\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", + " fig.send_message('closing', {});\n", + " fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -4520,20 +4470,14 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -4614,7 +4558,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4677,7 +4621,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 29, "metadata": { "collapsed": false }, @@ -4757,6 +4701,7 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -5087,13 +5032,11 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -5142,19 +5085,6 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys (original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object')\n", - " obj[key] = original[key]\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -5168,8 +5098,7 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event)});\n", + " step: event.step});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -5209,8 +5138,7 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value,\n", - " guiEvent: simpleKeys(event)});\n", + " this.send_message(name, {key: value});\n", " return false;\n", "}\n", "\n", @@ -5227,7 +5155,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -5286,8 +5214,6 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", - " fig.root.unbind('remove')\n", - "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -5295,12 +5221,8 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.close_ws(fig, msg);\n", - "}\n", - "\n", - "mpl.figure.prototype.close_ws = function(fig, msg){\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", + " fig.send_message('closing', {});\n", + " fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -5355,20 +5277,14 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -5449,7 +5365,7 @@ { "data": { "text/html": [ - "" + "
" ], "text/plain": [ "" @@ -5487,25 +5403,14 @@ "pyplot.xlabel('Postion ($x_1$)')\n", "pyplot.ylabel('Velocity ($x_2$)')\n", "pyplot.legend();\n", - "pyplot.title('Phase portrait for damped harmonic oscillator');\n" + "pyplot.title('Phase portrait for damped harmonic oscillator');" ] }, { - "cell_type": "code", - "execution_count": 21, + "cell_type": "markdown", "metadata": { "collapsed": false }, - "outputs": [ - { - "ename": "SyntaxError", - "evalue": "invalid syntax (, line 1)", - "output_type": "error", - "traceback": [ - "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m1\u001b[0m\n\u001b[1;33m Sources:\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" - ] - } - ], "source": [ "Sources: \n", "\n", @@ -5527,11 +5432,166 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# This cell loads the style of the notebook, which is modified from the \n", "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", @@ -5562,21 +5622,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.4.3" } }, "nbformat": 4, From 43c1db174679ab5616d94409ca7baf5b09f2f075 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 7 Dec 2015 23:15:08 -0500 Subject: [PATCH 36/61] fix Hamiltonian EOM --- randy_schur/Double_Pendulum_Problem.ipynb | 73 ++++++++++++----------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 2f8e1d9..4b86a69 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -22,7 +22,7 @@ "\n", "The double pendulum problem is a relatively simple system which can produce surprisingly complex motion. It is a chaotic system, meaning it is unpredictable and small changes in initial conditions lead to large changes in motion. There is no closed form solution to the motion of the two masses. This is a frequent example problem in the study of dynamic systems, nonlinear controls, and mechanics.\n", "\n", - "In this notebook we will be treating the two rods as massless and we'll start out ignoring the effects of friction. For a treatment of this problem without these assumptions, see (cite).\n", + "In this notebook we will be treating the two rods as massless and we'll start out ignoring the effects of friction. For a treatment of this problem without these assumptions, see [4].\n", "\n", "See the image below for the definition of constants." ] @@ -160,31 +160,45 @@ "source": [ "### Equations of Motion from Hamiltonian\n", "\n", - "Here we derive the equations of motion from the Hamiltonian. If you want good practice, you can derive these on your own. We start with the same potential and kinetic energy, but we'll need to use postion ($\\theta$) and momentum ($I\\omega$) to give us:\n", + "Here we derive the equations of motion from the Hamiltonian. If you want good practice, you can derive these on your own. If you want to see another derivation with nice animations, you can see [1]. We start with the same potential and kinetic energy as before, but we'll need to use postion ($\\theta$) and momentum ($I\\omega$) to give us:\n", "\n", "$$\\begin{eqnarray*}\n", "U &=& m_1g(-y_1) + m_2g(-y_2) \\\\\n", - " &=& -m_1g(l_1cos\\theta_1 - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2)\n", + " &=& -m_1gl_1cos\\theta_1 - m_2g(l_1cos\\theta_1 + l_2cos\\theta_2) \\\\\n", + " &=& -m_1gl_1cos(p_1) - m_2g(l_1cos(p_1) + l_2cos(p_2))\n", "\\end{eqnarray*}$$\n", "\n", "Kinetic Energy for rotational motion calculated as:\n", "\n", "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\n", + "T &=& \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\\\\\n", + "&=& \\frac{1}{2}(I_1 + m_2l_1^2)\\omega_1^2+\\left[m_2l_1l_2\\omega_1\\omega_2cos(\\theta_2-\\theta_1)+\\frac{I_2\\omega_2^2}{2}\\right] \\\\\n", + "&=& \\frac{q_1^2}{2I_1}+\\frac{m_2l_1^2q_1^2}{2m_1I_1} + \\left[ \\frac{q_2}{l_2} \\frac{q_1}{m_1l_1}cos(p_2-p_1) + \\frac{q_2^2}{2I_2} \\right]\n", "\\end{eqnarray*}$$\n", - "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "where $I_i$ is the angular moment of inertia and $\\omega$ is the angular velocity. So our Hamiltonian quantity becomes:\n", "$$\\begin{equation*}\n", - "H = \\frac{1}{2}\\left(\\frac{q_1^2}{I_1} + \\frac{q_2^2}{I_2}\\right) - (m_1+m_2)gl_1cos\\theta_1 - m_2gl_2cos\\theta_2\n", - "\\end{equation*}$$\n", - "\n", + "H = \\frac{q_1^2}{2I_1}+\\frac{m_2l_1^2q_1^2}{2m_1I_1} + \\left[ \\frac{q_2}{l_2} \\frac{q_1}{m_1l_1}cos(p_2-p_1) + \\frac{q_2^2}{2I_2} \\right] -m_1gl_1cos(p_1) - m_2g\\left( l_1cos(p_1) + l_2cos(p_2) \\right)\n", + "\\end{equation*}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Since we have two degrees of freedom, we will have four equations of motion. These will be the following:\n", "$$\\begin{eqnarray*}\n", - "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = -\\frac{q_1}{I_1} \\\\\n", - "\\dot{q_1} &=& \\frac{\\partial H}{\\partial p_1} = (m_1+m_2)gl_1sin(p_1) \\\\\n", - "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = -\\frac{q_2}{I_2} \\\\\n", - "\\dot{q_2} &=& \\frac{\\partial H}{\\partial p_2} = m_2gl_2sin(p_2) \\\\\n", - "\\end{eqnarray*}$$\n" + "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = -\\frac{q_1}{I_1} - \\frac{m_2l_1^2q_1}{m_1I_1} - \\frac{q_1}{l_2m_1l_1}cos(p_2-p_1) \\\\\n", + "\\dot{q_1} &=& \\frac{\\partial H}{\\partial p_1} = \\frac{q_1q_2}{m_1l_1l_2}sin(p_2-p_1) + (m_1+m_2)gl_1sin(p_1) \\\\\n", + "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = -\\frac{q_1}{m_1l_1l_2}cos(p_2-p_1) - \\frac{q_2}{2I_2} \\\\\n", + "\\dot{q_2} &=& \\frac{\\partial H}{\\partial p_2} = -\\frac{q_1q_2}{m_1l_1l_2}sin(p_2-p_1) + m_2l_2gsin(p_2)\\\\\n", + "\\end{eqnarray*}$$" ] }, { @@ -198,20 +212,11 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 19, "metadata": { "collapsed": false }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/randy/anaconda3/lib/python3.4/site-packages/IPython/kernel/__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", - " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" - ] - } - ], + "outputs": [], "source": [ "import numpy\n", "from scipy.linalg import solve\n", @@ -228,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -264,7 +269,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -302,7 +307,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 22, "metadata": { "collapsed": true }, @@ -327,7 +332,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -353,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 24, "metadata": { "collapsed": false }, @@ -1097,7 +1102,7 @@ { "data": { "text/html": [ - "
" + "" ], "text/plain": [ "" @@ -1845,7 +1850,7 @@ { "data": { "text/html": [ - "
" + "" ], "text/plain": [ "" @@ -2685,7 +2690,7 @@ { "data": { "text/html": [ - "
" + "" ], "text/plain": [ "" @@ -3527,7 +3532,7 @@ { "data": { "text/html": [ - "
" + "" ], "text/plain": [ "" @@ -4275,7 +4280,7 @@ { "data": { "text/html": [ - "
" + "" ], "text/plain": [ "" From 692463f0e41dc084b168207c9b08a622e0b378c3 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 7 Dec 2015 23:33:58 -0500 Subject: [PATCH 37/61] Verlet for double pendulum - still needs checking --- randy_schur/Double_Pendulum_Problem.ipynb | 207 +++++++++++++++++++--- 1 file changed, 181 insertions(+), 26 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 4b86a69..7d8febf 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -196,7 +196,7 @@ "$$\\begin{eqnarray*}\n", "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = -\\frac{q_1}{I_1} - \\frac{m_2l_1^2q_1}{m_1I_1} - \\frac{q_1}{l_2m_1l_1}cos(p_2-p_1) \\\\\n", "\\dot{q_1} &=& \\frac{\\partial H}{\\partial p_1} = \\frac{q_1q_2}{m_1l_1l_2}sin(p_2-p_1) + (m_1+m_2)gl_1sin(p_1) \\\\\n", - "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = -\\frac{q_1}{m_1l_1l_2}cos(p_2-p_1) - \\frac{q_2}{2I_2} \\\\\n", + "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = -\\frac{q_1}{m_1l_1l_2}cos(p_2-p_1) - \\frac{q_2}{I_2} \\\\\n", "\\dot{q_2} &=& \\frac{\\partial H}{\\partial p_2} = -\\frac{q_1q_2}{m_1l_1l_2}sin(p_2-p_1) + m_2l_2gsin(p_2)\\\\\n", "\\end{eqnarray*}$$" ] @@ -212,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 25, "metadata": { "collapsed": false }, @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 31, "metadata": { "collapsed": false }, @@ -269,7 +269,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 32, "metadata": { "collapsed": false }, @@ -307,7 +307,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 33, "metadata": { "collapsed": true }, @@ -332,7 +332,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 34, "metadata": { "collapsed": false }, @@ -358,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 35, "metadata": { "collapsed": false }, @@ -1906,7 +1906,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 52, "metadata": { "collapsed": false }, @@ -1926,20 +1926,20 @@ " p2 = u[2]\n", " q2 = u[3]\n", " \n", - " p1_half = p1 + dt/2*(q1/I1)\n", - " q1 = q1 - dt*((m1+m2)*g*l1*sin(p1_half))\n", - " p1 = p1_half + dt/2*(q1/I1)\n", + " p1_half = p1 - dt/2*(q1/I1 + m2*l1**2*q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", + " q1 = q1 + dt*(q1*q2/(m1*l1*l2)*sin(p2-p1_half) + (m1+m2)*g*l1*sin(p1_half))\n", + " p1 = p1_half - dt/2*(q1/I1 + m2*l1**2*q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", " \n", - " p2_half = p2 + dt/2*(q2/I2)\n", - " q2 = q2 - dt*m2*g*l2*sin(p2_half)\n", - " p2 = p2_half + dt/2*(q2/I2)\n", + " p2_half = p2 - dt/2*(q1/(m1*l1*l2)*cos(p2-p1) - q2/I2)\n", + " q2 = q2 - dt*(-q1*q2/(m1*l1*l2)*sin(p2_half-p1) + m2*l2*g*sin(p2))\n", + " p2 = p2_half - dt/2*(q1/(m1*l1*l2)*cos(p2-p1) - q2/I2)\n", " \n", " return numpy.array([p1, q1, p2, q2]) " ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 54, "metadata": { "collapsed": false }, @@ -2690,7 +2690,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2713,7 +2713,7 @@ "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Double Pendulum Euler');\n", - "pyplot.plot(t, q3_dp[:,0], lw=2, label='Joint 1');\n", + "#pyplot.plot(t, q3_dp[:,0], lw=2, label='Joint 1');\n", "pyplot.plot(t, q3_dp[:,2], lw=2, label='Joint 2')\n", "pyplot.legend();" ] @@ -2751,7 +2751,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 38, "metadata": { "collapsed": false }, @@ -2788,7 +2788,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 39, "metadata": { "collapsed": false }, @@ -3532,7 +3532,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4280,7 +4280,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4339,7 +4339,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 40, "metadata": { "collapsed": false }, @@ -4376,7 +4376,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 41, "metadata": { "collapsed": false }, @@ -5158,7 +5158,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 42, "metadata": { "collapsed": false }, @@ -5204,11 +5204,166 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 43, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# This cell loads the style of the notebook, which is modified from the \n", "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", From 987be27ff25b5472fa3068e69d7210e17f117cc3 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 7 Dec 2015 23:37:01 -0500 Subject: [PATCH 38/61] evening commit --- randy_schur/Double_Pendulum_Problem.ipynb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 7d8febf..a8ec71d 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -1906,7 +1906,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 55, "metadata": { "collapsed": false }, @@ -1939,7 +1939,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 56, "metadata": { "collapsed": false }, @@ -2720,12 +2720,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 57, "metadata": { "collapsed": true }, "outputs": [], - "source": [] + "source": [ + "#To Do:\n", + "# plot total energy in system\n", + "# animate motion of double pendulum\n", + "# Poincare section for symplectic DP\n", + "# animate Poincare sections\n", + "# check through sources" + ] }, { "cell_type": "markdown", From 16024076179d1f7fd75b421889c7edf4d7e6c565 Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 8 Dec 2015 16:48:23 -0500 Subject: [PATCH 39/61] fix naming convention --- randy_schur/Double_Pendulum_Problem.ipynb | 97 +++++---- ...on Techniques for Mechanical Systems.ipynb | 198 +++++++++--------- 2 files changed, 157 insertions(+), 138 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index a8ec71d..e4275e4 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -173,7 +173,7 @@ "$$\\begin{eqnarray*}\n", "T &=& \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+\\frac{1}{2}m_2\\left(2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+l_2^2\\dot\\theta_2^2\\right)\\\\\n", "&=& \\frac{1}{2}(I_1 + m_2l_1^2)\\omega_1^2+\\left[m_2l_1l_2\\omega_1\\omega_2cos(\\theta_2-\\theta_1)+\\frac{I_2\\omega_2^2}{2}\\right] \\\\\n", - "&=& \\frac{q_1^2}{2I_1}+\\frac{m_2l_1^2q_1^2}{2m_1I_1} + \\left[ \\frac{q_2}{l_2} \\frac{q_1}{m_1l_1}cos(p_2-p_1) + \\frac{q_2^2}{2I_2} \\right]\n", + "&=& \\frac{q_1^2}{2I_1}+\\frac{m_2q_1^2}{2m_1I_1} + \\left[ \\frac{q_2}{l_2} \\frac{q_1}{m_1l_1}cos(p_2-p_1) + \\frac{q_2^2}{2I_2} \\right]\n", "\\end{eqnarray*}$$\n", "\n" ] @@ -184,7 +184,7 @@ "source": [ "where $I_i$ is the angular moment of inertia and $\\omega$ is the angular velocity. So our Hamiltonian quantity becomes:\n", "$$\\begin{equation*}\n", - "H = \\frac{q_1^2}{2I_1}+\\frac{m_2l_1^2q_1^2}{2m_1I_1} + \\left[ \\frac{q_2}{l_2} \\frac{q_1}{m_1l_1}cos(p_2-p_1) + \\frac{q_2^2}{2I_2} \\right] -m_1gl_1cos(p_1) - m_2g\\left( l_1cos(p_1) + l_2cos(p_2) \\right)\n", + "H = \\frac{q_1^2}{2I_1}+\\frac{m_2q_1^2}{2m_1I_1} + \\left[ \\frac{q_2}{l_2} \\frac{q_1}{m_1l_1}cos(p_2-p_1) + \\frac{q_2^2}{2I_2} \\right] -m_1gl_1cos(p_1) - m_2g\\left( l_1cos(p_1) + l_2cos(p_2) \\right)\n", "\\end{equation*}$$" ] }, @@ -194,7 +194,7 @@ "source": [ "Since we have two degrees of freedom, we will have four equations of motion. These will be the following:\n", "$$\\begin{eqnarray*}\n", - "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = -\\frac{q_1}{I_1} - \\frac{m_2l_1^2q_1}{m_1I_1} - \\frac{q_1}{l_2m_1l_1}cos(p_2-p_1) \\\\\n", + "\\dot{p_1} &=& -\\frac{\\partial H}{\\partial q_1} = -\\frac{q_1}{I_1} - \\frac{m_2q_1}{m_1I_1} - \\frac{q_1}{l_2m_1l_1}cos(p_2-p_1) \\\\\n", "\\dot{q_1} &=& \\frac{\\partial H}{\\partial p_1} = \\frac{q_1q_2}{m_1l_1l_2}sin(p_2-p_1) + (m_1+m_2)gl_1sin(p_1) \\\\\n", "\\dot{p_2} &=& -\\frac{\\partial H}{\\partial q_2} = -\\frac{q_1}{m_1l_1l_2}cos(p_2-p_1) - \\frac{q_2}{I_2} \\\\\n", "\\dot{q_2} &=& \\frac{\\partial H}{\\partial p_2} = -\\frac{q_1q_2}{m_1l_1l_2}sin(p_2-p_1) + m_2l_2gsin(p_2)\\\\\n", @@ -212,11 +212,20 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 2, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\rschur\\Anaconda2\\lib\\site-packages\\IPython\\kernel\\__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", + " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" + ] + } + ], "source": [ "import numpy\n", "from scipy.linalg import solve\n", @@ -233,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -269,7 +278,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -307,7 +316,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -332,7 +341,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -358,7 +367,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -1102,7 +1111,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1850,7 +1859,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1906,7 +1915,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -1926,9 +1935,9 @@ " p2 = u[2]\n", " q2 = u[3]\n", " \n", - " p1_half = p1 - dt/2*(q1/I1 + m2*l1**2*q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", + " p1_half = p1 - dt/2*(q1/I1 + m2**q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", " q1 = q1 + dt*(q1*q2/(m1*l1*l2)*sin(p2-p1_half) + (m1+m2)*g*l1*sin(p1_half))\n", - " p1 = p1_half - dt/2*(q1/I1 + m2*l1**2*q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", + " p1 = p1_half - dt/2*(q1/I1 + m2**q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", " \n", " p2_half = p2 - dt/2*(q1/(m1*l1*l2)*cos(p2-p1) - q2/I2)\n", " q2 = q2 - dt*(-q1*q2/(m1*l1*l2)*sin(p2_half-p1) + m2*l2*g*sin(p2))\n", @@ -1939,7 +1948,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -2690,7 +2699,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2720,7 +2729,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 10, "metadata": { "collapsed": true }, @@ -2758,7 +2767,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -2795,7 +2804,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -3539,7 +3548,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4287,7 +4296,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4346,7 +4355,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -4383,7 +4392,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 15, "metadata": { "collapsed": false }, @@ -5134,7 +5143,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5142,6 +5151,16 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -5153,19 +5172,19 @@ "pyplot.ylabel(r'$\\dot{\\theta_2}$', fontsize=18);#pyplot.title('Poincare Section');\n", "pyplot.plot(v[0,:], v[1,:], 'bo', lw=2, );\n", "\n", - "from matplotlib import animation\n", - "from JSAnimation.IPython_display import display_animation\n", - "def animate(data):\n", - " print(numpy.shape(data))\n", - " x = data[0,:]\n", - " y = data[1,:]\n", - " line.set_data(x, y)\n", - " return line," + "#from matplotlib import animation\n", + "#from JSAnimation.IPython_display import display_animation\n", + "#def animate(data):\n", + "# print(numpy.shape(data))\n", + "# x = data[0,:]\n", + "# y = data[1,:]\n", + "# line.set_data(x, y)\n", + "# return line," ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -5392,21 +5411,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.3" + "pygments_lexer": "ipython2", + "version": "2.7.11" } }, "nbformat": 4, diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index dec9b79..5d7175b 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -157,25 +157,25 @@ "$$ H = T+V$$\n", "where $T$ is the kinetic energy in the system, and $V$ is the potential energy in the system. So, we get:\n", "$$\\begin{eqnarray*}\n", - "T &=& \\frac{1}{2}m\\dot{x}^2 = \\frac{q^2}{2m}\\\\\n", - "V &=& \\frac{1}{2}k{x}^2 = \\frac{kp^2}{2} \n", + "T &=& \\frac{1}{2}m\\dot{x}^2 = \\frac{p^2}{2m}\\\\\n", + "V &=& \\frac{1}{2}k{x}^2 = \\frac{kq^2}{2} \n", "\\end{eqnarray*}$$\n", "\n", "and \n", "\n", - "$$H = \\frac{q^2}{2m} + \\frac{kp^2}{2}$$\n", + "$$H = \\frac{p^2}{2m} + \\frac{kq^2}{2}$$\n", "\n", "\n", "\n", "The Hamilton's equations are:\n", "$$\\begin{eqnarray*}\n", - "\\frac{dp}{dt} &=& -\\frac{\\partial H}{\\partial q}\\\\\n", - "\\frac{dq}{dt} &=& \\frac{\\partial H}{\\partial p}\n", + "\\frac{dq}{dt} &=& -\\frac{\\partial H}{\\partial p}\\\\\n", + "\\frac{dp}{dt} &=& \\frac{\\partial H}{\\partial q}\n", "\\end{eqnarray*}$$\n", "\n", "So our equations of motion become:\n", "$$\\begin{equation*}\n", - "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q}\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -\\frac{q}{m} \\\\ kp\\end{bmatrix}\n", + "\\begin{bmatrix} \\dot q \\\\ \\dot p \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial p}\\\\ \\frac{\\partial H}{\\partial q} \\end{bmatrix} = \\begin{bmatrix} -\\frac{p}{m} \\\\ kq\\end{bmatrix}\n", "\\end{equation*}$$" ] }, @@ -222,7 +222,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -231,7 +231,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/randy/anaconda3/lib/python3.4/site-packages/IPython/kernel/__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", + "C:\\Users\\rschur\\Anaconda2\\lib\\site-packages\\IPython\\kernel\\__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" ] } @@ -252,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -270,7 +270,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -293,7 +293,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -320,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -344,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "metadata": { "collapsed": true }, @@ -362,18 +362,18 @@ " pos = u[0]\n", " mom = u[1]\n", " \n", - " p_half = pos - dt/2*mom/m\n", - " q_half = mom + dt/2*(k*pos)\n", + " q_half = pos - dt/2*mom/m\n", + " #p_half = mom + dt/2*(k*pos)\n", " \n", - " q = mom + dt/2*(2*k*p_half)\n", - " p = p_half - dt/2*q/m\n", + " p = mom + dt/2*(2*k*q_half)\n", + " q = q_half - dt/2*p/m\n", " \n", - " return numpy.array([p, q]) " + " return numpy.array([q, p]) " ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -384,16 +384,16 @@ "xdot0 = 0 #[m/s]\n", "\n", "#Equivalent initial conditions for the Hamiltonian system\n", - "p0 = x0\n", - "q0 = m*xdot0\n", + "q0 = x0\n", + "p0 = m*xdot0\n", "\n", "x_init = numpy.array([x0, xdot0])\n", - "x_init_H = numpy.array([p0, q0])" + "x_init_H = numpy.array([q0, p0])" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -1137,7 +1137,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1149,29 +1149,29 @@ ], "source": [ "#Euler Integration\n", - "q1 = numpy.zeros((N,2)) #initialize array\n", - "q1[0] = x_init.copy() #set initial conditions\n", + "x1 = numpy.zeros((N,2)) #initialize array\n", + "x1[0] = x_init.copy() #set initial conditions\n", "for n in range(N-1): #integrate with Euler\n", - " q1[n+1] = euler(q1[n], f_harmonic_oscillator, dt)\n", + " x1[n+1] = euler(x1[n], f_harmonic_oscillator, dt)\n", " #print(q1[n])\n", " \n", "#Runge-Kutta Integration\n", - "q2 = numpy.zeros((N,2))\n", - "q2[0] = x_init.copy()\n", + "x2 = numpy.zeros((N,2))\n", + "x2[0] = x_init.copy()\n", "for n in range(N-1):\n", - " q2[n+1] = RK4(q2[n], f_harmonic_oscillator, dt)\n", + " x2[n+1] = RK4(x2[n], f_harmonic_oscillator, dt)\n", "\n", "#Symplectic Integration\n", - "q3 = numpy.zeros((N,2))\n", - "q3[0] = x_init_H.copy()\n", + "x3 = numpy.zeros((N,2))\n", + "x3[0] = x_init_H.copy()\n", "for n in range(N-1):\n", - " q3[n+1] = verlet_SHM(q3[n], dt)\n", + " x3[n+1] = verlet_SHM(x3[n], dt)\n", "\n", "#Analytical Solution \n", "A = x0 #with no forcing and no initial velocity, the max amplitude is equal to initial amplitude\n", "phi = 0\n", "omega = sqrt(k/m)\n", - "q_analytical = A*numpy.cos(omega*t + phi)\n", + "x_analytical = A*numpy.cos(omega*t + phi)\n", "#analytical velocity:\n", "vel_analytical = -A*omega*numpy.sin(omega*t + phi)\n", " \n", @@ -1180,10 +1180,10 @@ "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Harmonic oscillator position');\n", - "pyplot.plot(t, q1[:,0], lw=2, label='Euler');\n", - "pyplot.plot(t, q2[:,0], 'r-', lw=2, label='RK4');\n", - "pyplot.plot(t, q3[:,0], 'g', lw=2, label='Verlet');\n", - "pyplot.plot(t, q_analytical, 'k--', lw=2, label='analytical');\n", + "pyplot.plot(t, x1[:,0], lw=2, label='Euler');\n", + "pyplot.plot(t, x2[:,0], 'r-', lw=2, label='RK4');\n", + "pyplot.plot(t, x3[:,0], 'g', lw=2, label='Verlet');\n", + "pyplot.plot(t, x_analytical, 'k--', lw=2, label='analytical');\n", "pyplot.legend();" ] }, @@ -1199,7 +1199,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 11, "metadata": { "collapsed": true }, @@ -1222,7 +1222,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 12, "metadata": { "collapsed": true }, @@ -1235,15 +1235,15 @@ "Verlet_error_values = Euler_error_values.copy()\n", "for i, dt in enumerate(dt_values):\n", " ###Call error function\n", - " Euler_error_values[i] = get_error(q1[:,0], q_analytical, dt)\n", - " RK_error_values[i] = get_error(q2[:,0], q_analytical, dt)\n", - " Verlet_error_values[i] = get_error(q3[:,0], q_analytical, dt)\n", + " Euler_error_values[i] = get_error(x1[:,0], x_analytical, dt)\n", + " RK_error_values[i] = get_error(x2[:,0], x_analytical, dt)\n", + " Verlet_error_values[i] = get_error(x3[:,0], x_analytical, dt)\n", " \n" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -1987,7 +1987,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2032,7 +2032,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "metadata": { "collapsed": true }, @@ -2055,42 +2055,42 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 17, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ "#Euler total energy\n", "Euler_energy = numpy.zeros_like(t)\n", "for i in range(N):\n", - " Euler_energy[i] = get_Energy(q1[i,:])\n", + " Euler_energy[i] = get_Energy(x1[i,:])\n", " \n", "\n", "#RK4 total energy\n", "RK4_energy = numpy.zeros_like(t)\n", "for i in range(N):\n", - " RK4_energy[i] = get_Energy(q2[i,:])\n", + " RK4_energy[i] = get_Energy(x2[i,:])\n", "\n", "#Verlet total energy\n", "#Here we will convert from momentum to velocity so we can use the same get_energy function.\n", "#Momemtum (q) is mass*velocity.\n", "Verlet_energy = numpy.zeros_like(t)\n", - "Verlet_vel = q3[:,1]/m\n", + "Verlet_vel = x3[:,1]/m\n", "for i in range(N):\n", - " Verlet_energy[i] = get_Energy(numpy.array([q3[i,0], Verlet_vel[i]]))\n", + " Verlet_energy[i] = get_Energy(numpy.array([x3[i,0], Verlet_vel[i]]))\n", "\n", "\n", "#analytic total energy\n", "analytical_energy = numpy.zeros_like(t)\n", - "anayltical_state = numpy.array([q_analytical, vel_analytical]).T\n", + "anayltical_state = numpy.array([x_analytical, vel_analytical]).T\n", "for i in range(N):\n", " analytical_energy[i] = get_Energy(anayltical_state[i,:])" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -2834,7 +2834,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2918,7 +2918,7 @@ "Now that we have this, we can add a similar term to the Hamiltonian equations of motion to deal with the same problem. For more information on the derivation of the Hamiltonian from the Lagrangian, see Tveter (cite). The Hamiltonian equations of motion become:\n", "\n", "$$\\begin{equation*}\n", - "\\begin{bmatrix} \\dot p \\\\ \\dot q \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial q} + Q\\\\ \\frac{\\partial H}{\\partial p} \\end{bmatrix} = \\begin{bmatrix} -\\frac{q}{m} - \\frac{c}{m}q \\\\ kp \\end{bmatrix}\n", + "\\begin{bmatrix} \\dot q \\\\ \\dot p \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial p} + Q\\\\ \\frac{\\partial H}{\\partial q} \\end{bmatrix} = \\begin{bmatrix} -\\frac{p}{m} - \\frac{c}{m}p \\\\ kq \\end{bmatrix}\n", "\\end{equation*}$$\n", "\n", "From here, the we can use the same Verlet integration method as before. If this explanation has too much hand-waving for you, please see [7]." @@ -2926,7 +2926,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -2950,7 +2950,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": { "collapsed": true }, @@ -2967,19 +2967,19 @@ " pos = u[0]\n", " mom = u[1]\n", " \n", - " p_half = pos - dt/2*(mom/m)\n", + " q_half = pos - dt/2*(mom/m)\n", " #q_half = mom + dt/2*(k*pos)\n", " \n", - " q = mom + dt*(k*p_half - c/m*mom)\n", - " p = p_half - dt/2*(q/m)\n", + " p = mom + dt*(k*q_half - c/m*mom)\n", + " q = q_half - dt/2*(p/m)\n", " \n", - " return numpy.array([p, q]) \n", + " return numpy.array([q, p]) \n", " " ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": { "collapsed": true }, @@ -2997,11 +2997,11 @@ "x0 = 1 #[m]\n", "xdot0 = 0 #[m/s]\n", "\n", - "p0 = x0\n", - "q0 = m*xdot0\n", + "q0 = x0\n", + "p0 = m*xdot0\n", "\n", "x_init = numpy.array([x0, xdot0])\n", - "x_init_H = numpy.array([p0, q0])\n", + "x_init_H = numpy.array([q0, p0])\n", "zeta = 0.1 #[N/(m/s)]\n", "wn = sqrt(k/m)\n", "wd = wn*sqrt(1-zeta**2)\n", @@ -3010,7 +3010,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -3754,7 +3754,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3766,28 +3766,28 @@ ], "source": [ "#Euler\n", - "q1_d = numpy.zeros((N,2)) \n", - "q1_d[0] = x_init.copy() #set initial conditions\n", + "x1_d = numpy.zeros((N,2)) \n", + "x1_d[0] = x_init.copy() #set initial conditions\n", "for n in range(N-1): #integrate with Euler\n", - " q1_d[n+1] = euler(q1_d[n], f_damped_HM, dt)\n", + " x1_d[n+1] = euler(x1_d[n], f_damped_HM, dt)\n", " #print(q1[n])\n", " \n", "#Runge-Kutta \n", - "q2_d = numpy.zeros((N,2))\n", - "q2_d[0] = x_init.copy()\n", + "x2_d = numpy.zeros((N,2))\n", + "x2_d[0] = x_init.copy()\n", "for n in range(N-1):\n", - " q2_d[n+1] = RK4(q2_d[n], f_damped_HM, dt)\n", + " x2_d[n+1] = RK4(x2_d[n], f_damped_HM, dt)\n", "\n", "#Symplectic\n", - "q3_d = numpy.zeros((N,2))\n", - "q3_d[0] = x_init_H.copy()\n", + "x3_d = numpy.zeros((N,2))\n", + "x3_d[0] = x_init_H.copy()\n", "for n in range(N-1):\n", - " q3_d[n+1] = Verlet_damped_HM(q3_d[n], dt)\n", + " x3_d[n+1] = Verlet_damped_HM(x3_d[n], dt)\n", "\n", "#Analytical Solution \n", "A = x0 #with no forcing, the max amplitude is equal to initial amplitude\n", "phi = 0\n", - "q_d_analytical = A*numpy.exp(-zeta*wn*t)*numpy.cos(wd*t + phi)\n", + "x_d_analytical = A*numpy.exp(-zeta*wn*t)*numpy.cos(wd*t + phi)\n", "vel_d_analytical = -A*numpy.exp(-zeta*wn*t)*wd*numpy.sin(wd*t+phi)\n", " \n", "pyplot.figure(figsize=(10,8));\n", @@ -3795,10 +3795,10 @@ "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Harmonic oscillator position');\n", - "pyplot.plot(t, q1_d[:,0], lw=2, label='Euler');\n", - "pyplot.plot(t, q2_d[:,0], 'r-', lw=2, label='RK4');\n", - "pyplot.plot(t, q3_d[:,0], 'g', lw=2, label='Verlet');\n", - "pyplot.plot(t, q_d_analytical, 'k--', lw=2, label='analytical');\n", + "pyplot.plot(t, x1_d[:,0], lw=2, label='Euler');\n", + "pyplot.plot(t, x2_d[:,0], 'r-', lw=2, label='RK4');\n", + "pyplot.plot(t, x3_d[:,0], 'g', lw=2, label='Verlet');\n", + "pyplot.plot(t, x_d_analytical, 'k--', lw=2, label='analytical');\n", "pyplot.legend();\n" ] }, @@ -4558,7 +4558,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4572,26 +4572,26 @@ "#Euler total energy\n", "Euler_energy_d = numpy.zeros_like(t)\n", "for i in range(N):\n", - " Euler_energy_d[i] = get_Energy(q1_d[i,:])\n", + " Euler_energy_d[i] = get_Energy(x1_d[i,:])\n", " \n", "\n", "#RK4 total energy\n", "RK4_energy_d = numpy.zeros_like(t)\n", "for i in range(N):\n", - " RK4_energy_d[i] = get_Energy(q2_d[i,:])\n", + " RK4_energy_d[i] = get_Energy(x2_d[i,:])\n", "\n", "#Verlet total energy\n", "#Here we will convert from momentum to velocity so we can use the same get_energy function.\n", "#Momemtum (q) is mass*velocity.\n", "Verlet_energy_d = numpy.zeros_like(t)\n", - "Verlet_vel_d = q3_d[:,1]/m\n", + "Verlet_vel_d = x3_d[:,1]/m\n", "for i in range(N):\n", - " Verlet_energy_d[i] = get_Energy(numpy.array([q3_d[i,0], Verlet_vel_d[i]]))\n", + " Verlet_energy_d[i] = get_Energy(numpy.array([x3_d[i,0], Verlet_vel_d[i]]))\n", "\n", "\n", "#analytic total energy\n", "analytical_energy_d = numpy.zeros_like(t)\n", - "anayltical_state_d = numpy.array([q_d_analytical, vel_d_analytical]).T\n", + "anayltical_state_d = numpy.array([x_d_analytical, vel_d_analytical]).T\n", "for i in range(N):\n", " analytical_energy_d[i] = get_Energy(anayltical_state_d[i,:])\n", " \n", @@ -4621,7 +4621,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 26, "metadata": { "collapsed": false }, @@ -5365,7 +5365,7 @@ { "data": { "text/html": [ - "
" + "" ], "text/plain": [ "" @@ -5396,10 +5396,10 @@ "\n", "pyplot.figure(figsize=(10,8));\n", "pyplot.quiver(X1, X2, u, v, pivot='mid', color='k', label='direction field');\n", - "pyplot.plot(q_d_analytical, vel_d_analytical, 'k-', label='analytical');\n", - "pyplot.plot(q1_d[:,0], q1_d[:,1],'r-', label='Euler');\n", - "pyplot.plot(q2_d[:,0], q2_d[:,1], 'b-', label='RK4');\n", - "pyplot.plot(q3_d[:,0], -Verlet_vel_d, 'g-', label='Verlet');\n", + "pyplot.plot(x_d_analytical, vel_d_analytical, 'k-', label='analytical');\n", + "pyplot.plot(x1_d[:,0], x1_d[:,1],'r-', label='Euler');\n", + "pyplot.plot(x2_d[:,0], x2_d[:,1], 'b-', label='RK4');\n", + "pyplot.plot(x3_d[:,0], -Verlet_vel_d, 'g-', label='Verlet');\n", "pyplot.xlabel('Postion ($x_1$)')\n", "pyplot.ylabel('Velocity ($x_2$)')\n", "pyplot.legend();\n", @@ -5622,21 +5622,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.3" + "pygments_lexer": "ipython2", + "version": "2.7.11" } }, "nbformat": 4, From d881ab9df349807778f9c31044dbe9e07c7a0419 Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 8 Dec 2015 16:53:19 -0500 Subject: [PATCH 40/61] fix naming convention - Double pendulum --- randy_schur/Double_Pendulum_Problem.ipynb | 100 ++++++++---------- ...on Techniques for Mechanical Systems.ipynb | 2 +- 2 files changed, 43 insertions(+), 59 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index e4275e4..53124ab 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -212,20 +212,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 17, "metadata": { "collapsed": false }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\rschur\\Anaconda2\\lib\\site-packages\\IPython\\kernel\\__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", - " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" - ] - } - ], + "outputs": [], "source": [ "import numpy\n", "from scipy.linalg import solve\n", @@ -242,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -278,7 +269,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -304,19 +295,19 @@ "theta1_dot_0 = 0 #[rad/s]\n", "theta2_dot_0 = 0 #\n", "\n", - "p1_0 = theta1_0\n", - "q1_0 = m1*theta1_dot_0\n", - "p2_0 = theta2_0\n", - "q2_0 = m2*theta2_dot_0\n", + "q1_0 = theta1_0\n", + "p1_0 = m1*theta1_dot_0\n", + "q2_0 = theta2_0\n", + "p2_0 = m2*theta2_dot_0\n", "\n", "x_init_dp = numpy.array([theta1_0, theta2_0, theta1_dot_0, theta2_dot_0])\n", - "x_init_H_dp = numpy.array([p1_0, q1_0, p2_0, q2_0])\n", + "x_init_H_dp = numpy.array([q1_0, p1_0, q2_0, p2_0])\n", "\n" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 20, "metadata": { "collapsed": true }, @@ -341,7 +332,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -367,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -1871,25 +1862,25 @@ ], "source": [ "#Euler\n", - "q1_dp = numpy.zeros((N,4)) \n", - "q1_dp[0,:] = x_init_dp.copy() #set initial conditions\n", + "x1_dp = numpy.zeros((N,4)) \n", + "x1_dp[0,:] = x_init_dp.copy() #set initial conditions\n", "for n in range(N-1): #integrate with Euler\n", - " q1_dp[n+1,:] = euler_DP(q1_dp[n,:], f_double_pendulum, dt)\n", + " x1_dp[n+1,:] = euler_DP(x1_dp[n,:], f_double_pendulum, dt)\n", " #print(q1[n])\n", " \n", "#Runge-Kutta\n", - "q2_dp = numpy.zeros((N,4)) \n", - "q2_dp[0,:] = x_init_dp.copy() #set initial conditions\n", + "x2_dp = numpy.zeros((N,4)) \n", + "x2_dp[0,:] = x_init_dp.copy() #set initial conditions\n", "for n in range(N-1): #integrate with Euler\n", - " q2_dp[n+1,:] = RK4_DP(q2_dp[n,:], f_double_pendulum, dt)\n", + " x2_dp[n+1,:] = RK4_DP(x2_dp[n,:], f_double_pendulum, dt)\n", " \n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Double Pendulum Euler');\n", - "pyplot.plot(t, q1_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, q1_dp[:,1], lw=2, label='Joint 2')\n", + "pyplot.plot(t, x1_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, x1_dp[:,1], lw=2, label='Joint 2')\n", "pyplot.legend();\n", "\n", "pyplot.figure(figsize=(10,8));\n", @@ -1897,8 +1888,8 @@ "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Double Pendulum RK4');\n", - "pyplot.plot(t, q2_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, q2_dp[:,1], lw=2, label='Joint 2')\n", + "pyplot.plot(t, x2_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, x2_dp[:,1], lw=2, label='Joint 2')\n", "pyplot.legend();" ] }, @@ -1915,7 +1906,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 29, "metadata": { "collapsed": false }, @@ -1930,36 +1921,29 @@ " Returns: \n", " state at next time step.\n", " \"\"\"\n", - " p1 = u[0]\n", - " q1 = u[1]\n", - " p2 = u[2]\n", - " q2 = u[3]\n", + " q1 = u[0]\n", + " p1 = u[1]\n", + " q2 = u[2]\n", + " p2 = u[3]\n", " \n", - " p1_half = p1 - dt/2*(q1/I1 + m2**q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", - " q1 = q1 + dt*(q1*q2/(m1*l1*l2)*sin(p2-p1_half) + (m1+m2)*g*l1*sin(p1_half))\n", - " p1 = p1_half - dt/2*(q1/I1 + m2**q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", + " q1_half = q1 - dt/2*(q1/I1 + m2**q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", + " p1 = p1 + dt*(q1*q2/(m1*l1*l2)*sin(p2-q1_half) + (m1+m2)*g*l1*sin(q1_half))\n", + " q1 = q1_half - dt/2*(q1/I1 + m2**q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", " \n", - " p2_half = p2 - dt/2*(q1/(m1*l1*l2)*cos(p2-p1) - q2/I2)\n", - " q2 = q2 - dt*(-q1*q2/(m1*l1*l2)*sin(p2_half-p1) + m2*l2*g*sin(p2))\n", - " p2 = p2_half - dt/2*(q1/(m1*l1*l2)*cos(p2-p1) - q2/I2)\n", + " q2_half = q2 - dt/2*(q1/(m1*l1*l2)*cos(p2-p1) - q2/I2)\n", + " p2 = p2 - dt*(-q1*q2/(m1*l1*l2)*sin(q2_half-p1) + m2*l2*g*sin(p2))\n", + " q2 = q2_half - dt/2*(q1/(m1*l1*l2)*cos(p2-p1) - q2/I2)\n", " \n", - " return numpy.array([p1, q1, p2, q2]) " + " return numpy.array([q1, p1, q2, p2]) " ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 30, "metadata": { "collapsed": false }, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ 1. 0. 3.14159265 0. ]\n" - ] - }, { "data": { "application/javascript": [ @@ -2699,7 +2683,7 @@ { "data": { "text/html": [ - "" + "
" ], "text/plain": [ "" @@ -2711,19 +2695,19 @@ ], "source": [ "#Verlet integration\n", - "q3_dp = numpy.zeros((N,4)) \n", - "q3_dp[0,:] = x_init_H_dp.copy() #set initial conditions\n", - "print(q3_dp[0,:])\n", + "x3_dp = numpy.zeros((N,4)) \n", + "x3_dp[0,:] = x_init_H_dp.copy() #set initial conditions\n", + "#print(q3_dp[0,:])\n", "for n in range(N-1): #integrate with Euler\n", - " q3_dp[n+1,:] = Verlet_DP(q3_dp[n,:], dt)\n", + " x3_dp[n+1,:] = Verlet_DP(x3_dp[n,:], dt)\n", " \n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Double Pendulum Euler');\n", - "#pyplot.plot(t, q3_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, q3_dp[:,2], lw=2, label='Joint 2')\n", + "pyplot.plot(t, x3_dp[:,0], lw=2, label='Joint 1');\n", + "pyplot.plot(t, x3_dp[:,2], lw=2, label='Joint 2')\n", "pyplot.legend();" ] }, diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index 5d7175b..9b21a8a 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -4621,7 +4621,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "metadata": { "collapsed": false }, From 314a53d15e746f9756c8ebcbbe55a18e803a0f73 Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 8 Dec 2015 17:16:35 -0500 Subject: [PATCH 41/61] add figure --- randy_schur/Double_Pendulum_Problem.ipynb | 20 +- ...on Techniques for Mechanical Systems.ipynb | 1339 ++++++++++++----- 2 files changed, 1007 insertions(+), 352 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 53124ab..3d5ecd5 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -1939,7 +1939,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 38, "metadata": { "collapsed": false }, @@ -2683,7 +2683,7 @@ { "data": { "text/html": [ - "
" + "" ], "text/plain": [ "" @@ -2707,7 +2707,7 @@ "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Double Pendulum Euler');\n", "pyplot.plot(t, x3_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, x3_dp[:,2], lw=2, label='Joint 2')\n", + "#pyplot.plot(t, x3_dp[:,2], lw=2, label='Joint 2')\n", "pyplot.legend();" ] }, @@ -2751,7 +2751,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 32, "metadata": { "collapsed": false }, @@ -2788,7 +2788,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 33, "metadata": { "collapsed": false }, @@ -4339,7 +4339,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 34, "metadata": { "collapsed": false }, @@ -4376,7 +4376,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 35, "metadata": { "collapsed": false }, @@ -5139,10 +5139,10 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 15, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -5168,7 +5168,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 36, "metadata": { "collapsed": false }, diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index 9b21a8a..9df054b 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -220,180 +220,983 @@ "### Let's Get to Programming" ] }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\rschur\\Anaconda2\\lib\\site-packages\\IPython\\kernel\\__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", + " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" + ] + } + ], + "source": [ + "import numpy\n", + "from scipy.linalg import solve\n", + "from numpy.linalg import det\n", + "from math import pi, cos, sin, sqrt\n", + "\n", + "from matplotlib import pyplot\n", + "from matplotlib.pyplot import quiver\n", + "%matplotlib notebook\n", + "from matplotlib import rcParams, cm\n", + "rcParams['font.family'] = 'serif'\n", + "rcParams['font.size'] = 16" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#Set up system parameters:\n", + "m = 12\n", + "k = 150\n", + "\n", + "T = 25; #[seconds]\n", + "dt = .02; #time step size\n", + "N = int(T/dt)+1 #number of time steps\n", + "t = numpy.linspace(0.0, T, N) #array of time values" + ] + }, { "cell_type": "code", "execution_count": 3, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def euler(u, f, dt):\n", + " \"\"\" Euler's method for integrating a system of differential equations.\n", + " \n", + " Parameters:\n", + " u - state at current step \n", + " f - RHS of equation\n", + " dt- time step size\n", + " \n", + " Returns: \n", + " x - array of values at next time step.\n", + " \"\"\"\n", + " \n", + " return u + dt*f(u) " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def RK4(u, f, dt):\n", + " \"\"\"Runge Kutta fourth order integration method\n", + " \n", + " Parameters:\n", + " u - state of the system at time t\n", + " f - function for RHS of state equations\n", + " dt - time step\n", + " \n", + " Returns: \n", + " state values at next time step.\n", + " \"\"\"\n", + " k1 = f(u)\n", + " k2 = f(u) + 0.5*dt*k1\n", + " k3 = f(u) + 0.5*dt*k2\n", + " k4 = f(u) + dt*k3\n", + " \n", + " return u + dt/6*(k1+2*k2+2*k3+k4)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def f_harmonic_oscillator(u):\n", + " \"\"\"Returns RHS of harmonic oscillator EOM\n", + " \n", + " Parameters:\n", + " u - initial state\n", + " \n", + " Returns:\n", + " RHS of harmonic oscillator eqn.\n", + " \n", + " \"\"\"\n", + " pos = u[0]\n", + " vel = u[1]\n", + " \n", + " return numpy.array([vel, -k/m*pos])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def verlet_SHM(u, dt):\n", + " \"\"\" Verlet integration for integrating from Hamiltonian equations of motion\n", + " for a simple harmonic oscillator\n", + " Parameters:\n", + " u - state at current time step\n", + " dt - time step size\n", + " Returns: \n", + " state at next time step.\n", + " \"\"\"\n", + " pos = u[0]\n", + " mom = u[1]\n", + " \n", + " q_half = pos - dt/2*mom/m\n", + " #p_half = mom + dt/2*(k*pos)\n", + " \n", + " p = mom + dt/2*(2*k*q_half)\n", + " q = q_half - dt/2*p/m\n", + " \n", + " return numpy.array([q, p]) " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "#Initial Conditions\n", + "x0 = 0.5 #[m]\n", + "xdot0 = 0 #[m/s]\n", + "\n", + "#Equivalent initial conditions for the Hamiltonian system\n", + "q0 = x0\n", + "p0 = m*xdot0\n", + "\n", + "x_init = numpy.array([x0, xdot0])\n", + "x_init_H = numpy.array([q0, p0])" + ] + }, + { + "cell_type": "code", + "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\rschur\\Anaconda2\\lib\\site-packages\\IPython\\kernel\\__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", - " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" - ] + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "def euler(u, f, dt):\n", + " \"\"\" Euler's method for integrating a system of differential equations.\n", + " \n", + " Parameters:\n", + " u - state at current step \n", + " f - RHS of equation\n", + " dt- time step size\n", + " \n", + " Returns: \n", + " x - array of values at next time step.\n", + " \"\"\"\n", + " \n", + " return u + dt*f(u) " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ - "#Euler Integration\n", - "x1 = numpy.zeros((N,2)) #initialize array\n", - "x1[0] = x_init.copy() #set initial conditions\n", - "for n in range(N-1): #integrate with Euler\n", - " x1[n+1] = euler(x1[n], f_harmonic_oscillator, dt)\n", - " #print(q1[n])\n", + "def RK4(u, f, dt):\n", + " \"\"\"Runge Kutta fourth order integration method\n", " \n", - "#Runge-Kutta Integration\n", - "x2 = numpy.zeros((N,2))\n", - "x2[0] = x_init.copy()\n", - "for n in range(N-1):\n", - " x2[n+1] = RK4(x2[n], f_harmonic_oscillator, dt)\n", - "\n", - "#Symplectic Integration\n", - "x3 = numpy.zeros((N,2))\n", - "x3[0] = x_init_H.copy()\n", - "for n in range(N-1):\n", - " x3[n+1] = verlet_SHM(x3[n], dt)\n", - "\n", - "#Analytical Solution \n", - "A = x0 #with no forcing and no initial velocity, the max amplitude is equal to initial amplitude\n", - "phi = 0\n", - "omega = sqrt(k/m)\n", - "x_analytical = A*numpy.cos(omega*t + phi)\n", - "#analytical velocity:\n", - "vel_analytical = -A*omega*numpy.sin(omega*t + phi)\n", + " Parameters:\n", + " u - state of the system at time t\n", + " f - function for RHS of state equations\n", + " dt - time step\n", + " \n", + " Returns: \n", + " state values at next time step.\n", + " \"\"\"\n", + " k1 = f(u)\n", + " k2 = f(u) + 0.5*dt*k1\n", + " k3 = f(u) + 0.5*dt*k2\n", + " k4 = f(u) + dt*k3\n", " \n", - "pyplot.figure(figsize=(12,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", - "pyplot.title('Harmonic oscillator position');\n", - "pyplot.plot(t, x1[:,0], lw=2, label='Euler');\n", - "pyplot.plot(t, x2[:,0], 'r-', lw=2, label='RK4');\n", - "pyplot.plot(t, x3[:,0], 'g', lw=2, label='Verlet');\n", - "pyplot.plot(t, x_analytical, 'k--', lw=2, label='analytical');\n", - "pyplot.legend();" + " return u + dt/6*(k1+2*k2+2*k3+k4)" ] }, { - "cell_type": "markdown", - "metadata": {}, + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ - "If we look closely at the plot, we can see that Euler and Runge-Kutta integration are close to each other, but begin to diverge as time goes on. This should be the behavior we expect for the integration of a non-linear system. But how close are these methods to the analytical solution? Does Verlet do any better? In this case it is obvious that Euler and Runge-Kutta are not correct (and possibly unstable). One thing to check is whether the behavior changes as we change our time step size. Maybe the timestep is too large to capture some aspect of the system response." + "def f_harmonic_oscillator(u):\n", + " \"\"\"Returns RHS of harmonic oscillator EOM\n", + " \n", + " Parameters:\n", + " u - initial state\n", + " \n", + " Returns:\n", + " RHS of harmonic oscillator eqn.\n", + " \n", + " \"\"\"\n", + " pos = u[0]\n", + " vel = u[1]\n", + " \n", + " return numpy.array([vel, -k/m*pos])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def verlet_SHM(u, dt):\n", + " \"\"\" Verlet integration for integrating from Hamiltonian equations of motion\n", + " for a simple harmonic oscillator\n", + " Parameters:\n", + " u - state at current time step\n", + " dt - time step size\n", + " Returns: \n", + " state at next time step.\n", + " \"\"\"\n", + " pos = u[0]\n", + " mom = u[1]\n", + " \n", + " q_half = pos - dt/2*mom/m\n", + " #p_half = mom + dt/2*(k*pos)\n", + " \n", + " p = mom + dt/2*(2*k*q_half)\n", + " q = q_half - dt/2*p/m\n", + " \n", + " return numpy.array([q, p]) " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "#Initial Conditions\n", + "x0 = 0.5 #[m]\n", + "xdot0 = 0 #[m/s]\n", + "\n", + "#Equivalent initial conditions for the Hamiltonian system\n", + "q0 = x0\n", + "p0 = m*xdot0\n", + "\n", + "x_init = numpy.array([x0, xdot0])\n", + "x_init_H = numpy.array([q0, p0])" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -1276,7 +464,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -1607,11 +794,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -1660,6 +849,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -1673,7 +875,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -1713,7 +916,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -1730,7 +934,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1789,6 +993,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -1796,8 +1002,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -1852,14 +1062,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -1940,7 +1156,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1951,49 +1167,42 @@ } ], "source": [ - "# Use the same initial conditions and parameters, just change dt.\n", - "T = 25; #[seconds]\n", - "dt_2 = .005; #time step size\n", - "N_2 = int(T/dt_2)+1 #number of time steps\n", - "t_2 = numpy.linspace(0.0, T, N_2) #array of time values\n", - "\n", - "\n", "#Euler Integration\n", - "x1_2 = numpy.zeros((N_2,2)) #initialize array\n", - "x1_2[0] = x_init.copy() #set initial conditions\n", - "for n in range(N_2-1): #integrate with Euler\n", - " x1_2[n+1] = euler(x1_2[n], f_harmonic_oscillator, dt)\n", + "x1 = numpy.zeros((N,2)) #initialize array\n", + "x1[0] = x_init.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " x1[n+1] = euler(x1[n], f_harmonic_oscillator, dt)\n", " #print(q1[n])\n", " \n", "#Runge-Kutta Integration\n", - "x2_2 = numpy.zeros((N_2,2))\n", - "x2_2[0] = x_init.copy()\n", - "for n in range(N_2-1):\n", - " x2_2[n+1] = RK4(x2_2[n], f_harmonic_oscillator, dt)\n", + "x2 = numpy.zeros((N,2))\n", + "x2[0] = x_init.copy()\n", + "for n in range(N-1):\n", + " x2[n+1] = RK4(x2[n], f_harmonic_oscillator, dt)\n", "\n", "#Symplectic Integration\n", - "x3_2 = numpy.zeros((N_2,2))\n", - "x3_2[0] = x_init_H.copy()\n", - "for n in range(N_2-1):\n", - " x3_2[n+1] = verlet_SHM(x3_2[n], dt)\n", + "x3 = numpy.zeros((N,2))\n", + "x3[0] = x_init_H.copy()\n", + "for n in range(N-1):\n", + " x3[n+1] = verlet_SHM(x3[n], dt)\n", "\n", "#Analytical Solution \n", "A = x0 #with no forcing and no initial velocity, the max amplitude is equal to initial amplitude\n", "phi = 0\n", "omega = sqrt(k/m)\n", - "x_2_analytical = A*numpy.cos(omega*t_2 + phi)\n", + "x_analytical = A*numpy.cos(omega*t + phi)\n", "#analytical velocity:\n", - "vel_2_analytical = -A*omega*numpy.sin(omega*t_2 + phi)\n", + "vel_analytical = -A*omega*numpy.sin(omega*t + phi)\n", " \n", "pyplot.figure(figsize=(12,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Harmonic oscillator position');\n", - "pyplot.plot(t_2, x1_2[:,0], lw=2, label='Euler');\n", - "pyplot.plot(t_2, x2_2[:,0], 'r-', lw=2, label='RK4');\n", - "pyplot.plot(t_2, x3_2[:,0], 'g', lw=2, label='Verlet');\n", - "pyplot.plot(t_2, x_2_analytical, 'k--', lw=2, label='analytical');\n", + "pyplot.plot(t, x1[:,0], lw=2, label='Euler');\n", + "pyplot.plot(t, x2[:,0], 'r-', lw=2, label='RK4');\n", + "pyplot.plot(t, x3[:,0], 'g', lw=2, label='Verlet');\n", + "pyplot.plot(t, x_analytical, 'k--', lw=2, label='analytical');\n", "pyplot.legend();" ] }, @@ -2001,15 +1210,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "So changing the timestep does make a difference. Let's do an error analysis to examine how the error changes with the timestep size.\n", - "\n", + "If we look closely at the plot, we can see that Euler and Runge-Kutta integration are close to each other, but begin to diverge as time goes on. This should be the behavior we expect for the integration of a non-linear system. But how close are these methods to the analytical solution? Does Verlet do any better? In this case it is obvious that Euler and Runge-Kutta are not correct (and possibly unstable). One thing to check is whether the behavior changes as we change our time step size. Maybe the timestep is too large to capture some aspect of the system response." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "### Error Analysis\n", "The error analysis code below is based on the numerical methods MOOC notebook on phugoid oscillation. " ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": { "collapsed": true }, @@ -2032,7 +1246,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": { "collapsed": true }, @@ -2053,7 +1267,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -2133,7 +1347,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -2464,11 +1677,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -2517,6 +1732,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -2530,7 +1758,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -2570,7 +1799,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -2587,7 +1817,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -2646,6 +1876,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -2653,8 +1885,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -2709,14 +1945,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -2797,7 +2039,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2842,7 +2084,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": { "collapsed": true }, @@ -2865,7 +2107,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": { "collapsed": false }, @@ -2900,7 +2142,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -2980,7 +2222,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -3311,11 +2552,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -3364,6 +2607,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -3377,7 +2633,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -3417,7 +2674,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -3434,7 +2692,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -3493,6 +2751,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -3500,8 +2760,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -3556,14 +2820,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -3644,7 +2914,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3736,7 +3006,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": { "collapsed": true }, @@ -3760,7 +3030,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": { "collapsed": true }, @@ -3789,7 +3059,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -3820,7 +3090,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -3900,7 +3170,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -4231,11 +3500,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -4284,6 +3555,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -4297,7 +3581,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -4337,7 +3622,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -4354,7 +3640,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4413,6 +3699,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -4420,8 +3708,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -4476,14 +3768,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -4564,7 +3862,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4624,7 +3922,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -4704,7 +4002,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -5035,11 +4332,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -5088,6 +4387,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -5101,7 +4413,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -5141,7 +4454,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -5158,7 +4472,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -5217,6 +4531,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -5224,8 +4540,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -5280,14 +4600,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -5368,7 +4694,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5431,7 +4757,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -5511,7 +4837,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -5842,11 +5167,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -5895,6 +5222,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -5908,7 +5248,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -5948,7 +5289,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -5965,7 +5307,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -6024,6 +5366,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -6031,8 +5375,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -6087,14 +5435,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -6175,7 +5529,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -6423,21 +5777,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.5.0" } }, "nbformat": 4, From 0d9581b0059d05054f4cde942428e6e4a466d634 Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 8 Dec 2015 23:24:56 +0000 Subject: [PATCH 44/61] fix Verlet for DP --- randy_schur/Double_Pendulum_Problem.ipynb | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index ed1bb56..be1d3bb 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -358,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -1962,7 +1962,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -1982,20 +1982,20 @@ " q2 = u[2]\n", " p2 = u[3]\n", " \n", - " q1_half = q1 - dt/2*(q1/I1 + m2**q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", - " p1 = p1 + dt*(q1*q2/(m1*l1*l2)*sin(p2-q1_half) + (m1+m2)*g*l1*sin(q1_half))\n", - " q1 = q1_half - dt/2*(q1/I1 + m2**q1/(m1*I1) + q1/(m1*l1*l2)*cos(p2-p1))\n", + " p1_half = p1 - dt/2*(p1*p2/(m1*l1*l2)*sin(q2-q1)+(m1+m2)*g*l1*sin(q1))\n", + " q1_plus = q1 + dt*(-p1_half/I1 - m2*p1_half/(m1*I1) - p2/(m1*l1*l2)*cos(q2-q1))\n", + " p1_plus = p1_half - dt/2*(p1*p2/(m1*l1*l2)*sin(q2-q1_plus)+(m1+m2)*g*l1*sin(q1_plus))\n", " \n", - " q2_half = q2 - dt/2*(q1/(m1*l1*l2)*cos(p2-p1) - q2/I2)\n", - " p2 = p2 - dt*(-q1*q2/(m1*l1*l2)*sin(q2_half-p1) + m2*l2*g*sin(p2))\n", - " q2 = q2_half - dt/2*(q1/(m1*l1*l2)*cos(p2-p1) - q2/I2)\n", + " p2_half = p2 - dt/2*( -p1*p2/(m1*l1*l2)*sin(q2-q1) + m2*g*l2*sin(q2))\n", + " q2_plus = q2 + dt*(-p1/(m1*l1*l2)*cos(q2-q1) - p2_half/I2)\n", + " p2_plus = p2_half - dt/2*( -p1*p2/(m1*l1*l2)*sin(q2_plus-q1) + m2*g*l2*sin(q2_plus))\n", " \n", - " return numpy.array([q1, p1, q2, p2]) " + " return numpy.array([q1_plus, p1_plus, q2_plus, p2_plus]) " ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -2767,7 +2767,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2791,13 +2791,13 @@ "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Double Pendulum Euler');\n", "pyplot.plot(t, x3_dp[:,0], lw=2, label='Joint 1');\n", - "#pyplot.plot(t, x3_dp[:,2], lw=2, label='Joint 2')\n", + "pyplot.plot(t, x3_dp[:,2], lw=2, label='Joint 2')\n", "pyplot.legend();" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 21, "metadata": { "collapsed": true }, From 14bb5f858c539aa75907cb68c081bb20cf1dd5e9 Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 8 Dec 2015 23:48:14 -0500 Subject: [PATCH 45/61] evening commit --- randy_schur/Double_Pendulum_Problem.ipynb | 1895 +++++++++++++++++++-- 1 file changed, 1773 insertions(+), 122 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index be1d3bb..f5587f4 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -269,7 +269,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 77, "metadata": { "collapsed": false }, @@ -285,12 +285,12 @@ "I2 = m2*l2**2\n", "\n", "T = 25; #[seconds]\n", - "dt = .02; #\n", + "dt = .01; #\n", "N = int(T/dt)+1\n", "t = numpy.linspace(0.0, T, N)\n", "\n", "#Initial Conditions\n", - "theta1_0 = 1 #[radians]\n", + "theta1_0 = pi/2 #[radians]\n", "theta2_0 = pi\n", "theta1_dot_0 = 0 #[rad/s]\n", "theta2_dot_0 = 0 #\n", @@ -307,7 +307,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 78, "metadata": { "collapsed": true }, @@ -332,7 +332,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 79, "metadata": { "collapsed": false }, @@ -358,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 80, "metadata": { "collapsed": false }, @@ -908,7 +908,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1130,7 +1130,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1684,7 +1684,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1906,7 +1906,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1955,47 +1955,1748 @@ "collapsed": false }, "source": [ - "Again, we can see that these integrators cause the system to go unstable. In fact, there is a physical interpretation for what is happening. In the RK4 plot, you can see the position of joint 1 oscillating betwee -pi and pi (corresponding to straight up in the air), until it finally reaches a value less than -pi. So, once joint 1 'flips around,' the system diverges.\n", + "Again, we can see that these integrators cause the system to go unstable. In fact, there is a physical interpretation for what is happening. In the RK4 plot, you can see the position of joint 1 oscillating between almost -pi and almost pi (corresponding to straight up in the air), until it finally reaches a value greater than pi. So, once joint 1 'flips around,' the system diverges.\n", "\n", "With simple harmonic motion, the symplectic integrator was able to accurately represent the state of the system without adding energy. Let's see if it is successful with the double pendulum. " ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 82, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def Verlet_DP(u, dt):\n", + " \"\"\" Verlet integration for integrating from Hamiltonian equations of motion\n", + " for a ddouble pendulum\n", + " Parameters:\n", + " u - state at current time step\n", + " dt - time step size\n", + " Returns: \n", + " state at next time step.\n", + " \"\"\"\n", + " q1 = u[0]\n", + " p1 = u[1]\n", + " q2 = u[2]\n", + " p2 = u[3]\n", + " \n", + " p1_half = p1 - dt/2*(p1*p2/(m1*l1*l2)*sin(q2-q1)+(m1+m2)*g*l1*sin(q1))\n", + " q1_plus = q1 + dt*(-p1_half/I1 - m2*p1_half/(m1*I1) - p2/(m1*l1*l2)*cos(q2-q1))\n", + " p1_plus = p1_half - dt/2*(p1*p2/(m1*l1*l2)*sin(q2-q1_plus)+(m1+m2)*g*l1*sin(q1_plus))\n", + " \n", + " p2_half = p2 - dt/2*( -p1*p2/(m1*l1*l2)*sin(q2-q1) + m2*g*l2*sin(q2))\n", + " q2_plus = q2 + dt*(-p1/(m1*l1*l2)*cos(q2-q1) - p2_half/I2)\n", + " p2_plus = p2_half - dt/2*( -p1*p2/(m1*l1*l2)*sin(q2_plus-q1) + m2*g*l2*sin(q2_plus))\n", + " \n", + " return numpy.array([q1_plus, p1_plus, q2_plus, p2_plus]) " + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Verlet_energy = numpy.zeros_like(t)\n", + "Verlet_vel1 = x3_dp[:,1]/m1\n", + "Verlet_vel2 = x3_dp[:,3]/m2\n", + "\n", + "\n", + "u = numpy.array([x3_dp[:,0], Verlet_vel1, x3_dp[:,2], Verlet_vel2])\n", + "#print(u[0,:])\n", + "for i in range(N):\n", + " Verlet_energy[i] = get_Energy(u[:,i])\n", + "\n", + "pyplot.figure(figsize=(8,6));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'Total energy in system', fontsize=18);\n", + "pyplot.title('Energy in System');\n", + "pyplot.plot(t, Verlet_energy, lw=2, label='Verlet');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, we are very far from perfect. But, at least the energy in the system is stable, even if it isn't constant! " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "### Investigating system behavior\n", + "\n", + "In order to investigate system behavior for damped harmonic motion, we used a phase portrait where we plotted position vs. velocity. In this system, we have 2 positions and 2 velocities, so a phase plot won't accurately capture the behavior of the system. Instead we are going to use something called a Poincare section (https://en.wikipedia.org/wiki/Poincar%C3%A9_map). The way these work is to capture the system behavior at a specific, repetetive point. For example, each time that $\\theta_2 = 0$ or any multiple of 2*pi (mass 2 is hanging straight down) we can capture the angular position and velocity of mass 2. This way, we are taking a 'snapshot' of the system, and plotting the state each time. We can now investigate system behavior on a simple 2D plot, similar to the phase portrait." + ] + }, + { + "cell_type": "code", + "execution_count": 96, "metadata": { "collapsed": false }, "outputs": [], "source": [ - "def Verlet_DP(u, dt):\n", - " \"\"\" Verlet integration for integrating from Hamiltonian equations of motion\n", - " for a ddouble pendulum\n", - " Parameters:\n", - " u - state at current time step\n", - " dt - time step size\n", - " Returns: \n", - " state at next time step.\n", - " \"\"\"\n", - " q1 = u[0]\n", - " p1 = u[1]\n", - " q2 = u[2]\n", - " p2 = u[3]\n", - " \n", - " p1_half = p1 - dt/2*(p1*p2/(m1*l1*l2)*sin(q2-q1)+(m1+m2)*g*l1*sin(q1))\n", - " q1_plus = q1 + dt*(-p1_half/I1 - m2*p1_half/(m1*I1) - p2/(m1*l1*l2)*cos(q2-q1))\n", - " p1_plus = p1_half - dt/2*(p1*p2/(m1*l1*l2)*sin(q2-q1_plus)+(m1+m2)*g*l1*sin(q1_plus))\n", + "def poincare(state, sf, val, s1, s2):\n", + " '''Creates a poincare section, capturing system state each time q[state]=val\n", " \n", - " p2_half = p2 - dt/2*( -p1*p2/(m1*l1*l2)*sin(q2-q1) + m2*g*l2*sin(q2))\n", - " q2_plus = q2 + dt*(-p1/(m1*l1*l2)*cos(q2-q1) - p2_half/I2)\n", - " p2_plus = p2_half - dt/2*( -p1*p2/(m1*l1*l2)*sin(q2_plus-q1) + m2*g*l2*sin(q2_plus))\n", - " \n", - " return numpy.array([q1_plus, p1_plus, q2_plus, p2_plus]) " + " Parameters:\n", + " state - state vector\n", + " sf - the row in q to fix\n", + " val - the value to capture state at \n", + " s1 - state to capture 1\n", + " s2 - state to capture 2\n", + " \n", + " Returns:\n", + " v - array with information for poincare section\n", + " '''\n", + " v = numpy.zeros((2,1))\n", + " for i in range(len(state[:,sf])-1):\n", + " if (state[i,sf]>val):\n", + " if (state[i+1,sf] < val):\n", + " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", + " \n", + " if (state[i,sf] < val):\n", + " if (state[i+1,sf] > val):\n", + " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", + " \n", + " return v" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 97, "metadata": { "collapsed": false }, @@ -2545,7 +4246,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -2767,7 +4468,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2778,36 +4479,32 @@ } ], "source": [ - "#Verlet integration\n", - "x3_dp = numpy.zeros((N,4)) \n", - "x3_dp[0,:] = x_init_H_dp.copy() #set initial conditions\n", - "#print(q3_dp[0,:])\n", - "for n in range(N-1): #integrate with Euler\n", - " x3_dp[n+1,:] = Verlet_DP(x3_dp[n,:], dt)\n", - " \n", + "v = poincare(x3_dp, 1, 2*pi, 0, 2) #use states from RK4 integration\n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", - "pyplot.title('Double Pendulum Euler');\n", - "pyplot.plot(t, x3_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, x3_dp[:,2], lw=2, label='Joint 2')\n", - "pyplot.legend();" + "pyplot.xlabel(r'$\\theta_2$', fontsize=18);\n", + "pyplot.ylabel(r'$\\dot{\\theta_2}$', fontsize=18);\n", + "pyplot.title('Poincare Section');\n", + "pyplot.plot(v[0,:], v[1,:], 'bo', lw=2, );" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There isn't a clear pattern here. So, we can say that our system is NOT likely to be asymptotically stable. However, this shouldn't be surprising. If we continued simulating, our system would continue rotating since there is no damping. Let's see what happens when there is some damping in the system. We're going to use RK4 for the following analysis." ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 98, "metadata": { "collapsed": true }, "outputs": [], "source": [ "#To Do:\n", - "# plot total energy in system\n", "# animate motion of double pendulum\n", - "# Poincare section for symplectic DP\n", - "# animate Poincare sections\n", "# check through sources" ] }, @@ -2835,7 +4532,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 99, "metadata": { "collapsed": false }, @@ -2872,7 +4569,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 100, "metadata": { "collapsed": false }, @@ -3422,7 +5119,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -3644,7 +5341,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4198,7 +5895,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4420,7 +6117,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4468,66 +6165,20 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ - "### Investigating system behavior\n", + "It looks like the system is chaotic at first, but becomes periodic as it settles.\n", "\n", - "In order to investigate system behavior for damped harmonic motion, we used a phase portrait where we plotted position vs. velocity. In this system, we have 2 positions and 2 velocities, so a phase plot won't accurately capture the behavior of the system. Instead we are going to use something called a Poincare section (https://en.wikipedia.org/wiki/Poincar%C3%A9_map). The way these work is to capture the system behavior at a specific, repetetive point. For example, each time that $\\theta_1 = 0$ (mass 1 is hanging straight down) we can capture the angular position and velocity of mass 2. This way, we are taking a 'snapshot' of the system, and plotting the state each time. We can now investigate system behavior on a simple 2D plot, similar to the phase portrait." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def poincare(state, sf, val, s1, s2):\n", - " '''Creates a poincare section, capturing system state each time q[state]=val\n", - " \n", - " Parameters:\n", - " state - state vector\n", - " sf - the row in q to fix\n", - " val - the value to capture state at \n", - " s1 - state to capture 1\n", - " s2 - state to capture 2\n", - " \n", - " Returns:\n", - " v - array with information for poincare section\n", - " '''\n", - " v = numpy.zeros((2,1))\n", - " for i in range(len(state[:,sf])-1):\n", - " if (state[i,sf]>val):\n", - " if (state[i+1,sf] < val):\n", - " # print(v)\n", - " # print([[state[i, s1],[state[i, s2]]]])\n", - " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", - " \n", - " if (state[i,sf] < val):\n", - " if (state[i+1,sf] > val):\n", - " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", - " \n", - " return v\n", - " " + "Let's do a Poincare section of this plot as well. For brevity, we'll use only the RK4 system response." ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 101, "metadata": { "collapsed": false }, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(2, 19)\n" - ] - }, { "data": { "application/javascript": [ @@ -5073,7 +6724,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -5295,7 +6946,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5307,21 +6958,21 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 14, + "execution_count": 101, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "v = poincare(q2d_dp, 0, 0, 2, 3) #use states from RK4 integration\n", - "print(numpy.shape(v))\n", + "v = poincare(q2d_dp, 0, 0, 1, 3) #use states from RK4 integration\n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r'$\\theta_2$', fontsize=18);\n", - "pyplot.ylabel(r'$\\dot{\\theta_2}$', fontsize=18);#pyplot.title('Poincare Section');\n", + "pyplot.ylabel(r'$\\dot{\\theta_2}$', fontsize=18);\n", + "pyplot.title('Poincare Section');\n", "pyplot.plot(v[0,:], v[1,:], 'bo', lw=2, );\n", "\n", "#from matplotlib import animation\n", @@ -5336,7 +6987,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 92, "metadata": { "collapsed": false }, @@ -5355,7 +7006,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Other than a few outliers, the plot converges to (0,0). So, this system is stable! This would not be the case for the undamped double pendulum, as integrated by RK4. " + "Other than a few outliers, the plot converges seems to converge. So, this system is stable! This would not be the case for the undamped double pendulum, as integrated by RK4. " ] }, { @@ -5382,7 +7033,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -5537,7 +7188,7 @@ "" ] }, - "execution_count": 16, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -5577,7 +7228,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.0" + "version": "3.4.3" } }, "nbformat": 4, From 68c6243dad992c4e663983079e9a4be19400a634 Mon Sep 17 00:00:00 2001 From: rbds Date: Thu, 10 Dec 2015 12:32:57 +0000 Subject: [PATCH 46/61] fix RK4 --- randy_schur/Double_Pendulum_Problem.ipynb | 16 ++++---- ...on Techniques for Mechanical Systems.ipynb | 39 +++++++++++++------ 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index f5587f4..54eefa1 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -269,7 +269,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -307,7 +307,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -332,7 +332,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -358,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -908,7 +908,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1684,7 +1684,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1955,7 +1955,7 @@ "collapsed": false }, "source": [ - "Again, we can see that these integrators cause the system to go unstable. In fact, there is a physical interpretation for what is happening. In the RK4 plot, you can see the position of joint 1 oscillating between almost -pi and almost pi (corresponding to straight up in the air), until it finally reaches a value greater than pi. So, once joint 1 'flips around,' the system diverges.\n", + "Again, we can see that these integrators cause the system to go unstable. In fact, there is a physical interpretation for what is happening. In the Euler plot, you can see the position of joint 1 oscillating between almost -pi and almost pi (corresponding to straight up in the air), until it finally reaches a value greater than pi. So, once joint 1 'flips around,' the system diverges.\n", "\n", "With simple harmonic motion, the symplectic integrator was able to accurately represent the state of the system without adding energy. Let's see if it is successful with the double pendulum. " ] @@ -7228,7 +7228,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.3" + "version": "3.5.0" } }, "nbformat": 4, diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index 32fdc22..a67690c 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -222,7 +222,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -243,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -261,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -284,7 +284,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 8, "metadata": { "collapsed": true }, @@ -311,7 +311,24 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 24, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def RK4_2(u, f, dt):\n", + " k1 = dt*f(u)\n", + " k2 = dt*f(u+ k1/2)\n", + " k3 = dt*f(u + k2/2)\n", + " k4 = dt*f(u + k3)\n", + " \n", + " return u + 1/6*(k1 + 2*k2 + 2*k3 + k4)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, "metadata": { "collapsed": true }, @@ -335,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 26, "metadata": { "collapsed": true }, @@ -364,9 +381,9 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 27, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ @@ -384,7 +401,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 29, "metadata": { "collapsed": false }, @@ -1156,7 +1173,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1178,7 +1195,7 @@ "x2 = numpy.zeros((N,2))\n", "x2[0] = x_init.copy()\n", "for n in range(N-1):\n", - " x2[n+1] = RK4(x2[n], f_harmonic_oscillator, dt)\n", + " x2[n+1] = RK4_2(x2[n], f_harmonic_oscillator, dt)\n", "\n", "#Symplectic Integration\n", "x3 = numpy.zeros((N,2))\n", From ccadd6271ee5a0a43a7f47fa64b9122e7cc48e6b Mon Sep 17 00:00:00 2001 From: rbds Date: Thu, 10 Dec 2015 13:59:41 +0000 Subject: [PATCH 47/61] adding in RK2 --- ...on Techniques for Mechanical Systems.ipynb | 2702 ++++++++++++++++- 1 file changed, 2590 insertions(+), 112 deletions(-) diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index a67690c..26a16a3 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -243,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 45, "metadata": { "collapsed": false }, @@ -261,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 46, "metadata": { "collapsed": true }, @@ -284,13 +284,13 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 47, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def RK4(u, f, dt):\n", + "def RK2(u, f, dt):\n", " \"\"\"Runge Kutta fourth order integration method\n", " \n", " Parameters:\n", @@ -301,23 +301,30 @@ " Returns: \n", " state values at next time step.\n", " \"\"\"\n", - " k1 = f(u)\n", - " k2 = f(u) + 0.5*dt*k1\n", - " k3 = f(u) + 0.5*dt*k2\n", - " k4 = f(u) + dt*k3\n", - " \n", - " return u + dt/6*(k1+2*k2+2*k3+k4)" + " k1 = u + 0.5*dt*f(u)\n", + " k2 = u + dt*f(k1)\n", + " return k2" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 48, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def RK4_2(u, f, dt):\n", + "def RK4(u, f, dt):\n", + " \"\"\"Runge Kutta fourth order integration method\n", + " \n", + " Parameters:\n", + " u - state of the system at time t\n", + " f - function for RHS of state equations\n", + " dt - time step\n", + " \n", + " Returns: \n", + " state values at next time step.\n", + " \"\"\"\n", " k1 = dt*f(u)\n", " k2 = dt*f(u+ k1/2)\n", " k3 = dt*f(u + k2/2)\n", @@ -328,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 49, "metadata": { "collapsed": true }, @@ -352,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 50, "metadata": { "collapsed": true }, @@ -381,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 51, "metadata": { "collapsed": false }, @@ -401,7 +408,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 52, "metadata": { "collapsed": false }, @@ -1173,7 +1180,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1191,17 +1198,25 @@ " x1[n+1] = euler(x1[n], f_harmonic_oscillator, dt)\n", " #print(q1[n])\n", " \n", - "#Runge-Kutta Integration\n", + "#Runge-Kutta Integration Second Order\n", "x2 = numpy.zeros((N,2))\n", "x2[0] = x_init.copy()\n", "for n in range(N-1):\n", - " x2[n+1] = RK4_2(x2[n], f_harmonic_oscillator, dt)\n", + " x2[n+1] = RK2(x2[n], f_harmonic_oscillator, dt)\n", "\n", + "\n", + "#Runge-Kutta Integration Fourth Order\n", + "x4 = numpy.zeros((N,2))\n", + "x4[0] = x_init.copy()\n", + "for n in range(N-1):\n", + " x4[n+1] = RK4(x4[n], f_harmonic_oscillator, dt)\n", + " \n", + " \n", "#Symplectic Integration\n", - "x3 = numpy.zeros((N,2))\n", - "x3[0] = x_init_H.copy()\n", + "xv = numpy.zeros((N,2))\n", + "xv[0] = x_init_H.copy()\n", "for n in range(N-1):\n", - " x3[n+1] = verlet_SHM(x3[n], dt)\n", + " xv[n+1] = verlet_SHM(xv[n], dt)\n", "\n", "#Analytical Solution \n", "A = x0 #with no forcing and no initial velocity, the max amplitude is equal to initial amplitude\n", @@ -1217,8 +1232,9 @@ "pyplot.ylabel(r'position (meters)', fontsize=18);\n", "pyplot.title('Harmonic oscillator position');\n", "pyplot.plot(t, x1[:,0], lw=2, label='Euler');\n", - "pyplot.plot(t, x2[:,0], 'r-', lw=2, label='RK4');\n", - "pyplot.plot(t, x3[:,0], 'g', lw=2, label='Verlet');\n", + "pyplot.plot(t, x2[:,0], 'r', lw=2, label='RK2');\n", + "pyplot.plot(t, x4[:,0], 'c', lw=2, label='RK4');\n", + "pyplot.plot(t, xv[:,0], 'g', lw=2, label='Verlet');\n", "pyplot.plot(t, x_analytical, 'k--', lw=2, label='analytical');\n", "pyplot.legend();" ] @@ -1227,7 +1243,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If we look closely at the plot, we can see that Euler and Runge-Kutta integration are close to each other, but begin to diverge as time goes on. This should be the behavior we expect for the integration of a non-linear system. But how close are these methods to the analytical solution? Does Verlet do any better? In this case it is obvious that Euler and Runge-Kutta are not correct (and possibly unstable). One thing to check is whether the behavior changes as we change our time step size. Maybe the timestep is too large to capture some aspect of the system response." + "If we look closely at the plot, we can see that Verlet and Runge-Kutta integration are close to each other, but something is clearly off with Euler's method. TBut how close are these methods to the analytical solution? Does Verlet do any better than we expect? In this case it is obvious that Euler is not correct (and possibly unstable). One thing to check is whether the behavior changes as we change our time step size. Maybe the timestep is too large to capture some aspect of the system response." ] }, { @@ -1240,7 +1256,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 53, "metadata": { "collapsed": true }, @@ -1263,28 +1279,30 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 55, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ "dt_values = numpy.array([0.1, 0.05, 0.01, 0.005, 0.001, 0.0001])\n", "\n", "Euler_error_values = numpy.zeros_like(dt_values)\n", - "RK_error_values = Euler_error_values.copy()\n", + "RK2_error_values = Euler_error_values.copy()\n", + "RK4_error_values = Euler_error_values.copy()\n", "Verlet_error_values = Euler_error_values.copy()\n", "for i, dt in enumerate(dt_values):\n", " ###Call error function\n", " Euler_error_values[i] = get_error(x1[:,0], x_analytical, dt)\n", - " RK_error_values[i] = get_error(x2[:,0], x_analytical, dt)\n", - " Verlet_error_values[i] = get_error(x3[:,0], x_analytical, dt)\n", + " RK2_error_values[i] = get_error(x2[:,0], x_analytical, dt)\n", + " RK4_error_values[i] = get_error(x4[:,0], x_analytical, dt)\n", + " Verlet_error_values[i] = get_error(xv[:,0], x_analytical, dt)\n", " \n" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 56, "metadata": { "collapsed": false }, @@ -2056,7 +2074,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2073,7 +2091,8 @@ "pyplot.xlabel('$\\Delta t$', fontsize=16)\n", "pyplot.ylabel('Error', fontsize=16)\n", "pyplot.loglog(dt_values, Euler_error_values, 'bo-', label='Euler')\n", - "pyplot.loglog(dt_values, RK_error_values, 'ro-', label='RK4')\n", + "pyplot.loglog(dt_values, RK2_error_values, 'ro-', label='RK2')\n", + "pyplot.loglog(dt_values, RK4_error_values, 'co-', label='RK4')\n", "pyplot.loglog(dt_values, Verlet_error_values, 'go-', label='Symplectic')\n", "pyplot.axis('equal')\n", "pyplot.legend();" @@ -2087,83 +2106,31 @@ "\n", "![Image](figures/combined_response.png)\n", "\n", - "![Image](figures/error_combined.png)\n" + "![Image](figures/error_combined.png)\n", + " # FIX THESE IMAGES" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Despite the numerical errors, ordinarily we don't see a second order scheme (Verlet) outperforming a fourth order scheme (RK4) by three orders of magnitude. What is going on?\n", - "\n", - "One thing we notice in the plots of position is that for Euler and Runge-Kutta, the spring is stretched farther and farther each oscillation. This means it has more and more potential energy. With no outside forces adding energy to the system and no friction to remove energy, Conservation of Energy says that value should be constant! Let's take a look at total energy in the system. Since we derived equations of motion from the Lagrangian, this should be easy! All we need to do is calculate kinetic and potential energy at each time step, and we already have the information we need to do this. " - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def get_Energy(u):\n", - " \"\"\" Calculates total energu in the system at given time step for a simple harmonic oscillator\n", - " Parameters:\n", - " u - state of system [pos, vel]\n", - " Returns:\n", - " total energy in system.\n", - " \"\"\"\n", - " pos = u[0]\n", - " vel = u[1]\n", - " T = 0.5*m*vel**2\n", - " V = 0.5*k*pos**2\n", - " \n", - " return T+V" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "#Euler total energy\n", - "Euler_energy = numpy.zeros_like(t)\n", - "for i in range(N):\n", - " Euler_energy[i] = get_Energy(x1[i,:])\n", - " \n", - "\n", - "#RK4 total energy\n", - "RK4_energy = numpy.zeros_like(t)\n", - "for i in range(N):\n", - " RK4_energy[i] = get_Energy(x2[i,:])\n", - "\n", - "#Verlet total energy\n", - "#Here we will convert from momentum to velocity so we can use the same get_energy function.\n", - "#Momemtum (q) is mass*velocity.\n", - "Verlet_energy = numpy.zeros_like(t)\n", - "Verlet_vel = x3[:,1]/m\n", - "for i in range(N):\n", - " Verlet_energy[i] = get_Energy(numpy.array([x3[i,0], Verlet_vel[i]]))\n", - "\n", - "\n", - "#analytic total energy\n", - "analytical_energy = numpy.zeros_like(t)\n", - "anayltical_state = numpy.array([x_analytical, vel_analytical]).T\n", - "for i in range(N):\n", - " analytical_energy[i] = get_Energy(anayltical_state[i,:])" + "Despite the numerical errors, why is one second order scheme (Verlet) outperforming another (RK2) by so much? What is going on? We can check the order of convergence to make sure that these schemes are actually 2nd order." ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 65, "metadata": { "collapsed": false }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/mint/miniconda3/lib/python3.5/site-packages/ipykernel/__main__.py:24: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future\n" + ] + }, { "data": { "application/javascript": [ @@ -2931,7 +2898,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2942,23 +2909,2534 @@ } ], "source": [ - "pyplot.figure(figsize=(8,6));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'Total energy in system (J)', fontsize=18);\n", - "pyplot.title('Energy in Simple Harmonic Oscillator');\n", - "pyplot.plot(t, Euler_energy, lw=2, label='Euler');\n", - "pyplot.plot(t, RK4_energy, 'r-', lw=2, label='RK4');\n", - "pyplot.plot(t, Verlet_energy, 'g', lw=2, label='Verlet');\n", - "pyplot.plot(t, analytical_energy, 'k--', lw=2, label='analytical');\n", - "pyplot.legend();" + "def get_diffgrid(u_current, u_fine, dt):\n", + " \"\"\"Returns the difference between one grid and the fine one using L-1 norm.\n", + " \n", + " Parameters\n", + " ----------\n", + " u_current : array of float\n", + " solution on the current grid.\n", + " u_finest : array of float\n", + " solution on the fine grid.\n", + " dt : float\n", + " time-increment on the current grid.\n", + " \n", + " Returns\n", + " -------\n", + " diffgrid : float\n", + " difference computed in the L-1 norm.\n", + " \"\"\"\n", + " \n", + " N_current = len(u_current[:,0])\n", + " N_fine = len(u_fine[:,0])\n", + " \n", + " grid_size_ratio = numpy.ceil(N_fine/N_current)\n", + " \n", + " diffgrid = dt * numpy.sum( numpy.abs(\\\n", + " u_current[:,0]- u_fine[::grid_size_ratio,0])) \n", + " \n", + " return diffgrid\n", + "\n", + "# use a for-loop to compute the solution on different grids - this code is modified from Numerical MOOC lesson 1 module 4\n", + "dt_values = numpy.array([0.1, 0.05, 0.01, 0.005, 0.001])\n", + "u1_values = numpy.empty_like(dt_values, dtype=numpy.ndarray)\n", + "u2_values = u1_values.copy()\n", + "u4_values = u1_values.copy()\n", + "uv_values = u1_values.copy()\n", + "\n", + "#At each value of dt, integrate the system using 4 different methods from t=0 to T\n", + "for i, dt in enumerate(dt_values):\n", + " N = int(T/dt)+1 \n", + " t = numpy.linspace(0.0, T, N)\n", + " u1 = numpy.empty((N,2))\n", + " u1[0] = numpy.array([x0, xdot0])\n", + " u2 = u1.copy()\n", + " u4 = u1.copy()\n", + " uv = u1.copy()\n", + " for n in range(N-1):\n", + " u1[n+1] = euler(u1[n], f_harmonic_oscillator, dt)\n", + " u2[n+1] = RK2(u2[n], f_harmonic_oscillator, dt)\n", + " u4[n+1] = RK4(u4[n], f_harmonic_oscillator, dt)\n", + " uv[n+1] = verlet_SHM(uv[n], dt)\n", + " # store the value of u related to one grid\n", + " u1_values[i] = u1\n", + " u2_values[i] = u2\n", + " u4_values[i] = u4\n", + " uv_values[i] = uv\n", + " \n", + "# compute diffgrid\n", + "diffgrid1 = numpy.empty_like(dt_values)\n", + "diffgrid2 = diffgrid1.copy()\n", + "diffgrid4 = diffgrid1.copy()\n", + "diffgridv = diffgrid1.copy()\n", + "for i, dt in enumerate(dt_values):\n", + " diffgrid1[i] = get_diffgrid(u1_values[i], u1_values[-1], dt)\n", + " diffgrid2[i] = get_diffgrid(u2_values[i], u2_values[-1], dt)\n", + " diffgrid4[i] = get_diffgrid(u4_values[i], u4_values[-1], dt)\n", + " diffgridv[i] = get_diffgrid(uv_values[i], uv_values[-1], dt)" ] }, { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It looks like Euler and Runge-Kutta integration are adding a significant amount of energy to the system. Our symplectic integrator is more accurate because it seems to preserve energy. Maybe all of that work deriving the equations of motion was worth it! We now know that the Hamiltonian equations do better with Conservation of Energy, and this can make a big difference even in a simple system." + "cell_type": "code", + "execution_count": 70, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "#Verlet integration\n", - "x3_dp = numpy.zeros((N,4)) \n", - "x3_dp[0,:] = x_init_H_dp.copy() #set initial conditions\n", - "#print(q3_dp[0,:])\n", - "for n in range(N-1): #integrate with Euler\n", - " x3_dp[n+1,:] = Verlet_DP(x3_dp[n,:], dt)\n", - " \n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (radians)', fontsize=18);\n", - "pyplot.title('Double Pendulum Verlet Integration');\n", - "pyplot.plot(t, x3_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, x3_dp[:,2], lw=2, label='Joint 2')\n", - "pyplot.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This certainly looks better! It's an interesting result. There is clearly some periodicity, although the motion isn't exactly periodic. But how accurate is this simulation? The short answer is that since we don't have an analytical solution, it is difficult to know exactly. However, there are still some properties of the system that we can look at. For one thing, the total energy in the system should be constant. Let's see if that is the case.\n", - "\n", - "Recall from the analysis above that the total energy is the combined potential and kinetic energy at each time step.\n", - "\n", - "$$\\begin{eqnarray*}\n", - "U &=& -m_1gl_1cos(q_1) - m_2g(l_1cos(q_1) + l_2cos(q_2))\\\\\n", - "T &=& \\frac{p_1^2}{2I_1}+\\frac{m_2p_1^2}{2m_1I_1} + \\left[ \\frac{p_2}{l_2} \\frac{p_1}{m_1l_1}cos(q_2-q_1) + \\frac{p_2^2}{2I_2} \\right]\n", - "\\end{eqnarray*}$$\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 84, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def get_Energy(u):\n", - " \"\"\" Calculates total energu in the system at given time step for a simple harmonic oscillator\n", - " Parameters:\n", - " u - state of system [pos, vel]\n", - " Returns:\n", - " total energy in system.\n", - " \"\"\"\n", - " q1 = u[0]\n", - " p1 = u[1]\n", - " q2 = u[2]\n", - " p2 = u[3]\n", - " \n", - " \n", - " \n", - " V = -m1*g*l1*cos(q1) - m2*g*(l1*cos(q1)+l2*cos(q2))\n", - " T = p1**2/(2*I1)+m2*p1**2/(2*m1*I1) + p1*p2/(m1*l1*l2)*cos(q2-q1)+p2**2/(2*I1)\n", - " #print(T)\n", - " #print(V)\n", - " return T+V" - ] - }, - { - "cell_type": "code", - "execution_count": 85, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "v = poincare(x3_dp, 1, 2*pi, 0, 2) #use states from RK4 integration\n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r'$\\theta_2$', fontsize=18);\n", - "pyplot.ylabel(r'$\\dot{\\theta_2}$', fontsize=18);\n", - "pyplot.title('Poincare Section');\n", - "pyplot.plot(v[0,:], v[1,:], 'bo', lw=2, );" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There isn't a clear pattern here. So, we can say that our system is NOT likely to be asymptotically stable. However, this shouldn't be surprising. If we continued simulating, our system would continue rotating since there is no damping. Let's see what happens when there is some damping in the system. We're going to use RK4 for the following analysis." - ] - }, - { - "cell_type": "code", - "execution_count": 98, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "#To Do:\n", - "# animate motion of double pendulum\n", - "# check through sources" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Damped Double Pendulum\n", - "\n", - "Just like with simple harmonic motion, we can add in a damping term to help remove some of the energy that the integration scheme adds in. In the problem setup, we actually used an abbreviated form of a standard equation. The full equation is here:\n", - "\n", - "\\begin{equation}\n", - "\\textbf{M}(x)\\vec{\\ddot{x}}(t) + \\textbf{V}(x, \\dot x ) \\vec{\\dot{x}} + \\textbf{G(x)} + f(\\dot x) + \\tau_d(t) = \\tau(t)\n", - "\\end{equation}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "where $\\textbf{M, V}$, and $\\textbf{G}$ are defined as above, $f$ is frictional or damping terms, and the $\\tau$ terms represent added torque to the system. Since we aren't adding any torque, the $\\tau$ terms will remain 0. However, we can add in some linear damping by including the frictional term. This requires redefining our RHS function, but no other changes!" - ] - }, - { - "cell_type": "code", - "execution_count": 99, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def f_damped_double_pendulum(u):\n", - " \"\"\"Returns RHS of double pendulum EOM\n", - " \n", - " Parameters:\n", - " q - initial state\n", - " \n", - " Returns:\n", - " RHS - RHS of harmonic oscillator eqn.\n", - " \n", - " \"\"\"\n", - " x1 = u[0]\n", - " x2 = u[1]\n", - " x3 = u[2]\n", - " x4 = u[3]\n", - " \n", - " M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[m2*l1*l2*cos(x2-x1), m2*l2**2]])\n", - " V = numpy.array([[0, -m2*l1*l2*sin(x2-x1)],[-m2*l1*l2*sin(x2-x1), 0]])\n", - " G = numpy.array([[(m1+m2)*g*l1*sin(x1)],[m2*l2*g*sin(x2)]])\n", - " f = c*numpy.array([[x3],[x4]])\n", - " qdd = numpy.linalg.inv(M).dot(V.dot(numpy.array([[x3],[x4]]))+G -f) \n", - " #print(qdd)\n", - " #print(M)\n", - " #print(V.dot(numpy.array([[x3],[x4]])))\n", - " #print(G)\n", - " RHS = numpy.array([[x3, x4, qdd[0], qdd[1]]])\n", - " #print(RHS.T)\n", - " return RHS.T" - ] - }, - { - "cell_type": "code", - "execution_count": 100, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "#We use the same initial conditions as the example without damping, so they aren't redefined here.\n", - "c = 2 #[N/(rad/s)] define some damping\n", - "\n", - "#Euler\n", - "q1d_dp = numpy.zeros((N,4)) \n", - "q1d_dp[0,:] = x_init_dp.copy() #set initial conditions\n", - "for n in range(N-1): #integrate with Euler\n", - " q1d_dp[n+1,:] = euler_DP(q1d_dp[n,:], f_damped_double_pendulum, dt)\n", - " #print(q1[n])\n", - " \n", - "#Runge-Kutta\n", - "q2d_dp = numpy.zeros((N,4)) \n", - "q2d_dp[0,:] = x_init_dp.copy() #set initial conditions\n", - "for n in range(N-1): #integrate with Euler\n", - " q2d_dp[n+1,:] = RK4_DP(q2d_dp[n,:], f_damped_double_pendulum, dt)\n", - " \n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", - "pyplot.title('Double Pendulum Euler');\n", - "pyplot.plot(t, q1d_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, q1d_dp[:,1], lw=2, label='Joint 2')\n", - "pyplot.legend();\n", - "\n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", - "pyplot.title('Double Pendulum RK4');\n", - "pyplot.plot(t, q2d_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, q2d_dp[:,1], lw=2, label='Joint 2')\n", - "pyplot.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It looks like the system is chaotic at first, but becomes periodic as it settles.\n", - "\n", - "Let's do a Poincare section of this plot as well. For brevity, we'll use only the RK4 system response." - ] - }, - { - "cell_type": "code", - "execution_count": 101, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "#Euler Integration\n", - "x1 = numpy.zeros((N,2)) #initialize array\n", - "x1[0] = x_init.copy() #set initial conditions\n", - "for n in range(N-1): #integrate with Euler\n", - " x1[n+1] = euler(x1[n], f_harmonic_oscillator, dt)\n", - " #print(q1[n])\n", - " \n", - "#Runge-Kutta Integration Second Order\n", - "x2 = numpy.zeros((N,2))\n", - "x2[0] = x_init.copy()\n", - "for n in range(N-1):\n", - " x2[n+1] = RK2(x2[n], f_harmonic_oscillator, dt)\n", - "\n", - "\n", - "#Runge-Kutta Integration Fourth Order\n", - "x4 = numpy.zeros((N,2))\n", - "x4[0] = x_init.copy()\n", - "for n in range(N-1):\n", - " x4[n+1] = RK4(x4[n], f_harmonic_oscillator, dt)\n", - " \n", - " \n", - "#Symplectic Integration\n", - "xv = numpy.zeros((N,2))\n", - "xv[0] = x_init_H.copy()\n", - "for n in range(N-1):\n", - " xv[n+1] = verlet_SHM(xv[n], dt)\n", - "\n", - "#Analytical Solution \n", - "A = x0 #with no forcing and no initial velocity, the max amplitude is equal to initial amplitude\n", - "phi = 0\n", - "omega = sqrt(k/m)\n", - "x_analytical = A*numpy.cos(omega*t + phi)\n", - "#analytical velocity:\n", - "vel_analytical = -A*omega*numpy.sin(omega*t + phi)\n", - " \n", - "pyplot.figure(figsize=(11,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", - "pyplot.title('Harmonic oscillator position');\n", - "pyplot.plot(t, x1[:,0], lw=2, label='Euler');\n", - "pyplot.plot(t, x2[:,0], 'r', lw=2, label='RK2');\n", - "pyplot.plot(t, x4[:,0], 'c', lw=2, label='RK4');\n", - "pyplot.plot(t, xv[:,0], 'g', lw=2, label='Verlet');\n", - "pyplot.plot(t, x_analytical, 'k--', lw=2, label='analytical');\n", - "pyplot.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we look closely at the plot, we can see that Verlet and Runge-Kutta integration are close to each other, but something is clearly off with Euler's method. But how close are these methods to the analytical solution? Does Verlet do any better than we expect? In this case it is obvious that Euler is not correct (and possibly unstable). One thing to check is whether the behavior changes as we change our time step size. Maybe the timestep is too large to capture some aspect of the system response." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Error Analysis\n", - "The error analysis code below is based on the numerical methods MOOC notebook on phugoid oscillation. " - ] - }, - { - "cell_type": "code", - "execution_count": 12, + "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def get_error(y, z, dt):\n", - " \"\"\"Returns error relative to analytical solution using L-1 norm.\n", - " \n", + "def verlet_SHM(u, dt):\n", + " \"\"\" Verlet integration for integrating from Hamiltonian equations of motion\n", + " for a simple harmonic oscillator\n", " Parameters:\n", - " y - numerical solution\n", - " z - analytical solution\n", + " u - state at current time step\n", " dt - time step size\n", - " \n", - " Returns:\n", - " error - L_1 norm of the error W.R.T. analytical solution \n", + " Returns: \n", + " state at next time step.\n", " \"\"\"\n", - " err = dt*numpy.sum(numpy.abs(y-z))\n", - " return err" + " pos = u[0]\n", + " mom = u[1]\n", + " \n", + " q_half = pos - dt/2*mom/m\n", + " #p_half = mom + dt/2*(k*pos)\n", + " \n", + " p = mom + dt/2*(2*k*q_half)\n", + " q = q_half - dt/2*p/m\n", + " \n", + " return numpy.array([q, p]) " ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [], "source": [ - "dt_values = numpy.array([0.1, 0.05, 0.01, 0.005, 0.001, 0.0001])\n", + "#Initial Conditions\n", + "x0 = 0.5 #[m]\n", + "xdot0 = 0 #[m/s]\n", "\n", - "Euler_error_values = numpy.zeros_like(dt_values)\n", - "RK2_error_values = Euler_error_values.copy()\n", - "RK4_error_values = Euler_error_values.copy()\n", - "Verlet_error_values = Euler_error_values.copy()\n", - "for i, dt in enumerate(dt_values):\n", - " ###Call error function\n", - " Euler_error_values[i] = get_error(x1[:,0], x_analytical, dt)\n", - " RK2_error_values[i] = get_error(x2[:,0], x_analytical, dt)\n", - " RK4_error_values[i] = get_error(x4[:,0], x_analytical, dt)\n", - " Verlet_error_values[i] = get_error(xv[:,0], x_analytical, dt)\n", - " \n" + "#Equivalent initial conditions for the Hamiltonian system\n", + "q0 = x0\n", + "p0 = m*xdot0\n", + "\n", + "x_init = numpy.array([x0, xdot0])\n", + "x_init_H = numpy.array([q0, p0])" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -2074,7 +1180,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2085,16 +1191,51 @@ } ], "source": [ - "pyplot.figure(figsize=(10,6))\n", - "pyplot.tick_params(axis='both', labelsize=14)\n", - "pyplot.grid(True)\n", - "pyplot.xlabel('$\\Delta t$', fontsize=16)\n", - "pyplot.ylabel('Error', fontsize=16)\n", - "pyplot.loglog(dt_values, Euler_error_values, 'bo-', label='Euler')\n", - "pyplot.loglog(dt_values, RK2_error_values, 'ro-', label='RK2')\n", - "pyplot.loglog(dt_values, RK4_error_values, 'co-', label='RK4')\n", - "pyplot.loglog(dt_values, Verlet_error_values, 'go-', label='Symplectic')\n", - "pyplot.axis('equal')\n", + "#Euler Integration\n", + "x1 = numpy.zeros((N,2)) #initialize array\n", + "x1[0] = x_init.copy() #set initial conditions\n", + "for n in range(N-1): #integrate with Euler\n", + " x1[n+1] = euler(x1[n], f_harmonic_oscillator, dt)\n", + " #print(q1[n])\n", + " \n", + "#Runge-Kutta Integration Second Order\n", + "x2 = numpy.zeros((N,2))\n", + "x2[0] = x_init.copy()\n", + "for n in range(N-1):\n", + " x2[n+1] = RK2(x2[n], f_harmonic_oscillator, dt)\n", + "\n", + "\n", + "#Runge-Kutta Integration Fourth Order\n", + "x4 = numpy.zeros((N,2))\n", + "x4[0] = x_init.copy()\n", + "for n in range(N-1):\n", + " x4[n+1] = RK4(x4[n], f_harmonic_oscillator, dt)\n", + " \n", + " \n", + "#Symplectic Integration\n", + "xv = numpy.zeros((N,2))\n", + "xv[0] = x_init_H.copy()\n", + "for n in range(N-1):\n", + " xv[n+1] = verlet_SHM(xv[n], dt)\n", + "\n", + "#Analytical Solution \n", + "A = x0 #with no forcing and no initial velocity, the max amplitude is equal to initial amplitude\n", + "phi = 0\n", + "omega = sqrt(k/m)\n", + "x_analytical = A*numpy.cos(omega*t + phi)\n", + "#analytical velocity:\n", + "vel_analytical = -A*omega*numpy.sin(omega*t + phi)\n", + " \n", + "pyplot.figure(figsize=(11,8));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.title('Harmonic oscillator position');\n", + "pyplot.plot(t, x1[:,0], lw=2, label='Euler');\n", + "pyplot.plot(t, x2[:,0], 'r', lw=2, label='RK2');\n", + "pyplot.plot(t, x4[:,0], 'c', lw=2, label='RK4');\n", + "pyplot.plot(t, xv[:,0], 'g', lw=2, label='Verlet');\n", + "pyplot.plot(t, x_analytical, 'k--', lw=2, label='analytical');\n", "pyplot.legend();" ] }, @@ -2102,35 +1243,70 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. Don't believe that these are significant? The discretization is the only difference between the first two plots above, and with a smaller time step the response from Runge-Kutta and Euler are greatly improved. To exame precision errors, take a look at the following screenshot, which shows the plots above, built with identical code on two different computers. The responses on the left are from a 64-bit computer, and the responses on the right are from a 32-bit computer. Look closely at the phase shift in the system response. That's a huge difference! Even the precision of the machine can have a significant impact on numerical integration.\n", - "\n", - "![Image](figures/combined_response.png)\n", - "\n", - "![Image](figures/error_combined.png)\n", - " # FIX THESE IMAGES" + "If we look closely at the plot, we can see that Verlet and Runge-Kutta integration are close to each other, but something is clearly off with Euler's method. But how close are these methods to the analytical solution? Does Verlet do any better than we expect? In this case it is obvious that Euler is not correct (and possibly unstable). One thing to check is whether the behavior changes as we change our time step size. Maybe the timestep is too large to capture some aspect of the system response." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Despite the numerical errors, why is one second order scheme (Verlet) outperforming another (RK2) by so much? What is going on? We can check the order of convergence to make sure that these schemes are actually 2nd order." + "### Error Analysis\n", + "The error analysis code below is based on the numerical methods MOOC notebook on phugoid oscillation. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def get_error(y, z, dt):\n", + " \"\"\"Returns error relative to analytical solution using L-1 norm.\n", + " \n", + " Parameters:\n", + " y - numerical solution\n", + " z - analytical solution\n", + " dt - time step size\n", + " \n", + " Returns:\n", + " error - L_1 norm of the error W.R.T. analytical solution \n", + " \"\"\"\n", + " err = dt*numpy.sum(numpy.abs(y-z))\n", + " return err" ] }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dt_values = numpy.array([0.1, 0.05, 0.01, 0.005, 0.001, 0.0001])\n", + "\n", + "Euler_error_values = numpy.empty_like(dt_values)\n", + "RK2_error_values = Euler_error_values.copy()\n", + "RK4_error_values = Euler_error_values.copy()\n", + "Verlet_error_values = Euler_error_values.copy()\n", + "for i, dt in enumerate(dt_values):\n", + " ###Call error function\n", + " Euler_error_values[i] = get_error(x1[:,0], x_analytical, dt)\n", + " RK2_error_values[i] = get_error(x2[:,0], x_analytical, dt)\n", + " RK4_error_values[i] = get_error(x4[:,0], x_analytical, dt)\n", + " Verlet_error_values[i] = get_error(xv[:,0], x_analytical, dt)\n", + " \n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/mint/miniconda3/lib/python3.5/site-packages/ipykernel/__main__.py:24: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future\n" - ] - }, { "data": { "application/javascript": [ @@ -2898,7 +2074,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2908,6 +2084,54 @@ "output_type": "display_data" } ], + "source": [ + "pyplot.figure(figsize=(10,6))\n", + "pyplot.tick_params(axis='both', labelsize=14)\n", + "pyplot.grid(True)\n", + "pyplot.xlabel('$\\Delta t$', fontsize=16)\n", + "pyplot.ylabel('Error', fontsize=16)\n", + "pyplot.loglog(dt_values, Euler_error_values, 'bo-', label='Euler')\n", + "pyplot.loglog(dt_values, RK2_error_values, 'ro-', label='RK2')\n", + "pyplot.loglog(dt_values, RK4_error_values, 'co-', label='RK4')\n", + "pyplot.loglog(dt_values, Verlet_error_values, 'go-', label='Symplectic')\n", + "pyplot.axis('equal')\n", + "pyplot.legend();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. Don't believe that these are significant? The discretization is the only difference between the first two plots above, and with a smaller time step the response from Runge-Kutta and Euler are greatly improved. To exame precision errors, take a look at the following screenshot, which shows the plots above, built with identical code on two different computers. The responses on the left are from a 64-bit computer, and the responses on the right are from a 32-bit computer. Look closely at the phase shift in the system response. That's a huge difference! Even the precision of the machine can have a significant impact on numerical integration.\n", + "\n", + "![Image](figures/combined_response.png)\n", + "\n", + "![Image](figures/error_combined.png)\n", + " # FIX THESE IMAGES" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Despite the numerical errors, why is one second order scheme (Verlet) outperforming another (RK2) by so much? What is going on? We can check the order of convergence to make sure that these schemes are actually 2nd order." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/mint/miniconda3/lib/python3.5/site-packages/ipykernel/__main__.py:24: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future\n" + ] + } + ], "source": [ "def get_diffgrid(u_current, u_fine, dt):\n", " \"\"\"Returns the difference between one grid and the fine one using L-1 norm.\n", @@ -2978,7 +2202,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -3791,7 +3015,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 22, "metadata": { "collapsed": true }, @@ -3814,7 +3038,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 24, "metadata": { "collapsed": false }, @@ -3834,7 +3058,7 @@ "#Verlet total energy\n", "#Here we will convert from momentum to velocity so we can use the same get_energy function.\n", "#Momemtum (q) is mass*velocity.\n", - "Verlet_vel = x3[:,1]/m\n", + "Verlet_vel = xv[:,1]/m\n", "#analytic total energy\n", "analytical_energy = numpy.zeros_like(t)\n", "anayltical_state = numpy.array([x_analytical, vel_analytical]).T\n", @@ -3849,7 +3073,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 25, "metadata": { "collapsed": false }, @@ -5501,7 +4725,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 26, "metadata": { "collapsed": true }, @@ -5525,7 +4749,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 27, "metadata": { "collapsed": true }, @@ -5554,7 +4778,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 28, "metadata": { "collapsed": true }, @@ -5585,7 +4809,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 29, "metadata": { "collapsed": false }, @@ -6415,7 +5639,7 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 30, "metadata": { "collapsed": false }, @@ -7247,7 +6471,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 31, "metadata": { "collapsed": false }, @@ -8019,7 +7243,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -8094,166 +7318,11 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 90, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# This cell loads the style of the notebook, which is modified from the \n", "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", From 6f6710397b52ba94ea7ed664b16039d8c9a6092e Mon Sep 17 00:00:00 2001 From: rbds Date: Thu, 10 Dec 2015 16:27:54 +0000 Subject: [PATCH 51/61] update DP notebook - check EOM again --- randy_schur/Double_Pendulum_Problem.ipynb | 5424 ++++++++++++++++- ...on Techniques for Mechanical Systems.ipynb | 12 +- 2 files changed, 5136 insertions(+), 300 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index f26a4a8..840ad08 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -266,7 +266,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 15, "metadata": { "collapsed": false }, @@ -304,7 +304,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 16, "metadata": { "collapsed": true }, @@ -329,7 +329,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -357,7 +357,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -1960,7 +1960,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -1993,287 +1993,4190 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "#Verlet integration\n", - "x3_dp = numpy.zeros((N,4)) \n", - "x3_dp[0,:] = x_init_H_dp.copy() #set initial conditions\n", - "#print(q3_dp[0,:])\n", - "for n in range(N-1): #integrate with Euler\n", - " x3_dp[n+1,:] = Verlet_DP(x3_dp[n,:], dt)\n", - " \n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (radians)', fontsize=18);\n", - "pyplot.title('Double Pendulum Verlet Integration');\n", - "pyplot.plot(t, x3_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, x3_dp[:,2], lw=2, label='Joint 2')\n", - "pyplot.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This certainly looks better! It's an interesting result. There is clearly some periodicity, although the motion isn't exactly periodic. But how accurate is this simulation? The short answer is that since we don't have an analytical solution, it is difficult to know exactly. However, there are still some properties of the system that we can look at. For one thing, the total energy in the system should be constant. Let's see if that is the case.\n", - "\n", - "Recall from the analysis above that the total energy is the combined potential and kinetic energy at each time step.\n", - "\n", - "$$\\begin{eqnarray*}\n", - "U &=& -m_1gl_1cos(q_1) - m_2g(l_1cos(q_1) + l_2cos(q_2))\\\\\n", - "T &=& \\frac{p_1^2}{2I_1}+\\frac{m_2p_1^2}{2m_1I_1} + \\left[ \\frac{p_2}{l_2} \\frac{p_1}{m_1l_1}cos(q_2-q_1) + \\frac{p_2^2}{2I_2} \\right]\n", - "\\end{eqnarray*}$$\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def get_Energy(u):\n", - " \"\"\" Calculates total energu in the system at given time step for a simple harmonic oscillator\n", - " Parameters:\n", - " u - state of system [pos, vel]\n", - " Returns:\n", - " total energy in system.\n", - " \"\"\"\n", - " q1 = u[0]\n", - " p1 = u[1]\n", - " q2 = u[2]\n", - " p2 = u[3]\n", - " \n", - " \n", - " \n", - " V = -m1*g*l1*cos(q1) - m2*g*(l1*cos(q1)+l2*cos(q2))\n", - " T = p1**2/(2*I1)+m2*p1**2/(2*m1*I1) + p1*p2/(m1*l1*l2)*cos(q2-q1)+p2**2/(2*I1)\n", - " #print(T)\n", - " #print(V)\n", - " return T+V" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "Verlet_energy = numpy.zeros_like(t)\n", - "Verlet_vel1 = x3_dp[:,1]/m1\n", - "Verlet_vel2 = x3_dp[:,3]/m2\n", - "\n", - "\n", - "u = numpy.array([x3_dp[:,0], Verlet_vel1, x3_dp[:,2], Verlet_vel2])\n", - "#print(u[0,:])\n", - "for i in range(N):\n", - " Verlet_energy[i] = get_Energy(u[:,i])\n", - "\n", - "pyplot.figure(figsize=(8,6));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'Total energy in system', fontsize=18);\n", - "pyplot.title('Energy in System');\n", - "pyplot.plot(t, Verlet_energy, lw=2, label='Verlet');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So, we are very far from perfect. But, at least the energy in the system is stable, even if it isn't constant! " - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "### Investigating system behavior\n", - "\n", - "In order to investigate system behavior for damped harmonic motion, we used a phase portrait where we plotted position vs. velocity. In this system, we have 2 positions and 2 velocities, so a phase plot won't accurately capture the behavior of the system. Instead we are going to use something called a Poincare section (https://en.wikipedia.org/wiki/Poincar%C3%A9_map). The way these work is to capture the system behavior at a specific, repetetive point. For example, each time that $\\theta_2 = 0$ or any multiple of 2*pi (mass 2 is hanging straight down) we can capture the angular position and velocity of mass 2. This way, we are taking a 'snapshot' of the system, and plotting the state each time. We can now investigate system behavior on a simple 2D plot, similar to the phase portrait." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def poincare(state, sf, val, s1, s2):\n", - " '''Creates a poincare section, capturing system state each time q[state]=val\n", - " \n", - " Parameters:\n", - " state - state vector\n", - " sf - the row in q to fix\n", - " val - the value to capture state at \n", - " s1 - state to capture 1\n", - " s2 - state to capture 2\n", - " \n", - " Returns:\n", - " v - array with information for poincare section\n", - " '''\n", - " v = numpy.zeros((2,1))\n", - " for i in range(len(state[:,sf])-1):\n", - " if (state[i,sf]>val):\n", - " if (state[i+1,sf] < val):\n", - " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", - " \n", - " if (state[i,sf] < val):\n", - " if (state[i+1,sf] > val):\n", - " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", - " \n", - " return v" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "v = poincare(x3_dp, 1, 2*pi, 0, 2) #use states from RK4 integration\n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r'$\\theta_2$', fontsize=18);\n", - "pyplot.ylabel(r'$\\dot{\\theta_2}$', fontsize=18);\n", - "pyplot.title('Poincare Section');\n", - "pyplot.plot(v[0,:], v[1,:], 'bo', lw=2, );" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There isn't a clear pattern here. So, we can say that our system is NOT likely to be asymptotically stable. However, this shouldn't be surprising. If we continued simulating, our system would continue rotating since there is no damping. Let's see what happens when there is some damping in the system. We're going to use RK4 for the following analysis." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "#To Do:\n", - "# animate motion of double pendulum\n", - "# check through sources" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Damped Double Pendulum\n", - "\n", - "Just like with simple harmonic motion, we can add in a damping term to help remove some of the energy that the integration scheme adds in. In the problem setup, we actually used an abbreviated form of a standard equation. The full equation is here:\n", - "\n", - "\\begin{equation}\n", - "\\textbf{M}(x)\\vec{\\ddot{x}}(t) + \\textbf{V}(x, \\dot x ) \\vec{\\dot{x}} + \\textbf{G(x)} + f(\\dot x) + \\tau_d(t) = \\tau(t)\n", - "\\end{equation}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "where $\\textbf{M, V}$, and $\\textbf{G}$ are defined as above, $f$ is frictional or damping terms, and the $\\tau$ terms represent added torque to the system. Since we aren't adding any torque, the $\\tau$ terms will remain 0. However, we can add in some linear damping by including the frictional term. This requires redefining our RHS function, but no other changes!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def f_damped_double_pendulum(u):\n", - " \"\"\"Returns RHS of double pendulum EOM\n", - " \n", - " Parameters:\n", - " q - initial state\n", - " \n", - " Returns:\n", - " RHS - RHS of harmonic oscillator eqn.\n", - " \n", - " \"\"\"\n", - " x1 = u[0]\n", - " x2 = u[1]\n", - " x3 = u[2]\n", - " x4 = u[3]\n", - " \n", - " M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[m2*l1*l2*cos(x2-x1), m2*l2**2]])\n", - " V = numpy.array([[0, -m2*l1*l2*sin(x2-x1)],[-m2*l1*l2*sin(x2-x1), 0]])\n", - " G = numpy.array([[(m1+m2)*g*l1*sin(x1)],[m2*l2*g*sin(x2)]])\n", - " f = c*numpy.array([[x3],[x4]])\n", - " qdd = numpy.linalg.inv(M).dot(V.dot(numpy.array([[x3],[x4]]))+G -f) \n", - " #print(qdd)\n", - " #print(M)\n", - " #print(V.dot(numpy.array([[x3],[x4]])))\n", - " #print(G)\n", - " RHS = numpy.array([[x3, x4, qdd[0], qdd[1]]])\n", - " #print(RHS.T)\n", - " return RHS.T" - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 32, "metadata": { "collapsed": false }, - "outputs": [], - "source": [ - "#We use the same initial conditions as the example without damping, so they aren't redefined here.\n", - "c = 2 #[N/(rad/s)] define some damping\n", - "\n", - "#Euler\n", - "q1d_dp = numpy.zeros((N,4)) \n", - "q1d_dp[0,:] = x_init_dp.copy() #set initial conditions\n", - "for n in range(N-1): #integrate with Euler\n", - " q1d_dp[n+1,:] = euler_DP(q1d_dp[n,:], f_damped_double_pendulum, dt)\n", - " #print(q1[n])\n", - " \n", - "#Runge-Kutta\n", - "q2d_dp = numpy.zeros((N,4)) \n", - "q2d_dp[0,:] = x_init_dp.copy() #set initial conditions\n", - "for n in range(N-1): #integrate with Euler\n", - " q2d_dp[n+1,:] = RK4_DP(q2d_dp[n,:], f_damped_double_pendulum, dt)\n", - " \n", - "pyplot.figure(figsize=(10,8));\n", - "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", - "pyplot.title('Double Pendulum Euler');\n", - "pyplot.plot(t, q1d_dp[:,0], lw=2, label='Joint 1');\n", - "pyplot.plot(t, q1d_dp[:,1], lw=2, label='Joint 2')\n", - "pyplot.legend();\n", - "\n", - "pyplot.figure(figsize=(10,8));\n", + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Verlet_energy = numpy.zeros_like(t)\n", + "Verlet_vel1 = xv_dp[:,1]/m1\n", + "Verlet_vel2 = xv_dp[:,3]/m2\n", + "\n", + "\n", + "u = numpy.array([xv_dp[:,0], Verlet_vel1, xv_dp[:,2], Verlet_vel2])\n", + "#print(u[0,:])\n", + "for i in range(N):\n", + " Verlet_energy[i] = get_Energy(u[:,i])\n", + "\n", + "pyplot.figure(figsize=(8,6));\n", + "pyplot.grid(True);\n", + "pyplot.xlabel(r't', fontsize=18);\n", + "pyplot.ylabel(r'Total energy in system', fontsize=18);\n", + "pyplot.title('Energy in System');\n", + "pyplot.plot(t, Verlet_energy, lw=2, label='Verlet');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, we are very far from perfect. But, at least the energy in the system is stable, even if it isn't constant! " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "### Investigating system behavior\n", + "\n", + "In order to investigate system behavior for damped harmonic motion, we used a phase portrait where we plotted position vs. velocity. In this system, we have 2 positions and 2 velocities, so a phase plot won't accurately capture the behavior of the system. Instead we are going to use something called a Poincare section (https://en.wikipedia.org/wiki/Poincar%C3%A9_map). The way these work is to capture the system behavior at a specific, repetetive point. For example, each time that $\\theta_2 = 0$ or any multiple of 2*pi (mass 2 is hanging straight down) we can capture the angular position and velocity of mass 2. This way, we are taking a 'snapshot' of the system, and plotting the state each time. We can now investigate system behavior on a simple 2D plot, similar to the phase portrait." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def poincare(state, sf, val, s1, s2):\n", + " '''Creates a poincare section, capturing system state each time q[state]=val\n", + " \n", + " Parameters:\n", + " state - state vector\n", + " sf - the row in q to fix\n", + " val - the value to capture state at \n", + " s1 - state to capture 1\n", + " s2 - state to capture 2\n", + " \n", + " Returns:\n", + " v - array with information for poincare section\n", + " '''\n", + " v = numpy.zeros((2,1))\n", + " for i in range(len(state[:,sf])-1):\n", + " if (state[i,sf]>val):\n", + " if (state[i+1,sf] < val):\n", + " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", + " \n", + " if (state[i,sf] < val):\n", + " if (state[i+1,sf] > val):\n", + " v = numpy.append(v, [[state[i, s1]],[state[i, s2]]], axis=1)\n", + " \n", + " return v" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "v = poincare(q2d_dp, 0, 0, 1, 3) #use states from RK4 integration\n", + "v = poincare(q4d_dp, 0, 0, 1, 3) #use states from RK4 integration\n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r'$\\theta_2$', fontsize=18);\n", @@ -2314,7 +7004,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 52, "metadata": { "collapsed": false }, @@ -2360,11 +7050,166 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 53, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# This cell loads the style of the notebook, which is modified from the \n", "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", @@ -2373,15 +7218,6 @@ "css_file = './numericalmoocstyle.css'\n", "HTML(open(css_file, \"r\").read())" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index 78cd23b..6f7e3b3 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -308,7 +308,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 32, "metadata": { "collapsed": true }, @@ -335,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 33, "metadata": { "collapsed": true }, @@ -359,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 34, "metadata": { "collapsed": true }, @@ -388,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 35, "metadata": { "collapsed": false }, @@ -408,7 +408,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 36, "metadata": { "collapsed": false }, @@ -1180,7 +1180,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" From 6ab30c3dc2f53dbd5d132e6b28300fac2b71608d Mon Sep 17 00:00:00 2001 From: rbds Date: Thu, 10 Dec 2015 20:22:05 +0000 Subject: [PATCH 52/61] RK4 Double Pendulum adding energy --- randy_schur/Double_Pendulum_Problem.ipynb | 118 +++++----- ...on Techniques for Mechanical Systems.ipynb | 206 ++++++++++++++++-- 2 files changed, 246 insertions(+), 78 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 840ad08..8698c1f 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 63, "metadata": { "collapsed": false }, @@ -256,8 +256,10 @@ " x4 = u[3]\n", " #print(x1)\n", " \n", - " M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[m2*l1*l2*cos(x2-x1), m2*l2**2]])\n", - " V = numpy.array([[0, -m2*l1*l2*sin(x2-x1)],[-m2*l1*l2*sin(x2-x1), 0]])\n", + " #M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[m2*l1*l2*cos(x2-x1), m2*l2**2]])\n", + " M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x1-x2)],[m2*l1*l2*cos(x1-x2), m2*l2**2]])\n", + " #V = numpy.array([[0, -m2*l1*l2*sin(x2-x1)],[-m2*l1*l2*sin(x2-x1), 0]])\n", + " V = numpy.array([[0, -m2*l1*l2*sin(x1-x2)],[-m2*l1*l2*sin(x1-x2), 0]])\n", " G = numpy.array([[(m1+m2)*g*l1*sin(x1)],[m2*l2*g*sin(x2)]])\n", " qdd = numpy.linalg.inv(M).dot(V.dot(numpy.array([[x3],[x4]])) +G) #- 2*numpy.array([[x3],[x4]])) \n", " RHS = numpy.array([x3, x4, qdd[0], qdd[1]])\n", @@ -266,7 +268,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 64, "metadata": { "collapsed": false }, @@ -281,7 +283,7 @@ "I1 = m1*l1**2\n", "I2 = m2*l2**2\n", "\n", - "T = 25; #[seconds]\n", + "T = 50; #[seconds]\n", "dt = .01; #\n", "N = int(T/dt)+1\n", "t = numpy.linspace(0.0, T, N)\n", @@ -304,7 +306,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 65, "metadata": { "collapsed": true }, @@ -329,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 66, "metadata": { "collapsed": false }, @@ -357,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 67, "metadata": { "collapsed": false }, @@ -1129,7 +1131,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1905,7 +1907,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1960,7 +1962,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 47, "metadata": { "collapsed": false }, @@ -1993,7 +1995,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 48, "metadata": { "collapsed": false }, @@ -2765,7 +2767,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2781,7 +2783,7 @@ "xv_dp[0,:] = x_init_H_dp.copy() #set initial conditions\n", "#print(q3_dp[0,:])\n", "for n in range(N-1): #integrate with Euler\n", - " xv_dp[n+1,:] = Verlet_DP(x3_dp[n,:], dt)\n", + " xv_dp[n+1,:] = Verlet_DP(xv_dp[n,:], dt)\n", " \n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", @@ -2810,7 +2812,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 49, "metadata": { "collapsed": true }, @@ -2839,7 +2841,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 70, "metadata": { "collapsed": false }, @@ -3611,7 +3613,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3619,25 +3621,40 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "Verlet_energy = numpy.zeros_like(t)\n", "Verlet_vel1 = xv_dp[:,1]/m1\n", "Verlet_vel2 = xv_dp[:,3]/m2\n", + "RK4_energy = numpy.zeros_like(t)\n", "\n", "\n", "u = numpy.array([xv_dp[:,0], Verlet_vel1, xv_dp[:,2], Verlet_vel2])\n", "#print(u[0,:])\n", "for i in range(N):\n", " Verlet_energy[i] = get_Energy(u[:,i])\n", + " RK4_energy[i] = get_Energy(x4_dp[i,:])\n", + "\n", + " \n", "\n", - "pyplot.figure(figsize=(8,6));\n", + "pyplot.figure(figsize=(11,6));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'Total energy in system', fontsize=18);\n", "pyplot.title('Energy in System');\n", - "pyplot.plot(t, Verlet_energy, lw=2, label='Verlet');" + "pyplot.plot(t, Verlet_energy, lw=2, label='Verlet');\n", + "pyplot.plot(t, RK4_energy, 'r', lw=2, label='RK4')" ] }, { @@ -3660,7 +3677,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 51, "metadata": { "collapsed": false }, @@ -3694,7 +3711,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 52, "metadata": { "collapsed": false }, @@ -4466,7 +4483,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4490,12 +4507,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There isn't a clear pattern here. So, we can say that our system is NOT likely to be asymptotically stable. However, this shouldn't be surprising. If we continued simulating, our system would continue rotating since there is no damping. Let's see what happens when there is some damping in the system. We're going to use RK4 for the following analysis." + "There isn't a clear pattern here, although there is some clustering. So, we can say that our system is NOT likely to be asymptotically stable. However, this shouldn't be surprising. If we continued simulating, our system would continue rotating since there is no damping. Let's see what happens when there is some damping in the system. We're going to use RK4 for the following analysis." ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 53, "metadata": { "collapsed": true }, @@ -4530,7 +4547,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 54, "metadata": { "collapsed": false }, @@ -4564,29 +4581,7 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - " #print(u)\n", - " x1 = u[0]\n", - " x2 = u[1]\n", - " x3 = u[2]\n", - " x4 = u[3]\n", - " #print(x1)\n", - " \n", - " M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x2-x1)],[m2*l1*l2*cos(x2-x1), m2*l2**2]])\n", - " V = numpy.array([[0, -m2*l1*l2*sin(x2-x1)],[-m2*l1*l2*sin(x2-x1), 0]])\n", - " G = numpy.array([[(m1+m2)*g*l1*sin(x1)],[m2*l2*g*sin(x2)]])\n", - " qdd = numpy.linalg.inv(M).dot(V.dot(numpy.array([[x3],[x4]])) +G) #- 2*numpy.array([[x3],[x4]])) \n", - " RHS = numpy.array([x3, x4, qdd[0], qdd[1]])" - ] - }, - { - "cell_type": "code", - "execution_count": 50, + "execution_count": 55, "metadata": { "collapsed": false }, @@ -5358,7 +5353,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -6134,7 +6129,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -6191,7 +6186,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 56, "metadata": { "collapsed": false }, @@ -6963,7 +6958,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -6975,10 +6970,10 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 51, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -7004,7 +6999,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 41, "metadata": { "collapsed": false }, @@ -7050,7 +7045,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 42, "metadata": { "collapsed": false }, @@ -7205,7 +7200,7 @@ "" ] }, - "execution_count": 53, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -7218,6 +7213,15 @@ "css_file = './numericalmoocstyle.css'\n", "HTML(open(css_file, \"r\").read())" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration Techniques for Mechanical Systems.ipynb index 6f7e3b3..927c75b 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration Techniques for Mechanical Systems.ipynb @@ -308,7 +308,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -335,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -359,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -388,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -408,7 +408,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -1180,7 +1180,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1279,7 +1279,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -1302,7 +1302,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -2119,7 +2119,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -2202,7 +2202,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -3015,7 +3015,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 17, "metadata": { "collapsed": true }, @@ -3038,7 +3038,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -3073,7 +3073,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -4725,7 +4725,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 20, "metadata": { "collapsed": true }, @@ -4749,7 +4749,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 21, "metadata": { "collapsed": true }, @@ -4778,7 +4778,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 22, "metadata": { "collapsed": true }, @@ -4809,7 +4809,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -5639,7 +5639,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 24, "metadata": { "collapsed": false }, @@ -6471,7 +6471,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 25, "metadata": { "collapsed": false }, @@ -7318,11 +7318,166 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# This cell loads the style of the notebook, which is modified from the \n", "# Numerical Methods in Python Course: http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about\n", @@ -7332,6 +7487,15 @@ "HTML(open(css_file, \"r\").read())" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, From b0d17c9d478cf41afa529f5b91d25b80670fe740 Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 13 Dec 2015 22:01:50 -0500 Subject: [PATCH 53/61] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e39f9a5..9cab8a3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # assignment-bank-2015 For submission of class projects at GW, Fall 2015 + +The code in this repository is under Creative Commons Attribution license CC-BY 4.0, code under MIT license © 2015 Randall Schur From e8e43bccae0475822fbdb17c4cfd6f0af61bf163 Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 13 Dec 2015 22:02:51 -0500 Subject: [PATCH 54/61] edit wording --- randy_schur/Double_Pendulum_Problem.ipynb | 120 +++++------------- ...n_Techniques_for_Mechanical_Systems.ipynb} | 52 ++++---- 2 files changed, 61 insertions(+), 111 deletions(-) rename randy_schur/{Integration Techniques for Mechanical Systems.ipynb => Integration_Techniques_for_Mechanical_Systems.ipynb} (99%) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 8698c1f..2c51bba 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -268,7 +268,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -306,7 +306,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -331,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -359,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -909,7 +909,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1685,7 +1685,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1962,7 +1962,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -1995,7 +1995,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -2545,7 +2545,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -2812,7 +2812,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 24, "metadata": { "collapsed": true }, @@ -2841,7 +2841,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 25, "metadata": { "collapsed": false }, @@ -3391,7 +3391,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -3613,7 +3613,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3621,16 +3621,6 @@ }, "metadata": {}, "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 70, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -3654,7 +3644,8 @@ "pyplot.ylabel(r'Total energy in system', fontsize=18);\n", "pyplot.title('Energy in System');\n", "pyplot.plot(t, Verlet_energy, lw=2, label='Verlet');\n", - "pyplot.plot(t, RK4_energy, 'r', lw=2, label='RK4')" + "pyplot.plot(t, RK4_energy, 'r', lw=2, label='RK4')\n", + "pyplot.legend();" ] }, { @@ -3677,7 +3668,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 26, "metadata": { "collapsed": false }, @@ -3711,7 +3702,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 27, "metadata": { "collapsed": false }, @@ -4261,7 +4252,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4510,19 +4501,6 @@ "There isn't a clear pattern here, although there is some clustering. So, we can say that our system is NOT likely to be asymptotically stable. However, this shouldn't be surprising. If we continued simulating, our system would continue rotating since there is no damping. Let's see what happens when there is some damping in the system. We're going to use RK4 for the following analysis." ] }, - { - "cell_type": "code", - "execution_count": 53, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "#To Do:\n", - "# animate motion of double pendulum\n", - "# check through sources" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -4547,7 +4525,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 29, "metadata": { "collapsed": false }, @@ -4573,15 +4551,13 @@ " G = numpy.array([[(m1+m2)*g*l1*sin(x1)],[m2*l2*g*sin(x2)]])\n", " f = c*numpy.array([[x3],[x4]])\n", " qdd = numpy.linalg.inv(M).dot(V.dot(numpy.array([[x3],[x4]]))+G -f) \n", - " #print(qdd)\n", " RHS = numpy.array([x3, x4, qdd[0], qdd[1]])\n", - " #print(RHS.T)\n", " return RHS.T" ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 31, "metadata": { "collapsed": false }, @@ -5131,7 +5107,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -5353,7 +5329,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5907,7 +5883,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -6129,7 +6105,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -6159,7 +6135,7 @@ "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.ylabel(r'position (radians)', fontsize=18);\n", "pyplot.title('Double Pendulum Euler');\n", "pyplot.plot(t, q1d_dp[:,0], lw=2, label='Joint 1');\n", "pyplot.plot(t, q1d_dp[:,1], lw=2, label='Joint 2')\n", @@ -6168,7 +6144,7 @@ "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'position (meters)', fontsize=18);\n", + "pyplot.ylabel(r'position (radians)', fontsize=18);\n", "pyplot.title('Double Pendulum RK4');\n", "pyplot.plot(t, q4d_dp[:,0], lw=2, label='Joint 1');\n", "pyplot.plot(t, q4d_dp[:,1], lw=2, label='Joint 2')\n", @@ -6186,7 +6162,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 33, "metadata": { "collapsed": false }, @@ -6736,7 +6712,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -6970,10 +6946,10 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 56, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -6985,33 +6961,7 @@ "pyplot.xlabel(r'$\\theta_2$', fontsize=18);\n", "pyplot.ylabel(r'$\\dot{\\theta_2}$', fontsize=18);\n", "pyplot.title('Poincare Section');\n", - "pyplot.plot(v[0,:], v[1,:], 'bo', lw=2, );\n", - "\n", - "#from matplotlib import animation\n", - "#from JSAnimation.IPython_display import display_animation\n", - "#def animate(data):\n", - "# print(numpy.shape(data))\n", - "# x = data[0,:]\n", - "# y = data[1,:]\n", - "# line.set_data(x, y)\n", - "# return line," - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# #(try to animate plot)\n", - "#fig = pyplot.figure();\n", - "#ax = pyplot.axes(xlim=(-10, 10), ylim=(-10,10),xlabel=(r'$\\theta_2$'), ylabel=(r'$\\dot{\\theta_2}$'))\n", - "#line, = ax.plot([],[],'bo', lw=2);\n", - "\n", - "#anim = animation.FuncAnimation(fig, animate, frames= v[:,:], interval=10)\n", - "#display_animation(anim, default_mode='once')" + "pyplot.plot(v[0,:], v[1,:], 'bo', lw=2);\n" ] }, { @@ -7045,7 +6995,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 34, "metadata": { "collapsed": false }, @@ -7200,7 +7150,7 @@ "" ] }, - "execution_count": 42, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -7240,7 +7190,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.0" + "version": "3.4.3" } }, "nbformat": 4, diff --git a/randy_schur/Integration Techniques for Mechanical Systems.ipynb b/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb similarity index 99% rename from randy_schur/Integration Techniques for Mechanical Systems.ipynb rename to randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb index 927c75b..371b290 100644 --- a/randy_schur/Integration Techniques for Mechanical Systems.ipynb +++ b/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb @@ -958,7 +958,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1852,7 +1852,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -2128,7 +2128,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/mint/miniconda3/lib/python3.5/site-packages/ipykernel/__main__.py:24: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future\n" + "/home/randy/anaconda3/lib/python3.4/site-packages/ipykernel/__main__.py:24: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future\n" ] } ], @@ -2752,7 +2752,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -3003,7 +3003,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "So yes, Verlet and RK2 both exhibit second order convergence as we expected. RK4 integration looks like fourth order. Euler integration looks weird. It appears to get exponentially worse with the time discretization. What is going on? One explanation is that there is some behavior that the first order method of Euler integration fails to capture." + "So yes, Verlet and RK2 both exhibit second order convergence as we expected. RK4 integration looks like fourth order. Euler integration looks weird. It appears to get exponentially worse with the time discretization. What is going on? One explanation is that there is some behavior that the first order method of Euler integration fails to capture until the time step is extremely small." ] }, { @@ -3015,7 +3015,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -3038,7 +3038,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -3073,7 +3073,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -3623,7 +3623,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4399,7 +4399,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4660,7 +4660,7 @@ "source": [ "It looks like Euler integration is adding a significant amount of energy to the system.\n", "\n", - "The really interesting results come from the other integrators. RK2 continuously adds energy to the system. Our symplectic integrator looks like it may be oscillating, but the energy in the system is stable! Maybe all of that work deriving the equations of motion was worth it! We now know that the Hamiltonian equations do better with Conservation of Energy, and this can make a big difference even in a simple system. " + "The really interesting results come from the other integrators. RK2 continuously adds energy to the system. Our symplectic integrator looks like it may be oscillating, but the energy in the system is stable! Maybe all of that work deriving the equations of motion was worth it! We now know that the Hamiltonian equations do better with Conservation of Energy, and this can make a difference even in a simple system. " ] }, { @@ -4705,7 +4705,7 @@ "\n", "The big change comes in the Hamiltonian equations. If these are dependent on convservation of energy, how do we deal with a system where energy is not conserved?\n", "\n", - "Our first step is to re-examine the Lagrangian. Since losses due to friction are neither potential nor kinetic energy, we'll need a new term. The Lagrangian equation of motion with non-conservative forces is:\n", + "Our first step is to re-examine the Lagrangian. Since losses due to friction are neither potential nor kinetic energy, we'll need a new term. The Lagrangian equation with non-conservative forces is:\n", "\n", "$$\\begin{equation}\n", "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{x_i}}\\right) - \\frac{\\partial L}{\\partial x_i} = Q\n", @@ -4725,7 +4725,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 18, "metadata": { "collapsed": true }, @@ -4749,7 +4749,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -4778,7 +4778,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 20, "metadata": { "collapsed": true }, @@ -4809,7 +4809,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -5359,7 +5359,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -5631,7 +5631,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "How does each integrator perform compared to the case of simple harmonic motion? Just by looking at the system response, it seems like the error is actually better here. Why is that? It is important to note here that the integrators perform differently for different system models. This is part of why we study numerical methods! There isn't a single answer to which scheme to use, it is dependent on the characteristics of both the scheme and the system mode.\n", + "How does each integrator perform compared to the case of simple harmonic motion? Just by looking at the system response, it seems like the error is actually better here. Why is that? It is important to note here that the integrators perform differently for different system models. This is part of why we study numerical methods! There isn't a single answer to which scheme to use, it is dependent on the characteristics of both the scheme and the system.\n", "\n", "The effect of the damper is to remove energy from the system. So, in this case the damping term is helping to remove some energy from the system that the integration adds. As time goes to infinity, these solutions will converge, and the Euler and Runge-Kutta methods are stable. We can again look at the energy in the system according to each method. \n", "\n" @@ -5639,7 +5639,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -6189,7 +6189,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -6461,17 +6461,17 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can see some oscillations in the total energy, which come from the discretization error. Overall, we see that Verlet and the Runge-Kutta methods perform better here than Euler (although not perfectly - it does have induced oscillations from the damping term), but just like the analytical solution the energy in the system asymptotically approaches zero.\n", + "We can see some oscillations in the total energy, which come from the discretization error. Overall, we see that Verlet and the Runge-Kutta methods perform better here than Euler (although not perfectly - there are induced oscillations from the damping term), but just like the analytical solution the energy in the system asymptotically approaches zero.\n", "\n", "\n", "### Phase Portrait\n", "\n", - "Another way to investigate stability is with a phase plot. Instead of position vs. time, we can plot velocity vs. position. This plot is in phase space (a subset of state space). For any given state (position and velocity), we should be able to determine what the next state will be. This gives us an entire direction field, over which we can plot the actual behavior of the system to see if they match up. If the phase plot becomes repetitive, we can call it stable, and if the phase plot goes to the origin, we can call it asymptotically stable." + "Another way to investigate stability is with a phase plot. Instead of position vs. time, we can plot velocity vs. position. This plot is in phase space (this is similar to state space, if you have a control systems background). For any given state (position and velocity), we should be able to determine what the next state will be. This gives us an entire direction field, over which we can plot the actual behavior of the system to see if they match up. If the phase plot becomes repetitive, we can call it stable, and if the phase plot goes to the origin, we can call it asymptotically stable." ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -7021,7 +7021,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -7289,7 +7289,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this case, it appears that Verlet, RK2, and RK4 give almost identical results, which are close but not exactly the same as the analytical solution. So for the damped harmonic oscillator, there is less of an advantage to using a more complex integration scheme." + "In this case, it appears that Verlet, RK2, and RK4 give almost identical results, which are close but not exactly the same as the analytical solution. So for the damped harmonic oscillator, there is less of an advantage to using a more complex integration scheme. Try modifying the code above to make a phase portrait for the simple harmonic oscillator. We should see a circle (or an ellipse) form, which is called a limit cycle. Which integrators create this behavior?" ] }, { @@ -7522,7 +7522,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.0" + "version": "3.4.3" } }, "nbformat": 4, From f0658a1a8a4604085311291dfff6f4bd9b2dc894 Mon Sep 17 00:00:00 2001 From: rbds Date: Sun, 13 Dec 2015 22:27:15 -0500 Subject: [PATCH 55/61] evening commit --- randy_schur/Double_Pendulum_Problem.ipynb | 101 +++++++++++++--------- 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 2c51bba..266c871 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -268,7 +268,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -306,7 +306,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -331,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -359,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -1962,7 +1962,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -1995,7 +1995,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -2812,7 +2812,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -2829,19 +2829,25 @@ " p1 = u[1]\n", " q2 = u[2]\n", " p2 = u[3]\n", + " #make sure q1 and q1 are between 0 and 2pi. \n", + " while q1 > 2*pi:\n", + " q1 -= 2*pi\n", + " while q2 > 2*pi:\n", + " q2 -= 2*pi\n", + " while q1 < 0:\n", + " q1+=2*pi\n", + " while q2 <0:\n", + " q2+=2*pi \n", " \n", " \n", - " \n", - " V = -m1*g*l1*cos(q1) - m2*g*(l1*cos(q1)+l2*cos(q2))\n", - " T = p1**2/(2*I1)+m2*p1**2/(2*m1*I1) + p1*p2/(m1*l1*l2)*cos(q2-q1)+p2**2/(2*I1)\n", - " #print(T)\n", - " #print(V)\n", - " return T+V" + " V = -m1*g*l1*cos(q1) - m2*g*l1*cos(q1)+l2*cos(q2)\n", + " T = p1**2/(2*I1)+m2*p1**2/(2*m1*I1) + p1*p2/(m1*l1*l2)*cos(q2-q1)+p2**2/(2*I2)\n", + " return T+V\n" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -3613,7 +3619,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3621,6 +3627,16 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -3634,25 +3650,38 @@ "#print(u[0,:])\n", "for i in range(N):\n", " Verlet_energy[i] = get_Energy(u[:,i])\n", - " RK4_energy[i] = get_Energy(x4_dp[i,:])\n", + " #RK4_energy[i] = get_Energy(x4_dp[i,:])\n", "\n", " \n", "\n", - "pyplot.figure(figsize=(11,6));\n", + "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", - "pyplot.xlabel(r't', fontsize=18);\n", - "pyplot.ylabel(r'Total energy in system', fontsize=18);\n", + "\n", "pyplot.title('Energy in System');\n", - "pyplot.plot(t, Verlet_energy, lw=2, label='Verlet');\n", - "pyplot.plot(t, RK4_energy, 'r', lw=2, label='RK4')\n", - "pyplot.legend();" + "ax1 = pyplot.gca()\n", + "pyplot.plot(t, Verlet_energy, lw=2, label='Verlet', color='b');\n", + "pyplot.xlabel('time (s)')\n", + "pyplot.ylabel('Energy')\n", + "\n", + "## use to plot RK4 energy on same graph.\n", + "#for tl in ax1.get_yticklabels():\n", + "# tl.set_color('b')\n", + "\n", + "#ax2 = ax1.twinx();\n", + "#ax2.plot(t, RK4_energy, 'r', lw=2, label='RK4')\n", + "#for tl in ax2.get_yticklabels():\n", + "# tl.set_color('r')\n", + "\n", + "#ax1.set_xlabel('time (s)')\n", + "#ax1.set_ylabel('Verlet', color='b')\n", + "#ax2.set_ylabel('RK4', color='r')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "So, we are very far from perfect. But, at least the energy in the system is stable, even if it isn't constant! " + "So, we are very far from perfect. But, at least with Verlet the energy in the system is stable, even if it isn't constant! " ] }, { @@ -3668,7 +3697,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -3702,7 +3731,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -4525,7 +4554,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -4557,7 +4586,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -6162,7 +6191,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 15, "metadata": { "collapsed": false }, @@ -6942,16 +6971,6 @@ }, "metadata": {}, "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -6995,7 +7014,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -7150,7 +7169,7 @@ "" ] }, - "execution_count": 34, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } From 4a9c04921ff850f1860ae0c06b7d6e67b43a3e99 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 14 Dec 2015 10:13:58 -0500 Subject: [PATCH 56/61] stupid computer --- ...on_Techniques_for_Mechanical_Systems.ipynb | 1475 +++-------------- randy_schur/figures/POS_computer.png | Bin 82838 -> 64372 bytes randy_schur/figures/combined_response.png | Bin 212034 -> 170544 bytes 3 files changed, 260 insertions(+), 1215 deletions(-) diff --git a/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb b/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb index 371b290..16abbe7 100644 --- a/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb +++ b/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb @@ -226,7 +226,16 @@ "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\rschur\\Anaconda2\\lib\\site-packages\\IPython\\kernel\\__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.\n", + " \"You should import from ipykernel or jupyter_client instead.\", ShimWarning)\n" + ] + } + ], "source": [ "import numpy\n", "from scipy.linalg import solve\n", @@ -488,6 +497,7 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -818,13 +828,11 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -873,19 +881,6 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys (original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object')\n", - " obj[key] = original[key]\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -899,8 +894,7 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event)});\n", + " step: event.step});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -940,8 +934,7 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value,\n", - " guiEvent: simpleKeys(event)});\n", + " this.send_message(name, {key: value});\n", " return false;\n", "}\n", "\n", @@ -1017,8 +1010,6 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", - " fig.root.unbind('remove')\n", - "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -1026,12 +1017,8 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.close_ws(fig, msg);\n", - "}\n", - "\n", - "mpl.figure.prototype.close_ws = function(fig, msg){\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", + " fig.send_message('closing', {});\n", + " fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -1086,20 +1073,14 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " button.mouseover('Close figure', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -1180,7 +1161,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1249,960 +1230,60 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "### Error Analysis\n", - "The error analysis code below is based on the numerical methods MOOC notebook on phugoid oscillation. " - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def get_error(y, z, dt):\n", - " \"\"\"Returns error relative to analytical solution using L-1 norm.\n", - " \n", - " Parameters:\n", - " y - numerical solution\n", - " z - analytical solution\n", - " dt - time step size\n", - " \n", - " Returns:\n", - " error - L_1 norm of the error W.R.T. analytical solution \n", - " \"\"\"\n", - " err = dt*numpy.sum(numpy.abs(y-z))\n", - " return err" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "dt_values = numpy.array([0.1, 0.05, 0.01, 0.005, 0.001, 0.0001])\n", - "\n", - "Euler_error_values = numpy.empty_like(dt_values)\n", - "RK2_error_values = Euler_error_values.copy()\n", - "RK4_error_values = Euler_error_values.copy()\n", - "Verlet_error_values = Euler_error_values.copy()\n", - "for i, dt in enumerate(dt_values):\n", - " ###Call error function\n", - " Euler_error_values[i] = get_error(x1[:,0], x_analytical, dt)\n", - " RK2_error_values[i] = get_error(x2[:,0], x_analytical, dt)\n", - " RK4_error_values[i] = get_error(x4[:,0], x_analytical, dt)\n", - " Verlet_error_values[i] = get_error(xv[:,0], x_analytical, dt)\n", - " \n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width);\n", - " canvas.attr('height', height);\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pyplot.figure(figsize=(10,6))\n", + "pyplot.tick_params(axis='both', labelsize=14)\n", + "pyplot.grid(True)\n", + "pyplot.xlabel('$\\Delta t$', fontsize=16)\n", + "pyplot.ylabel('Error', fontsize=16)\n", + "pyplot.loglog(dt_values, Euler_error_values, 'bo-', label='Euler')\n", + "pyplot.loglog(dt_values, RK2_error_values, 'ro-', label='RK2')\n", + "pyplot.loglog(dt_values, RK4_error_values, 'co-', label='RK4')\n", + "pyplot.loglog(dt_values, Verlet_error_values, 'go-', label='Symplectic')\n", + "pyplot.axis('equal')\n", + "pyplot.legend();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. Don't believe that these are significant? The discretization is the only difference between the first two plots above, and with a smaller time step the response from Runge-Kutta and Euler are greatly improved. To exame precision errors, take a look at the following screenshot, which shows the plots above, built with identical code on two different computers. The responses on the left are from a 64-bit computer, and the responses on the right are from a 32-bit computer. Look closely at the phase shift in the system response. That's a huge difference! Even the precision of the machine can have a significant impact on numerical integration.\n", + "\n", + "![Image](figures/combined_response.png)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Despite the numerical errors, why is one second order scheme (Verlet) outperforming another (RK2) by so much? What is going on? We can check the order of convergence to make sure that these schemes are actually 2nd order." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/mint/miniconda3/lib/python3.5/site-packages/ipykernel/__main__.py:24: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future\n" + ] + } + ], + "source": [ + "def get_diffgrid(u_current, u_fine, dt):\n", + " \"\"\"Returns the difference between one grid and the fine one using L-1 norm.\n", + " \n", + " Parameters\n", + " ----------\n", + " u_current : array of float\n", + " solution on the current grid.\n", + " u_finest : array of float\n", + " solution on the fine grid.\n", + " dt : float\n", + " time-increment on the current grid.\n", + " \n", + " Returns\n", + " -------\n", + " diffgrid : float\n", + " difference computed in the L-1 norm.\n", + " \"\"\"\n", + " \n", + " N_current = len(u_current[:,0])\n", + " N_fine = len(u_fine[:,0])\n", + " \n", + " grid_size_ratio = numpy.ceil(N_fine/N_current)\n", + " \n", + " diffgrid = dt * numpy.sum( numpy.abs(\\\n", + " u_current[:,0]- u_fine[::grid_size_ratio,0])) \n", + " \n", + " return diffgrid\n", + "\n", + "# use a for-loop to compute the solution on different grids - this code is modified from Numerical MOOC lesson 1 module 4\n", + "dt_values = numpy.array([0.1, 0.05, 0.01, 0.005, 0.001])\n", + "u1_values = numpy.empty_like(dt_values, dtype=numpy.ndarray)\n", + "u2_values = u1_values.copy()\n", + "u4_values = u1_values.copy()\n", + "uv_values = u1_values.copy()\n", + "\n", + "#At each value of dt, integrate the system using 4 different methods from t=0 to T\n", "for i, dt in enumerate(dt_values):\n", - " ###Call error function\n", - " Euler_error_values[i] = get_error(x1[:,0], x_analytical, dt)\n", - " RK2_error_values[i] = get_error(x2[:,0], x_analytical, dt)\n", - " RK4_error_values[i] = get_error(x4[:,0], x_analytical, dt)\n", - " Verlet_error_values[i] = get_error(xv[:,0], x_analytical, dt)\n", - " \n" + " N = int(T/dt)+1 \n", + " t = numpy.linspace(0.0, T, N)\n", + " u1 = numpy.empty((N,2))\n", + " u1[0] = numpy.array([x0, xdot0])\n", + " u2 = u1.copy()\n", + " u4 = u1.copy()\n", + " uv = u1.copy()\n", + " for n in range(N-1):\n", + " u1[n+1] = euler(u1[n], f_harmonic_oscillator, dt)\n", + " u2[n+1] = RK2(u2[n], f_harmonic_oscillator, dt)\n", + " u4[n+1] = RK4(u4[n], f_harmonic_oscillator, dt)\n", + " uv[n+1] = verlet_SHM(uv[n], dt)\n", + " # store the value of u related to one grid\n", + " u1_values[i] = u1\n", + " u2_values[i] = u2\n", + " u4_values[i] = u4\n", + " uv_values[i] = uv\n", + " \n", + "# compute diffgrid\n", + "diffgrid1 = numpy.empty_like(dt_values)\n", + "diffgrid2 = diffgrid1.copy()\n", + "diffgrid4 = diffgrid1.copy()\n", + "diffgridv = diffgrid1.copy()\n", + "for i, dt in enumerate(dt_values):\n", + " diffgrid1[i] = get_diffgrid(u1_values[i], u1_values[-1], dt)\n", + " diffgrid2[i] = get_diffgrid(u2_values[i], u2_values[-1], dt)\n", + " diffgrid4[i] = get_diffgrid(u4_values[i], u4_values[-1], dt)\n", + " diffgridv[i] = get_diffgrid(uv_values[i], uv_values[-1], dt)" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -1363,7 +2279,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -1694,11 +2609,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -1747,6 +2664,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -1760,7 +2690,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -1800,7 +2731,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -1817,7 +2749,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -1876,6 +2808,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -1883,8 +2817,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -1939,14 +2877,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -2027,7 +2971,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2037,138 +2981,6 @@ "output_type": "display_data" } ], - "source": [ - "pyplot.figure(figsize=(10,6))\n", - "pyplot.tick_params(axis='both', labelsize=14)\n", - "pyplot.grid(True)\n", - "pyplot.xlabel('$\\Delta t$', fontsize=16)\n", - "pyplot.ylabel('Error', fontsize=16)\n", - "pyplot.loglog(dt_values, Euler_error_values, 'bo-', label='Euler')\n", - "pyplot.loglog(dt_values, RK2_error_values, 'ro-', label='RK2')\n", - "pyplot.loglog(dt_values, RK4_error_values, 'co-', label='RK4')\n", - "pyplot.loglog(dt_values, Verlet_error_values, 'go-', label='Symplectic')\n", - "pyplot.axis('equal')\n", - "pyplot.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. Don't believe that these are significant? The discretization is the only difference between the first two plots above, and with a smaller time step the response from Runge-Kutta and Euler are greatly improved. To exame precision errors, take a look at the following screenshot, which shows the plots above, built with identical code on two different computers. The responses on the left are from a 64-bit computer, and the responses on the right are from a 32-bit computer. Look closely at the phase shift in the system response. That's a huge difference! Even the precision of the machine can have a significant impact on numerical integration.\n", - "\n", - "![Image](figures/combined_response.png)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Despite the numerical errors, why is one second order scheme (Verlet) outperforming another (RK2) by so much? What is going on? We can check the order of convergence to make sure that these schemes are actually 2nd order." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\rschur\\Anaconda2\\lib\\site-packages\\ipykernel\\__main__.py:24: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future\n" - ] - }, - { - "ename": "ValueError", - "evalue": "operands could not be broadcast together with shapes (251,) (253,) ", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 59\u001b[0m \u001b[0mdiffgridv\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdiffgrid1\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 60\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdt\u001b[0m \u001b[1;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdt_values\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 61\u001b[1;33m \u001b[0mdiffgrid1\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mget_diffgrid\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mu1_values\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mu1_values\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 62\u001b[0m \u001b[0mdiffgrid2\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mget_diffgrid\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mu2_values\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mu2_values\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 63\u001b[0m \u001b[0mdiffgrid4\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mget_diffgrid\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mu4_values\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mu4_values\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m\u001b[0m in \u001b[0;36mget_diffgrid\u001b[1;34m(u_current, u_fine, dt)\u001b[0m\n\u001b[0;32m 22\u001b[0m \u001b[0mgrid_size_ratio\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnumpy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mceil\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mN_fine\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0mN_current\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 23\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 24\u001b[1;33m \u001b[0mdiffgrid\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdt\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mnumpy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msum\u001b[0m\u001b[1;33m(\u001b[0m \u001b[0mnumpy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mabs\u001b[0m\u001b[1;33m(\u001b[0m \u001b[0mu_current\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m-\u001b[0m \u001b[0mu_fine\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m:\u001b[0m\u001b[0mgrid_size_ratio\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 25\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 26\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mdiffgrid\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mValueError\u001b[0m: operands could not be broadcast together with shapes (251,) (253,) " - ] - } - ], - "source": [ - "def get_diffgrid(u_current, u_fine, dt):\n", - " \"\"\"Returns the difference between one grid and the fine one using L-1 norm.\n", - " \n", - " Parameters\n", - " ----------\n", - " u_current : array of float\n", - " solution on the current grid.\n", - " u_finest : array of float\n", - " solution on the fine grid.\n", - " dt : float\n", - " time-increment on the current grid.\n", - " \n", - " Returns\n", - " -------\n", - " diffgrid : float\n", - " difference computed in the L-1 norm.\n", - " \"\"\"\n", - " \n", - " N_current = len(u_current[:,0])\n", - " N_fine = len(u_fine[:,0])\n", - " \n", - " grid_size_ratio = numpy.ceil(N_fine/N_current)\n", - " \n", - " diffgrid = dt * numpy.sum( numpy.abs(\\\n", - " u_current[:,0]- u_fine[::grid_size_ratio,0])) \n", - " \n", - " return diffgrid\n", - "\n", - "# use a for-loop to compute the solution on different grids - this code is modified from Numerical MOOC lesson 1 module 4\n", - "dt_values = numpy.array([0.1, 0.05, 0.01, 0.005, 0.001])\n", - "u1_values = numpy.empty_like(dt_values, dtype=numpy.ndarray)\n", - "u2_values = u1_values.copy()\n", - "u4_values = u1_values.copy()\n", - "uv_values = u1_values.copy()\n", - "\n", - "#At each value of dt, integrate the system using 4 different methods from t=0 to T\n", - "for i, dt in enumerate(dt_values):\n", - " N = int(T/dt)+1 \n", - " t = numpy.linspace(0.0, T, N)\n", - " u1 = numpy.empty((N,2))\n", - " u1[0] = numpy.array([x0, xdot0])\n", - " u2 = u1.copy()\n", - " u4 = u1.copy()\n", - " uv = u1.copy()\n", - " for n in range(N-1):\n", - " u1[n+1] = euler(u1[n], f_harmonic_oscillator, dt)\n", - " u2[n+1] = RK2(u2[n], f_harmonic_oscillator, dt)\n", - " u4[n+1] = RK4(u4[n], f_harmonic_oscillator, dt)\n", - " uv[n+1] = verlet_SHM(uv[n], dt)\n", - " # store the value of u related to one grid\n", - " u1_values[i] = u1\n", - " u2_values[i] = u2\n", - " u4_values[i] = u4\n", - " uv_values[i] = uv\n", - " \n", - "# compute diffgrid\n", - "diffgrid1 = numpy.empty_like(dt_values)\n", - "diffgrid2 = diffgrid1.copy()\n", - "diffgrid4 = diffgrid1.copy()\n", - "diffgridv = diffgrid1.copy()\n", - "for i, dt in enumerate(dt_values):\n", - " diffgrid1[i] = get_diffgrid(u1_values[i], u1_values[-1], dt)\n", - " diffgrid2[i] = get_diffgrid(u2_values[i], u2_values[-1], dt)\n", - " diffgrid4[i] = get_diffgrid(u4_values[i], u4_values[-1], dt)\n", - " diffgridv[i] = get_diffgrid(uv_values[i], uv_values[-1], dt)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], "source": [ "# plot using the matplotlib function loglog()\n", "pyplot.figure(figsize=(7,10))\n", @@ -2200,7 +3012,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -2223,7 +3035,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -2258,7 +3070,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -2338,7 +3150,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -2669,11 +3480,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -2722,6 +3535,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -2735,7 +3561,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -2775,7 +3602,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -2792,7 +3620,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -2851,6 +3679,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -2858,8 +3688,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -2914,14 +3748,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -3002,7 +3842,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3086,7 +3926,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -3417,11 +4256,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -3470,6 +4311,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -3483,7 +4337,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -3523,7 +4378,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -3540,7 +4396,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -3599,6 +4455,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -3606,8 +4464,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -3662,14 +4524,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -3750,7 +4618,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3854,7 +4722,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": { "collapsed": true }, @@ -3878,7 +4746,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -3907,7 +4775,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": { "collapsed": true }, @@ -3938,7 +4806,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -4018,7 +4886,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -4349,11 +5216,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -4402,6 +5271,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -4415,7 +5297,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -4455,7 +5338,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -4472,7 +5356,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -4531,6 +5415,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -4538,8 +5424,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -4594,14 +5484,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -4682,7 +5578,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4740,7 +5636,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -4820,7 +5716,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -5151,11 +6046,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -5204,6 +6101,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -5217,7 +6127,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -5257,7 +6168,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -5274,7 +6186,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -5333,6 +6245,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -5340,8 +6254,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -5396,14 +6314,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -5484,7 +6408,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5544,7 +6468,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -5624,7 +6548,6 @@ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " fig.waiting = false;\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", @@ -5955,11 +6878,13 @@ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " evt.data);\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", " return;\n", " }\n", "\n", @@ -6008,6 +6933,19 @@ " return {\"x\": x, \"y\": y};\n", "};\n", "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys (original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object')\n", + " obj[key] = original[key]\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", "mpl.figure.prototype.mouse_event = function(event, name) {\n", " var canvas_pos = mpl.findpos(event)\n", "\n", @@ -6021,7 +6959,8 @@ " var y = canvas_pos.y;\n", "\n", " this.send_message(name, {x: x, y: y, button: event.button,\n", - " step: event.step});\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event)});\n", "\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We want\n", @@ -6061,7 +7000,8 @@ "\n", " this._key_event_extra(event, name);\n", "\n", - " this.send_message(name, {key: value});\n", + " this.send_message(name, {key: value,\n", + " guiEvent: simpleKeys(event)});\n", " return false;\n", "}\n", "\n", @@ -6078,7 +7018,7 @@ "};\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n", "\n", "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", @@ -6137,6 +7077,8 @@ "};\n", "\n", "mpl.figure.prototype.handle_close = function(fig, msg) {\n", + " fig.root.unbind('remove')\n", + "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", @@ -6144,8 +7086,12 @@ " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable()\n", " $(fig.parent_element).html('');\n", - " fig.send_message('closing', {});\n", - " fig.ws.close()\n", + " fig.close_ws(fig, msg);\n", + "}\n", + "\n", + "mpl.figure.prototype.close_ws = function(fig, msg){\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", "}\n", "\n", "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", @@ -6200,14 +7146,20 @@ "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", - " var button = $('');\n", + " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Close figure', toolbar_mouse_event);\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", @@ -6288,7 +7240,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -6363,7 +7315,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "metadata": { "collapsed": false }, @@ -6518,7 +7470,7 @@ "" ] }, - "execution_count": 26, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -6531,43 +7483,25 @@ "css_file = './numericalmoocstyle.css'\n", "HTML(open(css_file, \"r\").read())" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.5.0" } }, "nbformat": 4, From 8f93e8d9ccdeb7021088a63c662cadf57f49d831 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 14 Dec 2015 11:48:32 -0500 Subject: [PATCH 58/61] editing --- randy_schur/Double_Pendulum_Problem.ipynb | 26 ++++----- ...on_Techniques_for_Mechanical_Systems.ipynb | 55 ++++++++++--------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index b361b53..ca0d848 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -63,7 +63,7 @@ "\n", "So the Lagrangian quantity becomes:\n", "$$\\begin{equation*}\n", - "L = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+(m_1+m_2)l_1gcos\\theta_1 + m_2l_2gcos\\theta_2\n", + "L = \\frac{1}{2}(m_1+m_2)l_1^2\\dot\\theta_1^2 + \\frac{1}{2}m_2l_2^2\\dot{\\theta_2}^2+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2cos(\\theta_2-\\theta_1)+(m_1+m_2)l_1gcos\\theta_1 + m_2l_2gcos\\theta_2\n", "\\end{equation*}$$\n" ] }, @@ -85,7 +85,7 @@ "$$\\begin{eqnarray*}\n", "\\frac{\\partial L}{\\partial \\dot{\\theta_1}} &=& (m_1+m_2)l_1^2\\dot\\theta_1+m_2l_1l_2\\dot\\theta_2cos(\\theta_2-\\theta_1) \\\\\n", "\\frac{d}{dt} \\left( \\frac{\\partial L}{\\partial \\dot{\\theta_1}}\\right) &=& (m_1+m_2)l_1^2\\ddot\\theta_1+m_2l_1l_2\\ddot\\theta_2cos(\\theta_2-\\theta_1)-m_2l_1l_2\\dot\\theta_2^2sin(\\theta_2-\\theta_1)+m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1) \\\\\n", - "-\\frac{\\partial L}{\\partial \\theta_1} &=& -m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)-(m_1+m_2)gl_1sin\\theta_1\n", + "-\\frac{\\partial L}{\\partial \\theta_1} &=& -m_2l_1l_2\\dot\\theta_1\\dot\\theta_2sin(\\theta_2-\\theta_1)+(m_1+m_2)gl_1sin\\theta_1\n", "\\end{eqnarray*}$$\n", "\n", "We can add together the second and third equations to find the first equation of motion. Some of these terms will cancel out, and we are left with a final equation of motion:\n", @@ -97,7 +97,7 @@ "We will also need the equation of motion for $\\theta_2$. This can be derived using the same process. It should work out to the following (check this by yourself!):\n", "\n", "$$\\begin{equation*}\n", - "0 = m_2l_2^2\\ddot\\theta_2+m_2l_1l_2\\ddot\\theta_1cos(\\theta_2-\\theta_1) - m_2l_2l_1\\dot\\theta_1^2sin(\\theta_2-\\theta_1)+ l_2m_2gsin\\theta_2\n", + "0 = m_2l_2^2\\ddot\\theta_2+m_2l_1l_2\\ddot\\theta_1cos(\\theta_2-\\theta_1) + m_2l_2l_1\\dot\\theta_1^2sin(\\theta_2-\\theta_1)+ l_2m_2gsin\\theta_2\n", "\\end{equation*}$$" ] }, @@ -132,7 +132,7 @@ "V = \n", "\\left[\\begin{array}{c}\n", "0 & -m_2l_1l_2sin(\\theta_2 - \\theta_1)\\\\\n", - " -m_2l_1l_2sin(\\theta_2 - \\theta_1) &0\n", + " m_2l_1l_2sin(\\theta_2 - \\theta_1) &0\n", "\\end{array}\\right]\n", "\\end{equation*}\n", "\n", @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 58, "metadata": { "collapsed": false }, @@ -255,7 +255,7 @@ " x4 = u[3]\n", " \n", " M = numpy.array([[(m1+m2)*l1**2, m2*l1*l2*cos(x1-x2)],[m2*l1*l2*cos(x1-x2), m2*l2**2]])\n", - " V = numpy.array([[0, -m2*l1*l2*sin(x1-x2)],[-m2*l1*l2*sin(x1-x2), 0]])\n", + " V = numpy.array([[0, m2*l1*l2*sin(x1-x2)],[-m2*l1*l2*sin(x1-x2), 0]])\n", " G = numpy.array([[(m1+m2)*g*l1*sin(x1)],[m2*l2*g*sin(x2)]])\n", " qdd = numpy.linalg.inv(M).dot(V.dot(numpy.array([[x3],[x4]])) +G) #- 2*numpy.array([[x3],[x4]])) \n", " RHS = numpy.array([x3, x4, qdd[0], qdd[1]])\n", @@ -264,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 59, "metadata": { "collapsed": false }, @@ -301,7 +301,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 60, "metadata": { "collapsed": true }, @@ -318,15 +318,13 @@ " Returns: \n", " x - array of values at next time step.\n", " \"\"\"\n", - " #print(f(u))\n", - " #print(u)\n", " \n", " return u + dt*f(u)" ] }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 61, "metadata": { "collapsed": false }, @@ -354,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 62, "metadata": { "collapsed": false }, @@ -1126,7 +1124,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1902,7 +1900,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" diff --git a/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb b/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb index aecb47b..56e6b02 100644 --- a/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb +++ b/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb @@ -138,7 +138,7 @@ "k_1 &=& f(t_n, x_n)\\\\\n", "k_2 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_1)\\\\\n", "k_3 &=& f(t_n+\\frac{h}{2}, x_n+\\frac{h}{2}k_2)\\\\\n", - "k_4 &=& f(t_n+h, y_n+hk_3)\\\\\n", + "k_4 &=& f(t_n+h, x_n+hk_3)\\\\\n", "t_{n+1} &=& t_n + h\n", "\\end{eqnarray}$$\n", "\n", @@ -183,11 +183,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's now take a look at symplectic integration methods [7]. We'll start with a first order method - symplectic Euler's method. This uses the equations:\n", + "Let's now take a look at symplectic integration methods [6]. We'll start with a first order method - symplectic Euler's method. This uses the equations:\n", "\n", "$$\\begin{eqnarray*}\n", "p_{n+1} = p_n - h \\frac{\\partial H}{\\partial q} \\Bigr|_{p_{n+1},q_n}\\\\\n", - "q_{n+1} = q_n + \\frac{h}{2} \\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1},q_n}\n", + "q_{n+1} = q_n + h\\frac{\\partial H}{\\partial p} \\Bigr|_{p_{n+1},q_n}\n", "\\end{eqnarray*}$$\n", "\n", "We can then plug in the partial derivatives and integrate the system. This system will do a better job of conserving energy than Euler's method (we'll investigate this later), $\\textit{but only to a truncation error.}$ Again, we can do better than first-order. Let's try a second-order symplectic scheme, also called Verlet integration. We'll have the same equations of motion, but this time a different set of integration equations [6]:\n", @@ -222,7 +222,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 31, "metadata": { "collapsed": false }, @@ -243,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 32, "metadata": { "collapsed": false }, @@ -261,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 33, "metadata": { "collapsed": true }, @@ -284,7 +284,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 34, "metadata": { "collapsed": true }, @@ -308,7 +308,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 35, "metadata": { "collapsed": true }, @@ -335,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 36, "metadata": { "collapsed": true }, @@ -359,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 37, "metadata": { "collapsed": true }, @@ -388,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 38, "metadata": { "collapsed": false }, @@ -408,7 +408,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 39, "metadata": { "collapsed": false }, @@ -1256,7 +1256,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 40, "metadata": { "collapsed": true }, @@ -1279,7 +1279,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 41, "metadata": { "collapsed": false }, @@ -1302,7 +1302,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 42, "metadata": { "collapsed": false }, @@ -2074,7 +2074,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2095,6 +2095,7 @@ "pyplot.loglog(dt_values, RK4_error_values, 'co-', label='RK4')\n", "pyplot.loglog(dt_values, Verlet_error_values, 'go-', label='Symplectic')\n", "pyplot.axis('equal')\n", + "pyplot.title('Error in Simple Harmonic Motion Integration')\n", "pyplot.legend();" ] }, @@ -2102,7 +2103,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. Don't believe that these are significant? The discretization is the only difference between the first two plots above, and with a smaller time step the response from Runge-Kutta and Euler are greatly improved. To exame precision errors, take a look at the following screenshot, which shows the plots above, built with identical code on two different computers. The responses on the left are from a 64-bit computer, and the responses on the right are from a 32-bit computer. Look closely at the phase shift in the system response. That's a huge difference! Even the precision of the machine can have a significant impact on numerical integration.\n", + "Is this what you would expect to see based on the plots of position above? What are the sources of this error? We have the usual suspects: truncation error, discretization error, possible precision errors. Don't believe that these are significant? To examine precision errors, take a look at the following screenshot, which shows the plots above, built with identical code on two different computers. The responses on the right are from a 64-bit computer, and the responses on the left are from a 32-bit computer. Look closely at the phase shift in the system response. That's a huge difference! Even the precision of the machine can have a significant impact on numerical integration.\n", "\n", "![Image](figures/combined_response.png)\n" ] @@ -2116,7 +2117,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 43, "metadata": { "collapsed": false }, @@ -2199,7 +2200,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 44, "metadata": { "collapsed": false }, @@ -3012,7 +3013,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 45, "metadata": { "collapsed": true }, @@ -3035,7 +3036,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 46, "metadata": { "collapsed": false }, @@ -3070,7 +3071,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 47, "metadata": { "collapsed": false }, @@ -4722,7 +4723,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 48, "metadata": { "collapsed": true }, @@ -4746,7 +4747,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 49, "metadata": { "collapsed": true }, @@ -4775,7 +4776,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 50, "metadata": { "collapsed": true }, @@ -4806,7 +4807,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 51, "metadata": { "collapsed": false }, @@ -5636,7 +5637,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 52, "metadata": { "collapsed": false }, From 1ced5e8b1dd564e8efa4884da780f43188def14a Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 14 Dec 2015 13:58:24 -0500 Subject: [PATCH 59/61] energy plots in DP for RK4 --- randy_schur/Double_Pendulum_Problem.ipynb | 72 ++++++++--------------- 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index ca0d848..62a614e 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -212,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -264,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -301,7 +301,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 13, "metadata": { "collapsed": true }, @@ -324,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -352,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 15, "metadata": { "collapsed": false }, @@ -1955,7 +1955,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -1988,7 +1988,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -2805,23 +2805,23 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 32, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def get_Energy(u):\n", - " \"\"\" Calculates total energy in the system at given time step for a simple harmonic oscillator\n", + " \"\"\" Calculates total energy in the system at given time step for a double pendulum\n", " Parameters:\n", " u - state of system [pos, vel]\n", " Returns:\n", " total energy in system.\n", " \"\"\"\n", " q1 = u[0]\n", - " p1 = u[1]\n", + " w1 = u[1]\n", " q2 = u[2]\n", - " p2 = u[3]\n", + " w2 = u[3]\n", " #make sure q1 and q1 are between 0 and 2pi. \n", " while q1 > 2*pi:\n", " q1 -= 2*pi\n", @@ -2834,13 +2834,13 @@ " \n", " \n", " V = -m1*g*l1*cos(q1) - m2*g*l1*cos(q1)+l2*cos(q2)\n", - " T = p1**2/(2*I1)+m2*p1**2/(2*m1*I1) + p1*p2/(m1*l1*l2)*cos(q2-q1)+p2**2/(2*I2)\n", - " return T+V\n" + " T = 1/2*(m1+m2)*l1**2*w1**2+1/2*m2*2*l1*l2*w1*w2*cos(q1-q2)+l2**2*w2**2\n", + " return T+V" ] }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 33, "metadata": { "collapsed": false }, @@ -3612,7 +3612,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3620,16 +3620,6 @@ }, "metadata": {}, "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -3643,7 +3633,7 @@ "#print(u[0,:])\n", "for i in range(N):\n", " Verlet_energy[i] = get_Energy(u[:,i])\n", - " #RK4_energy[i] = get_Energy(x4_dp[i,:])\n", + " RK4_energy[i] = get_Energy(x4_dp[i,:])\n", "\n", " \n", "\n", @@ -3655,19 +3645,9 @@ "pyplot.plot(t, Verlet_energy, lw=2, label='Verlet', color='b');\n", "pyplot.xlabel('time (s)')\n", "pyplot.ylabel('Energy')\n", - "\n", - "## use to plot RK4 energy on same graph.\n", - "#for tl in ax1.get_yticklabels():\n", - "# tl.set_color('b')\n", - "\n", - "#ax2 = ax1.twinx();\n", - "#ax2.plot(t, RK4_energy, 'r', lw=2, label='RK4')\n", - "#for tl in ax2.get_yticklabels():\n", - "# tl.set_color('r')\n", - "\n", - "#ax1.set_xlabel('time (s)')\n", - "#ax1.set_ylabel('Verlet', color='b')\n", - "#ax2.set_ylabel('RK4', color='r')\n" + "pyplot.plot(t, RK4_energy, 'r', lw=2, label='RK4')\n", + "pyplot.legend();\n", + "\n" ] }, { @@ -3690,7 +3670,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 24, "metadata": { "collapsed": false }, @@ -3724,7 +3704,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 26, "metadata": { "collapsed": false }, @@ -4507,7 +4487,7 @@ } ], "source": [ - "v = poincare(xv_dp, 1, 2*pi, 0, 2) #use states from RK4 integration\n", + "v = poincare(xv_dp, 1, 2*pi, 0, 2) #use states from Verlet integration\n", "pyplot.figure(figsize=(10,8));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r'$\\theta_2$', fontsize=18);\n", @@ -4547,7 +4527,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 27, "metadata": { "collapsed": false }, @@ -4579,7 +4559,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 28, "metadata": { "collapsed": false }, @@ -6184,7 +6164,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 29, "metadata": { "collapsed": false }, From ed144e237dd346a3f5d7e4a154174b2e39bf6de3 Mon Sep 17 00:00:00 2001 From: rbds Date: Tue, 15 Dec 2015 14:16:10 -0500 Subject: [PATCH 60/61] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cab8a3..00b523b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # assignment-bank-2015 -For submission of class projects at GW, Fall 2015 +For submission of class project at GW, Fall 2015 + +This assignment examines numerical integration techniques using mechanical systems. In the notebook Integration_Techniques_for_Mechanical_Systems, I start by introducing a simple harmonic oscillator and three different types of integrators (Euler, Runge-Kutta, and Verlet). Next I introduce damping and look at how the system behavior changes for each method of integration. The Double_Pendulum_Problem notebook looks at the double pendulum problem using Runge-Kutta and Verlet integration. The code in this repository is under Creative Commons Attribution license CC-BY 4.0, code under MIT license © 2015 Randall Schur From b41a3aa040e2e17f29fbb335796654036f19f195 Mon Sep 17 00:00:00 2001 From: rbds Date: Mon, 28 Dec 2015 07:33:47 -0500 Subject: [PATCH 61/61] address pull request comments --- randy_schur/Double_Pendulum_Problem.ipynb | 34 +++--- ...on_Techniques_for_Mechanical_Systems.ipynb | 112 ++++++++++++------ 2 files changed, 91 insertions(+), 55 deletions(-) diff --git a/randy_schur/Double_Pendulum_Problem.ipynb b/randy_schur/Double_Pendulum_Problem.ipynb index 62a614e..e9c8071 100644 --- a/randy_schur/Double_Pendulum_Problem.ipynb +++ b/randy_schur/Double_Pendulum_Problem.ipynb @@ -212,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 1, "metadata": { "collapsed": false }, @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -264,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -301,7 +301,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -324,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -352,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -1955,7 +1955,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -1988,7 +1988,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -2805,7 +2805,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -2840,7 +2840,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -3670,7 +3670,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -3704,7 +3704,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -4527,7 +4527,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -4559,7 +4559,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -6164,7 +6164,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 15, "metadata": { "collapsed": false }, @@ -6987,7 +6987,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -7142,7 +7142,7 @@ "" ] }, - "execution_count": 52, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } diff --git a/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb b/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb index 56e6b02..0ee3e41 100644 --- a/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb +++ b/randy_schur/Integration_Techniques_for_Mechanical_Systems.ipynb @@ -142,7 +142,7 @@ "t_{n+1} &=& t_n + h\n", "\\end{eqnarray}$$\n", "\n", - "For more information on the Runge-Kutta method, including the derivation and explanation of coefficients, see (cite)\n" + "For more information on the Runge-Kutta method, including the derivation and explanation of coefficients, see [9].\n" ] }, { @@ -222,7 +222,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -243,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -261,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -284,14 +284,14 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 5, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def RK2(u, f, dt):\n", - " \"\"\"Runge Kutta fourth order integration method\n", + " \"\"\"Runge Kutta second order integration method\n", " \n", " Parameters:\n", " u - state of the system at time t\n", @@ -308,7 +308,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -335,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 7, "metadata": { "collapsed": true }, @@ -359,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 8, "metadata": { "collapsed": true }, @@ -388,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -408,7 +408,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -1180,7 +1180,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1236,7 +1236,7 @@ "pyplot.plot(t, x4[:,0], 'c', lw=2, label='RK4');\n", "pyplot.plot(t, xv[:,0], 'g', lw=2, label='Verlet');\n", "pyplot.plot(t, x_analytical, 'k--', lw=2, label='analytical');\n", - "pyplot.legend();" + "pyplot.legend(loc='upper left');" ] }, { @@ -1256,7 +1256,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 11, "metadata": { "collapsed": true }, @@ -1279,7 +1279,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -1302,7 +1302,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -2117,7 +2117,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -2200,7 +2200,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -2972,7 +2972,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2980,6 +2980,16 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -2994,7 +3004,8 @@ "pyplot.loglog(dt_values[:-1], diffgrid1[:-1], label='Euler', color='b', ls='--', lw=2, marker='o');\n", "pyplot.loglog(dt_values[:-1], diffgrid2[:-1], label='RK2', color = 'r', ls='--', lw=2, marker='o');\n", "pyplot.loglog(dt_values[:-1], diffgrid4[:-1], label='RK4', color = 'c', ls='--', lw=2, marker='o');\n", - "pyplot.loglog(dt_values[:-1], diffgridv[:-1], label='Verlet', color = 'g', ls='--', lw=2, marker='o');" + "pyplot.loglog(dt_values[:-1], diffgridv[:-1], label='Verlet', color = 'g', ls='--', lw=2, marker='o');\n", + "pyplot.legend(loc='upper left')" ] }, { @@ -3008,12 +3019,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the plots of position using Euler integration, we notice that the spring is stretched farther and farther each oscillation. This means it has more and more potential energy. With no outside forces adding energy to the system and no friction to remove energy, Conservation of Energy says that value should be constant! Let's take a look at total energy in the system. Since we derived equations of motion from the Lagrangian, this should be easy! All we need to do is calculate kinetic and potential energy at each time step, and we already have the information we need to do this. " + "In the plots of position using Euler integration, we notice that the spring is stretched farther and farther each oscillation. This means it has more and more potential energy. With no outside forces adding energy to the system and no friction to remove energy, conservation of energy says that value should be constant! Let's take a look at total energy in the system. Since we derived equations of motion from the Lagrangian, this should be easy! All we need to do is calculate kinetic and potential energy at each time step, and we already have the information we need to do this. " ] }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -3036,7 +3047,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -3066,12 +3077,12 @@ " RK2_energy[i] = get_Energy(x2[i,:])\n", " RK4_energy[i] = get_Energy(x4[i,:])\n", " Verlet_energy[i] = get_Energy(numpy.array([xv[i,0], Verlet_vel[i]]))\n", - " analytical_energy[i] = get_Energy(anayltical_state[i,:]) " + " analytical_energy[i] = get_Energy(anayltical_state[i,:])" ] }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -4619,7 +4630,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4639,7 +4650,7 @@ "pyplot.plot(t, Euler_energy, 'b-', lw=2, label='Euler');\n", "\n", "#Plot other methods on a separate figure, so we can actually see behavior\n", - "pyplot.figure(figsize=(8,6));\n", + "pyplot.figure(figsize=(10,6));\n", "pyplot.grid(True);\n", "pyplot.xlabel(r't', fontsize=18);\n", "pyplot.ylabel(r'Total energy in system (J)', fontsize=18);\n", @@ -4649,7 +4660,7 @@ "pyplot.plot(t, RK4_energy, 'c-', lw=2, label='RK4');\n", "pyplot.plot(t, Verlet_energy, 'g-', lw=2, label='Verlet');\n", "pyplot.plot(t, analytical_energy, 'k--', lw=2, label='analytical');\n", - "pyplot.legend();" + "pyplot.legend(loc='upper left');" ] }, { @@ -4718,12 +4729,12 @@ "\\begin{bmatrix} \\dot q \\\\ \\dot p \\end{bmatrix} = \\begin{bmatrix} -\\frac{\\partial H}{\\partial p} + Q\\\\ \\frac{\\partial H}{\\partial q} \\end{bmatrix} = \\begin{bmatrix} -\\frac{p}{m} - \\frac{c}{m}p \\\\ kq \\end{bmatrix}\n", "\\end{equation*}$$\n", "\n", - "From here, the we can use the same Verlet integration method as before. If this explanation has too much hand-waving for you, please see [7]." + "From here, we can use the same Verlet integration method as before. If this explanation has too much hand-waving for you, please see [7]." ] }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 23, "metadata": { "collapsed": true }, @@ -4747,7 +4758,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 24, "metadata": { "collapsed": true }, @@ -4776,7 +4787,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 25, "metadata": { "collapsed": true }, @@ -4807,7 +4818,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 26, "metadata": { "collapsed": false }, @@ -5629,6 +5640,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "*Note: In the figure above, the energy in the system according to Verlet, RK2, and RK4 is so close that the lines are appearing on top of each other. If you zoom in really closely you can see the differences. \n", + "\n", "How does each integrator perform compared to the case of simple harmonic motion? Just by looking at the system response, it seems like the error is actually better here. Why is that? It is important to note here that the integrators perform differently for different system models. This is part of why we study numerical methods! There isn't a single answer to which scheme to use, it is dependent on the characteristics of both the scheme and the system.\n", "\n", "The effect of the damper is to remove energy from the system. So, in this case the damping term is helping to remove some energy from the system that the integration adds. As time goes to infinity, these solutions will converge, and the Euler and Runge-Kutta methods are stable. We can again look at the energy in the system according to each method. \n", @@ -7287,7 +7300,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this case, it appears that Verlet, RK2, and RK4 give almost identical results, which are close but not exactly the same as the analytical solution. So for the damped harmonic oscillator, there is less of an advantage to using a more complex integration scheme. Try modifying the code above to make a phase portrait for the simple harmonic oscillator. We should see a circle (or an ellipse) form, which is called a limit cycle. Which integrators create this behavior?" + "In this case, it appears that Verlet, RK2, and RK4 give almost identical results, which are close but not exactly the same as the analytical solution. So for the damped harmonic oscillator, there is less of an advantage to using a more complex integration scheme. Try modifying the code above to make a phase portrait for the simple harmonic oscillator. We should see a circle (or an ellipse) form because the system behavior repeats itself. This is called a limit cycle. Which integrators create this behavior?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Conclusion\n", + "\n", + "In this notebook we discussed four different numerical integrators and applied these to a mechanical system. Even for the relatively straightforward case of harmonic motion, we found that different integrators can have very different behavior. In order to choose the best method (not too complex, but still accurate) we investigated how each method affects conservation of energy. We also found that not every situation is the same - the integrators performed differently for damped and undamped systems.\n", + "\n", + "In the next notebook, we will take these numerical integrators and apply them to the more complex double pendulum system. We'll use some of the same tools to analyze the behavior of each numerical integrator. However, there is no closed form solution for the double pendulum, so we'll use what we learned in this notebook to try and determine the effectiveness of each method on the more complex system." ] }, { @@ -7296,7 +7320,7 @@ "collapsed": false }, "source": [ - "Sources: \n", + "Sources and additional resources: \n", "\n", "[1] http://scienceworld.wolfram.com/physics/DoublePendulum.html\n", "\n", @@ -7311,12 +7335,15 @@ "[6] http://www.unige.ch/~hairer/poly_geoint/week2.pdf\n", "\n", "[7] http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1994CeMDA..60..409T&defaultprint=YES&filetype=.pdf\n", - "\n" + "\n", + "[8] http://www.ujfi.fei.stuba.sk/fyzika-ballo/Runge-Kutta.pdf\n", + "\n", + "[9] http://web.mit.edu/10.001/Web/Course_Notes/Differential_Equations_Notes/node5.html" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -7471,7 +7498,7 @@ "" ] }, - "execution_count": 25, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -7484,6 +7511,15 @@ "css_file = './numericalmoocstyle.css'\n", "HTML(open(css_file, \"r\").read())" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": {