<?xml-model href='http://www.tei-c.org/release/xml/tei/custom/schema/relaxng/tei_all.rng' schematypens='http://relaxng.org/ns/structure/1.0'?><TEI xmlns="http://www.tei-c.org/ns/1.0">
	<teiHeader>
		<fileDesc>
			<titleStmt><title level='a'>Python for Classical Mechanics</title></titleStmt>
			<publicationStmt>
				<publisher>University Science Books</publisher>
				<date>09/01/2024</date>
			</publicationStmt>
			<sourceDesc>
				<bibl> 
					<idno type="par_id">10590262</idno>
					<idno type="doi"></idno>
					
					<author>Charlotte Christensen</author><author>Paul Tjossem</author>
				</bibl>
			</sourceDesc>
		</fileDesc>
		<profileDesc>
			<abstract><ab><![CDATA[heavily on computer programming for analyzing data and modeling systems, yet time constraints often prevent undergraduate physics students from taking the computer science courses needed to develop these skills. This textbook integrates scientific programming instruction directly into a standard undergraduate classical mechanics physics course. Built to accompany John Taylor's popular Classical Mechanics, this text provides a series of interactive Python computational exercises that analyze classical mechanical systems from both analytical and numerical perspectives. The exercises guide students chapter-by-chapter through modeling classical physics systems such as the simple pendulum at high angle, two or more gravitational bodies in orbit, and damped, driven oscillators leading to period-doubling and chaos. The text uses guided instruction in critical programming techniques such as loops, logic, array manipulation, numerical integration, and data analysis and plotting to help intermediate physics students gain proficiency in both analytical and computational methods. It assumes no prior knowledge of programming on the part of the student and includes step-by-step instructions for starting the student programming in Python with the interactive Jupyter Notebook interface"--Provided by publisher.]]></ab></abstract>
		</profileDesc>
	</teiHeader>
	<text><body xmlns="http://www.tei-c.org/ns/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink">
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Preface</head><p>A cross the past several decades, computer programming has arisen as an essential tool in the study of physics. Within the study and application of physics, computer programming is used extensively to analyze data, control instruments, and model systems. Students are also acutely aware of the value of programming ability not only as a practical tool for their physics pursuits but also as an asset to their employability within physics and beyond.</p><p>The impetus for this work comes from the 2013 Grinnell Physics Department self-study, wherein physics students demanded increased programming instruction. Unfortunately, completing a standard undergraduate physics major leaves little time for additional computer science courses. Moreover, the content of most computer science courses is understandably focused on the interests of computer scientists. Consequently, students might spend semesters mastering topics like sorting algorithms, memory management, and data structures while missing out on essential physics-related programming tools, such as data plotting and numerical integration. While we fully encourage the interested student to delve into the former, we believe that all physics majors should be exposed to the latter. With this in mind, we have crafted this text to equip students with a fundamental tool kit of programming skills tailored to the realm of physics.</p><p>While the motivation of this text was to introduce students to the basic programming tools of computational physics, our second goal is no less important. This text serves as a vehicle to reinforce mechanics concepts and enhance students' physical intuition. It achieves this by guiding students through explorations of various physical systems, such as harmonic oscillators, gravitating bodies, and rotating objects. The computational analysis of these systems and the accompanying visualizations complement the standard analytical treatment by allowing students to "see" the motion described by the equations.</p><p>This text consists of a series of units that guide students through the exploration of various systems. It is designed to be interactive and to encourage inquiry-driven analysis. Programming instruction is interwoven throughout the text. Students are provided ample opportunities to hone their programming skills, with guidance gradually tapering off as they progress.</p><p>By its nature, this text straddles the line between programming and physics instruction. It provides little discussion of the theory of computer science or even algorithms beyond numerical integration (for that, we direct the student to a computer science course). Nor should it be considered a comprehensive Python reference (for that, we direct the student xv xvi Preface to the extensive official online documentation of Python and its libraries). Similarly, it is not meant to be treated as a stand-alone mechanics physics text but rather to complement Classical Mechanics, by John Taylor. We hope, though, that it aptly fills its niche of providing interactive programming instruction within a physics context.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Introduction 1 1.1 Why Classical Mechanics? Why Python?</head><p>In designing a text that melds programming with physics, we had to decide both what physics content to focus on and what programming language to use.</p><p>Intermediate Classical Mechanics stood out as the natural place to incorporate programming into an undergraduate physics sequence. The content of Classical Mechanics is uniquely suited to computational modeling. A core concept underlying Classical Mechanics is the discovery and solving of the differential equations of motion that describe the behavior of a system. Programming a computer to numerically integrate the equations of motion is often the preferred or even only way to solve these equations. Consequently, Classical Mechanics provides an abundance of interesting computational problems. Additionally, the highly theoretical nature of the typical Classical Mechanics course makes computer simulations especially valuable for building a student's conceptual understanding. Such simulations aid students in bridging the mental gap between the equations describing a system and the system's observable behavior.</p><p>Moreover, students typically take their first Classical Mechanics course shortly after declaring their physics major. Placing programming instruction relatively early in their undergraduate physics education provides students with a skill base that can be used in future physics courses, research opportunities, and internships. Conversely, deferring programming instruction until after the first couple of introductory physics courses allows students to acclimate to physics coursework before adding additional programming challenges.</p><p>The selection of Python as the programming language was based on its broad utility to students and its approachable learning curve. Python is widely used across disciplines and at different proficiency levels. It is used by laboratory physicists controlling instruments, Google researchers developing natural language algorithms, data scientists developing network models, and many others. Therefore, basic knowledge of Python will position students well for a broad spectrum of career goals. In contrast, Mathematica, C++, and Fortran (all widely used in physics) provide a more specialized skill set.</p><p>The wide use of Python stems in part from its price (free!), in part from its intuitive syntax, and in part from the abundance of third-party packages that have been written for it. These packages include NumPy for manipulating arrays of data, SciPy for scientific computing, and Matplotlib for plotting and data visualization. The richness of these packages and their ongoing development mean that there are a large number of powerful tools available to the user. Finally, the use of Python allows for the Jupyter Notebook graphical, browser-based interface. This notebook interface allows the user to compile and run code interactively and in small segments, making it easier to understand and debug. Together, these qualities of Python make it particularly friendly to the beginning programmer.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="1.2">Who Is This Book For?</head><p>We wrote this text with the intermediate physics student with no or limited experience in programming in mind. The first two units, therefore, provide an overview of the basics of Python programming. More advanced instruction and opportunities to practice programming are progressively incorporated throughout the remainder of the text.</p><p>While the text primarily targets less experienced programmers, any Mechanics student should find the physics content engaging. In How to Use This Book, we suggest various entry points that will allow students with programming experience to bypass some of the introductory programming instruction. After the first couple of units, the physics concepts become the focus of the work and provide ample challenges in and of themselves. We also provide additional extension problems for those students who progress more swiftly or who seek greater challenges.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="1.3">A Word of Encouragement to the Student</head><p>In our own Mechanics courses, we see students enter with a wide range of programming backgrounds. It is common for students who start with less programming experience to be self-conscious if they move more slowly through the material or desire more instructor support. From survey responses, we have learned that students who enter with less background find the material more frustrating but, ultimately, find the most value in the experience. Therefore, we wanted to conclude with a word of encouragement to the novice programmer.</p><p>Anyone can learn to program. Everyone has a picture in their mind of the stereotypical programmer. But it doesn't matter whether you know what a kilobyte is, whether you have built your own computer, or even whether you have seen all of the original Star Trek -if you can think through a sequence of actions you would like a computer to undertake, programming is within your grasp. If we could grant any qualities to the beginner programmer, therefore, it wouldn't be a particular knowledge base, but rather the curiosity to explore, the courage to try new things, and the resilience to try again when the first attempt (or five) fails.</p><p>Programming is a skill, and like any skill -be it riding a bike or baking a cake -it has to be learned by doing. The only way to improve is through practice and time. Frustration and error are inherent to the experience no matter how long you have been programming (which, as people who have been programming a long time, we can attest to). Encountering a challenge or making a mistake doesn't make you a bad programmer, it just means you have to spend more time debugging until you figure it out. Your reward is the unique satisfaction of having solved the puzzle. units. Although it is natural to balk at reducing the amount of time devoted to analytical problem solving in a Classical Mechanics class, our assessments indicate that this replacement does not decrease students' ability to solve physics problems, while increasing their programming ability. We believe that this lack of trade-off is because the units remain centered on the physics concepts and thus reinforce analytical analysis rather than diluting it.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="2.2">Scheduling</head><p>For each of the chapters in the "Essentials" part in Classical Mechanics, this text provides one to two accompanying units. It concludes with three units exploring the Damped Driven Pendulum system that is the focus of Chapter 12, "Nonlinear Mechanics and Chaos", from Classical Mechanics. Although Chapter 12 is listed as one of the "Further Topics" chapters in Classical Mechanics, it is too well suited to numerical analysis for us to pass it by. Conversely, despite the importance of the Hamiltonian formalism to the study of classical mechanics, we do not include a computational unit on it. Not only is Chapter 13, "Hamiltonian Mechanics", not included in the "Essentials" part of Classical Mechanics, the concepts are almost entirely analytical and did not strongly benefit from additional numerical examination.</p><p>When we offer this course at Grinnell College, students complete twelve of the units during a semester course, one each week. While the assigned units roughly parallel the lecture content of the course, the content is sometimes offset by about a week in either direction. For example, students are assigned The Three-Body Problem while they are still completing Chapter 7, "Lagrange's Equations" from Classical Mechanics in preparation for Chapter 8, "Two-Body Central Force Problems. " Similarly, the second and third units on Chaos (Bifurcation Diagram and Poincar&#233; Diagram) are completed while they are covering Chapter 13, "Hamiltonian Mechanics" in the lecture. We find that this level of temporal offset is small enough to maintain the connection between the lecture content and the computational work while allowing for more or less computational analysis, as warranted.</p><p>We have designed these units to be modular to reflect the different scheduling needs of individual courses. While the scaffolding across units requires them to be completed roughly in order, not every unit needs to be assigned. Indeed, these units intentionally provide more content than could easily be covered in a semester-long course to allow the instructor to pick and choose among them. The flowchart indicates which units depend on which and provides varying points of entry, depending on student programming background. For instance, Python-savvy students might skip the initial one or two units -or even the initial fourif an in-depth exploration of numerical integration algorithms is not desired. Whenever a smaller programming concept, such as numerical root finding, is introduced, we provide a basic description in case the unit in which it previously appeared was omitted. For more complex programming techniques, such as numerical integration using the solve_ivp() solver from SciPy, we offer appendices as reference.</p><p>As students progress through the units, they will encounter more complex programming techniques with less guidance and example code. The flowchart specifies which core programming concepts are used in which units. Furthermore, each unit's introduction lists programming learning objectives (designated "PRO"), in addition to physics-focused learning objectives (designated "PHY"). Finally, the Index may be used to find where different programming techniques and Python tools are introduced.</p><p>Upon completing these units, we intend that all of the most important tools and techniques for the physics student will have been introduced. (Of course, not everything could be included, and some advanced topics such as data analysis via the Pandas library or symbolic mathematical analysis using SymPy are not included here.) Inherent in the structure of guided exercises, though, is that students will have had little practice in setting up their own programs from scratch. The "Challenge Exercises" address some of this gap by providing more open-ended prompts with minimal template code. We have also had success in assigning final projects. Following these units, students in our classes have modeled and analyzed such systems as the Duffing oscillator, a Hohmann transfer orbit, and the motion of a projectile subject to drag forces, the Coriolis force, and the centrifugal force. Such final projects encourage students to generate their own questions, make connections across concepts, and plan the structure of their program.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="2.3">Working through the Units</head><p>Each unit consists of ten to twenty analytical and computational exercises unified around a particular physics concept or system (with the exception of the first unit, Introduction to Python, which acquaints students with Python basics and has no unifying physics theme). These exercises should be tackled in sequence, although occasionally we note an analytical exercise that may be assigned ahead of time. The units take on average about three hours to complete. However, there is some variation across units and a large variation among students. To address these differences and offer flexibility, we have distinguished between essential exercises (all those listed in black) and extension exercises (those listed in gray and indicated by an asterisk). Any extension exercise may be omitted for time without compromising the main ideas of the unit or precluding the completion of the later essential exercises. Additionally, almost all units include suggestions for "Challenge Problems". These problems include much less guidance and are intended to stretch the abilities of the more experienced or faster programmer.</p><p>Instruction for these exercises is provided in multiple different ways. Text descriptions (given within the "markdown cells" in the downloadable notebooks) explain the general concepts and provide the exercise prompts. Additionally, there is a large amount of example code, which demonstrates how different programming tools and techniques operate, and template code, which should be modified to complete the exercises. This code is interwoven with the text and appears as runnable code cells within the downloadable notebooks. Comments within the example code describe the function of different lines of code. They also provide directions to the student. Places where the student is asked to modify the code are generally indicated by three hashmarks: "###". Text, code, and comments should all be read carefully as each provides its own form of instruction.</p><p>The exercises are meant to provide direction in how to explore a physical system or experiment with a new programming technique. They are only a starting point, though, and are not meant to be an exhaustive list of possibilities. The more one takes the time to explore, the more profound the learning. One shouldn't hesitate to dive in, play around, and experiment.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="2.4">Relationship between Units</head><p>A flowchart describing the relationship between the units is begun in Figure <ref type="figure">2</ref> Physics concepts are designated in green and programming techniques in blue. The corresponding chapters from Classical Mechanics, when applicable, are shown in black at the bottom of each block. The light blue flowlines show the suggested order of approach, as the programming topics in the source block are built on by the connected block. When the arrow extends from the side of the block, it indicates that the specific corresponding programming technique is used in both connected units. The first time the programming technique is introduced, it is listed in bold. Light green flowlines indicate a suggested order of approach for units that explore similar physics concepts. Even absent the light blue and green flowlines, the units should be completed approximately in vertical order, reading from the top down. The dark gray flowlines point to units that cannot be undertaken without completing the unit in the source block, as the exercises in the latter build on code developed in the former.</p><p>Three possible entry points are suggested by the mauve ovals. We anticipate that most students will start at the first unit, "Introduction to Python". However, students with a stronger background in Python may start at "Numerical Integration Applied to Projectile Motion" or later. Before proceeding to the units shown in Figure <ref type="figure">2</ref>.2 and Figure <ref type="figure">2</ref>.3, all students should either complete "Launching a Rocket" or have read through "Appendix: Using solve_ivp() for Numerical Integration, " as this technique for solving differential equations numerically is critical to all subsequent units other than "Principal Axes of a Cuboid". Appendices, which may be used to fill in gaps in understanding caused by missed units or simply for a more in-depth treatment of a topic, are shown in solid gray blocks. They may be referenced at any point, although they will be most useful for those units whose flowlines pass beneath the block.</p><p>While the light blue and green arrows may imply dependency, this is not strictly the case. We anticipate that most classes will only work through a subset of these units. Therefore, we generally provide some guidance on the more advanced programming tools and techniques each time they appear. As such, one should feel encouraged to work through a unit without completing a preceding unit. In fact, one might even choose to work through a set of less-connected units in order to maximize the variety of programming tools and techniques encountered. Light green flowlines similarly indicate a conceptual connection and an increase in complexity but not a dependency.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Introduction to Python</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Jupyter Notebook</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Printing text and variables</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Data types</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Mathematical calculations</head><p>accuracy by modifying the code to use the "improved Euler" or "leapfrog" method. This solver is applied to 2-D projectile motion.</p><p>Students gain their first experience writing an algorithm in this unit, and it ensures that students are familiar with the techniques and limitations of numerical ordinary differential equation (ODE) solvers before they use the built-in Python/SciPy ODE solvers.</p><p>Although students will likely already be familiar with the physics concepts in this unit (specifically, projectile motion without air resistance), this unit serves as a precursor to the following unit, Projectile Motion with Drag, and is therefore best completed in conjunction with or before Ch. 2 of Classical Mechanics. coordinates before completing the numerical integration. They check their code and build physical intuition by first computing trajectories for special cases and then exploring isolated changes to the initial conditions. Finally, students are guided through the modeling of realworld situations.</p><p>This unit provides students with extensive practice with spherical coordinates, in both their analytical calculations and their visualizations. It also provides instruction in threedimensional plotting.</p><p>This unit continues the exploration of material from Ch. 9.1-9.7, but extrapolates the ideas from a plane to the surface of a sphere.</p><p>Files: u16_coriolis.ipynb</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="4.1.1">Python installation</head><p>If you are installing Python for the first time, our recommendation is to use the Anaconda distribution of Python. With the continuous development of Python and, in particular, its libraries means it can be challenging to ensure compatibility among them. Anaconda eases this process enormously. Because the packages needed for these exercises are widely used, the default programming environment created for you by Anaconda during installation should include everything you need. However, if you eventually decide to use Python more extensively, Anaconda allows you to easily install other packages and to create and manage environments with distinct sets of packages.</p><p>You may download and install Python 3.x for free through Anaconda by using the link and following the instructions on the website here: <ref type="url">https://www.anaconda.com/products/   distribution</ref>.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="4.1.2">Running Jupyter Notebook</head><p>After installing Anaconda Python, use it to start Jupyter Notebook. On a Microsoft PC, you will find Jupyter Notebook within the "Anaconda3" folder accessible through the Start Menu. On a Macintosh computer, locate "Anaconda-Navigator" using the spotlight search or some other method. Once you have started the Anaconda-Navigator, select "Jupyter Notebook" (or JupyterLab, if you prefer) from the menu.</p><p>When you start Jupyter Notebook, a terminal window running Python will open. Do not close this window; you can minimize or move it aside. You should be able to ignore it for the remainder of your session. After the terminal window opens, a Jupyter notebook will automatically open as a tab in your default web browser window.</p><p>In the Jupyter Notebook tab, you will see a list of folders in your current directory. At this point, you will want to open the desired interactive unit (for example, tableOfContents. ipynb). The easiest way to access all the units is to download all the files into a single folder. Then you can navigate through the directory tree to locate that folder by selecting the appropriate folders listed in the Jupyter Notebook tab. Once in that folder, you can open individual units by selecting them. We recommend starting by opening tableOfContents.ipynb and then using the links within that document to open individual units.</p><p>Alternatively, you may click on the "Upload" button located toward the upper right of the screen or drag and drop files to upload individual units into the current working directory. The uploaded file should then appear in the list, and you will be able to open it by selecting it.</p><p>Should you prefer to create your own Jupyter notebooks and write code into those files rather than downloading the interactive exercises, you may do so. To create a new Jupyter notebook, select the New pull-down menu located in the upper right of the screen. Then select "Python 3" to create a new notebook using that version of Python code.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="4.1.3">Closing Jupyter Notebook</head><p>When you believe you have completed a unit, it is good practice to restart the kernel (found under the Kernel menu) and rerun the entire notebook in order to verify the code's functionality.</p><p>Before closing Jupyter Notebook, remember to save the notebook you're working on. You can do this by selecting the floppy disk "Save" icon or opting for "Save" within the Jupyter "File" menu within the browser tab. Following this, close the tab containing the notebook. Next, navigate to the browser tab running Jupyter Notebook and showing the directory contents. Select "Quit" in the upper-right corner of the screen to close the program. You may then close this browser tab and the terminal window that opened upon starting Jupyter Notebook.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="4.2">Using a Web Server to Run Jupyter Notebook</head><p>If you prefer to not install Python onto your computer, you can run Jupyter Notebook through your browser using a web server, for example Google Colab <ref type="url">https://colab.  research.google.com/</ref> or CoCalc <ref type="url">https://cocalc.com/</ref>. This approach eliminates the need for software installation and offers the advantage of cloud storage accessibility from any computer via the web. It can also facilitate collaboration among different users. To make use of this feature, add users to your project.</p><p>One of the primary limitations to using a web server is that the platforms differ in appearance from both Anaconda3 and each other. This is particularly the case with Google Colab, which uses a simplified version of Markdown for text cells called "Text, " such that some of the formatting of the notebooks may appear different. Google Colab also does not support internal links between notebooks, making file navigation more difficult. Since these platforms are under continuous development, their appearance may change over time. With these caveats in mind, we have attempted to provide general instructions. However, please note that some adjustments may be necessary when running the notebooks on these platforms.</p><p>Begin by creating an account on the web server and logging in. Once logged in, we recommend you upload all the files, both notebooks (ending in .ipynb) and supporting files (ending in .py, .csv, and or .png). Instructions for doing this using both CoCalc and Google Colab are below. Alternatively, though, you can manually transcribe the text contents into new Jupyter notebooks.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="4.2.1">CoCalc</head><p>Select "Your Projects" from the navigation bar near the top of your screen. Create a new project. Upload files to your project by scrolling to the bottom of the screen and dragging files into it from your file browser. You can also create new notebooks.</p><p>Then select "Explorer" on the left-hand toolbar. From this window, start with tableOfContents.ipynb and use the embedded links to navigate to the units you desire.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="4.2.2">Google Colab</head><p>Upon opening Google Colab, a window may automatically open giving you the option to open various notebooks. If so, select the "Upload" tab and either use the "Choose File" option or drag and drop from your file navigator to upload a notebook. Once a notebook is open, you may load an additional one by selecting "Upload notebook" from the File menu within Google Colab. Supporting files, such as those ending in .py, .csv and .png, must be added to your "Colab Notebooks" folder on Google Drive.</p><p>Unfortunately, at the time of writing, Google Colab lacks an easy method for uploading multiple files at once. Additionally, internal hyperlinks between notebooks, such as in table-OfContents.ipynb, also fail once the notebooks have been uploaded to Colab. Given these constraints, the most straightforward approach is to upload and open the Jupyter Notebook for the desired unit using its filename. Filenames of notebooks and associated supporting files are listed in the table of contents. You may also always see the filename linked to from a notebook by double clicking on the cell. Sometimes you will wish to access supporting files, such as images, data, or modules, from within your notebooks. To do this, you will need to make sure that file is within your "Colab Notebooks" folder on Google Drive. Then you must "mount" your Google Drive folder by adding the following commands to a cell in your notebook and running it.</p><p>W elcome to the computational component of your Mechanics course! This first unit is designed to get you started programming in Python. Through it you will learn how to edit and run Jupyter Notebook (an interface for interactively coding in Python). You will also learn basic Python data types and how to perform calculations, print, and plot your results.</p><p>If you are familiar with Python, you may wish to skip some or all of this unit. For example, you may wish to skip to "Lists and Arrays" if you are familiar with Python syntax but would like an introduction to NumPy. Similarly, "Plotting" provides an introduction to plotting using the Matplotlib library and can be completed independently of the rest of the unit. Finally, if you are already well-versed in Python, Jupyter Notebook, NumPy, and Matplotlib, and are interested only in the physics-related content, you can move directly to the next unit.</p><p>The basic tools introduced in this unit will set you on the path to using programming in your physics studies. There is no better way to learn to program than to start. Therefore, rather than reading much introductory text, you will immediately start executing commands and working from examples.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; learn how to open, edit, run, and save in Jupyter Notebook (PRO); &#8226; become familiar with different Python data types and how to print variables and use them in calculations (PRO);</p><p>&#8226; manipulate Python lists and NumPy arrays (PRO);</p><p>&#8226; plot data (PRO).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.2">Working with Jupyter Notebook</head><p>These exercises are written using the Jupyter Notebook interface, which provides a webbased interactive computer platform for Python programming. "Interactive" means that the programmer (you) will be writing parts of the code while it is already active. This is a handy format for developing code and learning to program as it provides immediate feedback and ready access to different variables. We provide a description of it here.</p><p>While we have written these instructions for a local installation of Jupyter Notebook, these interactive units should run easily using JupyterLab and web servers such as CoCalc and Google Colab. Be aware, though, that these have slightly different interfaces and may differ somewhat in appearance and functionality from what is described here.</p><p>The menu bar near the top of the notebook includes a File menu for creating new or opening existing notebooks, saving, renaming, and printing. The Edit menu allows you to copy notebook cells, move them up or down, split at the cursor, or merge two cells (of the same type) together. All operations work on the currently highlighted cell, indicated with a colored outline on the screen. The Insert menu allows you to add a blank cell above or below the highlighted cell. The Cell menu permits you to run different cells, select the cell type (for example Code or Markdown), or clear all the output cells. The Kernel menu controls the starting and stopping of the Python calculation engine. Go to the Help menu for all questions about the various features and functions of Python and its additional libraries. Many of these menu functions are duplicated in the pictorial toolbar below it, and all of them have keyboard shortcuts (as found in the Help menu).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.2.1">Cell types</head><p>Jupyter notebooks consist of sets of cells that can be run by the user. The cells are either Markdown (or Text) cells or Code cells. The type of a given cell is listed in the pulldown menu in the toolbar above (this one is labeled Markdown). You can change the type of cell by selecting a different type from the pulldown menu.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Markdown cells</head><p>Markdown cells (a.k.a. text cells) generally contain text, for instance, instructions or explanations. Markdown refers to a markup language with syntax designed for writing formatted text. It is easily converted to HTML and other formats. Markdown syntax includes headers of various sizes, lists, paragraphs, color, text emphasis, formatted math LaTeX using MathJax, etc. You can also use HTML tags. For more detailed information see: <ref type="url">https://www.markdownguide.org/cheat-sheet/</ref>.</p><p>As an example of how Markdown syntax can be used to format text, here's your favorite equation using MathJax math L A T E X:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Try this:</head><p>&#8226; To see the syntax of this Markdown cell, click on this cell with your cursor and push Return (or Enter on a PC).</p><p>&#8226; Then run the cell (type Shift-Return) to show the marked-up cell. Alternatively, click the triangle icon &#9654; Run in the toolbar above.</p><p>&#10148; Note: To get help on other such notebook commands you may be able to press Esc-H. The Esc puts you into command mode; the h calls for a help page. This command currently works in Jupyter Notebook and CoCalc. It does not currently work in Google Colab or when using JupyterLab.</p><p>It helps to have line numbers in each cell so that you can refer easily to specific lines. If they aren't already on by default, hit Esc-Shift-L, or go into the View menu and click "Toggle Line Numbers" in your code cells.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Try this:</head><p>&#8226; Insert a cell below this one and type something in it.</p><p>&#8226; Change the cell type from Code to Markdown.</p><p>&#8226; See if you can move the cell up and down.</p><p>&#8226; Explore splitting a cell, merging cells, and deleting a cell.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.2.2">The kernel</head><p>The kernel is the process that actually executes (a.k.a. "runs") the code in the code cells of your Jupyter notebook. Knowing how to interrupt, shut down, and restart kernel is, therefore, important for being able to execute your notebooks. (In Google Colab, the kernel is referred to as "runtime, " but the principle is the same.)</p><p>The kernel is programming language specific, so while this notebook should be run in Python 3, other notebooks could be run with other versions of Python, provided you had those versions available to you (for instance, installed on your computer). The language the kernel is running in is listed in the upper right of your screen.</p><p>Occasionally, you may want to halt the execution of a code cell, for example, if it is taking too long to run, or if you discover a bug in it you want to fix. You can do this by selecting "Interrupt Kernel" from the Kernel menu or clicking the Stop button on the toolbar.</p><p>The kernel stores the information gathered by running the notebook in its memory. This information includes such things as the value of variables and the definition of functions. If you shut down or restart the kernel, this information will be wiped from the memory. While clearing the memory may sound scary, it is often very useful. For example, coding problems can easily occur if the computer has an incorrect or outdated value stored in a variable. A basic debugging practice is to use the Kernel menu and select "Restart" to restart the kernel. Then you can use the command under the Cell menu to execute all the cells in order.</p><p>The kernel may also sometimes shut down on its own, usually because it has run out of memory. If this happens, select "Restart" under the Kernel menu and use the commands under the Cell menu to execute the cells up until the point where the problem occurred.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.3">Introduction to Python Programming</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.3.1">Libraries</head><p>One of the benefits of Python as a programming language is that many people have written extensive code to perform basic (and not so basic) tasks. This code is stored in Python libraries. For example, the NumPy (Numerical Python) library includes a large number of mathematical functions and support for useful datatypes, such as "arrays". The Matplotlib library allows for the creation of data visualizations, in particular, plots and graphs.</p><p>In order to have access to the code stored in these Python libraries, they need to be imported using the import command shown below. Once the following cell has been executed, you will have access to all the functionality of the NumPy and Matplotlib.pyplot libraries. Note that in the latter case, the ".pyplot" selects a subset of the Matplotlib library most useful for generating plots.</p><p># The '#' key is the comment character. <ref type="bibr">Any</ref> line that starts with it, such as # this one, will not be executed. # (You can comment out a set of lines by highlighting, then pressing # Ctrl+'/'. Repeat to uncomment.) # The following are import statements, which we always put at the # beginning of a notebook. These bring in packages (also called # libraries) which add functionality to Python # numpy (or NumPy), short for Numerical Python, covers a huge number of # mathematical functions and datatypes # matplotlib.pyplot is part of a library that makes it easy to make # nice-looking plots and graphs import numpy as np # "as np" means that numpy commands will be prefaced by # "np", for example, np.array(a).</p><p>import matplotlib.pyplot as plt # "as plt" means that matplotlib.pyplot # commands will be prefaced # by "plt", for example, plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.3.2">Variables</head><p>In programming, variables are used to store information. These variables can be recalled or manipulated by the program to accomplish the desired goal.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Changing variables</head><p>Work through the following cells of code, executing each one by one:</p><p># This cell defines a variable, aObj, sets it equal to 234, and prints a # statement about it aObj = 234 print("The variable aObj is equal to ", aObj)</p><p># Now that aObj has been set to a value, other cells in the notebook will also # know its value.</p><p>print("The variable aObj is still equal to ", aObj)</p><p># If we change the value of aObj in this cell and then rerun the previous cell # it will update to the newest value.</p><p>### DO THIS: Try it for yourself by executing this cell and re-executing the # previous one.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>aObj = 1</head><p>If you want to clear all your variables, you can run the magic command %reset in a code cell. Or you can clear your current session (including any imported packages) by selecting "Restart &amp; Clear Output" in the Kernel menu. Try this now, and then execute the following code cell. Notice that the number next to the code cell is now 1, indicating that it is the first cell that has been executed. But you'll also encounter an error because aObj is undefined. Once you have convinced yourself that the variable was cleared, rerun the notebook to this point (to pick up all imports and variable assignments). You can do this easily by selecting "Run All Above" in the Cell menu.</p><p>print("The variable aObj is equal to ", aObj)</p><p>As you can see, the value of a variable can change depending on the order in which the previous cells were executed. This is one of the tricky aspects of programming in an interactive environment. Watch out for how your program changes when cells are run out of order, as failing to keep track of how different cells have modified a variable can easily lead to unintended behavior.</p><p>The final version of your code should run correctly when all the cells are executed in order. To check this, when you finish a unit, clear your output by selecting from the pulldown menu at the top "Cell"-&gt;"All Output"-&gt;"Clear". Then run the entire notebook by selecting "Cell"-&gt;"Run All" from the pulldown menu.</p><p>If you would like to print out your completed notebook, you may find that some web browsers handle margins and extended output windows better than others.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.3.3">Using Python for calculations</head><p>Python can easily be used for computations. The cell below gives three examples. Be sure you understand the syntax used. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>In the next cell, calculate and print the following for c = 4 and a and b as defined above. You'll need to look up the syntax of the square root function (Hint: It's part of NumPy, so should be referred to as np.sqrt() ). And note that Python uses ** as the exponentiating, or power, operator rather than &#710;. For expressions containing multiplication and division, the expression is evaluated in leftright order. This can have some unusual behavior. When in doubt, use parentheses to clarify the order of operations. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.3.5">Data types</head><p>Numbers in Python can be either integers, floating point (i.e., numerical approximations of real numbers), or complex numbers. type() is a built-in Python function that will return the data type of an object. a = 20 print("a = ", a, " and a's datatype is ", type(a)) b = 55.2 print("b = ", b, " and b's datatype is ", type(b)) c = 5+100j print("c = ", c, " and c's datatype is ", type(c)) (Note that Python follows the engineering convention that &#8730; -1 &#8801; j.)</p><p>### DO THIS: Try modifying the following line of code to add and multiply # different numerical data types.</p><p># Make sure you understand the output print("a+b = ", a+b, " and a+b's datatype is ", type(a+b))</p><p>Data may also be stored as a string (i.e., a set of characters, such as a word or sentence). Either single (e.g., 'word') or double (e.g., "word") quotation marks can be used in Python to designate a string; triple quotation marks can create a multiline string. Generally, double quotation marks are preferred for the purposes of these exercises, although you may encounter cases where single quotation marks are needed for clarity, such as when the string itself contains double quotation marks, as shown in the example below: </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.3.6">Formatting output</head><p>Sometimes you may want to format numerical output (for example, the a/b output from the previous section), so it does not print so many decimal places. The cell below shows you how, by placing the string object within single or double quotation marks and then invoking the format() method.</p><p># Examples of formatting # 6 places, with 3 numbers after the decimal point.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>#</head><p>The "f" signifies that the number is a float (a number with a decimal # point). While in the example above the strings were defined in a separate line prior to printing, these commands can be combined into one line:</p><p>print("(a/b) = {:6.3f}".format(a/b))</p><p>The format method also allows you to include multiple variables in your formatted output by using multiple sets of curly brackets. By default, the first curly bracket refers to the first variable, the second to the second, and so on. If you would like to print the variables in a different order, a number can be included inside the curly brackets indicating which variable to use, with "0" indicating the first element. print("{0}/{2} = {1:6.3f}".format(a, a/b, b))</p><p>Exercise 2*</p><p>In the cell below, print out the first 10 digits of &#960;.</p><p>&#10148; Hint: NumPy has a stored version of &#960; . See if you can find how to call it. The web might be especially useful here.</p><p># &lt; Exercise 2, first 10 digits of pi &gt;</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.4">Lists and Arrays</head><p>Frequently it is useful to store sets of data. Python allows you to do this using "lists" while NumPy provides the added functionality of "arrays". Lists allow you to store multiple types of data together. Arrays store only one type of numerical data, but they have the advantage that you can perform vector (array) operations on them, as illustrated below. Calculations on arrays are usually much faster than corresponding calculations on lists because the Python interpreter does not need to check the datatype for each element. Arrays are defined in the NumPy package, the fundamental package for scientific computing in Python. This package has many built-in functions that you will find useful. You can get a list of all NumPy built-in functions from np.&lt;tab&gt;.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># A list of objects can contain variables of any datatype # Here is how you define a list:</head><p>List1 = [1, 2, 3, 4, 5, 6] # Note the square brackets surrounding the elements # of the list, separated by commas print("Here's a list of integers: ", List1)</p><p>print("This list is of type: {}, and length {}".format(type(List1),len(List1)))</p><p>List2 = [a, "b", "cdefg", "h", "i", <ref type="bibr">3,</ref><ref type="bibr">5,</ref><ref type="bibr">7]</ref> print("This list is of mixed datatype, consisting of integer variables, "</p><p>+ "strings, and integers: ", List2) When defining an array, the easiest way to do this is to send a list (elements surrounded by square brackets and separated by commas) to the np.array() function, for example: In the above code cell, an optional second parameter "float" was also included in the np.array() function call: nArrayConverted = np.array <ref type="bibr">(List1,</ref><ref type="bibr">float)</ref> This optional parameter tells Python that all the data stored in the array should be considered a float. If this parameter is not included, Python will make its best guess as to the appropriate data type.</p><p>Below is an example to illustrate another important distinction between lists and arrays. See if you can tell the difference. Likewise, you can multiply two arrays together, provided the arrays are the same length. Notice that this results in element-wise multiplication, neither a dot product nor a cross product. NumPy has separate routines for those manipulations that you can read about at np.dot and np.cross. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Multiplication of an array by a constant gives completely different results</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>#</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>In the cell below define two arrays: even = (2,4,6,8) and odd = (1,3,5,7). Add 3 to the even array and multiply the odd array by 2. Then multiply the two arrays together and print your answer. See if you can do everything except defining the arrays in one line of code.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 3, Defining and manipulating arrays &gt;</head><p>The added mathematical functionality of arrays means that they will generally be more useful than lists for computation. You should default to using arrays over lists in these exercises.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.4.1">Making arrays</head><p>You have seen that we can convert a list of numbers into an array using np.array(). We can avoid that intermediate step and declare arrays to make evenly spaced numbers. There are two main ways to generate arrays in Python: np.linspace() and np.arange(). An example of each is shown below.</p><p>Try to understand the purpose of each argument in these two calls. The main difference is that np.arange() excludes the right-hand endpoint, whereas np.linspace() includes the right-hand endpoint. array1 = np.arange(0, 3, 0.1) print("array generated with 'np.arange()': ", array1) array2 = np.linspace(0, 3, 30) print("array generated with 'np.linspace()': ", array2)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>Generate a regularly spaced array with values ranging from 1 to 5 (inclusive) and spacing between array values of 0.1. Do this with both np.arange() and with np.linspace() and print out your results in the cell below.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.4.2">Indexing and slicing</head><p>Data are stored in lists and arrays by index, with the index count starting at zero. One tool that makes lists and arrays especially powerful is the ability to select different elements from them. The block of code below will show you several examples of how to do so, including how to identify non-zero elements using the .nonzero() method, find the maximum value using the .max() method, find all elements greater than some value, and count how many times a value occurs. print("\tOr more simply, ", arrayA.max(), "using the .max() method directly.") print("\nWe can find an array containing the indices of arrayA that are not" + " zero, using the .nonzero() method:") print(arrayA.nonzero())</p><p>print("But note that this result is itself a tuple, so we extract the" + " and the step size:") print("Here is a better way to make an array of even values, using" + " np.arange(0,10,2): \n", np.arange(0,10,2))</p><p>print("Or odd values, using np.arange(1,11,2): \n", np.arange(1,11,2)) print("If you want to find the indices of all elements greater than 4,"</p><p>+" simply do this:")</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.4">Lists and Arrays 35</head><p># Below is a logical test. Use &gt;, &lt;, ==, &gt;=, &lt;=, != (not equal) print("arrayA &gt; 4", arrayA &gt; 4)</p><p>print(" and so here they are: ", arrayA[arrayA &gt; 4]) print("Here are the elements that are not equal to 5: ", arrayA[arrayA != 5])</p><p>print("And you can count the number of 5s like so, using the .sum() method") print("(arrayA==5).sum(): ", (arrayA == 5).sum())</p><p>print("\nHere is an illustration of slicing, selecting only certain elements"</p><p>+ " in an array.") print("Slicing uses three optional index values representing the start index,"</p><p>+ " the end index,") print("and the increment of the index, separated by colons, like so:") &#10148; Hint: The function len() will return the length of an array, and round() will round a float to an integer, suitable for indexing the elements within the array (which you cannot do with a floating-point number).</p><p># &lt; Exercise 5, Practice slicing arrays &gt; arrayA = np.array([1, -6, 5, 0, 0, 9, 5, -4, 1, 2, 2, 5, 20, 3])</p><p>Arrays can also have more than one dimension. When a multidimensional array is defined, as below, indices can be specified to select individual elements or entire columns and rows. When referencing an element, the row index is listed first and the column index is listed second. print("Reshape arrayC to have 6 rows and 2 columns:\n", arrayC.reshape(6, 2))</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6</head><p>Write code to print out the following for arrayB:</p><p>1) the element in column 1 and row 2; 2) the entire column 1; and 3) the entire row 2.</p><p># &lt; Exercise 6, Slicing 2-D arrays &gt;</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.4.3">An aside on additional data types</head><p>If you progress further into Python programming you may encounter tuples, sets, and dictionaries. Like lists, these are built-in (meaning you don't need to load an additional library like NumPy to use them) data types that can store collections of data items. A tuple is very similar to a list but has the additional restriction that once it is defined it cannot be changed without redefining it. (For instance individual items cannot be changed nor can items be appended to it.) Tuples are defined using parentheses instead of square brackets:</p><p>A set is like a tuple but the items within it have no set order, and it cannot contain duplicate items. They are written with curly brackets. In a dictionary, each item consists of both a "value" and a "key, " and specific values are referenced by their key rather than their location within the dictionary. Dictionaries are also defined with curly brackets, and colons are used between the values and the keys. For example, the code below would print 9 AM. course = {"name": "Classical Mechanics", "Department": "physics", \ "capacity": 32, "time": "9 AM"} print(course["time"])</p><p>You will not be required to use any of these data types in these units. In particular, lists can generally be used in place of tuples. When explaining what a function does, though, we will occasionally use this terminology because functions often return tuples.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.5">Plotting</head><p>One of the benefits of using Python is that it makes it very easy to create attractive graphics. In particular, we will be using it to plot data in these units. In learning how to generate a plot, the examples in the matplotlib references (in particular <ref type="url">http://matplotlib.org/</ref>) are good places to start.</p><p>There are several different ways to make plots in Python, but we will focus on the matplotlib object-oriented method, as it provides a nice balance between ease of use, readability, and adjustability.</p><p>Object programming is a particular approach in which programs are based on the use of "objects" -programming elements that can contain both code and data. Python is especially well suited to this approach, and many of the Python libraries you will use rely on objects. For example, the NumPy arrays that were introduced earlier are objects. These arrays contain data (e.g., the numbers stored in them) but also procedures (e.g., code to transpose the array, sum its terms, or compute its average value). You will learn more about object-oriented programming later when you write your own classes.</p><p>The core concept of object-oriented plotting methods is that you create Python objects, and then you can add new objects inside of those or change elements of those objects. The final plot you see is a </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>Fix the code below to generate example data to be plotted. Your x data should be an array of 1,000 elements that runs from 0 to 4&#960;. The y data should be the result of y = sin(x). plt.show() This is OK, but not great for a number of reasons. The minimum and maximum values chosen on the x and y axes are not ideal, and there are no labels to denote what you are plotting. We can improve it by, for example, setting the x-axis limits using ax.set _ xlim(min, max), which sets the limits, rather than letting Matplotlib try to choose them automatically. plt.show() Exercise 8* Try to make a nicer version of the same plot that looks like the one pasted in here: 0 2 4 6 8 10 12 x 1.0 0.5 0.0 0.5 1.0 y sin(x)</p><p>(You can also find a copy of this image stored as intro_Ex_8.png.)</p><p>&#10148; Hint: You can type ax. <ref type="bibr">[tab]</ref> to list all of the commands you can put after it. Once you have typed in a new command, typing Shift-Tab will provide you with additional help for that command, e.g., the types of data that can be passed as arguments. Also, Google and ChatGPT can be extremely helpful for finding sample code and examples. Just make sure to obey the rules of academic honesty and never pass off someone else's work as your own!</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.5">Plotting 39</head><p># &lt; Exercise 8, Improving a plot &gt; # Plot commands go here:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5.5.1">A few more plotting tricks</head><p>Matplotlib offers many, many ways to customize your plots. The code cell below illustrates how one can include a plot legend, add horizontal and vertical lines, and create custom labels for the ticks on the x-axis. # Include cos(x) as well: y2 = np.cos(x) ax.plot(x, y2, 'b',lw = 3.0) ax.set _ title('sin(x) and cos(x)') # Generate a legend ax.legend(['sin(x)', 'cos(x)']) # This line associates text using the color # of each curve plotted on ax, in the # order in which they were plotted # Note that if you plot more than one curve, # separate the text strings with commas # and place them into a list [] # Add horizontal and vertical lines to a plot # A dashed red line at y = 0 ax.axhline(0.0, color = 'red', linestyle ='--') # A solid light blue line at x = 2 * pi ax.axvline(2 * np.pi, color = 'DodgerBlue', linestyle = '-') # To get really fancy, label the x-axis in units of pi by providing a list of # labels at the correct locations.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>#</head><p>Here we make a list of 5 labels that we pass to the tick method, which # expects an array of values, followed by a list of tick labels.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>#</head><p>See matplotlib documentation for other options, including minor tick # placement. labels = ["$0$", "$\pi$", "$2\pi$", "$3\pi$", "$4\pi</p><p>$"] # The text between $ $ # symbols is # interpreted # as inline LaTeX, # where \pi is the # Greek letter pi # If you want your title or legend to look better, you could write # ax.set _ title('$\sin(x)$ and $\cos(x)$'), etc. ax.set _ xticks(np.pi * np.arange(0,5,1)) ax.set _ xticklabels(labels) plt.show()</p><p>You may find the default figure size of inline plots to be too small for your preference. There are two ways around this. The first is to create each new figure with the figsize option, as shown below. plt.show() Alternatively, one may change the default figure size for any new figure, using the global plt.rcParams[] method: plt.rcParams["figure.figsize"] = (12, 8) # To revert to the small default size, uncomment the following line: # plt.rcParams["figure.figsize"] = plt.rcParamsDefault["figure.figsize"] ### TRY THIS After you have executed this cell, regenerate one of your plots # above, for instance by re-executing your work in Exercise 8. # Then you can try reverting back to the smaller figure size and executing # your work in Exercise 8 again.</p><p>Taylor Series with Loops and Functions</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>6</head><p>I n this unit, you will continue your introduction to Python within the context of a very important mathematical concept: Taylor series. Taylor series are so useful in Mechanics (and physics, in general) that they appear inside the front cover of Classical Mechanics!</p><p>The way Taylor series will be primarily used in this course is to approximate a difficultto-solve problem with a more easily solvable one. For example, in Section 2.3 of Classical Mechanics, Taylor series are used to find the horizontal range of a projectile subject to air resistance. More often, you will encounter situations in which non-linear differential equations are approximated as linear differential equations. This is done by assuming that a function, f (x), can be described as a polynomial of infinite degree and then generating an approximation of the function by ignoring the higher-order terms.</p><p>Your final goal in this unit will be to determine what degree of Taylor series is needed to approximate a particular function to a desired degree of accuracy. To accomplish this, you will need to understand logical statements, loops, and functions. These three techniques allow you to control the flow of a program, making them critical to any programming beyond the most basic tasks. Therefore, a large fraction of this unit will be devoted to introducing you to them.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.1">Objectives</head><p>In this unit, you will learn how to:</p><p>&#8226; use Taylor series to approximate a function (PHY); &#8226; use logical statements to write conditional commands (PRO); &#8226; write programming loops to enable the easy repetition of code (PRO); &#8226; write and call functions (PRO); &#8226; use the above programming techniques to write code to determine what order of Taylor series is sufficient to approximate a particular function to a desired accuracy (PHY);</p><p>&#8226; fit a polynomial function to data (PRO).</p><p>As usual, we will start by importing the appropriate Python libraries. This same cell also increases the default figure size using the global plt.rcParams[] method described at the end of Introduction to Python. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.2">Taylor Series</head><p>Taylor's theorem states that, for any reasonable function f (x), the value of f at a point (x) can be expressed as an infinite series involving f and its derivatives at the point a:</p><p>When a location of interest x is close to the reference location a, &#948; = (xa) is small, so the higher order terms in the polynomial become very small and can frequently be ignored.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.2.1">Derivation of Taylor series</head><p>We can derive Taylor Series for the specific case when a = 0. Assume a function f (x) can be approximated by a power series of the form:</p><p>We would like to find the value of the coefficients a 0 , a 1 , a 2 , etc., that satisfy this equation. We can start by taking a derivative of f (x) with respect to x:</p><p>Next we'll take the second and third derivatives:</p><p>We can now determine the values of the a n coefficients by choosing to approximate f (x) about x = 0. Substituting x = 0 into the above equations gives:</p><p>. . .</p><p>6.2 Taylor Series 43 and so</p><p>is the n th -order Taylor approximation of the function f near x = 0.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.2.2">Taylor series for sin(x)</head><p>We can try this out for the example of y = sin(x):</p><p>Therefore,</p><p>which is commonly referred to as the small-angle approximation.</p><p>Taking just the first-order term in a Taylor series is typically called a first-order Taylor approximation. Taking all terms up to the third order would be called a third-order Taylor approximation. Let's try plotting some Taylor approximations of sin(x) about the point x = 0.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>On a separate sheet of paper, complete the derivation of the Taylor series approximation of sin(x) out to fifth-order.</p><p>Then plot the original function and its first-order Taylor approximation, using ax.legend() to label the curves.</p><p>Modify your plot to include higher-order Taylor approximations up to the fifth order, again labeling the curves.</p><p>&#10148; Hint: There is no need to write your own factorial calculator -Python has got you covered.</p><p>Confusingly, there are several ways to compute factorials in the Python/NumPy environment. We recommend not importing the math library (because of potential clashes with NumPy), so if you need a single value, use this form from the NumPy library:</p><p>np.math.factorial <ref type="bibr">(3)</ref> which takes an integer argument and returns an integer.</p><p>Over what range of x values would you say that the fifth-order Taylor series is a good (say, within 10%) approximation for sin(x)?</p><p>(Write your response here.)</p><p># &lt; Exercise 1, Taylor series for sin(x) &gt; n _ points = 1000 # Number of points # Create array from 0 to 2pi with n _ points total points x = np.linspace(0.0, 2 * np.pi, n _ points) y = np.sin(x) # Create sine data array y1 = x # first-order Taylor approximation # Set up plot fig, ax = plt.subplots() ax.set _ xlim(0, 2 * np.pi) ax.set _ ylim(-2., 2.0) ax.set _ xlabel('x') ax.set _ ylabel('y') ax.set _ title('sin(x) and Taylor series approximations') # Plot data ax.plot(x, y, color='red') # Plot sin(x) ax.plot(x, y1, color='blue') # Plot the first-order Taylor approx for sin(x)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.2.3">Taylor series about an arbitrary x value</head><p>So far, we have only looked at Taylor series about the point x = 0. This is technically a special case of a Taylor series, called a Maclaurin series. In general, to compute a Taylor series about an arbitrary location of x = a, we must make the following alteration to our Taylor series expression:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2*</head><p>Plot the third-order Taylor approximation to sin(x) around x = 3. Include sin(x) on your plot, as well. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>### INCLUDE your calculation and plot below</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>An aside on factorials</head><p>For future reference, if you want to compute an array of factorials, say up to n = 5, you can't do this using np.math.factorial(), because that method only works on single values.</p><p>Instead, in your toolbar above, search for "factorial" under the Help menu/SciPy Reference/Search the Docs. You'll find it under scipy.special.factorial. You will need to import the library of special functions as shown in the cell below. Finally, if you forget the syntax of these function calls, you could calculate, say 5!, using NumPy's function prod (which calculates the product of all elements in an array) like so: print("5! is the product of 1 * 2 * 3 * 4 * 5: ", arr5 <ref type="bibr">[1:</ref>].prod()) # better skip the # first element!</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.3">New Programming Tools</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.3.1">Logic</head><p>In order to write useful code, you will want your program to behave differently in different situations. To accomplish this, your program will 1) need to be able to evaluate whether certain conditions are satisfied and 2) evaluate different code depending on whether these conditions are or are not satisfied.</p><p>Logical and comparison operators allow us to test whether variables satisfy certain conditions. Comparison between two or more variables can be made using the following symbols:</p><p>&#8226; &gt;, &lt; ("greater than" and "less than") &#8226; &gt;=, &lt;= ("greater than or equal to" and "less than or equal to") &#8226; ==, != ("equal to" or "not equal to") Note that a single equals sign assigns the value of the variable or number or array on the right to the variable on the left. Two equal signs are needed for a comparison, yielding a logical True or False.</p><p>Multiple conditions can be combined together using</p><p>&#8226; and (if both the operands are true, then the condition becomes true)</p><p>&#8226; or (if any of the two operands are true, then the condition becomes true) and</p><p>&#8226; not (used to reverse the logical state of its operand; for instance, a True statement becomes False).</p><p>Parentheses may be used to specify the order of operations. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Write a line of code that will evaluate to True only if a is equal to b or if a is equal to c but not if both a is equal to b and a is equal to c (i.e., all three variables are the same). Test your code using different values for a, b, and c. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.3.2">Conditional statements</head><p>Once your program has evaluated whether a condition is True or False, it can make a decision to execute different code. This is done using if and if ... else statements, as shown below. In these statements, the condition (e.g., a == 10) is separated from the outcome by a colon, and indentation is used to separate the code inside the if statement from the rest of the code. Indentation must be consistent throughout a cell (typically set at four spaces).</p><p>Typically your Jupyter notebook will automatically create the indentation after you press Enter/Return after typing the colon character: if statement if expression: statement(s) if ... else statement if expression: statement(s) else: statement(s) # Examples of "if" and "if ... else" statements. ### DO THIS: Before running this cell, predict the outcomes of each of the # statements. a = 10 # If the outcome consists of only a single line of code, it may go on the same # line as the "if" statement. if a == 10 : print("a equals 10") # If the outcome consists of multiple lines of code, it is separated from the # rest of the code by indentation and forms a block paragraph: if a == 10 : print("a equals 10") print("hooray") # Once the "if" statement is done, the left-justification of the code returns # to as it was before. name1 = "Alice" # "if ... else" statements allow the programmer to specify an outcome if the # condition is not met. if name1 == "Carlos" : print("name1 is Carlos") else : print("name1 is not Carlos.") print("Who is it?") # "elif" (else-if) can be used to check multiple conditions. # Note that if the "if" statement evaluates to "True", the "elif" statement # will not be evaluated. if name1 == "Carlos" : print("name1 is Carlos") elif name1 == "Alice" : print("name1 is Alice") else : print("name1 is not Carlos or Alice.") print("Who is it?") # Multiple "if" and "if ... else" statements may be nested together to create # more complex conditionals. name2 = "Bob" if name1 == "Alice" : if name2 == "Bob" : print("Alice and Bob are both here.") else : print("Alice is here but where is Bob?") else : print("We are missing Alice.") ### DO THIS: Change the values of a, name1, and name2; reevaluate this cell</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>In the cell below, use nested (cascaded) if ... else statements to write code that will compare three variables, a, b, and c, and produce the following outcomes:</p><p>&#8226; print "a is False" if a equals 0;</p><p>&#8226; print "a is the greatest" if a is not equal to zero and a is greater than b and c;</p><p>&#8226; print "a is less than or equal to b or c" and print the values of each of the variables if a is less than or equal to either b or c and a is not equal to 0.</p><p>Set a, b, and c equal to different values to test that your code produces the expected results.</p><p># &lt; Exercise 4, Conditionals &gt; a = 10 b = 20 c = 30 ### Your code here</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.3.3">Loops</head><p>In other situations, you will want your program to complete the same actions multiple times. Loops are one way to accomplish this sort of task.</p><p>for loops enable you to iterate through each item in an array (or list or string). The syntax looks like for iterating _ var in sequence:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>statement(s)</head><p>As with if statements, the colon separates the statement(s) from the condition, and indentation is used to separate the code inside the loop from that outside of it. An example is below. There are times that you will wish to iterate over multiple variables in the same loop. For instance, you may have two different lists or arrays that you would like to manipulate elements on at the same time. This can be done using zip, as shown in the cell below, behaving like a zipper to match the two arrays term-by-term.</p><p>Specifically, what zip does is combine the matched elements in the array (or list or other iteratable object) into tuples. If you are curious, you can add print(list(zip(array1, array2)))</p><p>to the code block below to see the operation. In other circumstances, you may wish to keep track of how many iterations the loop has currently completed. For example, you may wish to know how many times the loop iterated before a break condition was met. This can be accomplished elegantly using the enumerate function, which returns both the count of the current iteration and the value of the item in the current iteration, as shown below.</p><p># Example of using enumerate to preform a sequential search for "cabbage" in a # list veg _ list = ["radish", "celery", "carrot", "cabbage", "asparagus"] for count, value in enumerate(veg _ list):</p><p>print(count, value)</p><p>There is much more that could be said about loops. For example, in addition to for loops, Python also supports while loops (loops which are iterated until a condition is met instead of evaluating for each iterator). Loops can also be nested (indented blocks) inside each other to allow one to iterate through multiple variables at once. You are encouraged to research both of those ideas as they come up, but for now you know enough to get started.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>Write a loop that will iterate through an array, printing for each element in the array the sum of it and all previous elements. Break out of the loop if your sum ever exceeds 50. Test your loop on an array that goes from 0 to 20 in steps of 1. You should find that it stops iterating when it reaches 55.</p><p>&#10148; Hint: Consider defining a variable that will be updated inside of the loop.</p><p># &lt; Exercise 5, Loops &gt; ### Your code here</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.3.4">An aside on speed</head><p>One last word to the wise: Loops can be a natural way to do computations in Python, but they are often not the fastest way. NumPy arrays, in particular, allow a user to do a calculation on each element in an array "all at once". <ref type="foot">1</ref> This can result in cleaner code and also faster code. For example, the block of code below does the same calculation as the first two loop examples above.</p><p>For the types of code you will be writing for these exercises, the speed usually does not matter, so you should feel free to use loops whenever it feels right. It is important to be aware that there may be other options, though. Similarly, NumPy has a method for cumulatively summing an array, as you did in Exercise 5. This method is referred to as np.cumsum (cumulative sum), and an illustration of how it could be used to accomplish the same task as Exercise 5 is below.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Example of using np.cumsum to sum all the elements in an array</head><p>iterator = np.arange(0,22,1) # make the array to be added up cumulative _ sum = iterator.cumsum() # perform the cumulative sum print("All elements of the running sum: ", cumulative _ sum) print("All elements less than 50: ", cumulative _ sum[cumulative _ sum &lt; 50])</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6*</head><p>To compare the efficiency of doing calculations using NumPy arrays rather than loops, we can use the magic command %%timeit, which runs any entire code cell several times and reports the amount of time taken for the whole cell (reported as a loop). Below, all the integers from 0 to 5,000 are added up using a while loop and the amount of time taken is reported. Write and time how long it takes to do a similar calculation using a for loop and with the np.sum() method.</p><p>You'll find that the NumPy version of the calculation is 10-40x faster than when using loops.</p><p># &lt; Exercise 6 &gt; import timeit # %%timeit must appear by itself at the top of the code cell to be timed, # so we import it into a separate cell first. %%timeit # &lt; Exercise 6 &gt; # calculate the sum of all integers from 0 to 5000 inclusive using a "while" # loop sum5000 = 0 i = 0 while i &lt; 5000: i+= 1 sum5000 = sum5000+i # comment out the print statement when using %%timeit; otherwise it will take a # long time #print(sum) %%timeit # &lt; Exercise 6 &gt; # calculate the sum of all integers from 0 to 5000 inclusive using a "for" loop %%timeit # &lt; Exercise 6 &gt; # Calculate the sum of all integers from 0 to 5000 inclusive using a NumPy array # and np.sum() # For future reference, note that %%timeit will not actually return any values # to the notebook except for its timing results. Here, for example, sum5000 # is undetermined after running the last cell. You need to comment out the # %%timeit in order to return any values from the code cell. ### UNCOMMENT this line to see the error #print(sum5000)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.3.5">Functions and other subroutines</head><p>In other situations, you may want to repeat the same operations in different contexts. Defining your own functions<ref type="foot">foot_1</ref> will allow you to do this without writing the same code over again.</p><p>In programming, repetitive code is bad -not only does repetition clutter your code, making it harder to read, but repetition also makes your code harder to debug and harder to update. Python and NumPy come with a huge number of built-in functions, many of which you are already using (e.g., print(), len(), np.arange(), and np.linspace()). Caution: print() and len() are Python primitives, but np.arange(), np.linspace() come from NumPy. Each of these functions performs some action and may return a value. For example, len() returns the length of a list or array. Functions frequently have parameters, which are listed inside the parentheses. When the function is called, a value (known as an argument) can be passed to that function and stored as a variable with that parameter name. For example, len() takes a list or array as its argument. Therefore, when I write length = len(array1) I am calling the function len() with the argument of array1. The len() function does some magic to determine the number of elements in array1, then assigns length to that returned value. When you define your own functions, you will call them the same way. (For future reference, NumPy gives more information about multidimensional array sizes via np.size().)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Defining a function</head><p>Many times, Python will not have a built-in function that accomplishes what you want, so you will have to define your own function. To define a function, you must follow a specific form:</p><p>&#8226; The function block begins with the keyword def followed by the function name and then a pair of parentheses.</p><p>&#8226; Any input parameters or arguments are listed inside the parentheses. If the function does not take any parameters or arguments, the parentheses are left empty.</p><p>&#8226; A colon separates the function header from the code block, and the entire code block is indented.</p><p>&#8226; The statement return [expression] exits the function, optionally passing back an expression to the caller. A return statement with no arguments is the same as returning nothing.</p><p>As a result, your function will look like</p><p>and will be called like</p><p>function _ name( parameters ) # Example of a simple user-defined function (technically a "procedure") that # returns nothing # Define the procedure "say _ hello" that takes one argument, "name". def say _ hello(name): """Print: Hello [name].""" # Three double quotation marks are another way # of designating a comment print("Hello ", name) return say _ hello("Dave") ### DO THIS: Add a line of code to call the say _ hello procedure with your name</p><p>It is good practice to document the purpose of a function in a comment immediately following the function definition. This comment forms a docstring, which is readily visible from the coding interface as shown below. Typically, we enclose it in pairs of triple quotation marks to allow for multiline commenting. This level of commenting may seem a bit overboard for the simple functions that you will initially be writing, but it is good to get in the habit now.</p><p># Print the docstring associated with the procedure say _ hello()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>#</head><p>Note that the word "doc" is surrounded by double underlines print(say _ hello. __ doc __ )</p><p>Another way you can access the docstring for this function is to add a question mark to the end of it: say _ hello?</p><p>Happily, this syntax will not only provide documentation for the functions you wrote but also for procedures, methods, objects, and so on, including ones you imported. This makes it a very powerful tool when learning how to use new libraries.</p><p># Print information about the function/procedure say _ hello?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Print information about a function that you imported np.arange?</head><p>To get an even deeper level of understanding of the code, you can use two question marks to access the source code for the function (or procedure, method, or object). (Sometimes the documentation information will be printed but not the source code. This is typically because the object was not implemented in Python, but in some other language such as C with Python providing the interface.)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.3">New Programming Tools 55</head><p># Print information about the function/procedure AND the source code say _ hello?? It is possible to set default values for the parameters in the function. The following block of code defines a function similar to say _ hello but sets a default name so that it can be called without a parameter.</p><p># Example of a simple user-defined procedure, with a default value for the # parameter # Define the procedure "say _ hello" that takes one optional argument, "name". def say _ hello _ default(name = "you"): """Print: Hello [name].""" print("Hello " + name) return say _ hello _ default() say _ hello _ default("Dave") ### DO THIS: Uncomment the following line of code # say _ hello _ default()</p><p>Functions can take multiple optional parameters with default values. In these circumstances, the function call can contain the name of the parameters to designate which one is being referred to. If a parameter name is not given, the first parameter in the function definition will be set to the first value passed in the function call and so on.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Example of a simple user-defined function, with two optional arguments</head><p># Define the function "say _ hello" that takes two optional # arguments, "greeting" and "name".</p><p>def greet _ default(greeting = "Hello", name = "you"):</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>"""Print: [greeting] [name]."""</head><p>print(greeting + ", " + name)</p><p>greet _ default(greeting = "Hi") greet _ default("Hey there") greet _ default("Greetings", "Earthling")</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Return values</head><p>The previous examples have been of procedures that are very simple and return no information, so here is a slightly more complex one involving multiple return statements.</p><p># Example of a slightly more complex function that returns a value # Define a function to determine the distance between two points (which may be # integers, arrays, or lists and have any number of dimensions). def find _ distance(point1, point2): """Return the distance between two points.""" # If point1 and point2 are both integers, the distance between them is just # the absolute value of their difference if (type(point1) == int and type(point2) == int): return abs(point1 -point2) # Exit the function and return the absolute # value of the difference # If the function is called with two parameters that do not have the same # number of elements, print an error and exit gracefully if (type(point1) == int or type(point2) == int or len(point1) != len(point2)): print("The two points do not have the same dimension") return # Exit the function and return nothing if type(point1) == list: point1 = np.array(point1) # If they are lists, # convert to arrays if type(point2) == list: point2 = np.array(point2) # If point1 and point2 are arrays of the same length, use array # calculations to find the distance. np.sum calculates the sum of all # elements in the array; alternatively, array _ name.sum() # np.sqrt(num) returns the square root of num dist = np.sqrt(np.sum((point1 -point2) ** 2)) return dist # And we now give an example of usage of the function 'find _ distance()' p1 = np.array([1, 2]) p2 = np.array([2, 7]) distance = find _ distance(p1, p2) print("Distance: ", distance) ### DO THIS: Test this function by calling it with different arguments. # See if you can come up with arguments that will result in each of the three # different return statements.</p><p>Of course, NumPy includes an efficient function that calculates the Euclidean distance between arrays of any dimension by using the norm function from its built-in linear algebra package np.linalg. It is less flexible than the user-defined function above, because it requires NumPy arrays as input, and the dimensions of the inputs need to be greater than 1: print("\nHere's the well-known distance between diagonally opposite corners" + " of a unit cube:") print(np.linalg.norm(p1 -p2)) print("which should equal the square root of 3: ", np.sqrt(3))</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>Write a function that will solve a 1-D constant-acceleration, kinematic problem analytically, meaning that you will use the familiar equations of motion for constant acceleration. Your function should take an initial position, an initial velocity, an acceleration, and a time and return the position after that time. Test your function by running it with a variety of different parameters, including x 0 = 2, v 0 = 1, a = -10, and t = 3 (which should produce -40). Include a docstring for your function. Try sending your function an array of times rather than a single value. Does it still work?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Scope of variables and functions</head><p>One of the beautiful things about functions is that they are their own little environments. This means that any variable defined inside the function will not be available outside of the function. Such variables are considered local variables in contrast to global variables, which are defined outside of functions and available everywhere, even inside other functions. Where a given variable is available is referred to as the scope of the variable. An example is below.</p><p># Example of local vs. global variables ### DO THIS: Before running this cell, predict the outcome. # Defining two "global" variables. These two variables will be available # everywhere globalvar _ a = 1 var _ b = 2 def scope _ function1(): """Print the value of two variables to illustrate scope""" var _ b = 3 localvar _ c = 4 print("I'm inside the function!") print("\t globalvar _ a equals ", globalvar _ a) # Since globalvar _ a is defined outside of a function, it is available # everywhere print("\t var _ b equals ", var _ b) print("\t localvar _ c equals ",localvar _ c) return scope _ function1() print("\nI'm outside the function!") print("\t globalvar _ a equals ", globalvar _ a) print("\t var _ b equals ", var _ b) # Even though var _ b was modified inside the function, var _ b outside the # function is unchanged. What happened is that the line "var _ b = 3" actually # defined a local copy of the variable inside of the function, rather than # altering the global variable var _ b. ### DO THIS: Uncomment the following line # print("\t localvar _ c equals ",localvar _ c) # Because localvar _ c was only defined inside the function, it is not available # outside of it. If you would like to see all presently used global variables and functions, use the %who magic command. # Print all presently used global variables %who ### TRY THIS: Restart the kernel (using the command under the Kernel pulldown # menu). Doing this will clear the global variables, as you will see # when you rerun this cell. When you rerun the cell after restarting the # kernel, notice that the number next to this cell has changed to 1. Cells # are numbered in the order they have been executed starting with 1 each # time the kernel starts. # Before you continue to Exercise 8, you will need to import your libraries # again (i.e., rerun the first code cell of this notebook). Even better, you # can use the command "Run all above" located under the Cell menu to rerun # all the code up until this point, then run this cell.</p><p>As you can see, keeping track of the scope of variables can get confusing. To keep things clean, it is smart to limit the use of variables that were defined outside of the function, unless they were passed as parameters.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.4">Taylor Series Using Loops, Functions, and Conditional Statements</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8</head><p>For this exercise, you will practice putting together the skills you have learned by writing code to approximate the function ln(1 + x) as a series. As you may have discovered in the "Taylor Series" exercises, by using Taylor series we can show that</p><p>If you have not already done so, verify on a separate sheet of paper that the above equation is true.</p><p>Your goal is to write code that will determine how many elements of the series are needed to approximate ln(1 + x) to a certain level of precision, given a value of x. To do this</p><p>&#8226; define a function that will return the nth element of the series, given the parameters x and n;</p><p>&#8226; write a for loop that will calculate the sum by calling your user-defined function, breaking out when the sum of the series is within some &#1013; of the actual value of ln(1 + x);</p><p>&#8226; use your loop to determine how many elements of the series are needed to achieve the desired level of precision.</p><p>Please test your code with a variety of different values for x and &#1013;, including x = 0.8 and &#1013; = 0.001 (which should require 16 elements in the series).</p><p>&#10148; Hint 1: The smartest way to write code is to build it from smaller pieces. In this case, I suggest writing and testing your function before using it in the loop.</p><p>&#10148; Hint 2: Printing variables inside your loop may help you determine if your code is working correctly.</p><p>&#10148; Hint 3: You'll have to look up the NumPy function for the natural logarithm.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 8, Approximating ln(1+x) &gt; ### YOUR CODE here</head><p>Exercise 9*</p><p>One way to validate a Taylor series approximation is to compare it to a polynomial fit to the data. First, find the Taylor series expression for cos(x) near x = 0.</p><p>(write your answer here)</p><p>Now compare to a polynomial fit to the data by using scipy.optimize.curve_fit to fit a second-order polynomial. Then compare the coefficients returned by curve fit for a secondorder polynomial to the values of a 0 , a 1 , and a 2 generated for the Taylor series.</p><p>To use scipy.optimize.curve _ fit(), you must provide a function in the form of the desired fit. This function should take as arguments your dependent variable array, x, along with the parameters that will be adjusted to provide a fit. In this exercise, the functional form is a second-order polynomial and the parameters are a 0 , a 1 , and a 2 :</p><p>Fix the template code in the cell below to define this function.</p><p>The syntax to call curve _ fit() is then popt, pcov = curve _ fit(fitFunc, x, y, p0)</p><p>where fitFunc is the name of the fitting function you defined (poly _ fit() in this case), x is the array of independent variables, and y is the array of dependent variables. The variable p0 is an array of initial guesses for the parameters in your fitting function, in this case, a guess for each of the coefficients. The returned values are 1) the array of fitted parameters (popt) optimized such that the square of the residuals (fitted y-values -data y-values) is minimized and 2) the estimated approximate covariance of the fitted parameters (pcov), which is a measurement of the quality of the fit.</p><p>In the second cell below, use curve _ fit() to fit a second-order polynomial to the function cos(x). You will get the best comparison to the Taylor series if you limit the range of x values, for example, -&#960;/8 &#8804; x &#8804; &#960;/8. This task can be accomplished by writing a logical statement that returns whether a given element in an array fulfills that statement:</p><p>This returned array of True/False values can then be used as the indices of an array:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Finally, plot the results of your best-fit polynomial along with the original function and the Taylor series approximation on the same set of provided axes.</head><p>&#10148; Hint: To plot your polynomial fit, you may, of course, write a polynomial using each of the values of popt as the coefficients: popt[0] + popt <ref type="bibr">[1]</ref> * x + popt[2] * x ** 2. However, a similar and cleaner way to accomplish this is to use the poly _ fit() function along with your best-fit values: poly _ fit(x, * popt). Here the asterisk before popt tells the computer to unpack the values of the popt array and pass each of the values individually as arguments to the poly _ fit() function.</p><p>How similar are the coefficients for the Taylor series to those found using curve _ fit()?</p><p>How do the coefficients to the polynomial fit change if the range of x-values used in the fitting is decreased?</p><p>Exercise 9, Curve fitting &gt; # Define the functional form to be used by curve _ fit 6.4 Taylor Series Using Loops, Functions, and Conditional Statements 61 def poly _ fit(x, a0, a1, a2): """Returns the result of a second-order polynomial for the provided coefficients""" result = 0 ### FIX return result # &lt; Exercise 9, Curve fitting, continued &gt; # Import the curve _ fit method from the scipy.optimize library from scipy.optimize import curve _ fit # An array of independent values x = np.arange(-np.pi, np.pi, 0.01) # cos(x) y = np.cos(x) # Taylor series approximation y3 = y ### FIX # Provide an array of initial guesses for each of the coefficients to be sent # to curve _ fit p0 = np.array([0.0, 0.0, 0.0]) ### SET your initial guesses # Limit the range of x-values over which the fit will happen ind = ((-np.pi/2 &lt; x) &amp; (x &lt; np.pi/2)) ### ADJUST the range # Call curve _ fit popt, pcov = curve _ fit(poly _ fit, x[ind], y[ind], p0) ### PRINT fit results # Set up plot fig, ax = plt.subplots() ax.set _ xlabel('x') ax.set _ ylabel('y') ax.set _ title('y = cos(x) near x = 0') ### PLOT cos(x), Taylor fit, and second-order polynomial fit plt.show() Exercise 10* This exercise is similar to Problem 2.18 of Classical Mechanics. 1. On a separate piece of paper, find the Taylor series expressions around x = 0 for 1. ln(1 + x) 2. e x , 3. (1 + x) n (where n is a constant). 2. In each of the cases, comment on the range of x and n values that will provide a convergent solution. 3. Next, plot the first three terms of the Taylor approximations for each function in separate plot windows in the cells below. &#10148; Note: The Taylor series that you just derived in this exercise and for sin(x) and cos(x) earlier are used often enough in physics that it is worth committing them to memory! # &lt; Exercise 10 A &gt; # ln(1+x) x = np.arange(0, 1, 0.01) y = np.log(1 + x) # y3 = # Set up plot fig, ax = plt.subplots() ax.set _ xlabel('x') ax.set _ ylabel('y') ax.set _ title('y = ln(1 + x) near x = 0') # Plot data # ax.plot(x, y, color='red') # plot y(x) # ax.plot(x, y3, color='blue') # plot Taylor series approximation # &lt; Exercise 10 B &gt; # e^x x = np.arange(-2, 2, 0.01) # &lt; Exercise 10 C &gt; # (1+x)^n x = np.arange(-2, 2, 0.01)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.5">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 11</head><p>Briefly summarize in the cell below the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Numerical Integration Applied to Projectile Motion</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>7</head><p>O ne of the fundamental questions in mechanics is how to solve the equation of motion (such as &#7821; = -k m x) for a system in order to determine how it evolves over time. Equations of motion typically relate the position of a mass to its time derivatives (velocity and acceleration), meaning that they are differential equations. In this course, you will be learning a number of techniques to solve analytically (i.e., via algebra and calculus) these differential equations, but oftentimes an analytical solution cannot be found. In these circumstances, scientists may turn to computers for numerical solutions to the differential equations. In this unit, and in most of the exercises hereafter, you will be doing exactly that -numerically solving the differential equations of motion. These solutions, say x(t) or v(t), arise from integrating a force law via a set of time derivatives, from known starting values of x(0) and v(0). As such, these are referred to as "initial value problems. " Specifically, in this unit you will write a numerical integrator to solve the trajectory of a projectile subject to constant acceleration. Later on, you will use the numerical integrator from the SciPy Python package, but it is instructive to write your own.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="7.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; write a numerical integrator that relies on the simple Euler method (PRO); &#8226; use your simple Euler integrator to find the trajectory of a projectile (PHY); &#8226; improve your simple Euler integration method by calculating the derivatives halfway between time steps (PRO). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="7.2">Simple Euler Integration of 2-D Projectile Motion</head><p>Euler integration is a method for numerically integrating to solve a differential equation.</p><p>The main idea of Euler integration while solving kinematic problems is to use Taylor series approximations to update the position of a projectile by 1) applying a constant acceleration for a small time interval to determine a new velocity, 2) applying the new velocity for a small time interval to get the new position, then 3) rinse and repeat steps 1) and 2), building up the solution via iteration.</p><p>&#10148; Note: We will be using only first-order Taylor approximations here.</p><p>You will need to store x, v x , y, and v y at each point in time. Rather than hold them as individual one-dimensional arrays, we use the following convention:</p><p>&#8226; r _ soln[0] will be the x position. We'll call it pos _ x.</p><p>&#8226; r _ soln <ref type="bibr">[1]</ref> will be the x component of the velocity. We'll call it vel _ x.</p><p>&#8226; r _ soln[2] will be the y position. We'll call it pos _ y.</p><p>&#8226; r _ soln <ref type="bibr">[3]</ref> will be the y component of the velocity. We'll call it vel _ y.</p><p>So this r _ soln array contains four elements for each time step. You will create solutions on an array of time points that we will call t _ arr.</p><p>Note that the complete solution will be found by pairing t _ arr with pieces of the solution array r _ soln. For example, we can plot y(t) via a call to ax.plot(t _ arr, r _ soln[2]); similarly we plot the trajectory using ax.plot(r _ soln[0], r _ soln[2]).</p><p>&#10148; Programming style tip: Why the long variable names? Variable names should be as descriptive as possible, and yet not standard words, which might be used in the future to represent something else (for instance, time-keeping functions in Python are in a library called time so we want to avoid redefining, a.k.a. "clobbering" that name). We'll need to distinguish between purely computational results and those derived algebraically, also known as analytical results. So, later, we'll have pos _ x _ analyt, vel _ x _ analyt, etc. Finally, if you are working on a large project, but you want to change the variable names throughout the notebook (or indeed, an entire directory of notebooks), you can use Search/Replace to substitute some other unique name for your chosen variable, without interfering with comments, which will generally be written in complete sentences. For ease of searching, it is also recommended that variables be longer than single characters, e.g., grav rather than g for the gravitational acceleration near the surface of Earth.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="7.2.1">Using simple Euler integration</head><p>The following cells will use first-order Euler integration to find the trajectory of a projectile without drag force. Examine and run each of them in turn.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>The following cell will set the initial conditions and parameters for the system. Please edit it to set the following (global) variables:</p><p>&#8226; g = 9.8 m s -2 ,</p><p>&#8226; t start = 0.0 s,</p><p>&#8226; t end = 15.0 s, &#8226; and the number of time steps to 3000.</p><p>Then edit it to define the initial conditions such that the projectile starts from (0, 0) with velocity = (20 m/s, 20 m/s), i.e., a launch from the origin at an angle of 45 degrees above horizontal.</p><p># &lt; Exercise 1, Setting parameters for projectile motion &gt; # No damping, numerical integration, projectile motion over level ground: # defining variables. # Define the variables that will be used throughout the integration grav = 0 # [m s^-2]; the acceleration due to gravity. By declaring it here it # will be available anywhere in the rest of the notebook # Create t _ start = 0 # [s]; starting time t _ end = 1 # [s]; ending time n _ steps = 1 # number of time steps h _ step = (t _ end-t _ start)/n _ steps # h is the time step, delta _ t t _ arr = np.arange(t _ start, t _ end, h _ step) # an array of evenly spaced time # points at which to find solutions # Initialize x and v. For a complete solution to the differential equations, we # need to specify the starting values of x, vx, y, and vy pos _ x0 = 0 # initial x position pos _ y0 = 0 # initial y position vel _ x0 = 0 # initial x-component of the velocity vel _ y0 = 0 # initial y-component of the velocity r _ init = np.array([pos _ x0, vel _ x0, pos _ y0, vel _ y0]) # this array contains the # initial values of x, # vx, y, vy, in order ### DO THIS: Add a line of code to print the variable h _ steps and the array # t _ arr so that you can see what they contain. # After you have done so, remove the line of code and rerun the cell so that # you don't clutter up the output.</p><p>In a first-order Euler integrator, the time derivative is used to propagate the motion forward by assuming x(t + &#948; t ) &#8776; x(t) + &#948; t dx(t) dt . The following cell defines a function to return the time derivative of all the variables.</p><p>In order to examine how the derivative function works, add the following line to the end of the cell before you run it:</p><p>This line will call the derivative function for t = 0 and print the derivatives of x, v x , y, and v y . Convince yourself that the values will print correctly.</p><p># No damping, numerical integration, projectile motion over level ground: # derivatives. # We need to define a function that returns the time derivatives of all the # variables. # The syntax for this defined function begins with the reserved word * def * , # then the function name. In parentheses go the variables (which are # typically arrays and scalars). Following the variables, we put a colon. # The colon tells the Python interpreter that the indented block that # follows falls under the scope of the defined function. Finally, the # reserved word * return * tells us the output of the function. In this case, # we return the derivatives in a 4-element array (x, vx, y, vy), using the # np.array() function. def deriv(t _ now, r _ now): """Return the time derivatives at time t _ now of pos _ x, vel _ x, pos _ y, vel _ y assuming constant velocity. r _ now contains 4 elements: x, vx, y, vy at this point in time""" # First, extract the variables from the r-array pos _ x = r _ now[0]</p><p>vel _ x = r _ now <ref type="bibr">[1]</ref> pos _ y = r _ now In order to continue propagating the trajectory forward in time, you will need to loop through the entire array of times (t _ arr from two code cells ago). Each time you do so, you will calculate the derivatives of position and velocity at the previous step and use them to find the new values of position and velocity. In order to keep track of all of the positions and velocities, store them in an array to which you append each new set of r _ soln values as they are calculated. For more information on the np.append() method, click on this link: numpy.append.</p><p># No damping, numerical integration, projectile motion over level ground: # define the integration function def simple _ euler(t _ arr, r _ arr): """Use the simple Euler method to integrate from initial state, r _ arr = r _ init, and return the x, vx, y, and vy values for each time step in t _ arr""" 7.2 Simple Euler Integration of 2-D Projectile Motion 67 # Accumulate arrays that hold the solutions. Let each array accumulate in # order to create x(t), vx(t), y(t), vy(t) # Create the empty arrays pos _ x = np.array([]) # an empty array that will hold pos _ x solutions vel _ x = np.array([]) # this will hold vel _ x solutions pos _ y = np.array([]) # pos _ y vel _ y = np.array([]) # vel _ y # The engine: initialize values, then step through with simple Euler method # (uses a first-order Taylor series) for time _ step in t _ arr: # This * for * loop sequentially takes on all the # elements of t _ arr, first to last pos _ x = np.append(pos _ x, r _ arr[0]) # Extend the array by tacking the # latest x on to the end of it # starting from r _ arr vel _ x = np.append(vel _ x, r _ arr[1]) pos _ y = np.append(pos _ y, r _ arr[2]) vel _ y = np.append(vel _ y, r _ arr[3]) # In words: a new 4-element array r _ arr is created that takes the # previous r _ arr and adds a piece that is the first-order Taylor # approximation of the extrapolated values at one time step later, # using the derivatives from the deriv() function applied to each # one of the elements. r _ arr = r _ arr + h _ step * deriv(time _ step, r _ arr) # update all 4 r _ arr # elements by using # a first-order # Taylor series # expansion in time, # advanced by one # time step h _ step. # Test to see when to cut off the solution by noting when the # projectile hits the ground if (time _ step &gt; 0.0) and (r _ arr[2] &lt;= 0.0): break # quit out of the loop # Pack all 4 values for each time point r _ soln = np.array([pos _ x, vel _ x, pos _ y, vel _ y]) return r _ soln # return pos _ x, vel _ x, pos _ y, vel _ y # No damping, numerical integration, projectile motion over level ground: # complete the integration print("Initial values of the parameters: ", r _ init) r _ soln = simple _ euler(t _ arr, r _ init)</p><p>Exercise 2</p><p>Now that we have completed the Euler integration, it is time to examine the results. The following block of code will output interesting values and plot the trajectory. Modify it to include values of:</p><p>&#8226; initial speed,</p><p>&#8226; launch angle,</p><p>&#8226; time in the air, and &#8226; range of the projectile.</p><p>Some of these values are directly available from the code in the cell above. Others you will have to make a simple calculation to find.</p><p>Additionally, add the code to graph the trajectory by plotting y vs. x.</p><p>&#10148; Note: In the plotting portion of the code below, you'll see an example of a long line of code that has been split across two lines. Python knows the line of code should continue because the outer set of parentheses hasn't closed yet. In cases where it doesn't make sense to use parentheses, a backslash can be inserted at the line break to indicate a continuation. plt.show() # Compare with the 'range formula' over level ground print('Analytical range = ', (2/grav) * vel _ y0 * vel _ x0)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="7.2.2">Accuracy of simple Euler integration</head><p>Congratulations, you have now just walked through your first numerical integration. The next step is to examine its accuracy, something that can be easily done by comparing the numerically calculated trajectory to the analytical solution.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>For comparison, set up the analytical (exact) solution evaluated at the same time points and plot it on the same axes as the numerical solution, along with an array representing the difference between the y values of the two solutions (called the residuals).</p><p>&#10148; Hint: You will want to make sure that your numerical and analytical solutions have the same number of points, evaluated at the same times, so that the y values of the numerical solution and the residuals can be plotted against the x values of the numerical solution. Remember that len() is the function that will return the length of an array and that you can select the first n elements in an array using square brackets and indexing:</p><p>Although you began with n = 3,000 points, you will have truncated your solution when the y-value got back to 0. The following cell illustrates this difference. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4*</head><p>You should observe that there is a small linear error term that arises because of when and where we evaluate the derivatives. How big was the error term by the end of the calculation (when the projectile returned to y = 0)? Be sure to include units in your answer.</p><p>Accumulated error =</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="7.3">Improving the Simple Euler Integration Method: Euler Half-Step Integration</head><p>We can reduce the accumulated error in the simple Euler integration method if we choose a smarter place to evaluate our derivatives. The following blocks of code show how evaluating the derivatives halfway between the time steps greatly improves the accuracy.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="7.3.1">Illustrated example</head><p>Consider the following simple quadratic function: the solution to a 1-D constant acceleration problem in which acceleration equals -1.0 m s -2 and the projectile starts from a height of 1.0 m with velocity 1.5 m s -1 . plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Using the Derivative at the Start of the Time Step</head><p>Now imagine trying to approximate that trajectory by using the local slope at t 0 = 0.0 s to take a single coarse step between t 0 = 0.0 s and t 1 = 0.5 s. That's what a simple Euler approximation with a step size of &#948; t = 0.5 s would do, as demonstrated by the following blocks of code.</p><p># The numerical solution: Defining the derivative function def deriv _ example(t _ now, r _ now):</p><p>"""Return the derivatives for a 1-D, constant accel. problem"""</p><p>vel _ y = r _ now <ref type="bibr">[1]</ref> dy _ dt = vel _ y dvy _ dt = acc _ y0 return np.array([dy _ dt, dvy _ dt]) # The numerical solution: Simple Euler # Defining the initial state h _ step = 0.5 # the step size, delta _ t 7.3 Improving the Simple Euler Integration Method: Euler Half-Step Integration 71 r _ init = np.array([pos _ y0, vel _ y0]) # array containing the initial values of y, # vy, in order pos _ y = np.array([r _ init[0]]) # y vel _ y = np.array([r _ init[1]]) # vy t _ arr _ trim = np.array([0, h _ step]) # only look at 1 time step so there will be # no "for" loop # Integrating along a single time step # this is y1 = (delta _ t) * (v0) + y0 and vy1 = (delta _ t) * a r _ soln = r _ init + h _ step * deriv _ example(t _ arr, r _ init) pos _ y = np.append(pos _ y, r _ soln[0]) # append the new y position vel _ y = np.append(vel _ y, r _ soln[1]) # append the new y velocity # Comparing the analytical and numerical solutions fig, ax = plt.subplots() ax.plot(t _ arr, pos _ y _ analyt, '-b') # analytical solution # Plot extrapolated position to get y1 at t = delta _ t = 0.5 s ax.plot(t _ arr _ trim, pos _ y,'--r') ax.plot(t _ arr _ trim, pos _ y,'ko', ms = 5) ax.set _ xlabel("Time [s]") ax.set _ ylabel("Height [m]") ax.set _ title("Illustration of the Simple Euler Method for a = -1.0,\n" + "with 0.5 s time step") ax.legend(["Analytical","Simple Euler"]) # creates a legend for plot. Legend # keys are listed in order of # plotting, and Python is usually # smart enough to identify the # corresponding linestyle and # color. Note that these are a # list of strings within square # brackets # Evaluated at full h _ step pos _ y _ analyt _ 1step = pos _ y0 + vel _ y0 * h _ step + 0.5 * acc _ y0 * h _ step ** 2 # Creates a vertical line at t = 0.5 extending between the simple Euler and # analytical solution ax.vlines(0.5, pos _ y _ analyt _ 1step, r _ soln[0], color = 'purple') ax.annotate("error", (0.51, 1.7)) # annotate adds text at the specified (x, y) # location on the plot plt.show() print("Simple Euler error [meters]: ", r _ soln[0] -pos _ y _ analyt _ 1step ) # remember that r[0] contains the # latest value of y</p><p>The black dot at the end of the red dashed line represents the result of taking a whole step of h = 0.5, using the slope that was evaluated back at the origin. The error in y (0.125 m) is the vertical distance between the black dot and the analytical solution at time h = 0.5 s. This is a substantial error, especially for only one step. Where might be a better place to evaluate the derivative? Halfway through the time step!</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Using the Derivative Halfway Through the Time Step</head><p>Instead of finding the derivatives at the start of the time step, let's take a half step (h/2 = 0.25 s), evaluate the derivatives there, and then use those derivatives for making the whole step. This is essentially a two-step process:</p><p>1. Use the derivative at t 0 to estimate the state of the system at t 0 + h/2. [Half step] 2. Use the derivatives at t 0 + h/2 to integrate from t 0 to t 0 + h. [Whole step] You can envision this like a game of leapfrog, and that, in fact, is another name for this algorithm.</p><p>You might recall the mean-value theorem from calculus, where the slope of the function matches the secant line somewhere in between its end points. For quadratic functions, we will see that halfway is exactly the right place for us to evaluate the derivatives. # 1) Evaluate the derivative at start of the time step; then multiply by length # of half time step to estimate the value at half time step k1 = (h _ step/2) * deriv _ example(t _ arr _ trim, r _ init) # 2) Evaluate derivative at half time step; integrate from start of time step by # multiplying half-step derivative by full time step r _ soln = r _ init + h _ step * deriv _ example(t _ arr _ trim, r _ init + k1) pos _ y = np.append(pos _ y, r _ soln[0]) # append the new y position vel _ y = np.append(vel _ y, r _ soln[1]) # append the new y velocity # Comparing the analytical and numerical solutions # Evaluated on a fine time vector pos _ y _ analyt = pos _ y0 + vel _ y0 * t _ arr + 0.5 * acc _ y0 * t _ arr ** 2 fig, ax = plt.subplots() ax.plot(t _ arr, pos _ y _ analyt, '-b') # analytical solution ax.plot(t _ arr _ trim, pos _ y, '--r') # plot extrapolated position to get y1 at # t = delta _ t = 0.5 s ax.plot(t _ arr _ trim, pos _ y, 'ko', ms = 5) # black dot ax.annotate("Evaluate the derivative at t = 0.25 s", xy=(0.25, 1.34375), xytext=(0.05,1.65), arrowprops={"arrowstyle":"-&gt;", "color":"gray"}) ax.annotate("and then apply it to the full 0.5 s step here", xy=(0.5, 1.625), xytext=(0.6,1.45), arrowprops={"arrowstyle":"-&gt;", "color":"gray"}) ax.set _ xlabel("Time [s]") ax.set _ ylabel("Height [m]") ax.set _ title("Illustration of the Improved Euler Method for a = -1.0") ax.legend(["Analytical Soln", "Improved Euler"]) plt.show() # Evaluated at the single half step pos _ y _ analyt = pos _ y0 + vel _ y0 * h _ step + 0.5 * acc _ y0 * h _ step ** 2 print("Half-step Euler error [m]: {:15.14e}". format(r _ soln[0] -pos _ y _ analyt)) # the difference, or residual 7.3.2 Applying improved Euler to projectile motion So now we know that there is a better time step at which to evaluate the derivatives before making the full step. By substituting these lines inside your integration loop we make the Euler half step, or improved Euler method: # Evaluate the increment on the half step k1 = (h _ step/2) * deriv(t _ arr, r _ soln[0]) # This is the whole step after sniffing out the derivatives at the midpoint r _ soln[0] = r _ soln[0]+ h _ step * deriv(t _ arr, r _ soln[0] + k1) # Improved Euler integration function def improved _ euler(t _ arr, r _ arr): """Integrate using the Improved Euler method""" # Create the empty arrays pos _ x = np.array([]) # this is an empty array that will hold x solutions vel _ x = np.array([]) # this will hold vx solutions pos _ y = np.array([]) # y vel _ y = np.array([]) # vy h _ step = (t _ arr[1] -t _ arr[0]) # time step # The engine: initialize values, then step through # (uses first-order Taylor series twice) for t _ step in t _ arr: # this * for * loop sequentially takes on all the # elements of t _ arr, first to last pos _ x = np.append(pos _ x, r _ arr[0]) # extend the array by tacking the # latest x-position on to the # end of it, starting from r _ arr # which is originally a copy of # r _ init vel _ x = np.append(vel _ x, r _ arr[1]) pos _ y = np.append(pos _ y, r _ arr[2]) vel _ y = np.append(vel _ y, r _ arr[3]) # Here is the half-step Euler: # Evaluate the increment on the half step k1 = (h _ step/2) * deriv(t _ step, r _ arr) # This is the whole step after sniffing out the derivatives at the # midpoint r _ arr = r _ arr + h _ step * deriv(t _ step, r _ arr + k1) # Test to see when to cut off the solution by noting when the # projectile hits the ground if (t _ step &gt; 0.0) and (r _ arr[2] &lt;= 0.0): break # break out of the loop r _ soln = np.array([pos _ x, vel _ x, pos _ y, vel _ y]) return r _ soln Exercise 5 Use this new improved _ euler() function to analyze the projectile motion problem from Exercise 1. To do this, you'll want to copy the relevant blocks of code from above into the cell below. You may use as few or as many code cells as you like. Try to copy only what is necessary. For example, you will need to redefine the initial conditions and time arrays, because those were redefined (a.k.a. "clobbered") during the improved Euler example. However, your derivative function from earlier should still be usable. &#10148; Coding style tip: Especially when developing new code, it is a good practice to define functions in separate cells. Then they can be edited and reevaluated separately from the rest of your code (or vice versa).</p><p>&#10148; Hint: We suggest making sure your copied code produces results for simple Euler integration before modifying it for improved Euler.</p><p>As before, compare your result with the analytical solution when the projectile hits the ground. What is the error now? If you've done everything correctly, you'll find that the error array will need to be multiplied by a big number to display in a useful fashion on the same plot as the trajectory.</p><p>Accumulated Error (Improved Euler) = # &lt; Exercise 5, Projectile motion w/o damping, Improved Euler; # Set initial conditions (ICs) &amp; call function to integrate the solution &gt; # &lt; Exercise 6, Projectile motion w/o damping, Improved Euler; Plot solution &gt; 7.5 Challenge Problem 75</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="7.4">Check-out</head><p>Exercise 7</p><p>Briefly summarize in the cell below the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="7.5">Challenge Problem</head><p>Exercise 8</p><p>You may wonder whether the increased accuracy you observed with the improved Euler method is simply a result of taking the derivatives twice as many times (twice for each time step). In a word, no! Prove this fact to yourself by increasing the step size in your improved Euler method above by a factor of two. Then find the accumulated error and compare it to what you found for simple Euler. Describe your findings below.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Accumulated Error (Improved Euler, twice the step size) =</head><p>T his unit builds upon the numerical integration code you wrote in Numerical Integra- tion Applied to Projectile Motion. Now, though, you will include linear and quadratic velocity drag into your calculations of projectile motion. The inclusion of quadratic drag means that for the first time you will have the opportunity to model systems that cannot be analytically solved. Not only will completing this unit give you a greater understanding of the effect of drag on projectile motion, you will also solidify your understanding of r, the unit vector of position, as it appears in the linear and quadratic drag equations.</p><p>These exercises are aligned with the content in Ch. 2.1-2.4 of Classical Mechanics. You will create plots analogous to Figures 2.7 and 2.8. References are also made to the situation described in Problem 2.1, which may be completed as a homework assignment.</p><p>This unit also includes a calculation of the work done by the drag force in Exercises 6 and 7. This content is covered in greater depth in Ch. 4 of Classical Mechanics, but you should have prior knowledge of it from an earlier physics class. These two exercises may also be skipped without affecting Exercise 8.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="8.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; include linear and quadratic drag into your equations of motion for a projectile (PHY); &#8226; validate your code by comparing your numerical solution to the analytical solution in several special cases (PHY);</p><p>&#8226; calculate terminal velocity and compare it to a plot of velocity over time (PHY);</p><p>&#8226; calculate the mechanical energy loss due to drag (PHY).</p><p>import numpy as np import matplotlib.pyplot as plt plt.rcParams["figure.figsize"] = (12,8) # make figures big for easier viewing # (width, height in inches) # To revert to the small default size, uncomment the following line # plt.rcParams["figure.figsize"] = plt.rcParamsDefault["figure.figsize"]</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="8.2">Improved Euler Numerical Integration</head><p>Exercise 1</p><p>Into a new code cell, copy your improved Euler method code from Numerical Integration Applied to Projectile Motion to solve projectile motion without air resistance. As before, include the analytical solution for projectile motion without air resistance on your graph (there is no need to plot the residuals, though). Try not to include extraneous code. Before proceeding, make sure that your copy produces reasonable results by plotting the trajectory.</p><p>The first cell includes a template for the deriv() function. Unlike the deriv() function in the previous unit, this function definition allows for two additional optional parameters, b _ drag (the coefficient for linear drag) and c _ drag (the coefficient for quadratic drag), to be passed into it. Later in this unit, you will modify your deriv() function to include the effects of these parameters, but for now you may ignore them in both your function and your function call. In other words, the interior of your deriv() function should remain the same as in Numerical Integration Applied to Projectile Motion for now. In cases where there is no drag, you may continue to call the function as</p><p>Similarly, the second cell includes a template for an improved _ euler() function, which will compute the integration. As with the deriv() function, the optional parameters, b _ drag and c _ drag, are included so that you can use them later in the unit.</p><p>You might wonder why deriv() needs to know about t _ now. It doesn't at the moment, but you could imagine a situation where the object is acted upon by an external time-varying force; perhaps your projectile is rocket powered. For that future reason we include t _ now in the calling signature.</p><p># &lt; Exercise 1, 2-D Projectile Motion, deriv() function &gt; # The deriv() function should take as parameters the present time t _ now and a # 4-element array defining the state (r _ arr = np.array([x, vx, y, vy])). # It should return a new 4-element array defining the derivatives at time t # (r = np.array([dx/dt, dvx/dt, dy/dt, dvy/dt])). grav = 9.8 # [m s^-2], gravitational acceleration near the surface of Earth mass = 0.5 # [kg], the mass of the projectile def deriv(t _ now, r _ now, b _ drag = 0, c _ drag = 0): """Return the time derivatives at time t _ now of x, v _ x, y, v _ y. b _ drag is the coefficient for linear drag and c _ drag is the coefficient for quadratic drag.""" ### UNPACK the variables stored in array r _ arr ### CALCULATE the derivatives for x, v _ x, y, and v _ y dr _ dt = np.array([0, 0, 0, 0]) ### FIX return dr _ dt 8.3 Projectile Motion with Linear Drag 79 # &lt; Exercise 1, 2-D Projectile Motion, Improved Euler Integration function &gt; def improved _ euler(t _ arr, r _ arr, b _ drag = 0, c _ drag = 0): """Integrate using the Improved Euler method b _ drag is the coefficient for linear drag, and c _ drag is the coefficient for quadratic drag.""" # Create the empty arrays pos _ x = np.array([]) # this is an empty array that will hold x solutions vel _ x = np.array([]) # this will hold vx solutions pos _ y = np.array([]) # y vel _ y = np.array([]) # vy h _ step = (t _ arr[1] -t _ arr[0]) # time step ### ADD integration code below r _ soln = np.array([pos _ x, vel _ x, pos _ y, vel _ y]) return r _ soln # return the 4 x n element solution (probably a little # shorter than n) # &lt; Exercise 1, 2-D Projectile Motion, initial conditions and integration &gt; # &lt; Exercise 1, 2-D Projectile Motion, analytical calculation &gt; # &lt; Exercise 1, 2-D Projectile Motion, plot of projectile to test code &gt;</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="8.3">Projectile Motion with Linear Drag</head><p>In order to include a drag force, you will need to adjust your deriv() function to include velocity-dependent damping coefficients of the form:</p><p>You will start with the simpler and analytically solvable case of just linear drag (i.e., c = 0). In later exercises, you will add a non-zero quadratic drag component.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Redo your improved Euler projectile problem with the same initial conditions and time steps as before to show the trajectory and find the range (over level ground) for b = 0.05 kg/s and c = 0.0. Assume that the projectile has mass m = 0.5 kg, as defined above in the second code cell. Please do not make a new copy of either deriv() or improved _ euler(). Instead, make the necessary modifications to your work above to include the effect of linear drag.</p><p>In your final plot, include the analytical solution for the motion without air resistance. This will allow you to compare the effect of linear drag on the trajectory. There is no need to recalculate the analytic solution from Exercise 1.</p><p>&#10148; Hint: Make sure that b _ drag is passed to the deriv() function from inside your integration function (improved _ euler). Also, make sure that you are passing your integration function a fresh copy of r _ init. How is the range and height of the projectile affected by the inclusion of linear drag? Explain here. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Drag</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="8.4.1">One-dimensional motion</head><p>You will start your exploration of quadratic drag by examining situations in which the linear drag term is negligible and the motion is entirely in one direction (e.g., something dropped or thrown vertically). Choosing to work in one dimension will allow easy comparison with the analytically derived terminal velocity, permitting you to validate your code in the simpler case before adding the complexity of two-dimensional motion.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Now modify your deriv() function to include quadratic drag. In your integration function call, let b = 0 and c = 0.002 kg/m; keep m = 0.5 kg. Because it is only possible to find an analytical solution with quadratic drag for one-dimensional motion (why is that?), start by using the following initial conditions (equivalent to a rock dropped vertically from a cliff): position (t = 0 s) = (0 m, 500 m) and velocity (t = 0 s) = (0 m/s, 0 m/s). Because the motion will be in one dimension, plot y vs. t rather than vs. x. Once again, include the analytical solution for the motion with no air resistance (this time, you will have to recalculate the analytic trajectory because the initial conditions have changed).</p><p>&#10148; Hint 1: Make sure that the quadratic drag force in your deriv() function is opposite the direction of motion.</p><p>&#10148; Hint 2: Remember that your t _ arr array will likely not have the same number of elements as your r _ soln array. How can you select the portion of t _ arr that is relevant?</p><p>&#10148; Hint 3: Only copy the minimal amount of code into the cell below. For instance, there is no need to make a copy of your deriv() or improved _ euler() functions.</p><p># &lt; Exercise 3 Solution for dropped rock, 1-D motion with quadratic drag, # using improved Euler &gt;</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>What terminal velocity would you expect the 0.5 kg rock to reach when the quadratic drag coefficient is c drag = 0.002 kg/m? Provide an analytical calculation showing your work:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Expected terminal velocity:</head><p>Plot y velocity vs. time and include the analytical solution for the velocity without drag.</p><p>Estimate the terminal velocity from the plot and compare it to the last value of vel _ y.</p><p>&#10148; Hint: You might want to add a grid to your plot, via ax.grid(). If you would like to plot a horizontal line, you can use axhline. The syntax to plot at a particular y-value (for example, your analytically calculated terminal velocity) on an already-defined Matplotlib axis object, ax, is ax.axhline(y = yvalue)</p><p>where yvalue is the desired value. If this parameter is not included, the line will default to y = 0. Similarly, ax.axvline(x = xvalue) will give a vertical line at some xvalue.</p><p>Is your plot consistent with the terminal velocity you calculated? Explain here.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 4, v _ y vs. t for dropped rock, 1-D motion with quadratic drag, # using improved Euler &gt;</head><p>Next, you will test your quadratic-drag code with a different set of initial conditions that will allow for the rock to have both positive and negative velocity. Make sure the direction of your drag force is always opposite to the direction of motion. As before, try to determine the minimal code you will need to copy into a new cell.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>Calculate and plot y vs. t and v y vs. t for these initial conditions: position (t = 0 s) = (0 m, 0 m) and velocity (t = 0 s) = (0 m/s, 28 m/s), and find how long the rock remains in the air. Keep all other parameters the same as in the previous two exercises. This is the situation where the rock is thrown vertically up into the air. Once again, include the analytical solution for the motion with no air resistance in your plots of y vs. t and v y vs. t. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="8.4.2">Energy loss in one-dimensional motion</head><p>Exercise 6* Drag forces are nonconservative forces so work done by them results in loss of mechanical energy from the system. To see this, first calculate and graph the kinetic energy, potential energy, and total energy vs. time (all on one plot). Then find the amount of mechanical energy (in joules) lost during the rock's entire trajectory.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Mechanical energy loss =</head><p>I get approximately 48 J. Show your result to 3 significant figures.</p><p>Notice that at one point in the trajectory, the rate of energy loss goes to zero. Explain here why that makes sense.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 6, Mechanical energy loss &gt;</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7*</head><p>Because work done on a system is equal to the change in energy, it is possible to equate the change in mechanical energy of the rock to the work done by the drag force. For this exercise, estimate the work done by the drag force across each time step and sum to find the total work done. Essentially, you will be finding</p><p>using the simple rectangle method (Riemann sum) for estimating the integral. Verify that the total work you calculate is close to the change in energy.</p><p>&#10148; Hint 1: The method np.sum() will calculate the sum over an array. This is cleaner and easier than writing a loop to sum over the elements, as you did in Taylor Series with Loops and Functions.</p><p>&#10148; Hint 2: You'll need to find the displacement across each time step. One way to do this is to subtract y[:-1] from y[1:]. Can you see why? Remember that referencing element -1 refers to the last element in the array. Alternatively, you can use the np.diff() function.</p><p>&#10148; Hint 3: Be aware of the negative/positive signs of your displacement.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Work done by drag force =</head><p># &lt; Exercise 7, Work done by drag force &gt;</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="8.5">Linear and Quadratic Drag</head><p>Now for two-dimensional motion with both linear and quadratic drag. This is a trajectory that cannot be found analytically and so must be calculated numerically!</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8</head><p>Looking at Example 2.1 in Taylor's Classical Mechanics (p. 45), match the coefficients for a baseball of diameter 7 cm, and mass m = 145 g and again plot the trajectory and find the range. In order to see clearly the effect of the drag, include the analytical solution for the case of zero-drag on your plot of the trajectory.</p><p>&#10148; Hint: If your results seem suspect, check that the quadratic drag term in your deriv() function is correctly accounting for both the x and y components of velocity. You may want to consider how you could write the x and y components of the quadratic drag term using vx and vy and do that in your definitions of dv x /dt and dv y /dt. Which has a greater effect on the trajectory of the baseball: the linear or quadratic term? Why do you think this is?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Drag</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="8.6">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>Briefly summarize in the cell below the ideas of this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="8.7">Challenge Problem</head><p>Repeat the work-energy analysis you did in Exercises 6 and 7 for the baseball. Note that you'll have to calculate the change in position for two-dimensional motion and that you'll have to include both forms of drag.</p><p>I n the previous exercises, we showed how numerical integration can be implemented with a simple Euler or improved Euler method. More sophisticated algorithms achieve higher accuracy by using more terms in the Taylor series approximation while taking the next step. (For example, one such easily coded algorithm is the fourth-order Runge-Kutta.) This exercise introduces the higher-accuracy ordinary differential equation solver included in Python, solve _ ivp(). You will apply it to the problem of a rocket launching in a gravitational field. Specifically, you will model the launch of the Space Shuttle, a two-stage rocket, with the simplifying assumption that the trajectory is vertical, and the acceleration due to gravity remains constant.</p><p>While this problem can be solved analytically, it is a good illustration of how sets of firstorder differential equations can be rewritten to be solved with a computer.</p><p>Exercises 9 and 10 ask students to compare the solutions from solve _ ivp() to those generated using an improved Euler method similar to that used in Numerical Integration Applied to Projectile Motion and Projectile Motion with Drag. These exercises may be omitted to save time without affecting the rest of the unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.1">Objectives</head><p>In this unit, you will</p><p>&#8226; learn how to use solve _ ivp() to solve ordinary differential equations (PRO); &#8226; analytically and computationally solve the motion of a rocket in an external gravitational field (PHY);</p><p>&#8226; model a two-stage rocket launch (PHY). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.2">Motion of Rocket without Gravity</head><p>In order to familiarize yourself with numerically solving differential equations, you will start with the simplest case: a rocket experiencing no external forces. In the next section, you will use the Python numerical solver, solve _ ivp(), to find the velocity as a function of mass. Before that, though, you will set up the problem and graph the analytical solution.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>In the code block below, please define variables for the initial mass of the rocket (mass _ 0), final mass of the rocket that remains after the payload is exhausted (mass _ final), the amount of time during which the rocket is expelling mass (burn _ time), exhaust speed (vel _ ex), and the initial velocity of the rocket (vel _ 0). Use the data on the Space Shuttle from Problem 3.7 of Classical Mechanics. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Using the analytical solution given in Equation <ref type="formula">3</ref>.8 of Classical Mechanics, graph the velocity as a function of mass. For the independent variable, mass, define an array of at least 50 elements that runs from the initial mass to the final mass. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Sometimes it is more natural to think in terms of velocity as a function of time, rather than mass of the rocket. This conversion can be done if the change in mass as a function of time is known. Assume here that mass is expelled at a constant rate, &#7745; = -k, and that the entire process takes place over the "burn time" defined in Exercise 1. Therefore,</p><p>In the cell below, calculate k (burn _ rate). Then rearrange the above equation for m(t) to find t(m), and use that relation to plot rocket velocity as a function of time.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 3, Convert mass to time &gt; ### FIX the two definitions below burn _ rate = 0 # [kg/s]; Rate at which mass is expelled time _ arr = mass # [s]; Array of times ### INCLUDE code for plotting velocity versus time</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>Perhaps just as interesting as velocity as a function of time is the position as a function of time. To find this, analytically integrate Equation <ref type="formula">3</ref>.8 from Classical Mechanics for velocity to find the position as a function of time. Use your equation for m(t) assuming a constant burn rate over 2 minutes. Then plot position versus time. You should find that the height already exceeds 100 km after 2 minutes. Assume that the initial height is zero.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Height after 2 minutes = # &lt; Exercise 4, Position vs. time &gt; ### INCLUDE code to plot the position versus time.</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.3">Introducing solve_ivp() for a Rocket</head><p>The differential equation solver, solve_ivp (short for "solve initial value problem"), is part of the SciPy package, which must be loaded at run time using the command import.</p><p>Previously, you solved differential equations by defining an "r-array" that consisted of the values of each variable to be integrated over (e.g., generalized positions and velocities) at a particular time. You then used a function to return the derivatives for each of those variables, determined the value of those variables at the subsequent time step, and appended the r-array for that subsequent time step to your solution. If you had n variables and N time steps, the solution would be a two-dimensional nxN array.</p><p>With solve _ ivp(), though, the solver determines the array of time points (using a userdefined time span as one of its arguments) and outputs an array of r-arrays (really, a twodimensional nxN array), eliminating the for loop we used in the previous units. Thus, you will no longer be building up the solution by repeatedly appending to an array; instead, the solve_ivp solver will output the entire solution as a two-dimensional array, along with the array of times over which it was evaluated. The output of solve _ ivp() is in the form of a programming "object": if the output is r _ soln, the times will be accessible as r _ soln.t and the two-dimensional array as r _ soln.y.</p><p>The exercises below will walk you through how to find and interpret the numerical solution to the rocket problem generated by solve _ ivp().</p><p>For a more comprehensive and general introduction to solve _ ivp(), refer to Appendix 1: Using solve _ ivp() for Numerical Integration.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Import the differential equation integrator #</head><p>To reduce demand on memory, we import only the subset of the SciPy # package that contains solve _ ivp from scipy.integrate import solve _ ivp</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.3.1">Defining the deriv() function</head><p>To use the solve _ ivp() solver, the user must provide the system of first-order ordinary differential equations in the form of a function (what will typically be called deriv()) that returns the derivative for each variable. The deriv() function takes as its parameters a single time, t _ now, an array of the variables, r _ arr, defining the state of the system at t _ now, and any other constants of the system necessary for solving the derivatives. It then returns an array containing the derivatives of each of those variables in the same order as they were passed to the function.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>In this case, the variables to be integrated are mass, position, and velocity. Modify the code below to create a function deriv() that will return the derivatives of each of these variables at a particular time, t _ now. The variables mass, position, and velocity are all passed to the function as elements of an r-array, r _ now, and the return of the function is an array of the derivatives of those variables in the same order.</p><p>In the base code below, the deriv() function also takes burn_rate and vel_ex as optional parameters. This will allow you to compute solutions for situations with different values of burn_rate and vel_ex. Note that if burn_rate and vel_ex are not passed to the deriv() function, they will default to zero (and the rocket will not move). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.3.2">Finding the solution with solve _ ivp()</head><p>Now that the derivatives have been defined, the user must define the initial conditions (the eponymous initial values in solve _ ivp()). This is done by defining an array of the initial states of the variables to be integrated, in the same order as assumed for the r-array in deriv().</p><p>Next, the integration time must be defined. Unlike other solvers, solve _ ivp() is special in that it can determine the time steps over which to find the solution to minimize the computational time (this is an example of an adaptive solver). Remarkably, these time steps typically have uneven spacing. While it is possible to ask solve _ ivp() to use a particular array of user-defined time steps, by default the user only passes the range of times. Generally, the user will define a two-element list stating the minimum and maximum time. (Alternatively, the range can be passed in the form of a tuple rather than a list. Tuples are like lists except that they are defined with parentheses rather than square brackets and are immutable, i.e., unchangeable. Using tuples rather than lists is slightly more memory efficient, but feel free to continue to use lists for the sake of simplicity.)</p><p>Finally, the name of the function containing the derivatives, the time span for the solution, and the initial conditions are all passed to solve _ ivp() along with any optional parameters for the deriv() function.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6</head><p>Fill in the requested information in the code cell below to find the computational solution. Use the same parameters as you did in Exercise 1 and complete the integration for the entire burn time. Exercise 6 </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.3.3">Accessing the solution from solve _ ivp()</head><p>The return from solve _ ivp() is a programming object that contains lots of information. Soon you will see an example of how to access the data in it. Initially, though, a complete description of the contents of r _ soln may be seen by using the help() function:</p><p>The help() function in Python returns information about variables, functions, classes, and so on, as illustrated in the cells below. It is a very useful debugging tool and a quick way to learn about functions and classes from Python libraries.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Print out information about the function, solve _ ivp() help(solve _ ivp)</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Once you have run this cell, you may wish to comment out the above commands # to avoid cluttering your output # Print out information about the variable r _ soln returned by solve _ ivp()</head><p>help(r _ soln)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Once you have run this cell, you may wish to comment out the above commands # to avoid cluttering your output</head><p>The cell below shows you the information contained in this particular instance of r _ soln, including how to access specific quantities from it. Most important is the success of the integration (accessible as r _ soln.success), the time steps (accessible as r _ soln.t), and the two-dimensional array of solutions (accessible through r _ soln.y). The first row of r _ soln.y (accessible as r _ soln.y[0] or r _ soln.y[0,:]) gives the values for the first variable listed in the r _ init and in the variable r _ now used by deriv(). The second row gives the values for the second variable and so on. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>In order to practice using the output from solve _ ivp() and to see the accuracy, plot the mass, velocity, and position of the rocket as a function of time. Include both your numerical and analytical solutions for each of these. Finally, determine the accuracy of the solution by finding the difference between the analytical and computational position at the end of the burn time.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Analytical Position after 120 s -Numerical Position after 120 s = # &lt; Exercise 7, Computational solution for rocket, plots &gt; ### PLOT the mass vs. time ### PLOT the position vs. time ### PLOT the velocity vs. time</head><p>You should have found that the difference in final position for the analytical and computational solutions is less than 0.0001%! This is a very high level of accuracy.</p><p>However, you also probably noticed that the time steps in the computational solution are very coarse, resulting in a choppy-looking plot. What happened is that solve _ ivp() did not need to use small time steps to find an accurate final step. For this exercise, though, we would like to know the solution with much finer time spacing. To request that solve _ ivp() use no time step spacing greater than 0.1 seconds, set max _ step in solve _ ivp() to 0.1.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8</head><p>Copy the code from the previous two exercises to again find and plot the computational solution. This time, though, replace the call to solve _ ivp() with the following line of code:</p><p>to define a maximum time step size.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 8, Computational solution with smaller step sizes &gt; ### INCLUDE code to find and plot the solution</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.3.4">Comparison between solve _ ivp() and improved Euler</head><p>It is clear that solve _ ivp() can produce accurate results. However, you have also previously had success using your own improved Euler integrator. How do these two methods compare in terms of both accuracy and efficiency (how long it takes to run)? While the answer will vary depending on the problem, the following exercise provides a simple comparison for the case of the rocket.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9*</head><p>The following cell contains the template for an improved Euler integration of the rocket in zero gravity. It has been modified from your work in Numerical ODE &amp; Projectile Motion and Projectile Motion with Drag to enable the integration of mass, position, and velocity, as well as to include variables for the burn rate and the exhaust speed. Basing your edits on your work in the previous units, add the integration code to the improved _ euler function adjusted to fit this problem.</p><p>&#10148; Hint: You do not need to (and should not) define a new derivative function. Simply call deriv(), as defined previously in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 9, Rocket w/o gravity, Improved Euler Integration function &gt;</head><p>def improved _ euler(t _ arr, r _ arr, burn _ rate = 0, vel _ ex = 0):</p><p>"""Integrate a rocket in zero gravity using the Improved Euler method.</p><p>burn _ rate is the rate at which the mass is expelled, and vel _ ex is the velocity at which it is expelled.""" The next three cells import the timeit library and then run the solve _ ivp() and improved _ euler() integration for a fixed array of times (time _ arr, previously defined to be used in the analytical calculation). To use a fixed array of times with solve _ ivp(), set the optional parameter t _ eval in solve _ ivp() to the desired array, in this case time _ arr. Exercise 10* Plot the positions versus time for the rocket, as calculated using solve _ ivp(), your improved Euler integrator, and analytically. Then calculate the difference between the position of the rocket after 120 seconds for each computational solution and the analytical solution.</p><p>&#10148; Hint: Because you will be plotting both the solution from your improved _ euler() function and solve _ ivp(), you should make sure to give those solutions different names.</p><p># &lt; Exercise 10, Rocket w/o gravity, Integration method comparison &gt; # Using Improved Euler to calculate trajectory r _ soln _ imp _ euler = improved _ euler(time _ arr, r _ init, burn _ rate, vel _ ex) # Using solve _ ivp to calculate trajectory r _ soln _ solve _ ivp = solve _ ivp(deriv, t _ span, r _ init, t _ eval = time _ arr, args = (burn _ rate, vel _ ex))</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>### PLOT the position vs. time</head><p>Comparing the results of solve _ ivp() and your improved Euler integration when calculated over the same set of time steps reveals that solve _ ivp() produces much more accurate results. While improved Euler was able to produce incredibly accurate results for a projectile subject to constant acceleration, its accuracy decreases as the complexity of the problem increases. By using a higher-order Taylor series approximation as the basis for the integrator solve _ ivp() is able to achieve much higher accuracy.</p><p>There are a number of additional advantages to using solve _ ivp(). For example, the adaptive time stepping means that solve _ ivp() can appropriately reduce the number of time steps during slowly changing portions of the integration to save time without significantly reducing the accuracy. Additionally, you probably noticed that it was much easier to use solve _ ivp() than to adapt your improved Euler integration method to a new problem.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.4">Motion of a Rocket in a Constant Gravitational Field</head><p>For the remainder of this unit, you will model the launch of the Space Shuttle to the International Space Station (ISS) located in Low Earth Orbit.</p><p>Suppose the rocket is taking off vertically from the surface of Earth. Because Earth's gravitational field is roughly constant in Low Earth Orbit, this situation can be approximated as a rocket subject to a constant external force along the same directional line as the velocity. (OK, we acknowledge that you ought to launch it at some angle so as to match the angular momentum required to keep it in a circular orbit, but that is a further refinement. Here we're illustrating why you want to use a staged rocket.)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.4.1">Analytical solution Exercise 11</head><p>On a separate sheet of paper, show that the equation of motion for this circumstance can be written as mv = -&#7745;v ex -mg (9.1)</p><p>Then rewrite the equation assuming a constant rate of mass ejection such that &#7745; = -k and m = m 0 -kt. Use separation of variables to solve for v as a function of t by rearranging the equation such that all terms involving v are on the left and all terms involving t are on the right and integrating.</p><p>This problem is equivalent to part b of Problem 3.11 from Classical Mechanics.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12*</head><p>Now integrate your above equation for v(t) (again assume the initial height is zero) to find that the rocket's height as a function of t is</p><p>You should be able to repeat much of your derivation from Exercise 4. This problem is equivalent to Problem 3.13 from Classical Mechanics.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13*</head><p>Use the equations you derived above to find the velocity and position of the rocket released vertically in a uniform gravitational field as a function of time. Continue to assume the same parameters for the rocket's initial mass, rate of mass expulsion, and velocity of mass expulsion. Plot the position and velocity as a function of time for the first 120 seconds of the launch.</p><p>How does the height of the rocket after 120 seconds change when gravity is included? </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.4.2">Computational solution Exercise 14</head><p>Now that you have found the analytical solution, find the computational solution. To do this, you will need to define a new deriv() function (I suggest renaming it deriv _ g())</p><p>with the appropriate modification to the calculation of the derivatives. Include plots for the mass, position, and velocity of the rocket as a function of time for the first 120 seconds of the launch. Overplot the analytical solution for the velocity and (provided you completed Exercises 12 and 13) the position on top of the computational solution. As in Exercise 8, tell solve _ ivp() to use a maximum time step spacing of 0.1 seconds.</p><p># &lt; Exercise 14, Computational solution for rocket in a gravitational field, # derivative function &gt; ### DEFINE a new derivative function that includes the effect of gravity # &lt; Exercise 14, Computational solution for rocket in a gravitational field &gt; ### CALCULATE the trajectory of the rocket using solve _ ivp() ### PLOT mass, position, and velocity versus time</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.5">Launching to the International Space Station</head><p>The launch of the Space Shuttle to the International Space Station actually takes place over two stages. During the first stage (what you have modeled thus far), the two solid rocket boosters provide the majority of the thrust. After 120 seconds, the empty tanks are jettisoned (and recovered via parachute), further reducing the rocket's mass. Over the following 6.5 minutes, the main engine continues to burn fuel from the external tank.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 15</head><p>Computationally model the two-stage launch of the Space Shuttle using the following approximate parameters. As in Exercise 8, tell solve _ ivp() to use a maximum time step of 0.1 seconds. When setting the initial conditions for the second stage, use the final conditions from the first stage, but be sure to reduce the mass of the rocket by the mass of the two solid rocket boosters. For defining the time span of the integration, t _ span, for the second stage, it is easiest to set the initial time equal to the final time from the first stage. For example t _ span = [0.0, 120] # Time span for integration of the first stage t _ span _ 2 = [120, 120 + 6.5 * 60] # Time span for integration of the second stage</p><p>&#8226; Stage 1: Let the initial mass of the rocket be 2 &#215; 10 6 kg. As in the previous exercises, assume a 120 s burn time and an expulsion of 1 &#215; 10 6 kg of fuel.</p><p>&#8226; Stage 2: After the two solid rocket boosters (each with mass 87,500 kg) are emptied, they are jettisoned (separated via explosive bolts). A further 733,500 kg of fuel is then expelled from the external tank across the next 6.5 minutes. This mass is expelled at a considerably higher velocity of 4,447 m/s because of the higher burning speed of hydrogen/oxygen compared with the solid-fuel boosters.</p><p>For ease of calculation, some approximations have been made. For instance, you will assume a constant burn rate during each of the two stages. In reality, though, the burn rate is initially higher in the second phase than stated and then reduced part way through to prevent the rocket from exceeding 29.4 m/s 2 (3 g), which would damage the structure and needlessly compress the occupants. Additionally, we are modeling a radial launch, when in reality the trajectory of the Space Shuttle curved soon after leaving the launchpad to be roughly tangential to the surface of the Earth.</p><p>&#10148; Hint: When plotting the trajectories during the first and second stage, there is no need to combine the arrays. You can simply plot the trajectory from each stage individually, perhaps in different colors. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 16*</head><p>Verify that the final velocity of the rocket is similar to the orbital velocity at the radius of the International Space Station. To do this, you will need to calculate the circular velocity for an object in a gravitational orbit around Earth 140 km above the surface of Earth. You should find that after Stage 2 in your model is complete, the Space Shuttle is traveling at about 90% speed of the International Space Station.</p><p>&#10148; Hint 1: Remember that the equation for Newtonian gravity is</p><p>where G is the gravitational constant, M and m are the masses of the two objects, and d is the vector between the two centers of the masses.</p><p>&#10148; Hint 2: You will find the final velocity you calculate to be somewhat lower than that of the International Space Station. This is because of the approximations alluded to in Exercise 15 and because we are neglecting the additional velocity boost the rocket receives from the rotational motion of the Earth as the rocket enters a near-equatorial orbit. A somewhat higher-energy launch is used for spy satellites, which tend to be launched into polar orbits. It's considered bad form to launch into a retrograde orbit (east-to-west) because potential head-on collisions are spectacularly un-neighborly.</p><p># &lt; Exercise 16, Calculating the orbital velocity of the ISS &gt; ### INCLUDE your calculation here Exercise 17* Would the Space Shuttle have been able to reach the orbital velocity of the International Space Station if the two solid rocket booster tanks had not been jettisoned before Stage 2 of the burn? Repeat your calculations from Exercise 15 with this adjustment to find the effect of jettisoning the solid rocket booster tanks. # &lt; Exercise 17, Two-stage rocket launch in a gravitational field, without # jettisoning first stage &gt; ### CALCULATE the trajectory across the two stages</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.6">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 18</head><p>Briefly summarize the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="9.7">Challenge Problem</head><p>Most of the flight of the Space Shuttle is at very high elevations where air resistance is negligible. To see the effect of air resistance, instead model the flight of a 0.2 m diameter water rocket. Create a new derivative function that includes the effect of quadratic drag and assume that &#947; = 0.25 N s 2 /m 4 . Let the initial mass of the water rocket be 0.7 kg and the final mass be 0.1 kg. Assume that all the fuel is expelled over 0.1 s at a constant exhaust speed of 20 m/s. Note that the latter approximation is a particularly poor one -in real life, the launch of a water rocket proceeds over two stages, one during which water is expelled and one during which the pressurized gas is expelled, and the speed of expulsion depends on the changing pressure inside the bottle. For more context, see: C. J. Gommes, "A more thorough analysis of water rockets: Moist adiabats, transient flows, and inertial forces in a soda bottle, " Am. J. Phys. 78, <ref type="bibr">236-243 (2010)</ref>. <ref type="url">https://doi.org/10.1119/1.3257702</ref> </p><p>Computationally determine the flight of a vertically launched water rocket subject to air resistance and gravity during the first 0.1 seconds and compare it to the analytical solution for the flight without air resistance.</p><p>You may also wish to extend the trajectory to see what happens after the fuel is expended. One way to do this is to numerically calculate the trajectory for the time span between 0.1 and 3.5 seconds, using the final state of the rocket from the earlier stage as your new initial conditions and letting burn _ rate = 0 and vel _ ex = 0.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Simple Pendulum with Large-Angle Release 10</head><p>T he simple pendulum is a classic example of a nonlinear system. Despite its simplic- ity, it offers a depth of analysis, as you will explore in this and the following unit. At small angles, the simple pendulum acts as a simple harmonic oscillator with a period almost independent of amplitude. As the release angle is allowed to increase, though, the sine dependency in the force causes the period to change. In this unit, you will numerically calculate how period depends on release angle for this nonlinear system.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; model the motion of a simple pendulum (PHY); &#8226; determine how the period of a simple pendulum depends upon the release angle (PHY); &#8226; use events in solve _ ivp() to determine the period at a given angle (PRO);</p><p>&#8226; learn how to write and read in a table of data (PRO). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.2">Simple Pendulum, Theory</head><p>To orient you, we will start by analytically analyzing the system.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.2.1">Period of pendulum released from small angles Exercise 1</head><p>Let us look at a simple pendulum of length = 0.816 m. Assuming local g = 9.803 m/s 2 , calculate the period it ought to have for small oscillations. Show your result to 4 significant figures.</p><p>Period of 0.816 m long pendulum for small oscillations, T 0 =</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.2.2">Simple pendulum equation of motion</head><p>You will recall that the usual expression for the simple pendulum makes a small-angle approximation, whereby sin(&#952; ) &#8776; &#952;. Let us relax that requirement in order to get an equation of motion that is valid for any angle.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Work through the following analytical derivation on a separate sheet of paper.</p><p>&#8226; Starting with Newton's second law in angular form, &#8407; &#964; = I &#8407; &#945;, with &#964; = &#8407; r &#215; &#8407; F, determine the restoring torque for a simple pendulum (just a point mass m on a frozen massless string):</p><p>&#8226; Write down the moment of inertia of a point mass at radius equal to the length of the pendulum:</p><p>&#8226; Hence the second derivative of the angle is some function of the angle. Write it down.</p><p>&#8226; What additional force (torque) term would need to go on the force (torque) side of the equation to account for damping?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.3">Numerical Solution for &#952;(t)</head><p>Now that you have the equation of motion for a simple pendulum released from an arbitrary angle, you will use solve _ ivp() to solve it numerically and find the angle as a function of time.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Start by defining the variables for this system. Fill in the code below to describe a 1.5 kg, 0.816 m pendulum. The next step is to use the equation of motion you determined in Exercise 2 for a simple pendulum without damping to write a function, deriv(), that will return the derivative of each of the variables. The equation of motion you found is a second-order differential equation in which the second derivative of the angle ( &#952;) is expressed as a function of the angle, &#952;. To use the solve _ ivp() solver, you will need to turn that single second-order differential equation into a system of coupled first-order differential equations somewhat like the following:</p><p>where &#969; is the angular velocity and you must provide the right-hand term of the second equation.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>The code cell below contains an outline of the deriv() function. Look over it carefully and make the necessary fixes to the calculations of the derivatives.</p><p>This function also takes the optional parameter, gamma_damp, which allows for the possibility of viscous damping. By default, this parameter is set to zero, and you may ignore it for now.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 4, Creating the deriv() function &gt;</head><p>def deriv(t _ now, r _ now, gamma _ damp = 0):</p><p>""" Supplies the derivatives of angle and angular velocity to be used by the differential equation integrator, solve _ ivp(), for a simple pendulum. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>The optional parameter, gamma _ damp, allows for viscous damping and</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.3.2">Finding the solution with solve _ ivp()</head><p>Now that the derivatives have been defined, the user must define the initial conditions and the time span over which to find the solution. The variables, along with the name of the function containing the derivatives and any optional parameters in the deriv() function, are then passed to solve _ ivp().</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>Fill in the requested information in the code cell below to find the computational solution for a pendulum released from rest at 5 degrees. Calculate your solution for the time period from 0 to 5 seconds using 0.02-second-long integration time steps.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 5, Integrating the solution &gt;</head><p># Create the time span for the solution. Just 5 seconds will be enough to # illustrate the technique for now In order to practice using the output from solve _ ivp() and to see the accuracy, make the following two labeled plots. On both, please also include the small-angle solution for the motion of a pendulum with the same initial conditions.</p><p>1. Pendulum angle vs. time 2. Pendulum angular velocity vs. time</p><p>Optionally, you may also be interested in the plot of pendulum angular velocity vs. pendulum angle, also known as the "state-space" trajectory.</p><p>&#10148; Hint: What is the functional form of the analytical solution for the angle and angular velocity?</p><p>Remember, you calculated the period of oscillations in Exercise 1.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 6, Plotting the trajectory &gt; ### PLOT the angle vs. time, analytical and computational solutions ### PLOT the angular velocity vs. time, analytical and computational solutions</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7*</head><p>Further check your computational solution by examining the exchange of the system's energy between kinetic and potential over time. To do this, plot the Kinetic Energy (KE, red), Potential Energy (PE, blue), and Total Energy (TE, green) vs. time all on the same axes.</p><p>&#10148; Hint: How should the total energy behave as a function of time, if there is no damping in the system? This result for total energy will be a powerful check on your values of potential energy and kinetic energy. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.4">Comparing Solutions for Simple Pendulum at Different Initial Angles</head><p>Next you will use your numerical solver to compare the behavior of a pendulum when released at a large angle to the same pendulum released at a small angle.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8</head><p>How do you expect the behavior of the pendulum to change as the initial angle is increased? Write down your prediction with justification in the space below.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9</head><p>The cell below includes code that will call the numerical solver to find the solution for the behavior of a pendulum released from 5 degrees for the span of 20 seconds. Copy those lines of code and modify them to solve for the behavior of a pendulum released from 60 degrees. Then plot &#952; vs. time for both cases on the same figure. You should see that the two pendula (small angle and high angle) get out of phase with one another. After 12.5 seconds, the 5-degree pendulum will go through about 7 periods, whereas the 60-degree pendulum, running more slowly, is closer to 6.5 periods. A nice way to observe this difference is to plot the high-angle solution vs. the small-angle solution.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.5">Period of Simple Pendulum as a Function of Release Angle</head><p>Our goal is to determine how the period of a pendulum changes as a function of its maximum angle. However, it is rather inconvenient to determine the period manually, so you will streamline your code and determine the periods automatically.</p><p>As it happens, solve _ ivp() can keep track of the times when certain things ("events") happen during the integration, such as when a projectile crosses a certain height, or in this unit, when a pendulum crosses through zero angle. To define such an event, you must define a function whose name is the same as the event and that takes the same parameters as the deriv() function:</p><p>def event _ name(t _ now, r _ now, param1, param2, etc.):</p><p>When the function returns zero, the event will be triggered.</p><p>To use the events, add the parameter events = event _ name to your solve _ ivp() call. The time of the event triggering is recorded in r _ soln.t _ events, if r _ soln is the name of the return from solve _ ivp(). The contents of r _ soln.t _ events will actually be a list of arrays, with each element in the list corresponding to a different event definition. So if you wanted to access those times the first event was triggered, you would use</p><p>An additional functionality you will want to use here is that you can request events only be triggered if the zero point is crossed from a particular direction (either positive to negative or negative to positive). To use this functionality, set event _ name.direction to a positive float if the event should trigger when going negative to positive and a negative float if the opposite is desired.</p><p>The following two cells illustrate how events can be used to calculate the period of a pendulum.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Define an event by creating a function that returns zero when you want the</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># event triggered</head><p>def zero _ cross(t _ now, r _ now):</p><p>""" Event triggered when the omega _ 1 is zero """</p><p># Set the direction of the event so that it is only recorded if the pendulum is print("Here are the differences in times between these crossings:") print(diffs) print("which yields an average period of {:7.4f} s".format(diffs.mean()))</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>Putting it all together:</p><p>1. Use a loop to make a numerical solution for the trajectory of a pendulum for release angles from 1 to 89 degrees in 1 degree steps. For each solution, calculate the average period of the pendulum.</p><p>To build up an array of periods for each release angle, start with an empty array and append the periods to it, similarly to how you built up your solution in Numerical Integration Applied to Projectile Motion. Our time step of 0.05 seconds is probably a little coarse for this exercise, and we have been finding only about ten positive-to-negative zero-crossings in our 20-second-long time array. You'll want to use a finer time step (your choice) and a longer time range (again, your choice). Example code to get you started is below. 2. Plot the period as a function of release angle, using the arguments marker = 'o' and linestyle = '' within your ax.plot() call to use unconnected circles for the data. 3. Create a second plot that normalizes the period by dividing by the small-angle value T 0 (which should be what you found in Exercise 1). Once again, plot your data as unconnected circles. # &lt; Exercise 10, Calculate the pendulum period for different release angles &gt; # Array of initial angles to calculate the periods for start _ angles = np.arange(1, 1, 1) ### FIX ### DEFINE the time span and array of times to be used by solve _ ivp() period _ arr = np.array([]) # Empty array to store the periods print("angle period (s)") # Loop through the angles from 1 to 89 degrees for pendulum _ angle in start _ angles: start _ angle _ radians = pendulum _ angle * np.pi/180.0 start _ anglev = 0 # [rad/s] start the pendulum from rest ### SOLVE for the trajectory of the pendulum at the desired ICs 10.5 Period of Simple Pendulum as a Function of Release Angle 107 ### CALCULATE the period and save it as the variable "this _ period" this _ period = 0 ### FIX print("{:}: {:7.5f}".format(pendulum _ angle, this _ period)) period _ arr = np.append(period _ arr, this _ period) # &lt; Exercise 10, Plot the pendulum period for different release angles &gt; ### PLOT period versus release angle ### PLOT period/"small-angle period" vs. release angle Exercise 11* Find the angle at which the period has increased by 10 % over its small-angle value. You may find the Python numpy.where method useful for returning the indices in an array that fulfill some criteria. For example, given an array arr, the indices of all the elements greater than or equal to a fixed value are given by np.where(arr &gt;= value)[0]. (The [0] indicates the first element of the returned values, which is what is desired here.) An example of the np.where() usage is contained in the code cell below. Angle (in degrees) at which the period is 10% greater than its small-angle period = # Illustration of the np.where() method arr = 2.6 * np.arange(0.0, 12.0, 1) print("The array: ", arr) print("The indices for which the elements are &gt;= 15: ", np.where(arr &gt;= 15)[0]) print("The index of the first element that is &gt;= 15: ", np.where(arr &gt;=15)[0][0]) print("The value of the first element that is &gt;= 15: ", arr[np.where(arr &gt;=15)[0][0]]) # &lt; Exercise 11, Angle for which the period increased by 10% &gt; ### USE your results from Exercise 10 and np.where to find the release angle # that results in a period 10% greater than that for the small-angle # approximation</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.5.1">Writing data to a file</head><p>After running a time-consuming calculation, such as the period of the pendulum as a function of release angle, it can be advantageous to save the results to a file. These results can then be accessed later on, for instance in the following unit, Simple Pendulum Comparison to Data and Theory.</p><p>There are many options for reading and writing data with Python, some of which are explored in Appendix 2: Basic Input/Output of Files in Python. Because in these circumstances you'll want to write NumPy arrays of data, we recommend using numpy.savetxt(). The required arguments for savetxt() are the filename and a one-or two-dimensional array:</p><p>np.savetxt(filename, data _ array)</p><p>The data can be formatted using the keyword fmt and the same format codes used when printing statements, preceded by "%". The keyword delimiter can be used to set the character-separating columns, and the keyword newline can be used to set the characterseparating rows. The header and footer keywords are used to set strings that will be written at the start or end of the file, respectively.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12</head><p>Use np.savetxt() to write the arrays of release angles and periods to a file. Because np.savetxt() takes only one data argument and you would like to write two arrays, the easiest thing to do is to combine them into one two-dimensional array by stacking them together as columns. This can be accomplished using numpy.column_stack, which takes a sequence of one-dimensional arrays and combines them into a two-dimensional array:</p><p>Note that np.hstack() has a similar functionality, and np.concatenate(), np.stack() and np.block() provide more general stacking and concatenation operations.</p><p>For this exercise, save your data as a comma-separated values (CSV) file by using a filename with a .csv filename extension and commas for your delimiter: np.savetxt("dataFile.csv", data_arr, delimiter=',')</p><p>For better documentation, you can use the header keyword to add the names of the columns separated by commas to the file, e.g., header = "Release Angle</p><p>Once you have written your data to a file, try opening it up using a text editor and/or a spreadsheet program such as Excel.</p><p>Finally, practice reading the data back into this notebook using numpy.loadtxt. Because you used comma delimiters when writing the data, set the delimiter argument to ','. If you used the header argument in np.savetxt(), then skip reading the first row using skiprows = 1. Finally, set the unpack argument to True so that the returned data are transposed and arguments may be unpacked using angle _ read _ arr, period _ read _ arr = np.loadtxt(...). Altogether, your line of code will look like the following:</p><p>angle_read_arr, period_read_arr = np.loadtxt('dataFile.csv', delimiter = ',', skiprows = 1, unpack = True) 10.7 Challenge Problems 109 # FOR GOOGLE COLAB ONLY # &lt; Exercise 12, Write data to a file &gt; ### UNCOMMENT the following lines if you are using Google Colab #from google.colab import drive ## Mount your Google drive #drive.mount('/content/drive/') #import sys # Append your Google drive "Colab Notebooks" directory to your # path so that files contained in it may be found #sys.path.append('/content/drive/My Drive/Colab Notebooks/') #file _ path = '/content/drive/My Drive/Colab Notebooks/' # &lt; Exercise 12, Write data to a file &gt; ### CREATE a 2-D array of data using np.column _ stack ### WRITE data using np.savetxt ### READ data using np.loadtxt # If you are using google.colab, use file _ path + "periodData.csv" for the # filename in the call to np.savetxt Checking elements: [1. 2. 3. 4. 5.] [1.81282 1.81292 1.81309 1.81333 1.81364]</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.6">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13</head><p>Briefly summarize in the cell below the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="10.7">Challenge Problems</head><p>Consider taking a peek at the effect of small damping. Verify, by direct substitution, that the solution to this equation of motion: dv dt = -&#947;v is a velocity that decays exponentially in time, like so:</p><p>Given a damping parameter of &#947; = 0.05, when might the velocity be expected to fall to its 1/e point?</p><p>At what time will v 0 1 e &#8776; 0.37v 0 ?</p><p>Returning to your computational solution for a pendulum released from 5 degrees, modify your code to include damping, generate the plots from Exercises 6 and 7, and describe what you see.</p><p>In what part of the motion is the energy loss the greatest? You might want to zoom in on a few cycles of the plot. Note that the instantaneous power loss due to the frictional damping is P = -&#8407; F &#8226; &#8407; v, so P = -&#947;v 2 . The cycle average power is half this, insofar as the average value of either sin 2 (&#969;t) or cos 2 (&#969;t) is 1  2 , thus &#10216;P&#10217; = -1 2 &#947;v 2 max . Given that the total energy is the maximum of the kinetic energy, 1  2 mv 2 max , the fractional energy loss goes as - _ span = [t _ start, t _ end] delta _ t = 0.01 # time step of integration t _ arr = np.arange(t _ start,t _ end,delta _ t) # Set the initial conditions # starting angle, in degrees start _ angle _ degrees = 0 ### FIX start _ angle _ radians = start _ angle _ degrees * np.pi/180.0 # Start at rest at start _ angle, after converting to radians rinit = np.array([start _ angle _ radians, 0],float) # As usual, create a function to evaluate the derivatives: def deriv(t _ now,r _ now): # note that time is now the first element in the # derivative function theta = r _ now[0] # unpack the r-array for the angle theta _ dot = r _ now[1] # unpack the r-array for the angular velocity d _ Theta _ dt = 0 # replace 0 with the appropriate expression from # Exercise 2 # Hint: This is simpler than you think --refer to # Appendix: Using solve _ ivp for Numerical Integration 10.7 Challenge Problems 111 d _ ThetaDot _ dt = 0 # replace 0 with the appropriate expression from # Exercise 2, including the drag force (torque). return np.array([d _ Theta _ dt,d _ ThetaDot _ dt],float) # This is it, the Python ODE solver, all in one line: # integrate to get the solution array r _ soln at all points in time r _ soln = solve _ ivp(deriv, t _ span, rinit, rtol = 1e-8, atol = 1e-8) # in which variable r _ soln.y[0] is the angle # and variable r _ soln.y[1] is the angular velocity # The times at which the solution is evaluated are given # by r _ soln.t # Placeholder (reminder) that when you're done, you'll need to make a solution # array of KE, PE, and total E for plot #4. In order to be able to plot, # we've prefilled this with * nonsense * copies of the time array, # which at least will be the correct length: KE = 1.0 * t _ arr.copy() PE = 2.0 * t _ arr.copy() TE = 3.0 * t _ arr.copy() Comparing Data and Theory for Simple Pendulum</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>11</head><p>P reviously, you analyzed the simple pendulum through a simulation. The system's sim- plicity means that it offers many opportunities for analytical and experimental analysis, too. Here you will compare the relationship between period and release angle using these three physics techniques.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="11.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; analytically calculate the period of the simple pendulum (PHY);</p><p>&#8226; compute an integral using numerical integration or determining the value from a table of special functions (PRO);</p><p>&#8226; read in and analyze a table of experimental data (PRO);</p><p>&#8226; compare the analytical, computational, and experimental results (PHY). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="11.2">Computational Solution</head><p>Exercise 1</p><p>In the previous unit, Simple Pendulum with Large Angle Release, you simulated the motion of a simple pendulum. The first step in comparing that simulation to either the analytical solution or a real experiment is to grab information from that unit. In the next cell, copy over your definitions for the gravitational acceleration and length of the pendulum. Calculate the period of the pendulum in the small-angle limit. Rather than repeat your calculation for the period as a function of maximum angle, it is better to read in the data you calculated in the previous lab. Follow the instructions from Simple Pendulum with Large Angle Release Exercise 12 to read in the contents of the data file you created in that unit.</p><p>Set equal to the period in the small-angle limit ### CALCULATE the period in the small-angle limit</p><p>the following lines if you are using Google Colab #from google.colab import drive ## Mount your Google drive #drive.mount('/content/drive/') #import sys # Append your Google drive "Colab Notebooks" directory to your # path so that files contained in it may be found #sys.path.append('/content/drive/My Drive/Colab Notebooks/') # &lt; Exercise 1, Load data &gt; ### READ data from the .csv file you created in Exercise 12 of "Simple Pendulum # with Large-Angle Release"</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="11.3">Analytical Solution</head><p>Although using the small-angle approximation greatly simplifies the math, it is possible to find the exact analytical solution to the period of a pendulum released at a particular angle.</p><p>The following three exercises will lead you through the steps to do this.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2*</head><p>Fill in the steps outlined in Problem 4.38, Equation 4.103 of Classical Mechanics, leading to the analytical solution for the simple pendulum at any angle:</p><p>1 -A 2 u 2 for some A (Eq. 4.103 from Classical Mechanics).</p><p>The resulting dimensionless definite integral is known as the complete elliptic integral of the first kind. It can be solved via direct numerical integration (using an efficient quadrature algorithm), or its values looked up in tables of special functions. The SciPy package calls it scipy.special.ellipk(), and it is a function of the variable A 2 , usually referred to as k 2 .</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3*</head><p>First, let's attempt to find the period by integrating Eq. 4.103 from Classical Mechanics using a quadrature formula.</p><p>The scipy.integrate.quad function numerically computes a definite integral by essentially computing a Riemann sum using adaptively subdivided intervals. To use quad(), one must first define the function that one wishes to integrate (in this case, Eq. 4.103). Then the required arguments for quad() are the name of the function, the lower limit of integration, and the upper limit of integration. The user must also pass to quad() using the keyword args any arguments the function takes in addition to the integration variable.</p><p>Fill in the code in the cells below to define the function. Then use quad() to calculate the integral for initial release angles used in the computational solution. def dtau(u, tau0, A):</p><p>"""Eq. 4.103. u is the variable of integration tau0 is the zero-angle period T0</p><p>A is a function of the starting angle """</p><p>so that it is set to the value of Eq. 4.103 return result # &lt; Exercise 3, Integrate using quad, cont. &gt; from scipy.integrate import quad # Import the function from scipy.integrate # Example of how to use quad() to find the period start _ angle _ degrees = 0 # Start _ angle in degrees start _ angle _ radians = (start _ angle _ degrees/180) * np.pi A = np.sin(start _ angle _ radians/2) period = quad(dtau, 0, 1, args = (t0, A)) # The quad() integrator takes the # function, the limits of the # integral, and the parameters in # a tuple called 'args' and # returns a tuple # The integrator returns the result and the error print("Result and error: ", period) print(period[0]) # extract the result # Loop to calculate the period for an array of initial angles print("\nangle, period") for start _ angle _ degrees in angle _ arr: ### FILL in to calculate the period using quad print(start _ angle _ degrees, period[0]) Exercise 4 As noted in Problem 4.38 in Classical Mechanics, the dimensionless integral with limits (0,1) is known as the complete elliptic integral of the first kind, and it is a function of A 2 . Because this is a well-known integral, you don't actually have to calculate it yourself. In fact, SciPy includes it as a special function, scipy.special.ellipk. Use ellipk() to calculate the period for the start angles in your computational data. All you need to do to run ellipk() is to pass it the value of A, e.g., ellipk(A ** 2) Even better, you can pass ellipk an array of values for A and have it do all the calculations together. # &lt; Exercise 4, Using ellipk to calculate period &gt; # Import the library from scipy.special import ellipk ### USE ellipk to calculate the period for all the release angles in the # computational data</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>How well do the results of your analytical calculation compare to the simulation results? To find out, create a well-labeled plot of the elliptic integral solution for period vs. angle and your solve _ ivp() solutions. Make a second plot showing the differences between the solutions, a.k.a. the residuals, as a function of release angle.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="11.4">Experimental Solution 117</head><p># &lt; Exercise 5, Elliptic integral, solve _ ivp() and their difference &gt; ### PLOT period vs. angle for your simulation and analytical calculations.</p><p>### PLOT the difference between these solutions vs. angle</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="11.4">Experimental Solution</head><p>You have now calculated the period of the pendulum using two different theoretical approaches: analytically and with a simulation. How does your calculated period compare to the actual period, though? For that you will need experimental data.</p><p>We have collected data for you using the following experimental setup and included it in the file "simplePendulum.csv". You are welcome, though, to replicate this experiment to collect your own data!</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="11.4.1">Experimental arrangement</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>L HeNe laser photogate timer</head><p>As shown in the figure above, a 25.4 mm steel ball was attached to a rigid support via a pair of lightweight strings, forming a V-shaped bifilar pendulum of length 0.816 meters constrained to move in a fixed vertical plane (into and out of the page). The ball was drawn back 45 degree and set in motion so that it crossed a photogate (Vernier) at its lowest point. The pendulum was allowed to wind down due to weak damping, while the times of blocking and unblocking of the photogate were recorded. The photogate was used with an external HeNe laser that was aligned with the retroreflection of the ball at the equilibrium position, ensuring that the beam passed through the center of the ball. The data for this example were exported from Vernier Logger Pro as a CSV (comma separated value) file, "simplePendulum.csv". The Logger Pro software records photogate data as the time at which the photogate is blocked (Gate State = 1, called gsH) or unblocked (Gate State = 0, called gsL), along with a column of digital gate states at these times.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Gate State</head><p>As noted by Kostov et al., <ref type="foot">1</ref> timing a pendulum in this way can be divided into six transitions: b1 are the times when the ball first blocks the beam, b2 are the times when it next unblocks the beam, b3 is blocking on the first retrace, b4 is unblocking of the first retrace, b5 is blocking again in the forward direction, and b6 is unblocking in the forward direction. The center of the ball is at the equilibrium position at times (b1+b2)/2 and at (b5+b6)/2; the difference between these two times gives the period for that cycle. The speed of the ball is proportional to the blocking time of the midpoint readings, (b4-b3). It is given approximately by the diameter of the ball divided by the blocking time.</p><p>We ignore some small corrections: the reflection of the laser beam tends to smear out the image at the detector as the beam grazes the ball making it appear slightly bigger than its diameter, the beam falls along an arc of radius L, rather than straight through the center of the ball, and the ball is not actually at the lowest point when it is measured (making the measured speed slightly smaller than it would be). By attenuating the beam and placing a small aperture in front of the photogate, the smearing of the beam was largely controlled. The arc error is much less than 1%. The height error correction is smaller and is expected to be significant only at very small amplitude.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="11.4.2">Experimental data Exercise 6*</head><p>In order to interpret the experimental data, we must account for the fact that the measurements are for a physical, rather than ideal, pendulum. As such, there is a tiny correction to the effective length of the pendulum due to the moment of inertia of the 25.4 mm steel ball at the end of a 0.816 meter string (assumed to be massless).</p><p>Calculate the effective length of the pendulum and the period in the small-angle limit below.</p><p># &lt; Exercise 6, Effective length of the pendulum &gt; ### CALCULATE the effective length of the pendulum using the moment of inertia # for a 25.4 mm steel ball at the end of a 0.816 m massless string Exercise 7 Read in the experimental data from simplePendulum.csv. When reading data in from a file, it is helpful to first open the file in a text or spreadsheet reader in order to see the format. When you do this, you'll notice that simplePendulum.csv contains two columns of comma-separated data. At the start of the file are two rows of header. The first lists information about when the file was created and the second indicates the contents of the columns. The first column contains the time (in seconds) when there was a gate change (i.e., when the bob passed in front of the laser, breaking the beam, or when it exited the beam). The second contains the gate state, 1 for blocked and 0 for open. Because this is comma-separated value data, you can use np.loadtxt() to read it in, as you did in Exercise 1. Once you have read in the data, print the first ten entries. Other ways of reading in files of different formats are discussed in Appendix 2: Basic Input/Output of Files in Python. # &lt; Exercise 7, Reading data &gt; ### REPLACE the dummy placeholder definitions for t _ arr and gs _ arr below by # data read in from simplePendulum.csv t _ arr = np.array([]) # [s] Times at which there was a gate change gs _ arr = np.array([]) # Gate state</p><p>The data file only contains the instances when the gate state changes. For the purposes of illustration, it is nice to be able to plot the gate state versus time, similar to the figure on p. 118. This can be accomplished by manipulating the data arrays, as done in the cell below.</p><p># This block of code makes two modified arrays, t _ plt _ arr and gs _ plt _ arr, for # use in plotting the gate state over time # Make a new array of gate state by duplicating each one except the last. # To do this, a new two-row array with duplicate rows is produced. # The array is then flattened into one array. The parameter "F" in the # flatten call tells it to flatten in "column-major" order, meaning that # columns of data appear contiguous in the new array gs _ plt _ arr = np.array([gs _ arr[:-1], gs _ arr[:-1]]).flatten("F") # Make a new array of times that provides duplicate values for all but the # first time. As for gs _ plt _ arr, an array of two new rows is constructed # and then flattened in "column-major" order t _ plt _ arr = np.array([t _ arr[0:-1], t _ arr[1:]]).flatten("F") print("Here are the ten gate changes and times:") for row in range(20): print("{:10.6f}, {:1.0f}".format(t _ plt _ arr[row], gs _ plt _ arr[row])) print("") # Plot of Gate State vs. Time for the first 5 seconds fig, ax = plt.subplots(2,1) ax[0].plot(t _ plt _ arr, gs _ plt _ arr) ax[0].plot(t _ arr, gs _ arr, ' * r') ax[0].set _ xlim([0, 5]) ax[0].set _ ylabel("Gate State") ax[0].set _ title("First and Final 5 seconds\nTransitions marked as red stars") # Plot of Gate State vs. Time for the final 5 seconds ax[1].plot(t _ plt _ arr, gs _ plt _ arr) ax[1].plot(t _ arr, gs _ arr, ' * r') ax[1].set _ xlim([t _ plt _ arr[-1] -5, t _ plt _ arr[-1]]) ax[1].set _ xlabel("Time [s]") ax[1].set _ ylabel("Gate State") plt.show() Exercise 8 Consider the plots above showing the gate state versus time for the first and final five seconds of the experiment. Based on these data, how is the period and maximum angular velocity of the pendulum changing over time? How can you tell?</p><p>As discussed in Experimental Arrangement, each complete back-forth-back swing of the pendulum results in six transitions: b1, b2, b3, b4, b5, and b6. To collect the maximum amount of data, we can imagine that these complete swings are interleaved together. In other words, a complete left-right-left swing is one period but so is a right-left-right swing. As a result, b3 and b4 for one period become b1 and b2 for the next period.</p><p>We slice the time arrays to produce the arrays of values for these transitions below. Remember that the double colon notation, [::n], means that every nth data point should be selected. Therefore, [::2] selects every other element starting from index 0 (i.e., 0, 2, 4 . . . ), while [1::2] selects every other element starting from index 1 (i.e., 1, 3, 5 . . . ).</p><p>We then have to trim the last two elements off of b1 and b2 because these transitions for the initial swing don't have data for the return swings. Similarly, we must trim off the final elements of b3 and b4. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9</head><p>The period of the pendulum (time for one complete swing back and forth) is the time between the midpoint between b1 and b2 and the midpoint between b5 and b6. Calculate the array of periods in the cell below. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>The velocity of the bob can be calculated by determining how long the beam is blocked during a middle swing (b4-b3) and assuming that the distance traveled during that time is the diameter of the bob. Make the calculation in the cell below. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12</head><p>You now have all the information you need to plot period versus release angle for the experimental data. Do so by plotting both the period versus release angle and normalized period (period/period in the small-angle limit) versus angle.</p><p># &lt; Exercise 12, Plot experimental period versus angle &gt; ### PLOT experimental period versus release angle ### PLOT normalized experimental period versus release angle</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13</head><p>Now compare the relationship between period and angle for the experimental data with your analytical solution and data from your simulations. To do this, make the following plots:</p><p>1) a plot showing the normalized periods versus angle generated experimentally, from simulations, and analytically and 2) a plot showing the residuals for the experimental and analytical data (analytical periodexperimental period) versus angle.</p><p>&#10148; Hint: When calculating the residuals, it is easiest to first recalculate the analytical solution using ellipk() for the angles with experimental data.</p><p>If you did not complete the portion of this unit on the analytical solution, you should leave that curve off of the first plot and skip the second plot.</p><p># &lt; Exercise 13, Compare period versus angle for experiment, analytical # and simulation results &gt; ### CALCULATE the analytical solution for the angles for which there are # experimental data ### PLOT normalized period versus angle for experiment, analytical solution, # and simulations ### PLOT the difference between the normalized periods for the experimental and # analytical data as a function of angle Exercise 14* Using the same dataset as above, make a plot of amplitude vs. time; better yet, try plotting the natural log of the amplitude vs. time. &#10148; Hint: What times should you use for your x-axis? Consider what gate state changes best represent the time of the swing.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="11.4">Experimental Solution 123</head><p>Compare the shape with the damped harmonic oscillator for when the amplitude is low.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>What happens at high amplitude?</head><p># &lt; Exercise 14, Plot experimental amplitude vs. time &gt; ### PLOT Experimental amplitude vs. time</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 15</head><p>At small angles, the plot of log(&#952; ) versus time looks very much like a line, which would indicate an exponential decay of the amplitude. Such an exponential decay rate is consistent with linear velocity-dependent drag. At high amplitude, the loss is greater, indicating a v 2 or more complicated regime as the flow around the ball becomes turbulent.</p><p>Calculate the decay rate by fitting a line to the small-angle portion of the graph using scipy.optimize.curve_fit, similarly to what you did in Exercise 9 from Taylor Series with Loops and Functions. As you saw in that exercise, the first step is to define the functional form of the fit. To do this, in the cell below define a function that returns m * x + b when passed the arguments x, b, and m. This will enable you to fit a line to the log of the amplitude. While it is possible to fit an exponential directly to the data, the method is less robust.</p><p>Then, use scipy.optimize.curve_fit to find the best-fit values of m and b for times after 500 seconds. The form of the function call is popt, pcov = curve _ fit(fit _ func, x, y, p0)</p><p>where fit _ func is the name of the fitting function you defined above, x is your independent variable (time), and y is your dependent variable (&#952;). The variable p0 is an array of initial guesses for the parameters in your fitting function, in this case, a guess for b and a guess for m. The returned values are 1) the array of fitted parameters (popt) optimized such that the square of the residuals is minimized and 2) the estimated approximate covariance of the fitted parameters (pcov), which is a measurement of the quality of the fit.</p><p>After you have found the best-fit curve, plot it atop your graph of log(&#952; ) versus time.</p><p>What is the exponential decay rate of the amplitude?</p><p># For fitting the decay of the amplitude from scipy.optimize import curve _ fit # &lt; Exercise 15, Linear fit to log(angle) &gt; ### DEFINE a fitting function: a function that takes x, the independent # variable, and two parameters (b, the y-intercept, and m, the slope) as # arguments and returns m * x + b # &lt; Exercise 15, Linear fit to log(angle), cont. &gt; ### DEFINE an array of initial guesses for b and m ### CALL CURVE _ FIT for log(angle) vs. t, assuming a linear fit ### PLOT your best-fit over the graph of log(angle) vs. t</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="11.5">Check-out</head><p>Exercise 16</p><p>Briefly summarize in the cell below the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Oscillations in a Potential Well 12</head><p>W hen faced with a differential equation that cannot be analytically solved, physicists typically take one of two routes: simplifying the equations so that they can be analytically solved or using a computer to numerically find the solution. In this unit, you will compare both of those methods. When completing your analysis, you will refer to the potential energy function. In this way, you will gain practice linking the shape of a potential energy function to the behavior of a system. This is a long unit, and it may be desirable to split it across two sessions (after Exercise 8 or 9 are natural break points) or to shorten it by adopting one or more of the following suggestions. Many of the analytical calculations (in particular, Exercises 1, 5, and 6) could be completed ahead of time by the student. The unit could also be truncated after Exercise 9 and still fulfill the first three objectives below. Exercises 14 through 16 compare small-and large-angle oscillations. They are similar to Simple Pendulum with Large-Angle Release and could be omitted, especially if that unit has already been completed.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; analyze a system, including determining stable and unstable equilibria, by graphing the potential energy function (PHY);</p><p>&#8226; learn how to use the scipy.optimize.root() function to solve an equation (PRO);</p><p>&#8226; use a Taylor series approximation to find an approximate analytical solution (PHY);</p><p>&#8226; determine how the behavior of the system is affected by its initial energy and its location relative to stable and unstable equilibria (PHY). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.2">Potential Energy Function for an Oscillating Wheel</head><p>Imagine a wheel with a mass m 1 mounted to the rim at angle &#966;. Another mass, m 2 , hangs below on the opposite side of the wheel from a string wrapped around the rim. The wheel is free to rotate back and forth. As it turns one way, m 1 rises while m 2 falls and vice versa. This situation is the subject of Problem 4.37 of Classical Mechanics and is illustrated in Figure <ref type="figure">4</ref>.28.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.2.1">Identifying stable and unstable equilibria Exercise 1</head><p>Determine the potential energy function.</p><p>To do this, write a function, pot _ wheel(), that takes as variables:</p><p>&#8226; value(s) of phi (angle at which m 1 lies on the wheel),</p><p>&#8226; radius, radius of the wheel,</p><p>&#8226; m1, mass of m 1 , and</p><p>The function should return the value(s) for the potential energy corresponding to the different value(s) of phi.</p><p>Note that Python and NumPy are clever enough that you should be able to send the function either a single value for phi or a NumPy array of values. The form of the potential energy returned will match that of phi and thus be either a single value or an array. """ Return the potential energy associated with the system in Fig. <ref type="figure">4</ref>.28 for a particular value or values of phi."""</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Using your function, calculate and plot the potential energy as a function of &#966; for -180 degrees &lt; &#966; &lt; 180 degrees. Let the radius of the wheel be 1 m and let m 1 = 1 kg. Show on the same set of axes the potential energy curves for m 2 = 0.7m 1 , 0.8m 1 , 0.9m 1 , 1.0m 1 , and 1.1m 1 . Include a legend labeling the different potential energy curves.</p><p>&#10148; Hint: You will likely have to convert between degrees and radians as np.cos() and similar NumPy trigonometric functions assume the angle is given in radians.</p><p>What differences in the potential energy curves appear when m 2 /m 1 increases? How will this affect the behavior of the system? Consider differences such as the number, location, and type of equilibria, and depth and width of the potential well (where applicable). ax.set _ xlabel("$\phi$ [degrees]") ax.set _ ylabel("U [J]") ax.set _ title("Potential Energy Well for Wheel") # Legend</p><p>ax.legend(["U = 0","m2 = 0.7 * m1", "m2 = 0.8 * m1", "m2 = 0.9 * m1", "m2 = m1", "m2 = 1.1 * m2"], loc = "lower left") plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>For m 1 = 1 kg and m 2 = 0.7 kg, calculate the value of &#966; &gt; 0 that corresponds to the stable and unstable equilibria.</p><p>&#10148; Hint: np.arcsin() only returns values between -&#960;/2 and &#960;/2 so you will need to make an adjustment to find values outside of that range.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>, Stable and unstable equilibria &gt; ### USE this cell for your calculation</head><p>print("For m2 = 0.7:\n")</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>Find the critical value for m 2 /m 1 at which on one side the system oscillates and on the other side it does not, when released from rest at &#966; = 0. Plot the potential energy function for this mass ratio, and explain how the graph illustrates your result.</p><p>&#10148; Hint 1: Unlike in the previous exercise, you will not be able to solve the resulting equation analytically. Instead, simplify your equation as far as possible and arrange it such that one side of the equation is zero. Then use the scipy.optimize.root function to find the root of the equation numerically (i.e., find the value of x at which y(x) = 0). An example of how to use scipy.optimize.root() can be found below.</p><p>&#10148; Hint 2: Give some thought to the initial guess you will use with scipy.optimize.root(), as there are multiple possible solutions that result in zero potential energy but only one meaningful one. You can use your plot from Exercise 2 to do this intelligently.</p><p>, it is apparent that . . .</p><p>Example of using scipy.optimize.root() to solve an equation numerically # Make sure you understand what is happening here. from scipy.optimize import root # Import root from the scipy.optimize package def func(x): """ A quadratic function for which we want to find the root """ return -10 + 2 * x ** 2 # To call root, provide the name of the function as the first argument # and a guess at the value of the root as the second argument result = root(func, 2) ### TRY using a different guess to find the other root. # What happens if your guess is right between the # two roots? # root can return multiple roots: try [-5,2] # instead of 2 # # Print the return of root() # This contains a lot of information # in the form of an object. For more of a description, see # <ref type="url">https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize</ref>. OptimizeResult.html#scipy.optimize.OptimizeResult print(result) # For our purposes, the key information is the value of the root, accessible as # "result.x[0]" root _ val = result.x[0] # The value of the root print("\nThe root: {}".format(root _ val)) # The block of code below provides a graph of func() to allow you to visualize # the solution fig, ax = plt.subplots() x _ array = np.linspace(-10, 10, 200) # Set of x values for plotting y _ array = func(x _ array) # The corresponding y values 12.2 Potential Energy Function for an Oscillating Wheel 129 ax.plot(x _ array, y _ array) # The function the root was calculated for ax.axhline(0.0, color = 'black', linestyle ='--') # horizontal line at zero ax.plot([root _ val, root _ val], [0,0], 'ko') # A point at the determined root of # the solution # The "k" indicates to make it # black, the "o" indicates to # make it a circle. Linestyles, # marks, and colors can often be # indicated together in the # plotting call this way. ax.set _ xlabel("x") ax.set _ ylabel("y") ax.set _ title("Example of root finding") plt.show() # &lt; Exercise 4, Use scipy.optimize.root to find the value of m2/m1 for ax.set _ xlabel("$\phi$ [degrees]") ax.set _ ylabel("U") ax.set _ title("Potential Energy Well for Wheel") phi = np.array([0,0]) ### FIX plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.3">Analytical Approximation Using Taylor Series</head><p>The presence of a stable equilibrium for many mass ratios indicates that the system can undergo oscillations. But what do these oscillations look like, and specifically, what is their period? In this following section, you use a Taylor series expansion of the force to determine the approximate behavior of the system when oscillating.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.3.1">Approximating the force and potential energy</head><p>Exercise 5</p><p>On a separate sheet of paper, write down a second-order Taylor series approximation for the potential energy expanded around the stable equilibrium point. You may include the angle of the stable equilibrium, &#966; stable , as a term in your final equation or expand it in terms of m 1 and m 2 as you did in Exercise 3.</p><p>&#10148; Hint: Remember that the first derivative of potential energy is zero at equilibrium points.</p><p>Exercise 6</p><p>Plot the approximate potential energy function along with the true potential energy function for m 1 = 1 kg and m 2 = 0.7 kg. What this graph demonstrates is that Taylor series may be used to fit a parabola to a potential energy function around a stable equilibrium. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>Making a second-order Taylor series approximation of the potential energy, as you did above, means that you are modeling the derivative of it with a first-order Taylor series approximation.</p><p>To show this equivalency, first determine the exact expression for the force on the system as a function of &#966;.</p><p>&#10148; Hint: Recall that in polar coordinates &#8711;f = 1 r &#8706;f &#8706;&#966; &#966; + &#8706;f &#8706;r r. Furthermore, since this is a curvilinear system, the potential energy is only changing with &#966; so its partial derivative with respect to r is zero.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.3">Analytical Approximation Using Taylor Series 131</head><p>Then, once again working in polar coordinates, find the negative of the gradient of the Taylor series approximation to the potential energy from Exercise 6. ax.axhline(0.0, color = 'black', linestyle ='--') # horizontal line at force = 0 plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.3.2">Theoretical analysis of oscillations</head><p>You have used Taylor series to find an approximate equation of motion of the system. Now you can analyze that equation of motion to determine the behavior of the system. To do this, you will compare it with a familiar system: the simple harmonic oscillator made from a mass and a spring.</p><p>In Chapter 5 of Classical Mechanics, you will derive the solution to the simple harmonic oscillator. In previous physics classes, however, you have likely learned that the equation of motion for a mass on an ideal spring is</p><p>and that the solution to this differential equation if the mass is released from rest at distance A from equilibrium is</p><p>i.e., oscillations of amplitude A at an angular frequency of k m , resulting in a period of T = 2&#960; m k .</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9</head><p>Express the force on the system in terms of the angular acceleration, &#966; and the inertial mass, (m 1 + m 2 ). Then set this expression for force equal to your first-order Taylor series approximation for the force. By slightly rearranging your equation and matching constants from the above equation for a mass on a spring, determine the period of oscillations. Then graph your approximate solution for &#966;(t) if the system is released from rest at an angle of 5 degrees away from the stable equilibrium. Show the solution for 0 &#8804; t &#8804; 10 s using time steps of 0.05 s.</p><p>&#10148; Hint: Remember np.cos() and np.sin() assume the parameters are in radians; be sure to do the conversion from degrees to radians. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Period</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.4">Computational Solution for &#966;(t)</head><p>As you can see, Taylor series expansion provides a simple way to find an approximate solution for oscillations around a stable equilibrium point. However, another option is to solve the exact equations of motion numerically. In this next section, you will use the Python numerical solver, solve _ ivp(), to find the angle as a function of time.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.4.1">Computational solution for small angles Exercise 10</head><p>The following code blocks are missing a few pieces, which you will have to supply. In particular, the function that evaluates the derivatives needs some work. To determine the appropriate expressions for the derivatives, remember that F = (m 1 + m 2 )R &#966; and that you found an exact expression for force in Exercise 7. Please supply those missing pieces of code, and create these two labeled plots for a wheel with radius = 1 m, m 1 = 1 kg and m 2 = 0.7 kg:</p><p>1. Angle vs. time for the numerical and Taylor series approximation solutions 2. Angular velocity vs. time for the numerical and Taylor series approximation solutions Let the wheel start from rest 5 degrees from the stable equilibrium. As in your approximate solution, your numerical solution should run from 0 &#8804; t &#8804; 10 s using time steps of 0.05 s. In this exercise and throughout this notebook, please set the t_eval parameter in solve _ ivp() to an array of times for which a solution is defined. Requiring that solve _ ivp() use a fixed array of times rather than selecting its own adaptive time steps will simplify comparisons among different solutions. r _ init = np.array([start _ angle, 0]) # Start at rest at start _ angle, after # converting to radians. # In general, r _ init contains an array # of the initial states of the # variables you'll be integrating. # Here, you have phi and # dphi/dt (angular velocity) # This is it, the Python ODE solver, all in one line: # "deriv" is the name of the function that returns the derivatives of the # two variables, angle and angular velocity. # t _ span is a list or tuple containing the range of times to solve over. # Variable r _ init[0] is the initial angle # and variable r _ init[1] is the initial angular velocity. # The variable t _ arr is the specific array of times to solve over. # args is set to the values of the additional parameters used in the call to # the deriv function. r _ soln = solve _ ivp(deriv, t _ span, r _ init, args = (radius, m1, m2), t _ eval = t _ arr ) # integrate to get the solution vector # r _ soln across the time span # r _ soln holds the time array and the values of angle and velocity as: # t _ array = r _ soln.t[0] # phi solution for all time is r _ soln.y[0] # phiDot solution is r _ soln.y[1] # &lt; Exercise 10, Computational solution for phi(t) &amp; dphi/dt(t), the plots &gt; # Plot the angle vs. time for the approximate analytical and computational # solutions fig, ax = plt.subplots() ax.set _ title("Angle vs. time") ax.set _ xlabel('Time [s]') ax.set _ ylabel('$\phi$ [degrees]') # Plot the angular velocity vs. time for the approximate analytical and # computational solutions fig, ax = plt.subplots() ax.set _ title("Angular velocity vs. time") ax.set _ xlabel('Time [s]') ax.set _ ylabel('$\omega$ [degrees/s]') plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 11*</head><p>Validate your code by showing that energy is conserved in your computational solution. To do this, plot potential energy, kinetic energy, and total energy over time on the same axes.</p><p>12.4 Computational Solution for &#966;(t) 135</p><p>&#10148; Hint: Your total energy will likely be a negative number (why is this?). If you would like to modify your plot so that the kinetic energy, total energy, and potential energy all cover the same range, you can adjust your potential energy by adding a constant offset. One reasonable choice for such an offset is to make the potential energy at the bottom of the potential well zero. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.4.2">Computational solution for different mass ratios</head><p>Next you will use your numerical solver to compare the behavior of the system for different values of m 2 /m 1 .</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12</head><p>In the cell below, copy your relevant code from above to call the numerical solver to find the solution for the behavior of the system released from rest at &#966; = 0 when m 2 /m 1 = 0.7. Then, copy those lines of code again and modify them to solve for the behavior if m 2 /m 1 = 0.8</p><p>from Exercise 4.</p><p>Note that you will want to save the solutions for each of the different mass ratios so that you can plot them together. One way to do this is to set the output of solve _ ivp() to different variable names: r _ soln _ 07 = solve _ ivp(deriv, t _ span, r _ init, args = (radius, m1, m1 * 0.7), t _ eval = t _ arr) r _ soln _ 08 = solve _ ivp(deriv, t _ span, r _ init, args = (radius, m1, m1 * 0.8), t _ eval = t _ arr)</p><p>Another way to do this is to have the output of solve _ ivp() always set to the same variable name (e.g. r _ soln) and to save the solution vector by copying it to another variable. Unfortunately, in some versions of Python with arrays one can't just use an equals sign to make a copy (e.g., phi _ 07 = r _ soln.y[0]), or Python will think that there are now two names for the same underlying phi vector, and future modifications of r _ soln.y will also modify phi _ 07. To be safe, use numpy.copy. For example, include the following line after solving for the m 2 /m 1 = 0.7 case before solving for the m 2 /m 1 = 0.8 case: </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.4.3">Computational solution for different release angles</head><p>Next you will use your numerical solver to compare oscillations of the system when released at a large angle to the same system released from a small angle.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13</head><p>So far you have examined initial conditions that lead to oscillations. Now, using your solution from Exercise 3, try starting the system from rest at the unstable equilibrium.</p><p>In the cell below, copy your relevant code from above to call the numerical solver to find the solution for the behavior of the system released from rest from the unstable equilibrium.</p><p>After you have found the solution when starting the system from rest, try giving it a small initial angular velocity. | &#966;| = 0.001 rad s -1 will be sufficient, but consider what sign for &#966; to use to produce the most interesting behavior. To do this, copy those lines of code to numerically integrate the system again and modify them for the different initial conditions. Finally, plot &#966; versus time for both sets of initial conditions on the same set of axes. As in Exercise 12, you will need to save solutions for both cases. You can accomplish this by either setting the output of solve _ ivp() to different variable names or by using np.copy(). You may need to adjust either your integration time or the range of the time axis in your plot to see the full behavior.</p><p>Using the information from the graph, describe the behavior of the system when started from the unstable equilibrium at rest and with some initial velocity.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 13, A non-oscillating solution &gt;</head><p>### DO THIS: Copy and modify the above code to find the solutions for the # system when released from rest at the unstable equilibrium and when # released with some very small initial velocity</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 14*</head><p>Let us return to conditions that lead to oscillating behavior.</p><p>How do you expect the behavior of the system to change as the initial angle is increased slightly away from the stable equilibrium point? Explain your reasoning. You may find it helpful to recall the behavior of the simple pendulum explored in Simple Pendulum with Large-Angle Release.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 15*</head><p>In the cell below, numerically solve for behavior of the system when it is released from rest at both 5 degrees and 40 degrees away from the stable equilibrium. As in Exercise 13, plot &#966; versus time for both cases on the same figure. You may find it interesting to also include the solution from the Taylor series approximation on your plots. You should see that the two solutions (small-angle and large-angle) get out of phase with one another. After 20 seconds, the 5 degrees release will go through a little over six and a half periods, whereas the 40 degrees release, running a bit slower, is closer to six periods.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 16*</head><p>Repeat the above exercise for a release angle of 66 degrees from equilibrium. Include an additional plot showing your 66 degrees solution versus the 5 degrees solution. This plot produces a Lissajous figure, useful for identifying how different frequencies relate to each other. You will encounter Lissajous figures again in Section 5.4 of Classical Mechanics.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>What is the approximate ratio between the number of periods completed within 15.5 seconds for the 5-degree angle release versus the 66-degree angle release? How is this ratio apparent in the Lissajous figure? How would the Lissajous figure change if a slightly different pair of angles were compared?</head><p>&#10148; Hint: You should notice that this ratio is close to a rational number. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.5">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 17</head><p>Briefly summarize the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="12.6">Challenge Problems</head><p>If you have time, consider completing one or more of the following problems:</p><p>&#8226; Determine the period as a function of release angle.</p><p>-Using the zero-crossing detector and associated technique introduced in Simple Pendulum with Large-Angle Release, write a function to determine the period of oscillations around the stable equilibria. -Loop through different release angles between 1 and 89 degrees from the stable equilibrium and calculate the period. -Plot the period as a function of angle from 1 to 89 degrees and create a second plot that normalizes the period by the small-angle value (which should be what you found using a Taylor series approximation in Exercise 9).</p><p>&#8226; In Exercise 16 you were asked to plot the Lissajous figure for the interesting case where the ratio between the periods for the small-and large-angle releases was close to a rational number. Such systems may be considered to be in resonance with each other. Using your results from the first challenge problem, identify a different large released angle that will also result in resonance; plot the resulting Lissajous figure.</p><p>&#8226; Using a similar technique as in the first example problem, plot the period for the system when released from rest 5 degrees from the stable equilibrium for different values of m 2 /m 1 .</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Damped and Undamped Harmonic Oscillators</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>13</head><p>S imple harmonic oscillators (SHO) and variations on them show up everywhere in physics. This is because, as you saw in Oscillations in a Potential Well, motion around a stable equilibrium can be approximated as simple harmonic motion. By becoming very familiar with them here, you will be well-positioned to work with them the next time they appear, for example, in quantum mechanics. In this unit, you will explore how the addition of damping affects harmonic motion.</p><p>This unit covers material from Ch. 5.5 and 5.6 of Classical Mechanics, including the creation of a resonance curve, similar to that in Figure <ref type="figure">5</ref>.17.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="13.1">Objectives</head><p>In this unit, you will analyze the equations of motion and model the behavior for:</p><p>&#8226; undamped (PHY); &#8226; overdamped (PHY); &#8226; critically damped (PHY);</p><p>&#8226; and underdamped oscillators (PHY). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="13.2">Harmonic Motion without Damping</head><p>Consider the mass-spring problem from introductory physics:</p><p>where the x values are functions of time.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="13.2.1">Analytical solution</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Assume a solution</head><p>x = A cos(&#969;t) + B sin(&#969;t).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1*</head><p>On a separate piece of paper, do the algebra to find the analytical solution by substituting your assumed solution (and its derivatives) into the differential equation and determining the value of &#969;.</p><p>Then, make a Python plot of the analytical result for x(t) and v(t), assuming that x 0 = 3 m, v 0 = 1 m/s, k = 8 N/m, and m = 2 kg. See if you can show both x and v as different color lines on the same plot. Make the plot run for 10 seconds, with 10,000 points.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 1, Analytical solution, no damping &gt;</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="13.2.2">Numerical solution</head><p>As usual, we convert this second-order equation into two coupled first-order equations by defining a velocity function, which is itself a first derivative of the position function:</p><p>Then we seek simultaneous solutions to these two first-order equations:</p><p>x. Now, for each moment in time you will get solutions for x and v, which will be stored as the usual r _ now for each time step. Following the conventions of the previous units using solve _ ivp(), let x be the first variable in r _ soln.y[0]), and v be the second variable in r _ soln.y <ref type="bibr">[1]</ref>).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Modify the code below to calculate a numerical solution using solve _ ivp() for the same initial conditions as your analytical solution from above, x(t = 0) = 3m, v(t = 0) = 1 m/s. When defining your deriv() function, note that it takes an optional parameter omega0. When solve _ ivp() is executed, it passes along omega0 to deriv(), via a command that looks like: where the comma after omega0 in (omega0,) is syntactically necessary. Technically, the comma tells the program that (omega0,) is a one-element tuple. Remember, tuples are immutable (i.e., unchangeable) lists.</p><p>To ease the comparison with your analytical solution, pass solve _ ivp() the parameter t _ eval set to the same time array you used for the analytical solution. This means that, as before, your solution will be generated for 10 seconds, with 10,000 points.</p><p>The code below also sets the desired accuracy for solve _ ivp() through the use of the optional parameters rtol and atol. These parameters set the maximum relative and absolute tolerance, respectively. Specifically, local error estimates are required to be less than atol + rtol * abs(y). In the code, they have been decreased from the default values of 10 -3 for rtol and 10 -6 for atol to 10 -11 to achieve an extremely accurate solution.</p><p>Plot x(t) and v(t) as different colored lines in the same figure. </p><p>Initial velocity r _ init = np.array([pos _ x0 ,vel _ x0]) # two-element array that gives the initial # position and velocity r _ soln = solve _ ivp(deriv, t _ span, r _ init, args = (omega0,), t _ eval = t _ arr, rtol = 1.0e-11, atol = 1.0e-11) # to maintain reasonable # accuracy, we need # to tighten these # tolerances (compare # error if you omit these # keywords). If you are # patient, you could set # these to 1E-13 to # reduce the residual # further # &lt; Exercise 2, Plots of Numerical solution to SHO without damping &gt; ### PLOT x and v versus t here Exercise 3* Check the accuracy of your code by comparing your numerical and analytical solutions. To do this, calculate the residual (difference) in position (x _ numerical -x _ analytical) and plot it as a function of time. If desired, you can control this accumulated error by reducing atol and rtol even further, taking smaller steps, or by applying a different integration method. # &lt; Exercise 3, Residuals &gt; ### PLOT the difference between the analytical and computational positions # versus time Exercise 4* Use your numerical solution to make a plot showing the kinetic energy, potential energy, and total energy versus time. Just for fun, make a second plot showing velocity vs. position, called a "state space" plot. Are these two plots what you would expect? Explain below. # &lt; Exercise 4, Plots of energy etc. for SHO &gt; ### PLOT the kinetic energy, potential energy, and total energy vs. time and # the velocity versus the position</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="13.3">Damped Harmonic Oscillator</head><p>The damped harmonic oscillator is most easily modeled as a velocity-dependent drag force of the form</p><p>which means that our equations of motion now look like:</p><p>We saw earlier in Chapter 2 of Classical Mechanics and Projectile Motion with Drag that a viscous damping force gave exponentially decaying solutions. In this situation, we expect some combination of exponentially decaying and oscillating solutions.</p><p>But first, anticipating the notation from Classical Mechanics, we define a new variable &#946; = b 2m , and we can substitute the undamped natural frequency &#969; 2 0 = k m so that &#7821; + 2&#946; &#7819; + &#969; 2 0 x = 0. Now given Euler's theorem, e it = cos(t) + i sin(t), we may compactly write solutions in the form:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>On a separate sheet of paper, substitute this trial solution, and solve (analytically) the resulting quadratic equation, yielding a pair of solutions:</p><p>0 . The complete solution can then be found by matching initial conditions in the form</p><p>+ C 2 e r -t . We now consider the four cases of no damping, large damping, critical damping, and small damping. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>You should already have plotted this case above. For completeness, though, edit the code below to solve the damped oscillator numerically for a given value of &#946;, and then set the value of &#946; to zero. In later sections, you will change the value of &#946;, so make sure your newly defined deriv _ damp() function is set up correctly to take both omega0 and beta _ damp as optional parameters. Run your solution from t = 0 s to t = 40 s with 10,000 steps by defining an array of times and passing that array to solve _ ivp() through t _ eval. Keep the same initial conditions and values for k and m. Plot x(t) and v(t) on the same figure using different line colors. It will also be handy for you to save a copy of the undamped solution x(t) for comparison with the other cases, so include the following line at the end of your code to save your solution: We call this the overdamped case, and it has exponentially decaying solutions. For example, when you opened the door to this room, you may have had to do work against a door-closing spring. In many doors when you let go, a pneumatic or hydraulic soft-closing mechanism makes the door overdamped up until the moment that it needs to latch shut, whereupon the damping is suddenly reduced (a valve opens) to snap the door closed.</p><p>Note that in the overdamped case you get two solutions as usual,</p><p>The overall shape will be determined by the more-slowly decaying exponential.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8</head><p>For a concrete illustration of the overdamped case, let &#946; = 10&#969; 0 and numerically calculate and plot the position and velocity versus time. Because you have already defined your time array and your deriv _ damped() function in the cell above (and your initial conditions and values for k, m, and &#969; 0 much earlier), you do not need to replicate that code. However, you will need to include a line of code to set &#946; and call the integrator r _ soln = solve _ ivp(deriv _ damp, t _ span, r _ init, t _ eval = t _ arr, args = (omega0, beta _ damp))</p><p>to solve the equations of motion, in addition to writing code to plot x(t) and v(t). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9</head><p>On a separate sheet of paper, analytically calculate the timescale over which the exponential decays, assuming &#946; = 10&#969; 0 . To do this, note that ( &#969; 0 &#946; ) 2 is &#171; 1. Taylor expand the square root in the expression for r &#177; to first order (you'll see that this is equivalent to a first-order binomial approximation). Now compare the two solutions. You will see that one of them is much more highly damped than the other. Assuming one of them dies off really quickly, the other one must determine the overall shape of x(t), that is, r + dominates. Solve for the time constant &#964; , which is the time that it takes a decaying exponential to fall to 1/e of its initial value.</p><p>&#10148; Hint: It should be about 10 seconds -show this, and look for it on your plot above, where 1/e &#8776; 0.37.</p><p>&#964; has units of seconds, and it is a measure of the timescale over which the amplitude falls off. You may have encountered in a previous physics course an identical term while discharging a capacitor through a resistor, where Q(t) = Q 0 e -t/&#964; represents the charge on the plates of the capacitor, and &#964; = RC is the RC time-constant. = &#969; 2 0 , this leads to a double root in the characteristic equation, which requires solutions of the form</p><p>+ Bte -&#946;t . This critical damping case is the quickest way to get an oscillator to shed its energy. Car suspensions employ struts or shock absorbers that are designed to be close to critically damped for the average passenger load.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>Numerically calculate the solution to the critical damping case for the same initial conditions as before, and plot x(t) and v(t). Use the same initial conditions, time span, and natural frequency as before.</p><p>From your graph, about how long does it take for the amplitude to decrease by a factor of 1/e?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 10, Critical damping &gt; ### ADD CODE to solve the overdamped case ### ADD CODE to plot position and velocity vs. time</head><p>Note that for the initial conditions x 0 = 3 m and v 0 = 1 m/s, the oscillator starts at 3 m and approaches the origin x = 0, reaching it in the limit of t approaching &#8734;. For other cases, though, the oscillator may first overshoot, crossing through x = 0, reapproach it, and finally reach 0 in the limit as t approaches &#8734;.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 11*</head><p>Find one possible pair of A and B for which the critically damped oscillator hits x = 0 before t &#8594; &#8734;, and solve analytically for the time at which the oscillator will first hit x = 0. To check your calculations, numerically solve for and plot x(t) and v(t) given those initial conditions. The small damping case is mathematically the most interesting because the system oscillates and decays. The general result for real solutions can be matched to</p><p>with A and B real constants, chosen to fit the initial conditions.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12</head><p>For a concrete illustration of the underdamped case, keep &#969; 0 = 2 s -1 and let &#946; = &#969; 0 10 . On a separate sheet of paper, calculate the time at which the amplitude will have fallen to its 1/e point (roughly 1/3 of its initial amplitude). Calculate, as well, the frequency of the oscillations, &#969; damp .</p><p>Time when A = A 0 e -1 : Frequency of oscillation:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13</head><p>Numerically calculate the solution to the underdamping case for the same initial conditions and natural frequency as before (x = 3 m, v = 1 m/s, &#969; 0 = 2 s -1 ) and &#946; = &#969; 0 10 . Plot x(t) and v(t).</p><p>In order to compare the period of the oscillations to the undamped case, overlay your saved solution for x(t) for zero damping.</p><p>Justify that your plots match your results from Exercise 12. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="13.4">Check-out</head><p>Exercise 14</p><p>Briefly summarize the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="13.5">Challenge Problems</head><p>If you have time, consider completing one or more of the following problems:</p><p>&#8226; For the underdamped case, how big does &#946; have to be to make &#969; damp 2% less than the frequency of the undamped oscillator? You should be able to solve this problem quickly by using the first-order Taylor series approximation in exactly the same way as you approached Exercise 8, though in this case &#969; 0 is the term you pull out of the radical, and you are expanding the radical for small &#946;. Do not feel that you need to do this numerically!</p><p>&#8226; Make plots of velocity vs. position for the underdamped, overdamped, and critically damped oscillators.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Driven Damped Harmonic</head><p>Oscillator and Resonance</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>14</head><p>I n this unit you will build on the work you did in Damped and Undamped Harmonic</p><p>Oscillators to characterize and model damped harmonic motion. Specifically, you will add a sinusoidal forcing term in order to measure the resonant response at different driving frequencies. This unit covers material from Ch. 5.5 and 5.6 of Classical Mechanics, including the creation of a resonance curve, similar to that in Figure <ref type="figure">5</ref>.17.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.1">Objectives</head><p>In this unit, you will learn about:</p><p>&#8226; the long-term and transient motion of driven damped harmonic oscillators (PHY);</p><p>&#8226; the phase-shift between the oscillator and the driver (PHY); &#8226; the amplitude of the resulting motion due to different driving frequencies (PHY);</p><p>&#8226; the effect of damping on the resonance curve (PHY).</p><p>The first and third challenge problems also provide an introduction to generator expressions and multiprocessing. This content is motivated by the resonance curves for Exercises 10 and 12 taking a few minutes to compute. If instruction in multiprocessing computing is particularly desired, Exercises 7 and 8 could easily be omitted to save time. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.2">Motion of a Driven Damped Harmonic Oscillator</head><p>Exercise 1</p><p>You will be modifying your code from Damped and Undamped Harmonic Oscillators. Therefore, start by copying and pasting your code to model a damped harmonic oscillator into the cell below. Make sure it runs as you would expect by regenerating one of your plots.</p><p>&#10148; Hint: You will probably find most of the code you need in your response to Exercise 7 of Damped and Undamped Harmonic Oscillators. However, you may still need to define some constants of the system (e.g., m and &#969; 0 ).</p><p># &lt; Exercises 1 and 2, Numerical solution to SHO with damping and # later driving &gt; # Deriv function definition # &lt; Exercises 1 and 2, Numerical solution to SHO with damping and # later driving &gt; # ADD code to numerically calculate the solution # &lt; Exercises 1 and 2, Plot numerical solution to SHO with damping and # later driving &gt; ### ADD code to plot x(t) and v(t) here</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Modify your code above (the damped harmonic oscillator) to include a driving term of the form F 0 cos(&#969;t), where &#969; is the driver frequency. Make the natural frequency, damping term, drive frequency (&#969;) and the amplitude of the driver (F 0 /m) parameters in your deriv() function, similarly to how the natural frequency and damping term were incorporated in Damped and Undamped Harmonic Oscillators.</p><p>Start your system from rest at the equilibrium point (i.e., x = 0, v = 0), and let the driving frequency be the same as the natural frequency (i.e., &#969; = &#969; 0 ). Let m = 2 kg, k = 8 N/m, &#946; = 0.1, and the size of the driver force, F 0 , equal 0.2 N.</p><p>Run your integrator for 60 seconds (recalling that &#946; = 0.1 corresponds to &#964; = 10 seconds, so you will see the long-term behavior by going out beyond 5&#964; , where more than 99% of the transient term has died away).</p><p>Graph x(t) and overlay a copy of the driver, via</p><p>You should find that the amplitude of the long-term motion is about 0.25 m if &#969; = 2 s -1 .</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Answer the following questions about the motion of the damped driven oscillator below. Make adjustments to your code to explore the different situations. When you are done, reset your code to the parameters outlined in Exercise 2.</p><p>&#8226; What happens to the long-term and short-term motion if you change the initial position, x, and/or initial velocity, v?</p><p>&#8226; Looking at your figure, what do you observe about the relationship between the driver waveform and the position waveform? (We call this the relative phase of the motion with respect to the driver.)</p><p>&#8226; If you change the frequency to be 0.75 &#969; 0 , what happens to the relative phase? What happens to the long-term amplitude of the motion?</p><p>&#8226; If you change the frequency to 1.25 &#969; 0 , what happens to the relative phase? What happens to the long-term amplitude?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.3">Amplitude of Driven Damped Oscillations at Resonance</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.3.1">Maximum steady-state amplitude, determined analytically</head><p>You should have found that this driven oscillator reaches a steady-state amplitude that is largest when driven at its natural frequency &#969; = &#969; 0 . We call this the resonance frequency.</p><p>Let us find out how large this amplitude becomes (you can find a version of the following derivation in Section 5.5 of Classical Mechanics).</p><p>Your equation of motion is</p><p>We look for solutions that have the same frequency as the driver (because if the solutions were at some other frequency, there is no way we could balance this equation). So let us assume x(t) = Ae i&#969;t , with A a real constant. With no loss in generality, we can consider the driver to look like F 0 e i&#969;t . Then the equation of motion is</p><p>or</p><p>At resonance (&#969; = &#969; 0 ), considering only the amplitudes of the real and imaginary parts, we see that</p><p>So we expect in the steady state that the amplitude</p><p>Alternatively, we could note that when the driving force matches the damping force (i.e., F 0 = bv), we must be in equilibrium, where the average work done by the driver exactly matches the average energy loss due to the damping force. Recalling that</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>Compare your numerical result at resonance (&#969; = &#969; 0 ) with the predicted steady-state amplitude for the system described in Exercise 2. Then see what happens if you 1) triple the force term or 2) double the damping parameter. Include your analysis here. When you are done, reset the force term and damping parameter to the values from Exercise 2.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>Change your driver frequency to 1.08&#969; 0 and include the plot of x(t) for the mass and the driver below. Then answer the following questions here.</p><p>&#8226; Approximately at what time does the oscillator amplitude hit its peak value?</p><p>&#8226; If you want the steady-state value of the oscillator amplitude, when should you start looking at it?</p><p>&#8226; What's the value of e -5 ?</p><p>&#8226; Why should we wait at least 50 seconds in this example? What's with the phase of the oscillations? The easy way to see the relative phase of the x(t) solution above and below resonance is to ignore damping altogether (or make sure we are very far from resonance). Let us ignore damping for a moment and look at the driven oscillator equation of motion:</p><p>, where A(&#969;) represents the steady-state amplitude of the oscillation for a given driver frequency &#969;. Solving for A(&#969;) = F 0 /m (&#969; 2 0 -&#969; 2 ) shows that if &#969; &lt; &#969; 0 , the amplitude has the same sign as F 0 , and so the motion of the mass is in phase with the driver. If &#969; &gt; &#969; 0 , then the amplitude has the opposite sign as F 0 , and so the motion is out of phase with the driver.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6</head><p>Check the relative phases of x(t) and F(t) for your &#969; = 0.75&#969; 0 and &#969; = 1.25&#969; 0 curves. Explain here whether your curves match the above prediction.</p><p>So, below resonance the mass is in phase with the driver, and above resonance the mass is out of phase with the driver. At resonance, what happens?</p><p>That question is hard to answer in the absence of damping, because the amplitude goes to &#8734;. But you can make a pretty bold guess that it lies between 0 degrees and 180 degrees relative phase. Does your numerical solution for &#969; = &#969; 0 provide a clue? Explain here.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.5">The Phase Dependence</head><p>By including damping in our equation of motion, we can come up with an expression for the difference in phase between x(t) and F(t), which we will call &#948;(&#969;). The derivation follows (and can also be found in Section 5.5 of Classical Mechanics).</p><p>Starting with our general equation of motion</p><p>F 0 m and solving for A(&#969;) yields:</p><p>.</p><p>This amplitude can be represented as a vector in the complex plane, with a real part along the x-axis and an imaginary part along the y-axis. The magnitude of the vector is given by A &#8226; A * , and the angle between the x-axis and the A vector can be found from</p><p>It is traditional to multiply the right side by -1 and then interpret this quantity as the tangent of the phase lag of the oscillator with respect to the driver:</p><p>Exercise 7*</p><p>Plot this analytically derived phase lag &#948; as a function of frequency. Assume our usual &#969; 0 = 2, &#946; = 0.1. Look over a range &#969; = [0, 2&#969; 0 ]. You will need a lot of points (try 100,000) to make this a symmetrical plot.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 7, Plot phase lag as a function of frequency near resonance &gt;</head><p>If you used np.arctan() to calculate &#948;, your plot of phase lag vs. frequency probably looks a bit funky. That is because we would like the angle to lie in the range [0,&#960;], rather than [-&#960;/2, &#960;/2]. We need to fix our plot to make it look like Fig. <ref type="figure">5</ref>.19 from Classical Mechanics, p. 192. You could adjust all negative values of &#948; by adding &#960;. Even easier, though, is to replace np.arctan() with numpy.arctan2, which maintains the correct quadrant for the solution. The syntax to call np.arctan2 is np.arctan2(y, x).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8*</head><p>Replot &#948; vs. &#969; below and answer the following questions here:</p><p>&#8226; What's the phase lag when the oscillator is driven at resonance? &#8226; What's the phase lag well below resonance?</p><p>&#8226; What's the phase lag well above resonance? 14.6 Amplitude as a Function of Driver Frequency 157 # &lt; Exercise 8, Plot of phase lag, plotted [0 -pi] &gt; 14.6 Amplitude as a Function of Driver Frequency</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.6.1">Analytical solution to the amplitude of a damped, driven harmonic oscillator</head><p>We solved for the steady-state solutions analytically, but it is a little inconvenient to plot the complex amplitude:</p><p>Instead, multiply by the complex conjugate and then take the square root to get the magnitude:</p><p>.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9*</head><p>Use the above analytical expression to plot the amplitude as a function of &#969; to observe the resonance. (This is probably most neatly done by creating an array of frequencies over which you want to plot, then making an analytical function that is evaluated at these frequencies.</p><p>Pay particular attention to all the squares and square roots.) If you would like to compare your curve with Figure <ref type="figure">5</ref>.17 on p. 190 of Classical Mechanics, plot the amplitude squared versus &#969;, as well.</p><p># &lt; Exercise 9 Analytical plot of amplitude vs. frequency &gt; # Outline of code to make a resonance curve from analytical expression.</p><p># 201 different values of omega from 0.9 to 1.1 omega _ arr = np.linspace(0.90 * omega0, 1.10 * omega0, 201) def calculate _ ampl _ analyt(omega, f _ 0, mass, omega0, beta _ damp):</p><p>""" Calculate the amplitude analytically for a particular value of omega""" ampl _ analyt = 1.0 * omega ### FIX this expression return ampl _ analyt</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Use the function above to calculate the amplitudes for an array of omega</head><p># values model = calculate _ ampl _ analyt(omega _ arr, f _ 0, mass, omega0, beta _ damp)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># INCLUDE code to plot amplitude versus omega below</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.7">Creating a Numerical Resonance Curve by Plotting the Maximum Amplitude vs. Frequency</head><p>The goal for this section is to replicate the resonance curve you just plotted analytically by solving the driven oscillator numerically. Doing so will provide proof that the analytical expression for the amplitude of a driven damped harmonic oscillator is correct. To do this, run your integrator for each of the 201 values in omega _ arr. At each &#969; step you will need to run the integrator out until the amplitude reaches a steady state. Find and store this final maximum amplitude each time. Remember, all we save from each run is the maximum steady-state amplitude. You should end up with an array of 201 points, each of which represents the steady-state amplitude A(&#969;).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>Create a loop in order to find |A(&#969;)| for 0.9&#969; 0 &lt; &#969; &lt; 1.1&#969; 0 and examine the resonance by plotting |A(&#969;)| versus &#969;. Use these parameters: m = 2 kg, k = 8 N/m, &#946; = 0.1, F 0 = 0.2. You may recall from previous units how to make an empty array, then append solutions to it using numpy.append. Remember that np.append() makes a new array and so must be assigned back to itself, like so: max _ amp _ arr = np.array([]) # create empty array from an empty list then in your loop, after you have found latest _ max _ amp _ value max _ amp _ arr = np.append(max _ amp _ arr, latest _ max _ amp _ value)</p><p>In this case, you will make an empty array to store the different amplitudes. Then, loop through various frequencies (say, from 0.9 &#969; 0 to 1.1 &#969; 0 ), and numerically solve for the trajectory. Finally, you should extract the highest features after the integration has run for a long time (say, after the time has encompassed at least five time constants). That means you need to go at least 5 &#946; before you look for the steady-state amplitude. To do so, you will need to run your integration for at least 50 seconds to let all the transient behavior die away and then let it run sufficient extra time to capture the steady-state motion. Hence 60 seconds is about right.</p><p>&#10148; Hint 1: Recall that a _ arr.max() identifies the maximum value in a _ arr. In the cell below is a reminder of how you might find the largest value in the second half of an array.</p><p>&#10148; Hint 2: When debugging code, it is often useful to print values from within an array. However, in long loops, it is easy to fill up the screen with output. The second cell block below shows a way to format output so that each print statement is separated by a space, rather than a new line.</p><p>It also includes a command, time.sleep(), that makes the program pause for a set amount of time. This pause makes it easier to visualize the loop but should be removed when running production code.</p><p># Hint</p><p>1: An example to illustrate how to find the largest element in the last # third of an array: 14.7 Creating a Numerical Resonance Curve 159 a _ arr = np.array([0, 1, 20, 3, 4, 3, 6, 4]) print("The whole array is: ", a _ arr) # Find the largest element in the entire array max _ a = a _ arr.max() print("The value of the largest item in the entire array: ", max _ a) # Find the largest element in the last third of the array last _ third _ index = len(a _ arr) * 2//3 # the double // slash indicates an integer # division, which truncates the # result to allow for easy indexing of # the array. Note that we do the # multiplication by 2, then integer # divide by 3. If you were to do # 2//3 first, you'd get 0 print("Start looking at index {:}".format(last _ third _ index)) # Restrict the search to the last third of the array max _ b = a _ arr[last _ third _ index:].max() print("The largest element in the last third of the array: ", max _ b) # Hint 2: A handy thing to know: if you want to print the current value of # omega _ arr without filling up the screen, you can modify the print # command to give a couple of spaces as a separator (this replaces the \n # newline character). # Import the sleep method to allow you to pause calculations for a fixed time # (in seconds) from time import sleep arr = np.linspace(0, 10, 201) # Example array for item in arr: # Pause for 0.02 seconds before continuing on. # Doing so makes it easier to visualize the loop. Remove for real # calculations! sleep(0.02) print ("{:5.2f}".format(item), end = " ") # Keep track of current omega, # so you know you're not # stuck. The end = " " avoids # a newline character at the # end # &lt; Exercise 10, A(omega) for beta = 0.1 &gt; # This calculation takes a bit of time to run. One of the challenge problems # will show you a way to speed it up. In preparation for that challenge # problem, you will time how long it takes for the calculation to run. # To do this, we will use the library "time", which returns the current time # clock time. By comparing the clock time at the start and the end of the # calculation, the time for calculation can be determined. import time time _ start = time.time() # initial time ### ADD your code to loop through values of omega, use solve _ ivp to find x(t), # and store the maximum amplitude after transients have died off # Time for the calculation is the final time minus the initial time. run _ time = time.time() -time _ start print("\nDone! Calculation took {:3.2f} seconds".format(run _ time)) # &lt; Exercise 10, A(omega) for beta = 0.1, Plots &gt; # ADD your code to plot amplitude versus omega</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.7.1">How are the widths of the peaks and their heights related for small damping?</head><p>The analytical solution showed us that the height of each peak falls a factor of 2 for each factor of 2 increase in the damping. What does the width of each peak do?</p><p>The easy way to answer this question is to look at the amplitude squared:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>At resonance the denominator becomes 2&#946;&#969;</head><p>2 and A 2 is at its peak value. A 2 reaches half of that peak value when &#969; is such that &#969; 2 0 -&#969; 2 ) 2 = (2&#946;&#969;) 2 , i.e., the two terms in the denominator are equal.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 11*</head><p>On a separate piece of paper, work through the following derivation for width of the resonant curve.</p><p>Let us look very close to the resonance by defining a new frequency &#8801; &#969;&#969; 0 , so we can write &#969; = + &#969; 0 .</p><p>First, take the square root of each side, so &#969; 2 0 -&#969; 2 ) = &#177;2&#946;&#969; Substitute for &#969; and expand the square. Then look at your result: anything multiplied by &#969; 0 is much larger than a term like &#946; . Simplify, and show that: &#8776; &#177;&#946;.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.7.2">Now for the Geometric Interpretation</head><p>The maximum A 2 grows inversely with &#946;, whereas the width of the peak grows proportionally to &#946;.</p><p>Consider: if you were to plot A 2 vs. &#969; for differing &#946;, what quantity stays approximately the same?</p><p>The Q of a damped driven oscillator is a figure of merit (quality factor) defined as</p><p>Q can be thought of as an amplification factor, whereby small motions in the driver get turned into large excursions of the mass at resonance. For example, at resonance in the steady state, the driver supplies a force of magnitude</p><p>Therefore, the greater the value of Q, the greater the amplitude at resonance. Experimentally, if you plot the amplitude squared vs. &#969;, the FWHM (full width at half maximum) of the peak is 2&#946;, and the Q of the resonance is</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12*</head><p>If desired, you can see how the resonance curve changes with &#946; by attempting the second challenge problem. For now, though, simply recreate your resonance curve from Exercise 10 for &#946; = 0.05 and verify by eye that the full width at half maximum and the peak amplitude change by the amount you would expect. Explain here.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 12, A(omega) for beta = 0.05 &gt; ### ADD your code to loop through values of omega, use solve _ ivp to find x(t),</head><p># and store the maximum amplitude after transients have died off # &lt; Exercise 12, A(omega) for beta = 0.05, Plots &gt; # ADD your code to plot amplitude versus omega</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.8">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13</head><p>Briefly summarize in the cell below the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.9">Challenge Problems</head><p>If you have time, consider working through the following challenge problems. They will introduce you to multiprocessing computing (1 and 3) and the effect of damping on the resonance curve (2 and 3). Challenge Problem 2 may be completed on its own, but challenge Problem 3 must be preceded by Problem 1 and, ideally, Problem 2.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.9.1">Challenge Problem 1: Multiprocessing</head><p>You probably noticed that creating a numerical resonance curve took a bit of time. One way to speed up calculations such as this is to use multiple computer cores at once so that different parts of the problem can be run at the same time, a.k.a., in parallel. Luckily, Python makes this method fairly straightforward. This challenge problem will walk you through the process. You will find the associated code in the cell below. By using the multiple cores (CPUs) in the PC, calculations can be sped up considerably, depending on the type of program (this one is "CPU bound, " because the rate-limiting step is calculations; another program may take time to print or plot, or get data from the Internetthese would be input/output, "I/O bound"). Nowadays, CPUs use "hyperthreading", which means that the core count will be the number of virtual cores. Often, the optimal number of cores to run a CPU-bound process is roughly half those reported. (If the process is I/O bound you can actually ask for many more cores than available.)</p><p>To use multiple processors on this problem, we need to have an external file called resonance _ for _ multi _ processingSolvers.py that contains a function solver() of one variable (a two-element tuple containing omega _ arr and beta _ damp; notice that this is similar to how you send parameters to solve _ ivp()) and returns the final amplitude for that integration. The external file needs to be self-contained, so it has to import numpy and solve _ ivp(), and also the deriv() function. Then, instead of a loop structure to loop over the omegas, we map them onto the function. This allows the pool of processors to divide up the job. When finished, the call will return the max _ amp _ arr (array of steady-state maximum values) as output.</p><p>In order to generate the list of tuples of &#946; and &#969; values, a "generator expression" is used. A generator expression is a concise way to create a list of values, similar to what could be accomplished through a loop but using only a single line of code. The syntax is</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>(expression for item in iterable)</head><p>where expression is the value that will be returned for each item contained in iterable.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>See if you can identify the generator expression in the example code.</head><p>Start by locating resonance _ for _ multi _ processingSolvers.py and moving it to the same directory as this Jupyter Notebook (this would require uploading it to CoCalc or to your "Colab Notebooks" directory on Google Drive if you are using Google Colab). Next, open resonance _ for _ multi _ processingSolvers.py and read through it. It should contain code that looks very similar to your work in Exercise 2 and Exercise 10. Then use the code provided in the cell below to calculate the resonance curve for &#946; = 0.1 and plot A(&#969;) 2 versus &#969;.</p><p>How much faster was your calculation than in Exercise 10? (You may also wish to explore the speed-up when you use different numbers of cores. Do not expect the run time to be divided by the number of cores employed; the CPU typically shuffles a computationally intensive job among the various cores to keep the chip from overheating.)</p><p>14.9 Challenge Problems 163 # FOR GOOGLE COLAB ONLY # &lt; Challenge problem 1, Use multiprocessing to determine a resonance curve &gt; ### UNCOMMENT the following lines if you are using Google Colab #from google.colab import drive ## Mount your Google drive #drive.mount('/content/drive/') #import sys # Append your Google drive "Colab Notebooks" directory to your # path so that files contained in it may be found #sys.path.append('/content/drive/My Drive/Colab Notebooks/') # &lt; Challenge problem 1, Use multiprocessing to determine a resonance curve &gt; # Use multiprocessing to plot a resonance curve for a single value of beta _ damp # (0.1). import multiprocessing # import library to allow for multiprocessing from multiprocessing import Pool # import file containing the solver (look inside # resonance _ for _ multi _ processing _ solvers.py) import resonance _ for _ multi _ processing _ solvers import time # import library that will allow us to time the process num _ processors = multiprocessing.cpu _ count() print("This computer has {:} processors".format(num _ processors)) n _ cores = int(np.ceil(num _ processors/2)) # Use half the available cores, # rounded up print("This program will use {:} processors".format(n _ cores)) # All parameters for solve _ ivp() except for the driving frequency (the variable # to be looped over) and beta _ damp are placed in the external file and can # be global or contained within solver(). We need these parameters here to # set up the range of omega _ arr driving frequencies mass = 2.0 # [kg] mass k _ spring = 8.0 # [N/m] spring constant omega0 = np.sqrt(k _ spring/mass) omega _ arr = np.linspace(0.90 * omega0, 1.10 * omega0, 201) # an array that samples # every 0.002 in # frequency beta _ damp = 0.1 # Define a list of tuples, each one containing a value for beta _ damp and omega ### DO THIS. Make sure you understand the syntax of the line below. Then print # the variable seq so that you can see the list of tuples. seq = [(beta _ damp, omega _ item) for omega _ item in omega _ arr] time _ start = time.time() # initial time if __ name __ == ' __ main __ ': # Ask the computer to select a number of processers p = Pool(processes = n _ cores) # Tell the pool of processors, p, to call the function solver in # resonance _ for _ multi _ processing _ solvers.py for each (beta _ damp, omega) # tuple contained in the list, seq. output = p.map(resonance _ for _ multi _ processing _ solvers.solver, seq) run _ time = time.time() -time _ start print("Done! Calculation took {:3.2f} seconds".format(run _ time)) # &lt; Challenge problem 1, Plots &gt; ### EXAMINE the returned value from # p.map(resonance _ for _ multi _ processing _ solvers.solver, seq), "output". # Then plot the amplitude versus omega.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.9.2">Challenge Problem 2: Effect of &#946; on the resonance curve</head><p>Plot on the same axes a series of five resonance curves in which you vary &#946; from &#946; = 0.16 (heavy damping) all the way down to &#946; = 0.01, with a factor of two difference in &#946; for each curve. Use the usual parameters, with a fixed driver of force amplitude F 0 = 0.2, so illustrating the effect of damping on the width of the resonance. Rather than manually running your |A(&#969;)| solution five times separately, make an outer for loop that changes &#946; and an inner loop that changes &#969;. In computer science terminology, you'll be making a nested for loop structure. Each time you get a resonance curve, plot it (so the plot command should be placed just outside of the inner omega loop). The easiest way to do the nesting is to copy your working numerical resonance curve from above into a new outer loop that iterates over a &#946; array. Make sure you indent the nested for loops properly.</p><p>Time-saving idea: Instead of running each simulation on the same time array, adjust the total time for each integration to be 6&#964; , allowing the transient to die off. (So you'll be redefining the time array within the outer loop, just after you set the latest value of &#946;.) Look for the maximum value of |A(&#969;)| after 5&#964; has elapsed, i.e., from 5/6 of the way through the x(t) array, all the way to the end. Then each doubling of &#946; ought to run twice as fast! </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="14.9.3">Challenge Problem 3: Multiprocessing for multiple resonance curves</head><p>Use multiprocessing computing to speed up your calculations for Challenge Problem 2. To do this, you will follow the same steps as in Challenge Problem 1, but use different values of &#946;.</p><p>There is one difficulty to this Challenge Problem. We want to run over the omega _ arr at each value of beta _ damp so each time the solver starts with a different value of (beta _ damp, omega _ item), and then we keep track of the results. The trick is how to construct the list of tuples.</p><p>Here is our suggested solution. First construct a sequence of pairs (0.01, 1.950), (0.01, 1.952), and so on, by using the generator expression equivalent to a nested loop:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>seq = [(x, y) for x in A for y in B]</head><p>where x is beta _ damp from the array beta _ arr and y is omega _ item from the array omega _ arr. Now pass each tuple (containing pairs of beta _ damp, omega _ item) into solver(). Inside solver(), the code unpacks the tuple via beta _ damp, omega _ item = params.</p><p>Note that you cannot map two independent values onto the function, because map will draw the first element from beta_arr and pair it with the first element from omega_arr, then the second element from beta_arr with the second element of omega_arr, all the way up to the fifth element in beta_arr, whereupon it quits. Not what we want. Turning them into paired tuples ensures that we get the desired parameters mapped together onto the solver function.</p><p>Your call to p.map will return a list of amplitudes the same length as seq. To create plots of A 2 versus &#969;, you will need to first convert that list into an array and then use numpy.reshape to reshape it into a two-dimensional array where each row corresponds to a different value of &#946;. A for loop can then be used to plot each set of amplitudes-squared versus &#969;.</p><p>&#10148; Hint: You will have to send reshape() a tuple indicating the dimensions of the new array, e.g., (n_row, n_col). If you don't know the value for one of those parameters, though, you can use "-1" for it and the computer will automatically determine the correct size. T he solution of the brachistochrone problem (Example 6.2 from Classical Mechanics) pro- vides the shortest time path between two points for an object sliding down a hill. It is neither a straight path nor a circular path but rather, one connected by a "cycloid" of unique generating radius between the two points. A cycloid is the curve traced out by a point on the rim of a wheel of radius a rolling horizontally (Figure <ref type="figure">6</ref>.5 from Classical Mechanics). The cycloid itself also has the interesting property that objects released from different initial positions along the curve will slide to the bottom in the same amount of time, i.e., they are "isochronous". This unit will guide you through a more in-depth examination of cycloids. It culminates in an animation comparing the motion of an object sliding down a cycloidal path to one following a straight-line path.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="15.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; examine a set of cycloids with different generating radii (PHY); &#8226; find the radius of the cycloid that connects two arbitary points with the shortest time path using numerical root finding (PRO);</p><p>&#8226; numerically calculate the time for an object to slide between two points on a cycloid (PHY);</p><p>&#8226; compare the motion of an object on a cycloidal and straight-line path (PHY);</p><p>&#8226; use an animation to observe the motion along a cycloidal path (PRO). radius, then plot the curve. This problem follows naturally from Problem 6.14 c of Classical Mechanics.</p><p>To find the angle, you should employ the root-finding algorithm, scipy.optimize.root, introduced in Oscillations Around Stable Equilibria.</p><p>&#8226; Remember, to use scipy.optimize.root(), you must first define a function describing the equation for which you wish to find the root. In this case, use your set of equations for x and y to define a new equation that will equal zero for the correct value of &#952;. A template of this function is given below. To make it general, the function takes the values of x and y of the intersecting point.</p><p>&#8226; scipy.optimize.root() requires you to provide an initial guess at the value of the root. This guess needs to be somewhat close, but away from 0, the other root. You may find it helpful to look at the graph above to determine a value.</p><p>&#8226; The syntax to use scipy.optimize.root() will look like root _ soln = root(func, guess, args = (x, y)), where "func" is the name of the function, "guess" is the initial guess, and "args" allows you to send additional arguments to func().</p><p>&#8226; The solution will be stored in root _ soln.x[0]</p><p>#/ &lt; Derivation of the angle equation to be solved, Instructor solution &gt;</p><p>will yield a concave-up cycloid (where the y-value has been multiplied by -1). Dividing these two gives (a is assumed to be non-zero):</p><p>Rearrange this into a transcendental function for which we seek the zeros of the left side:</p><p>Substituting (x, y) = (6, -2) gives:</p><p>After finding the unique (non-zero) angle &#952;, substitute into either the x or y equation to get the unique radius a.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>#/</head><p>In this example of (x, y) = (6, -2), you should find &#952; = 4.0516 radians, and a = 1.2394 m.</p><p># &lt; Exercise 3, Using root to find the cycloid that passes through (0, 0) and # (6, -2) &gt; # Find the radius of the unique cycloid that passes through (0, 0) and (6, -2).</p><p># Call it a _ radius _ 62, with the corresponding theta _ 62, when it intersects # the point (6, -2). Print your results to 5 significant figures. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="15.3">Cycloidal Path Length</head><p>There are a few properties of cycloids that are worth examining before we analyze the motion in time. For instance, the next two exercises will show you how to find the total length of the path as &#952; goes from 0 to 2&#960;.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>You might think of this problem as two-dimensional, but unlike for projectile motion, there is a spatially varying acceleration in both x and y components, subject to the constraint that they follow the cycloidal path. We would like to keep it as a one-dimensional problem, and we have a choice of integrating the s acceleration or &#952; acceleration. Here we keep everything in &#952; coordinates, for simplicity of interpreting the result. It does mean, however, that you will need to convert the familiar g sin(&#945;) linear acceleration for a ball on a plane inclined at angle &#945; to &#952;, the (parametric) angular acceleration. Edit the deriv() function below to be used by solve _ ivp() following these guidelines. First, find the linear acceleration along s (tangent to the curve) simply by 1. analytically finding the local slope, dy/dx, in terms of &#952; 2. taking the tangent to get the angle &#945; above horizontal 3. then s = g sin(&#945;)</p><p>Next we need to convert this linear acceleration d 2 s dt 2 into parametric angular acceleration</p><p>dt 2 , beginning with the defining equations for the cycloid:</p><p>x = a(&#952;sin &#952; ) (15.1)</p><p>and the definition of the path length 2. Differentiate with respect to time:</p><p>Notice that d 2 &#952; dt 2 is now given in terms of d 2 s dt 2 (the local linear acceleration, which you calculated above), and d&#952; dt . Use this result for d 2 &#952; dt 2 and the local linear acceleration to update the angular acceleration within the deriv() function. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8</head><p>Now, use solve _ ivp() to calculate the motion of an object under the influence of gravity along a cycloidal path with radius a = 1 m. Assume the object starts from rest at &#952; = 0.001 and integrate your solution from 0 to 3 seconds. In your call to solve _ ivp(), let max _ step = 0.001.</p><p>Plot the x and y positions as a function of time on the same set of axes. Then plot y versus x to verify that the path is that of a cycloid. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 8, Numerical calculation for cycloidal path, plots &gt; ### PLOT x(t) and y(t) on the same axes ### PLOT y vs. x for your numerical solution</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9</head><p>How well does your numerical integration do at calculating the time? Use your analytical calculation from Exercise 6 to determine the travel time to final angle theta = &#960;. Then compare this with the time at which the object in your numerical solution reaches &#960;.</p><p>&#10148; Hint 1: numpy.where (first introduced in Simple Pendulum with Large-Angle Release) will be helpful for finding when the x-position crosses a certain threshold. Remember that given an array arr, the indices of all the elements greater than or equal to a fixed value is given by np.where(arr &gt;= value)[0].</p><p>&#10148; Hint 2: Can you increase the accuracy by either decreasing max _ step or atol and rtol in your call to solve _ ivp() above?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 9, Compare the analytical and numerical travel time &gt; ### CALCULATE the travel time analytically ### FIND the numerical travel time</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>How long does it take the object to reach the bottom if it is started from a different initial position? Experiment with recalculating the numerical solution and travel time using a different initial value of &#952;.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>What do you observe about the travel time?</head><p>down the cycloid, you will have to calculate the position (x and y) of the object sliding down the straight-line path for each time step from your cycloid path solution (r _ soln.t). To do this, remember that you can calculate the acceleration from the slope and the distance traveled from a kinematic equation. Then convert from that distance to an x and y position using trigonometry.</p><p>After you have made the appropriate adjustments to the cell below, plot the trajectories for both paths. This step is to verify your trajectories. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>### ADD code to plot y _ soln vs. x _ soln and y2 _ soln vs. x2 _ soln</head><p>You've plotted the trajectories, but to animate them you will need to show how the positions of the objects on them change with time. That means you will need to plot some representation of the mass at a particular position corresponding to the time step.</p><p>We will use filled circles to represent the two masses. These can be drawn using matplotlib.patches.Circle(). This function must be provided the coordinates of the center of the circle. It may also be passed the radius of the circle, along with standard plotting arguments such as color and linestyle. For example, ball = patches.Circle((0,0), ball _ radius, fc ='red', ec = 'DarkSlateGray', lw = 2 , zorder = 3) from the code below creates a circle of radius equal to the variable ball _ radius at the origin point. The face color (fc) of the circle is red, the edgecolor (ec) is a dark gray, the width of the line outlining it (lw) is "2", and it is placed in front of other plot elements, such as lines, by setting zorder = 3.</p><p>Once the patch has been created, it must be placed on the figure using add _ patch(). For example, ax.add _ patch(ball) places the circle patch object on the ax object.</p><p>The patch can be moved by using set _ center. For example, # Create a circle representing the mass on the cycloid path ball = patches.Circle((0,0), ball _ radius, fc ='red', ec = 'DarkSlateGray', lw = 2 , zorder = 3) # "zorder = 3" ensures that it will # be plotted on top # Create a circle representing the mass on the straight path ball2 = patches.Circle((0,0), ball _ radius, fc ='DodgerBlue', ec = 'DarkSlateGray', lw = 2 , zorder = 3) # Add the circles to the plot ax.add _ patch(ball) Exercise 14*</p><p>You now have all the components you need for your animation, so it is just a matter of putting it together. The function to create an animation is animation.FuncAnimation() from matplotlib. This function creates an animation (a series of figures to be shown in a series to make a movie) by repeatedly calling a given function. The required arguments are the figure object (fig) and the function that will be called for each frame (animate). In this case, we also pass it the name of a function to be called once before the generation of the frames using init _ func = init, the number of frames to be used (frames), and the time interval between the frames in milliseconds (interval): FuncAnimation will first call the init _ func function. In this case, this function initializes the patches representing the masses. It then repeatedly calls the animate() function, passing it a variable representing the frame number. Once it reaches the number of frames indicated by the frames parameter, it will start over at zero.</p><p>After the animation is created by this code, it is saved as anim. It can then be displayed as a video by display(HTML(anim.to _ jshtml()))</p><p>Or, if you are using the CoCalc server, it can be displayed by display(HTML(anim.to _ html5 _ video()))</p><p>Fill in the code below to create your final animation. First, add the trajectories to the plot as you did in Exercise 13. Then modify animate() by adding calls to the set _ center() method to place the circles at the appropriate location for the particular frame. For a hint as to how to do this, look at how the time of the frame is displayed in the plot title. 15.5 An Animation 181 ax.set _ xlim(0, 2 * np.pi * a _ radius) ax.set _ ylim(2 * np.pi * a _ radius * (-2/6), 0) ax.set _ xlabel("Distance") ax.set _ ylabel("Height") ax.set _ title("Brachistochrone\n") ### ADD code to plot y _ soln vs. x _ soln and y2 _ soln vs. x2 _ soln # Create the patches representing the masses ball _ radius = 0.05 # Plot size of the circle representing the mass # Create a circle representing the mass on the cycloid path ball = patches.Circle((0,0), ball _ radius, fc ='red', ec = 'DarkSlateGray', lw = 2 , zorder = 3) # "zorder = 3" ensures that it will # be plotted on top # Create a circle representing the mass on the straight path ball2 = patches.Circle((0,0), ball _ radius, fc ='DodgerBlue', ec = 'DarkSlateGray', lw = 2 , zorder = 3) def init(): ax.add _ patch(ball) ax.add _ patch(ball2) return ball, ball2 # In order to produce a more manageable animation, only use every 20th point mod _ skip = 20 num _ frames = int(len(x _ soln)/mod _ skip) # number of frames is the floor of the # number of integrator time steps # divided by mod _ skip def animate(i): ax.set _ title("a = {:5.4f} cycloid and straight path\ntime = {:3.2f} s". format(a _ radius _ 62, r _ soln.t[i * mod _ skip])) # Multiplying i # by mod _ skip # ensures that # only every # 20th step is # shown ### USE set _ center() to place ball and ball2 at the appropriate locations # for the frame return ball, ball2 ### UNCOMMENT the following two lines of code to see the animation. #anim = animation.FuncAnimation(fig, animate, init _ func = init, # frames = num _ frames, interval = 50, blit = True) #display(HTML(anim.to _ jshtml())) # Display as JSHTML ### IF using CoCalc, comment out the above line and comment in the following # two #plt.rcParams['animation.html'] = 'html5' #HTML(anim.to _ html5 _ video())</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="15.6">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 15</head><p>Briefly summarize the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="15.7">Challenge Problem</head><p>If you have extra time, consider the following question:</p><p>&#8226; How far does the mass travel from (0,0) to (6, -2) along the cycloidal path? It is probably easiest to calculate this by computing a numerical integral via a Riemann summation.</p><p>How does this distance compare to the straight-line distance?</p><p>I n this unit, you will consider a spherical pendulum, as described in Problem 7.40 of Classical Mechanics. You will use Lagrangian mechanics to determine the equations of motion, analyze the equations of motion to approximate the trajectories under specific circumstances, and then calculate and compare the numerical solutions to the analytical approximations.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="16.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; use Lagrangian mechanics to determine the equations of motion for a spherical pendulum (PHY);</p><p>&#8226; numerically determine the trajectory for two special cases (PHY);</p><p>&#8226; show that the generalized momentum in &#966; is conserved (PHY);</p><p>&#8226; learn how to make polar plots (PRO);</p><p>&#8226; explore the motion of the spherical pendulum with different initial velocities in &#966; (PHY). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="16.2">Equations of Motion for a Spherical Pendulum</head><p>When a pendulum is not confined to a single vertical plane but free to move in both &#966; (angle around the vertical axes) and &#952; (angle from the vertical axes), it is referred to as a spherical pendulum. Here, you will analyze a spherical pendulum with a range of different release angular velocities. If you have not already completed Problem 7.40 from Classical Mechanics, work through the following exercise. Otherwise, you may begin at Exercise 2.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>On a separate sheet of paper, write down the Lagrangian for a spherical pendulum using generalized coordinates &#952; and &#966;. Then, determine the equations of motion for &#952; and &#966;.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Use the equations of motion determined from Exercise 1 to write a deriv() function that returns the derivatives of &#966;, &#966;, &#952;, and &#952;. """ System is a spherical pendulum.</p><p>Supplies the derivatives of phi, theta, dphi/dt, and dtheta/dt to be used by the differential equation integrator, solve _ ivp.</p><p>The optional parameter, length, is the length of the pendulum.</p><p>It is set to a default value of 1 for the system. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="16.3">Validating Your Code with Special Cases</head><p>Comparing analytical and numerical results for "special cases" is an excellent method for testing your code. Analyzing special cases is also a good way to build a strong understanding of a system before increasing its complexity.</p><p>The following four exercises have you numerically solve for the motion of a spherical pendulum under two special cases (planar motion and circular motion).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3*</head><p>Using solve _ ivp() along with the deriv() function defined above, determine &#966;(t) and &#952; (t) from 0 to 10 seconds. Assume that the pendulum has a length of 1 m, mass of 1 kg, and is released from rest with &#952; (t) = 0.2 and &#966;(t) = 0. This set of initial conditions should result in a "planar" pendulum, i.e., one that never has any velocity in &#966; (see Problem 7.40 c from Classical Mechanics). A reasonable maximum step size might be 0.01 s. Validate your solution by plotting &#952; (t) versus t along with the analytical approximation for a simple pendulum of the same length oscillating between small angles. Verify as well that &#966;(t) remains equal to zero.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 4, Computational solution for theta(t) and phi(t) &gt; ### PLOT the theta vs. time for the computational solution ### PLOT theta vs. time according to analytic approx ### VERIFY that phi remains zero</head><p>Exercise 5* Create a plot of the trajectory projected onto the z = 0 plane by using a polar plot and plotting &#961; (distance from the vertical axis) versus &#966;.</p><p>To make a plot with a polar projection, either declare the figure and then the axis, like this: or declare the figure and axis together, like this (sending the keyword arguments as a dictionary to subplot using subplot _ kw as the keyword argument for subplots): In your plot command, the first parameter in ax.plot is &#966; and the second parameter is the distance from the origin (in your case, &#961;). A second special case for the spherical pendulum is when the pendulum rotates in a circle around the vertical. In this case, both &#952; and &#966; remain constant. Determine an expression for the angular velocity in &#966; that will result in this motion for a particular value of &#952; 0 (see Problem 7.40 d of Classical Mechanics).</p><p>Calculate this value of &#966;0 for &#952; 0 = 0.6 radians.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>&#966;0 = # &lt; Exercise 6, Computational solution for theta(t) and phi(t) &gt; # INCLUDE your calculations in this cell</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>Numerically solve for &#966;(t) and &#952; (t), assuming that the initial value of &#952; is 0.6 and that the initial velocity in &#966; is such as to yield a circular trajectory, as defined above. Set your time span to allow for several complete circles.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="16.4">Varying Initial Velocity 187</head><p>Plot &#966; and &#952; versus t and a polar plot showing the motion projected onto the z = 0 plane following the instructions from Exercise 5.</p><p>&#10148; Hint 1: How could you calculate how long it will take for the pendulum to circle once? &#10148; Hint 2: To set the radial extent of your polar plot, you can use ax.set _ rmax(radial _ extent),</p><p>where radial _ extent is a variable defined by the user. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="16.4">Varying Initial Velocity</head><p>Exercise 8</p><p>Now explore the behavior of the spherical pendulum as the initial velocity in &#966; changes. Let &#952; (t = 0) = 0.6, &#952; (t = 0) = 0, and &#966;(t = 0) = 0, while &#966;(t = 0) ranges from 0 to the value resulting in the circular trajectory. Use solve _ ivp() to find the numerical solution and create the same plots as in Exercise 7. You will likely want to increase the integration time to see the trajectory. Describe your qualitative findings here.</p><p>Retain your initial conditions, solution, and plots for one of your favorite cases below.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 8, Varying the initial velocity in phi &gt;</head><p>### USE this space for your calculations and plots</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="16.5">Conservation of Generalized Momentum</head><p>Exercise 9</p><p>When determining your equations of motion in Exercise 1, you may have noticed that the generalized momentum in &#966; is conserved. Explicitly show this on a separate sheet of paper now. This quantity is the z-component of the angular momentum, &#8467; z .</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>As a final test of your code, check that &#8467; z is conserved for the case you used in Exercise 8. To do this, calculate the initial, minimum, and maximum values of &#8467; z . They should all be very similar.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 10, Conservation of l _ z &gt; ### CALCULATE the initial, minimum, and maximum values of l _ z</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="16.6">Near-Conical Motion</head><p>Exercise 11* Pick a value of &#966;(t = 0) that is close to the value that produces a circular trajectory at &#952; = 0.6. Numerically calculate and plot the trajectory, as in Exercises 6 and 7 and include your plots below. What do you notice about the trajectory, especially &#952; (t)? Describe it here.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 11, An initial velocity in phi resulting in # close to conical motion &gt; ### USE this space for your calculations and plots</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="16.7">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12</head><p>Briefly summarize the ideas in this unit.</p><p>16.8 Challenge Problem 189</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="16.8">Challenge Problem</head><p>To explore the near-conical motion of the pendulum further, complete the following two challenge problems.</p><p>&#8226; You should have noticed in Exercise 11 that &#966;(t) resembles simple harmonic motion centered on some non-zero angle. Another way to consider this situation is to let &#966;0 be the angular velocity in &#966; that produces conical motion for a particular value of &#952; 0 . Analytically show that if &#966;(t = 0) = &#966;0 , &#952; (t = 0) = 0, and &#952; = &#952; 0 + &#1013; where &#1013; is small, then &#952; oscillates about &#952; 0 in harmonic motion (Problem 7.40 d from Classical Mechanics).</p><p>&#8226; Using &#952; (t = 0) = 0.6 + &#1013;, &#952; = 0, and the value of &#966;0 from Exercise 5, numerically calculate the trajectory of the pendulum and plot &#952; (t), &#966;(t), and the motion projected onto the z = 0 plane. On top of your plot of &#952; (t), show your solution from the previous challenge problem for these particular initial conditions.</p><p>&#10148; Hint: To determine the correct frequency and amplitude of oscillations for your analytical prediction, you will have to be careful about how you set the initial conditions. In particular, your initial value for &#966; will not equal the value of &#966; that produces conical motion for &#952; = 0.6, because your initial value of &#952; = 0.6 + &#1013;. To determine the correct value of &#966;(t = 0), recognize that &#8467; z is conserved, so increasing &#952; away from &#952; 0 will necessarily reduce &#966;. Calculate &#8467; z for the case where &#952; = 0.6 and &#966; produces conical motion. Then, use this calculated value of &#8467; z to determine the appropriate value of &#966; at &#952; = 0.6 + &#1013;.</p><p>I n this unit, you will examine a problem that cannot generally be solved analytically, the three-body problem. Chapter 8 of Classical Mechanics covers the "Two-Body" problem, two massive objects moving within their mutually described Newtonian gravitational potential. These masses will undergo orbits shaped like conic sections -i.e., circles, ellipses, parabolas, and hyperbolas. But what are the shapes of orbits in a three-body system? The answer here is not straightforward and requires a computer to solve precisely, which is why a system of three bodies interacting gravitationally is one of the classic examples of chaos.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; create an integrator to find the motion of two-and three-body gravitational systems (PHY);</p><p>&#8226; use arrays to ensure code can flexibly handle different dimensionalities (PRO);</p><p>&#8226; test your integrator by applying it to stable situations where you can predict the orbit (PHY);</p><p>&#8226; transform the trajectories to the center-of-mass frame using two-dimensional arrays (PRO);</p><p>&#8226; create a movie of the motion (PRO);</p><p>&#8226; plot the trajectories in three dimensions (PHY);</p><p>&#8226; and observe chaotic three-body trajectories (PHY). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.2">Solving the Two-Body Problem Numerically for a Circular Orbit</head><p>Let us ease our way into the more complex three-body problem by first generating the orbital trajectory for a two-body system undergoing a circular orbit. Start with a large mass (say, a star) with M 0 = 2000 and a smaller mass (say, a planet) with M 1 = 1. We will work in a unit system where G = 1.0. We will confine the motion of both bodies to the x-y plane for now. Later, if desired, the solution can be expanded into three-dimensional space.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>Determine the appropriate initial conditions such that the lower mass body, M 1 , orbits in a circle 100 distance units away from the larger body, M 0 . Determine, as well, a time span that will encompass at least two orbits.</p><p>&#10148; Coding style tip: Rather than doing any necessary calculations to find the initial velocity of M 1 elsewhere (say, a piece of scratch paper), include those calculations in the code cell. That way you will have a record of what you did and it will be easy to reproduce the calculations for different values, for instance, a larger radius circle. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>In the following three cells follow the prompts to fill in the appropriate initial conditions, the integration call, and plots of your output. Choose initial conditions such that the lower mass body, M 1 , orbits in a circle 100 distance units away from the larger body, M 0 . Integrate the trajectory for at least two orbits with maximum time steps of 0.1.</p><p>In creating the rules for your integrator, it may help to think of the gravitational force on M 0 by M 1 as:</p><p>where &#8407; r is the relative position (i.e., the position of M 0 minus the position of M 1 or &#8407; r = &#8407; r 0 -&#8407; r 1 ) and &#1013; (a.k.a. the gravitational softening) is a small number (0.1 is a good value) that will ensure that the masses never experience any extremely close encounters. (However, in calculating your initial conditions, you should not include &#1013;.)</p><p>Create plots of the trajectories (y vs. x) of both objects on the same set of axes. To ensure a square plot, you can set the size of both the x and y axis to the same value when calling your plt.subplots command: # &lt; Exercise 2, 2-Body Deriv Function &gt; def deriv _ 2body(t _ now, r _ now, m _ arr = np.array([1, 1]), epsilon = 0): """ Derivative function for two gravitationally interacting masses. r _ now is an array ordered (pos _ 1, vel _ 1, pos _ 2, vel _ 2) where the position and velocities may be in 2 or 3 dimensions. The optional parameter m _ arr contains the masses, while epsilon is the gravitational softening""" # Determine if this is a two-or three-dimensional problem if np.size(r _ now) == 8: dimension = 2 elif np.size(r _ now) == 12: dimension = 3 else: return r _ now * 0 # If the positions are not in 2 or 3 dimensions, # return a derivative array just filled with zeros # Unpack the positions and velocities of the two masses from r _ now pos _ 0 = r _ now[ 0 : dimension] # Position of mass 1 vel _ 0 = r _ now[ dimension : 2 * dimension] # Velocity of mass 1 pos _ 1 = r _ now[2 * dimension : 3 * dimension] # Position of mass 2 vel _ 1 = r _ now[3 * dimension : ] # Velocity of mass 2 # Derivatives of the positions of the two masses dpos _ dt _ 0 = 0 ### FIX </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.3">Numerical Integration of the Three-Body Problem</head><p>Now you will extend your integrator to solve simultaneously for the orbits of three gravitationally interacting bodies.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.3.1">Two planets orbiting a central star</head><p>As you build your integrator, you will want to run it on a solvable problem so you can tell whether it is working correctly. Therefore, the first case you will look at is that of two planets orbiting a central star. The star (M 0 = 2000) is initially located at the origin. The two planets (M 1 = M 2 = 1) should be set up to undergo circular (or approximately circular) orbits with radii of 100 and 200, respectively.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Define your initial conditions for two planets orbiting a star in the cell below. Once again, set the time span to allow for the outer planet to complete at least two orbits. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>Follow the steps outlined below to build your three-body integrator. For now, continue to confine all motion to the x-y plane. Most of your calculations will look the same as for a two-body system. However, this time you will have to consider the gravitational interactions between all three pairs of bodies.</p><p>&#8226; Paste your two-body integration from above (deriv _ 2body _ 2D) into the cell below.</p><p>&#8226; Rename it as deriv _ 3body _ 2D.</p><p>&#8226; Update it to provide the correct position and velocity vector of a third body of mass M 2 , as well.</p><p>&#10148; Jupyter Notebook Tip: Oftentimes in coding, you will want to make the same edits to multiple lines of code. You can make these edits simultaneously in Jupyter! This is called "multicursor editing", and to enable it all you have to do is hold down the Ctrl key (Command on a Mac) while using your mouse to select the locations at which you want to make edits. Then just start typing. Give it a try below.</p><p>&#10148; Note: There are clever ways one can use arrays and loops in the deriv() function to limit the amount of written code (making it more elegant) and allow it to be generalized to more masses. For this unit, however, it is sufficient to explicitly calculate the acceleration of each of the three masses individually.</p><p># &lt; Exercise 4, Two planets orbiting a star, Deriv function &gt; ### COPY deriv _ 2body to here ### EDIT it to work for three bodies</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>Use solve _ ivp to find the solution for the system of two planets orbiting a star. In preparation for making a movie later on, set the optional parameter, t _ eval, so that the trajectory at regularly spaced time steps is returned. As in Exercise 2, plot the trajectories (y vs. x) of all objects on the same set of axes. Also create plots of x vs. t and y vs. t for the planets (M 1 and M 2 ).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 5, Two planets orbiting a star, Integration &gt; ### USE solve _ ivp to integrate for the solution # &lt; Exercise 5, Two planets orbiting a star, Plots &gt; ### PLOT the trajectory for both masses ### PLOT x and y vs. t for m1</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.3.2">Mystery system Exercise 6</head><p>Apply your new three-body integrator to a more physically interesting problem. Try running your integrator again with the following initial conditions:</p><p>Integrate the equations of motion for a long enough period of time to see at least ten orbits of each object. As in Exercise 5, use t _ eval to ensure solve _ ivp returns the solutions at regularly spaced time steps.</p><p>Plot the trajectories of all three objects on the same set of axes. For your second plot, show x(t) and y(t) for all objects. Then make an additional plot just showing x(t) and y(t) for M 0 and M 1 with the x-axis range set to be small enough that you can see their motion.</p><p>Write a description of the physical system these initial conditions correspond to.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 6, Mystery System, setting ICs &gt; ### SET masses, initial conditions, and t _ span # &lt; Exercise 6, Mystery System, Integration &gt; ### USE solve _ ivp to find the solution # &lt; Exercise 6, Mystery System, Plots &gt; ### PLOT the trajectory for all masses ### PLOT x and y vs. t for all masses ### PLOT x and y vs. t for m0 and m1</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.3.3">Plotting in the center-of-mass frame of reference</head><p>You may have noticed an interesting precession effect. While we start the three masses in orbits that should take place (approximately) centered on the origin, their mutual gravitational interactions slowly carry them off in one direction over time -the exact direction will depend on exactly how you chose your initial conditions. Essentially, because of the initial conditions, the system has some non-zero momentum, which causes the center of mass to move. If this effect was not clear to you earlier, take another look at your data and plots from Exercise 6 to convince yourself it is happening.</p><p>We can make our plots look much nicer by transforming to a coordinate system where the center of mass remains fixed at the origin.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>Define two new arrays, x _ com and y _ com, to track the location of the center of mass of the three-body system, and subtract these coordinates from the positions of the masses to find their locations in the center-of-mass frame. Plot 1) the trajectories of the three masses on the same set of axes and 2) just the trajectories of M 0 and M 1 for a shorter length of time so that you can see their orbits. If you have done your calculations correctly, you should find that the precession effect has disappeared.</p><p>The best way to accomplish this task is through a clever use of NumPy arrays. Your first goal will be to create arrays, x _ com and y _ com, each with a length equal to the number of time steps (r _ soln.t). Here is some guidance on how to do this:</p><p>&#8226; Define a two-dimensional array x _ arr of three columns (one for each mass) and the number of rows equal to the number of time steps. You may use numpy.column_stack to make this array, or simply use np.array() to create a new array by combining the x-positions for each of the masses and then numpy.transpose to exchange the columns and rows of your new x _ arr array. You can check that you have the right configuration by printing the shape of your x _ arr array (see numpy.shape; the number rows are listed first, then columns).</p><p>&#8226; Your desired x _ com can be found by using matrix multiplication to multiply x _ arr by your previously defined array of masses, m _ arr (a one-dimensional array, three-element array), and dividing the resulting array by the sum of the masses. This step is equivalent to finding 3 i=1 m i x i 3 i=1 m i at each time step. The command you will need for matrix multiplication is numpy.matmul, and the command for summing all elements in an array is numpy.sum.</p><p>&#8226; Finally, repeat the above two steps to find the y-coordinate of the center of mass. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.4">Making a Movie</head><p>Exercise 8*</p><p>Of course, one of the most enjoyable ways to visualize the motion of the three masses is with a movie. As in Brachistochrone Problem, most of the code to create the movie has already been provided for you. However, you are asked to provide the arrays of the x and y coordinates in the center of mass frame for each of the masses, and you will have to add two of the masses to the plot. Follow the prompts to fill in the code below to create a movie of the orbital motion. More instruction on animation can be found in Appendix 3: Creating Animations with FuncAnimation.</p><p>Once again, you will use animation.FuncAnimation() from matplotlib to create the animation. This function generates the series of figures (a.k.a. frames) that will constitute the animation by repeatedly calling a given function. The required arguments are the figure object (fig) and the function that will be called for each frame (animate). In this case, we also pass it the number of frames to be used (frames) and the time interval between the frames in milliseconds (interval): The blit keyword tells the animator not to redraw unchanged elements. The init _ func keyword provides the name of the function that draws the base frame. This is necessary for the blitting algorithm to know what elements change.</p><p>The function argument "animate" then repeatedly calls the animate() function, passing it a variable representing the frame number. Once it reaches the number of frames indicated by the frames parameter, it will start over at zero.</p><p>After the animation is created by this code, it is saved as anim. It can then be displayed as a video by display(HTML(anim.to _ jshtml()))</p><p>(or HTML(anim.to _ html5 _ video()), if you are using the CoCalc server).</p><p>The two stars and the planet are represented by "artists" 1 defined using ax.plot(): After these artists have been defined, their positions can be changed using set _ data(). For example,</p><p>places the m0 _ position artist (a green star outlined in black) at location (x0, y0) (technically, it plots the star twice at that location). These position changes should happen inside the animate() function, so that each frame shows the position of each object at the corresponding time. An example has been shown for M 0 . Make the appropriate changes in the code to add M 1 and M 2 . 1 artists are objects that are rendered on the axes, such as lines or patches. 200 The Three-Body Problem ### FILL in expressions for the coordinates of each object in the center of # mass frame # m0 x0 = np.empty(len(r _ soln.t)) y0 = np.empty(len(r _ soln.t)) # m1 x1 = np.empty(len(r _ soln.t)) y1 = np.empty(len(r _ soln.t)) # m2 x2 = np.empty(len(r _ soln.t)) y2 = np.empty(len(r _ soln.t)) # Plot orbital trajectories in the x-y plane ax.plot(x0, y0, color = 'g') ax.plot(x1, y1, color = 'r') ax.plot(x2, y2, color = 'b') # In order to produce a more manageable animation, only use every 20th point mod _ skip = 2 # Number of frames to include max _ frames = int(len(x0)/mod _ skip) # Max # of possible frames is the floor of # the number of integrator time steps # divided by mod _ skip num _ frames = min([max _ frames, 600]) # at 50 milliseconds a frame (the interval # in the FuncAnimation call), 600 frames # -&gt;30 s, which is sufficient for a good # animation # Set the number of frames to either 600 # or max _ frames, whichever is smaller m0 _ position, = ax.plot([],[], 'g * ', ms = 10, markeredgecolor = 'k') m1 _ position, = ax.plot([],[], 'r * ', ms = 10, markeredgecolor = 'k') m2 _ position, = ax.plot([],[], 'bo', ms = 6, markeredgecolor = 'k') # Function to draw the base frame def init(): </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.5">Three Dimensions</head><p>Finally, extend your code to work in three dimensions. If you followed the provided template for the deriv() function correctly, it should extend to three dimensions without any changes needed. That is because the array manipulations you used (e.g., subtraction and np.linalg.norm()) are already defined for arrays of any length. What you will need to change are the initial conditions you provide and how you access the different coordinates from r _ soln.y. You will also need to calculate the z-coordinate of the center of mass.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9*</head><p>Set up the initial conditions and solve the mystery system from Exercise 6 with the following adjustment: &#8407; v 2 = (0, 0, &#8730; 200), i.e., M 2 should now orbit in the x-z plane. Calculate the z-coordinate for the center of mass and then plot the trajectories of all three masses in the center of mass frame. For a three-dimensional projection plot, you can use</p><p>Alternatively, the figure and axes can be defined together by sending a dictionary of keyword arguments to subplots using "subplot_kw": (Notice that this is similar to how you set projection = 'polar' in Spherical Pendulum.)</p><p>To plot three-dimensional data on such axes, all you need to do is provide the z-coordinate after the x and y coordinates:</p><p>202 The Three-Body Problem &#10148; Hint: Your plot will look best if you set limits for the x, y, and z axes to all be equal. ### PLOT the trajectories of each of the masses plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10*</head><p>Now that you can calculate and plot the trajectories of three gravitationally interacting bodies, play around with your initial conditions to find some interesting trajectories. For example, can you find a configuration that results in unstable two-planet orbits?</p><p>Include the calculations and a plot of the trajectories for one of your favorite systems below.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 10, Exploration &gt; ### USE this space for setting ICs, calculations, and plots</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.6">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 11</head><p>Briefly summarize in the cell below the ideas from this unit. 17.7 Challenge Problem 203</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="17.7">Challenge Problem</head><p>Lagrange points are special locations where a small test mass will orbit the Sun with exactly the same orbital period as Earth. Look up the location of these points, and generate a movie of the Earth/Sun system with a third low-mass object orbiting at a Lagrange point.</p><p>&#10148; Hint: Some of the Lagrange points are stable and others are unstable. To best see predictable long-term behavior, use one of the stable points.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Lagrange point challenge problem &gt;</head><p>I n this unit, you will determine the numerical solutions to the two-body problem, as covered in Chapter 8 of Classical Mechanics. You will work these exercises in the center-ofmass frame of reference. As one often does in computing, you will first attempt a calculation to which you know the answer. You will then attack a second problem that is more complex and cannot be solved analytically.</p><p>To be precise, you will start out by numerically solving for the two-body problem under Newtonian gravity. Similarly to the process for arriving at an analytical solution, you will reduce the two-body problem to a one-body problem by solving for and plotting the motion of the reduced mass about the fixed center of mass of the system. Then you will switch to plotting the motion of both bodies with a simple coordinate transformation. You will use your solver to approximate the orbit of Pluto and compare your results to data from NASA.</p><p>Finally, you will adapt your code to solve for non-Keplerian orbits that obey different force laws and for which there is not an analytic solution.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; numerically model circular, elliptical, and hyperbolic Keplerian orbits (PHY); &#8226; visualize the motion of the two bodies with multipanel plots, polar projections, and a movie (PRO);</p><p>&#8226; read in a data file of the actual orbit of Pluto (PRO);</p><p>&#8226; compare the two-body numerical calculations to the actual orbit of Pluto (PHY);</p><p>&#8226; and model a non-Keplerian orbit (PHY). Let us initially work in a unit system where G = 1.0. This unit system is frequently used in cosmological simulations as it results in easier analytical calculations.</p><p>Assume the two masses are m 1 = 5 and m 2 = 1 and that they start at a separation of r 0 = 2.0. Then fill in the rest of the constants and initial conditions such that the reduced mass will undergo a circular (Keplerian) orbit of radius 2.0 around the center of mass.</p><p>&#10148; Coding style tip: Rather than doing any necessary calculations elsewhere (say, a piece of scratch paper), include those calculations in the code cell. This way, you have a record of what you did and it will be easy to reproduce the calculations for different values.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 1, Setting ICs and constants &gt; # Initial conditions and constants</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.3">Numerical Solution</head><p>Exercise 2</p><p>First, solve the equations of motion numerically. Define a function that will return the derivatives for your three variables (r, &#7769;, and &#966;). Then, use solve _ ivp() to solve for 0 &#8804; t &#8804; 20. Include mu, gamma _ g, and ang _ mom as parameters sent to solve _ ivp(). In preparation for making a movie later on, set the optional parameter, t _ eval, so that the trajectory at regularly spaced time steps is returned. Using a step size of 0.1 is a good place to start. Additionally, set rtol and atol to 1e-9 in order to achieve appropriate accuracy. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 2, Integration of circular Keplerian orbit, Integration &gt; ### ADD your call to solve _ ivp here</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Plot your results for r, &#7769;, and &#966; as a function of time. Be sure to label your plots and your axes.</p><p>In the past, you have made figures that consisted of a single panel (ax object). The code template below will show you how to use subplots from matplotlib.pyplot to make a figure with multiple panels. The subplots() routine can take as arguments the number of rows and the number of columns of panels: (When these arguments are not included, as you have typically done in previous units, a single panel plot is created.) Each panel will then be stored as one element in the ax array. For example, to plot on the first panel, use # Set a title for the entire set of panels fig.suptitle("Circular Keplerian Orbit\nReduced mass") plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>Make a polar plot to better show the trajectory. Remember, to make a plot with a polar projection, you must set the "projection" keyword argument to "polar". In your plot command, the first input array will then be the angles, and the second input array will be the radial values.</p><p>&#10148; Hint: See Spherical Pendulum for an example of how you have made a polar projection plot in the past. # Alternatively, you may define fig and ax simultaneously, as in the # commented-out line below #fig, ax = plt.subplots(subplot _ kw={'projection': 'polar'}) ### ADD the rest of the commands to plot below plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.4">An Elliptical Orbit</head><p>Exercise 5</p><p>Go through the same set of steps as above in Exercises 1-4 but with a set of parameters that will produce an elliptical orbit with an eccentricity of 0.8 and a pericentric distance (r min ) of 2.0. (You should use the same masses and gravitational constant from Exercise 1.)</p><p>As before, set the parameter t _ eval so that the solutions at evenly spaced time steps are returned. Additionally, set rtol and atol to 1e-9 in order to achieve appropriate precision.</p><p>&#10148; Hint 1: You might need to integrate over a larger time interval than in Exercise 2 to obtain a closed orbit.</p><p>&#10148; Hint 2: Remember, there is no need to copy over your deriv() function again.</p><p>Explain here why each of your plots above has the "expected" behavior. Be as quantitative as possible: </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.4.1">Visualizing two-body orbits</head><p>Visualizing the motion of the reduced mass is physically meaningful, but your plots will be even easier to interpret if you plot both masses together. This can be accomplished by transforming the coordinates of the reduced mass to the coordinates for each of the two masses.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6</head><p>By performing an appropriate coordinate transformation, plot the orbits of both masses on the same set of polar axes.</p><p>&#10148; Hint: The Matplotlib polar plot projection cannot handle negative values for r, so you will have to come up with some other way to represent these values.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 6, Plot trajectory of both masses &gt; ### CALCULATE r and phi coordinates for both masses ### PLOT the trajectory of both masses</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>It is even more revealing to watch the motion of the two objects rather than just plotting their trajectories. To do this, you will need to create an animation. The cells below provide a framework for the process. Note that there are several points where you will need to make edits to calculate the number of frames, draw the orbits, and place the masses in their orbits at each frame. Ideally, your animation will show one complete orbit. Therefore, you will need to determine the period of the orbit, in preparation for making the animation. Once you know the length of the period and you have decided how much time you want each frame to represent, a simple calculation will determine which of the integrator time steps should be used in the movie (e.g., every other? every third? every twentieth?). Add the code to calculate the length of the orbit to the cell below, and then run it to determine how many integrator time steps per movie frame will be used.</p><p>As in Brachistochrone Problem and Three-Body Problem, you will use animation. FuncAnimation() from matplotlib to create the animation. You will pass it a figure object (fig) and the function that will be called for each frame (animate).</p><p>The two masses are represented by "artists", defined using ax.plot(): After these artists have been defined, their positions can be changed using set _ data(). For example,</p><p>places the m1 _ position artist at location (x0, y0) (technically, it plots the star twice at that location). These position changes should happen inside the animate() function, so that each frame shows the position of each object at the corresponding time. More instruction on animation can be found in Appendix 3: Creating Animations with FuncAnimation. # Plot the center of mass as a green star com _ position = ax.plot(0,0, 'g * ', ms = 10) # Initialize plotting of the positions of m1 and m2 for the animation m1 _ position, = ax.plot([],[], 'ro', ms = 6) m2 _ position, = ax.plot([],[], 'bo', ms = 6) # Function used in the FuncAnimation to draw frames def animate(i): # This makes the plot by moving data into line's set _ data method j = int(i * steps _ per _ frame) # only plot every steps _ per _ frame-th step ### ADD the positions of m1 and m2 at frame j using set _ data return m1 _ position, m2 _ position # Function to draw the base frame def init(): </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Describe the motion of the two masses in the animation, including their relative positions and velocities. Is this motion what you would expect? Why or why not?</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.6">Comparing with Pluto Ephemeris</head><p>Because the Sun dominates the mass of the solar system, the orbits of planets and smaller solar system bodies around it may be approximated as solutions to the two-body problem.</p><p>In this section, you will use your orbital solver to calculate the orbit of Pluto if it and the Sun were the only masses in the solar system. You will compare your results to more precise data on the orbit of Pluto from the NASA Jet Propulsion Laboratory.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.6.1">Reading Pluto ephemeris</head><p>The NASA Jet Propulsion Laboratory makes available ephemeris (the term for tables of the location and sometimes velocity of celestial bodies over time) for many of the bodies in the solar system. These data may be downloaded from the NASA Jet Propulsion Laboratory website in their particular format. To simplify the process, though, we have formatted the ephemeris for Pluto (technically, the ephemeris for the center of mass of Pluto and its moons) as a downloadable CSV file (a common spreadsheet format) listing the Cartesian positions and velocities of Pluto over time.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9*</head><p>Start by obtaining a copy of "pluto_ephemeris.csv" and moving it to the same location as this notebook (for example, moving it to the correct directory on your computer or uploading it to CoCalc or your Google Drive server). As you do this, take the opportunity to peek inside the file. Some options for looking at it include opening it in a text file reader, opening it from within Jupyter Notebook or Jupyter-Lab (just click on it within the file browser window), or importing it to Excel. You will find that the data are organized into rows, each containing seven pieces of data separated by commas. Those seven pieces of data are as follows: (1) the Julian date in seconds (defined as seconds since noon Universal Time on January 1, 4713 BC), (2) x-coordinate of position, (3) y-coordinate of position, (4) z-coordinate of position, (5) x-component of velocity, (6) y-component of velocity, and (7) z-component of velocity. All the positions are given in kilometers, and the velocities in kilometers per second. The coordinate system is aligned such that Earth's orbit around the Sun is in the x-y plane and the origin is at the center of mass of the solar system (known as the "Solar System Barycenter"). The first two rows of the file are headers describing the data.</p><p>Read in data from pluto_ephemeris.csv and store it as seven arrays, each corresponding to one of the columns of data using numpy.loadtxt (introduced in Simple Pendulum with Large-Angle Release). Because the data are separated by commas, set the delimiter argument to ',', and because the first two rows of data contain header information, skip reading them using the skiprows argument. Finally, set the unpack argument to True so that the returned data are transposed and arguments may be unpacked using x, y, z = np.loadtxt(...). Altogether, your line of code will look like the following: t _ pluto, x _ pluto, y _ pluto, z _ pluto, vx _ pluto, vy _ pluto, vz _ pluto = \ np.loadtxt('pluto _ ephemeris.csv', delimiter = ',', skiprows = 2, unpack = True)</p><p>Once you have read the data in, calculate the distance between Pluto and the solar system barycenter, r, for each of the times (remember, NumPy makes it easy to do array calculations like this). This array of data is what you will compare against.</p><p>&#10148; Note: If you are using Google Colab, you will have to upload pluto_ephemeris.csv to your Google Drive (we suggest you store it in the subdirectory containing your Colab notebooks). Then before you can access the data, you will need to mount your Google Drive directory by running the following lines of code from google.colab import drive drive.mount('/content/drive')</p><p>Once your Google Drive directory is mounted, you can access it as above with the only difference being that you must provide the full file path ("/content/drive/My Drive/Colab Notebooks/pluto_ephemeris.csv" if you saved the file in the directory called "Colab Notebooks").</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 9, Pluto Ephemeris &gt; ### READ the data file and calculate the distance between Pluto and the # Solar System Barycenter</head><p>Exercise 10* Use your integrator to solve for Pluto's trajectory over the same period of time as the ephemeris.</p><p>To do this calculation, you will need to set the parameters and initial conditions of the system. Look up the mass of Pluto and the Sun, as well as the perihelion and aphelion distances for Pluto. Then use the latter two to calculate the eccentricity, and from that find the angular momentum. Redefine G to be in standard units (e.g., mks), and make sure that all your other units are also consistent. Use the first entry in the data file as the initial conditions for your calculation. Remember that while the data file lists all of the components of velocity in a Cartesian coordinate system, your initial conditions are for the radial component of the velocity.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.7">Non-Keplerian Orbits 215</head><p>&#10148; Hint: Dot products may be useful for finding the radial component.</p><p>To ease your comparison, you should ask solve _ ivp() to generate solutions for the same set of times as in the data file. However, solve _ ivp() assumes the initial conditions correspond to time zero, while the data file starts counting from 208657771210.0 seconds. The easy fix is to set t _ arr = t _ pluto -t _ pluto[0]. You will have to account for this change in making the comparison plots, though.</p><p>Finally, compare your results using the following plots:</p><p>1) r as calculated from the ephemeris and for a two-body problem using solve _ ivp() as a function of time, 2) the difference between the values of r as a function of time, and 3) the percent difference between the values of r as a function of time.</p><p>You should find that your two-body approximation of Pluto's distance from the center of the solar system is accurate to within about 0.6%. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.7">Non-Keplerian Orbits</head><p>A gravitational force law that deviates from 1/r 2 will not produce Keplerian orbits (defined here as orbits that trace the shape of a conic section). It is also impossible to solve analytically, so it must be approached computationally. This problem is explored in Problem 8.25 of Classical Mechanics.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 11</head><p>To explore the shapes of a non-Keplerian orbit, you will complete the same set of steps that you have already completed in this unit but for a force law that scales as 1/r 5/2 .</p><p>Use the set of initial conditions given in the cell below, and then:</p><p>&#8226; Define the correct derivative function for this force law. Call it something different, like deriv _ nonkep().</p><p>&#8226; Plot r(t), dr/dt(t), and &#966;(t).</p><p>&#8226; Plot the orbital trajectory of the reduced mass on a set of polar axes.</p><p>Perform your integration from 0 &#8804; t &#8804; 40 with a (maximum) time step size of 0.001. </p><p>Exercise 11, Integration of non-Keplerian orbit &gt; ### ADD your call to solve _ ivp here # &lt; Exercise 11, Non-Keplerian orbit plots &gt; ### PLOT r vs. t, v _ r vs. t, phi vs. t for the reduced-mass orbit # (in a polar plot)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.8">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12</head><p>Briefly summarize in the cell below the ideas from this unit.</p><p>18.9 Challenge Problems 217</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="18.9">Challenge Problems</head><p>For a further exploration of orbits, you may wish to attempt the following:</p><p>1. Play around with some other configurations of Keplerian orbits. Try systematically changing both the masses and the orbital eccentricity. 2. Confirm conservation of mechanical energy for your Keplerian orbits. 3. Confirm Kepler's third law for your elliptical orbit.</p><p>I n this unit, you will use Python to visualize motion in both a non-inertial (rotating) ref- erence frame and an inertial (fixed) reference frame. In this case, you will consider the trajectory of a frictionless puck released on a horizontal rotating turntable. It will give you practice mentally shifting between inertial and non-inertial reference frames and illustrate both the centrifugal and Coriolis forces.</p><p>First, you will predict the behavior of the puck in two special cases. Then, you will determine the equations of motion numerically and analyze the behavior of the puck under different initial conditions. For your final output, you will produce a movie of the puck in both the inertial and non-inertial reference frames.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; integrate the equation of motion to find the trajectory of the puck in a non-inertial reference frame (PHY);</p><p>&#8226; compare the behavior of the puck in both the inertial and non-inertial reference frames (PHY);</p><p>&#8226; explore the behavior of the puck with several different initial conditions (PHY);</p><p>&#8226; use "events" in solve _ ivp() to cause the integration to end when a condition is met (PRO);</p><p>&#8226; expand your plotting ability by practicing using subplots and patches (PRO);</p><p>&#8226; and use movies to visualize the motion (PRO). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.2">Preliminary Questions</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>To get you thinking about motion in a non-inertial reference frame, answer Problem 9.19 from Classical Mechanics, transcribed below.</p><p>I am standing (wearing crampons) on a perfectly frictionless, flat merry-go-round, which is rotating counterclockwise with angular velocity about its vertical axis.</p><p>&#8226; I am holding a puck at rest just above the floor (of the merry-go-round) and release it. Describe the puck's path as seen from above by an observer who is looking down from a nearby tower (fixed to the ground) and also as seen by me on the merry-go-round. In the latter case, explain what I see in terms of the centrifugal and Coriolis forces.</p><p>&#8226; Answer the same questions for a puck that is released from rest by a long-armed spectator who is standing on the ground leaning over the merry-go-round.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.3">Framing the Problem</head><p>In order to solve this problem either numerically or analytically, the first steps are to find the equations of motion and set the initial conditions.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.3.1">Equations of motion Exercise 2</head><p>On a piece of paper, write down Newton's second law for the coordinates (x) and (y) as seen by someone standing on the turntable. Assume that the turntable is rotating counterclockwise with angular velocity . (Be sure to include the centrifugal and Coriolis forces, but ignore Earth's rotation.)</p><p>Next, use your equations of motion to write your deriv() function in the cell below. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.3.2">Setting the initial conditions and other physical parameters</head><p>Exercise 3</p><p>In the cell below, define a set of variables for the initial position and velocity in the noninertial reference frame and set = 1 s -1 . To begin, let the initial position be r 0 = (1, 0) and the initial velocity in the rotating reference frame be v 0 = (0, 1). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.3.3">Numerically solving the equations of motion Exercise 4</head><p>Use solve _ ivp() to solve for r(t) in the non-inertial reference frame for 0 &#8804; t &#8804; 2&#960;. In preparation for making a movie, set the optional parameter, t _ eval, so that the trajectory at regularly spaced time steps is returned. Using 100 steps is a good place to start. Additionally, set rtol and atol to 1e-9 in order to achieve appropriate accuracy.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 4, Integrate for the trajectory &gt; ### SOLVE for the trajectory of the puck from 0 &lt;= t &lt;= 2pi</head><p>Exercise 5</p><p>Plot the position of the puck below. The existing code will create a gray circle representing the turntable. All that is needed is to plot the trajectory over it.</p><p>&#10148; Note: The plot below makes use of the patches Matplotlib library, as previously done in Exercise 14 of The Brachistochrone Problem. This library can be used to draw shapes on a figure. Here we use matplotlib.patches.Circle() to draw a circle representing the turntable. Other available shapes include ellipses (matplotlib.patches.Ellipse()), rectangles (matplotlib.patches.Rectangle()), and polygons (matplotlib.patches.Polygon()). For arguments, matplotlib.patches.Circle() takes the position of the center of the circle and (optionally) the radius, along with standard plotting arguments such as color and linestyle.</p><p>Once the patch has been created, it must be placed on the figure using add _ patch(). For example, in the code below you will create and place the circle patch object on the ax object using the following lines of code: # Draw a circle representing the turntable radius = 15 # defining the radius of the circle # Creates a circle centered at (0, 0) with radius set by "radius". # The parameter "facecolor" sets the color circle = patches.Circle((0,0), radius = radius, color = '0.75') # Add the circle to the plot ax.add _ patch(circle) ### FILL in the rest of the code to plot the trajectory plt.axis('scaled') # Scale the axes so that they are equal size (resulting in # a square plot frame) #ax.axis('equal') # Alternatively, keep the figure size constant, but make the # x and y have the same scale plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.3.4">Comparing the trajectory in the rotating reference frame to that in a fixed inertial reference frame</head><p>You will gain a better understanding of what is happening if you plot the trajectory of the puck in a fixed reference frame as well.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6</head><p>First, fill in the code below to create a function that will return the x-and y-coordinates of the trajectory in the fixed reference frame. Your function should take a time (either as a single value or in the form of an array) and a list of the initial conditions (similar to r _ init but in the inertial reference frame) and return a list of the x and y positions at that time.</p><p>If the function is properly set up, calling it with an array of time values should cause it to return a two-dimensional array in which one column is the x-coordinate and one column is the y-coordinate. Define your initial conditions in the inertial reference frame, and use your function to calculate the x and y coordinates at each point in time for your r _ soln.t array. # Function values: </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>Plot the trajectories in both the rotating and non-rotating reference frames (including the turntable). Your plots will be easier to compare if they are lined up horizontally next to each other. As in Orbits, Keplerian and Not use matplotlib.pyplot.subplots to define a multipanel plot. Then plot the non-inertial and inertial solutions side by side. Remember that the syntax to define a multipanel plot using plt.subplots() and then to plot data in the first panel is: # &lt; Exercise 7, Plots of trajectories &gt; fig, ax = plt.subplots(1, 2) # one row of panels, two columns # Rotating reference frame circle = patches.Circle((0, 0), radius = radius, facecolor = '0.75') ax[0].add _ patch(circle) ax[0].axis('scaled') ax[0].set _ xlabel('X') ax[0].set _ ylabel('Y') ax[0].set _ title('Non-inertial Reference Frame') ### ADD a line of code to plot the trajectory in the rotating reference frame # here # Fixed reference frame circle = patches.Circle((0, 0), radius = radius, facecolor = '0.75')</p><p>ax <ref type="bibr">[1]</ref>.add _ patch(circle)</p><p>ax <ref type="bibr">[1]</ref>.axis('scaled')</p><p>ax <ref type="bibr">[1]</ref>.set _ xlabel('X')</p><p>ax <ref type="bibr">[1]</ref>.set _ title('Inertial Reference Frame')</p><p>### ADD a line of code to plot the trajectory in the fixed reference frame here plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8</head><p>In the space below, justify that your plots of the trajectories in both reference frames are consistent with each other.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.3.5">Exploring different initial conditions Exercise 9</head><p>Change the initial velocity of the puck to reflect both situations asked about in Exercise 1, and include your plots below for both the inertial and non-inertial reference frames. Be sure to avoid any unnecessary duplication of code.</p><p>In the space below, compare your predictions to the numerically derived solutions. Be sure to explain any discrepancies.</p><p># &lt; Exercise 9, Person at rest relative to turntable &gt; ### FILL in the code for the person at rest relative to the turntable dropping # the puck. # &lt; Exercise 9, Person at rest relative to inertial reference frame &gt; ### FILL in the code for the person at rest relative to inertial reference # frame dropping the puck. 19.3 Framing the Problem 225</p><p>Exercise 10* Explore two or three other initial velocities, including the plots below. Some ideas for v 0 are (-0.5, -0.5), (-0.7, -0.7), and (0, -0.1). Include the plots for one of your favorite situations below. (This exercise is very similar to Problem 9.24 of Classical Mechanics.)</p><p>&#10148; Hint: You may need to change the radius of the gray circle to see the details of the puck's motion.</p><p>One minor annoyance when playing with different initial conditions is that if the radius is small or the velocity is high, the puck might fly off the turntable. One possible way to approach this is to set the integration time span equal to the time it takes for the puck to travel from the initial location to the edge of the turntable. This time can be calculated using the velocity in the inertial reference frame.</p><p>Alternatively, you can tell solve _ ivp() to end the integration when the puck reaches the edge of the turntable. This technique is analogous to what you did with your Euler integrator in Numerical Integration Applied to Projectile Motion, in which you ended the integration when the projectile hit the ground. To do something similar with solve _ ivp() it is possible to define one or more "events". solve _ ivp() will then track when the event(s) equals zero and, if asked, end the integration at that point. The time(s) at which the event condition is met is stored in r _ soln.t _ events. The following cell illustrates how to define such an event for when the puck crosses the edge of the turntable.</p><p>To call solve _ ivp() with the event, your code will look like r _ soln = solve _ ivp(deriv, t _ span, r _ init, args = (omega,), t _ eval = t _ eval, events = reach _ edge)</p><p>In this exercise you will practice using events with solve _ ivp(), so make the necessary change to the following code cell to define an event that will cause the integration to halt when the puck crosses the edge of the turntable. Then for each set of initial conditions print the length of time the puck is on the turntable by accessing r _ soln.t _ events.</p><p>Note that when you used events to calculate the period of a simple pendulum in Simple Pendulum with Large Angle Release, the integration continued after the first zero-crossing. The difference is that here you will set reach _ edge.terminal = True to tell the computer to stop the calculation the first time the event is triggered. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.4">Animating the Trajectory</head><p>Finally, we can make a movie of the puck. Given the right coordinates for the position in the non-inertial reference frame, these last blocks of code will create an animation of the puck with the trajectories in both the inertial and rotating reference frame shown.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12</head><p>In order to show the turntable turning in the inertial reference frame, your movie should include a marker on the edge of the turntable that moves with the turntable. To follow the position of this marker, fill in the function below, pos _ marker, to make it return the position of the marker in the inertial reference frame as a function of time. This function should take a time and a radius and return an array of positions. For ease, you can assume the edge marker starts at (r,0) (in Cartesian coordinates). def pos _ marker(t _ arr, radius, omega):</p><p>19.4 Animating the Trajectory 227</p><p>""" Document your function here"""</p><p>This animation is more complicated than previous examples seen in Brachistochrone Problem, Three-Body Problem, and Orbits, Keplerian and Not. Here, you will plot not just the motion of a mass (or two), but also the paths the mass sweeps out in different reference frames. The basic structure of creating the animation, though, remains the same.</p><p>Again, we use animation.FuncAnimation() from matplotlib to create the animation. This function makes the animation by repeatedly calling a function, here called animate().</p><p>animate() updates the positions of the four different artists: a patch representing the puck, a patch representing the marker on the edge, a line showing the path in the rotating reference frame, and a line showing the path in the inertial reference frame. The positions of these elements are set by set _ center() for the patches and set _ data() for the lines.</p><p>The init() function is called by animation.FuncAnimation() once before animate() to add the patches for the edge marker and puck to the axes.</p><p>After the animation has been created, it is displayed using display(HTML(anim.to _ jshtml()))</p><p>(or HTML(anim.to _ html5 _ video()), if you are using the CoCalc server).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13</head><p>In the cell immediately below, fill in code to select x(t) and y(t) for the puck from r _ soln.</p><p>Then, try running the following blocks of code below to produce a movie. After you have successfully run it once, try playing around with the code by changing the amount of time for which it is run, the length of time between frames, the initial conditions, and so forth. # Define and customize the markings that will be animated. puck _ radius = 0.2 # Plot size of the circle representing the mass # Create the marker at the edge of the turntable edgemarker = patches.Circle((0,0), puck _ radius, fc ='black', ec = 'black', lw = 2 , zorder = 3) # "zorder = 3" ensures that # it will be plotted on top # Create a circle representing the puck puckmarker = patches.Circle((0,0), puck _ radius, fc ='DarkSlateGray', ec = 'black', lw = 2 , zorder = 3) # The path of the puck in the inertial reference frame puck _ track, = ax.plot([],[], 'b', lw = 2) # The path of the puck in the rotating reference frame </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.5">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 14</head><p>Briefly summarize in the cell below the ideas from this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="19.6">Challenge Problems</head><p>For a further exploration of motion in a noninertial reference frame, you may wish to attempt the following. These exercises are all centered on solving for the analytical solution and comparing it to the numerical solution.</p><p>1) Solve for the analytical solution. To do this, solve the two equations by the trick of writing &#951; = x + iy and guessing a solution of the form &#951; = e -i&#945;t . As in the case of the critically damped simple harmonic motion (SHM), you will only get one solution. The other solution has the same form, x(t) = te -&#946;t (Eq. 5.43 from Classical Mechanics), as we found for the second solution in the damped SHM. Leave your general solution in terms of &#951; and appropriate constants. (Adapted from Problem 9.20 b of Classical Mechanics) 2) Solve for the constants of integration. To do this, assume that at time t = 0, the position was r 0 = (x 0 , 0) and the velocity was</p><p>(Problem 9.20 c from Classical Mechanics) 3) Plot your analytical solution and compare it against the numerical solution.</p><p>I n this unit, you will be examining the effect of the Coriolis force near the surface of Earth.</p><p>Generally, we don't notice the non-inertial forces acting on us as Earth rotates. However, at sufficiently high velocities and over large enough distances, the Coriolis force can have real-world effects. For instance, it drives weather patterns, and urban legend has it that it threw off British aiming in a World War I battle near the Falkland Islands.</p><p>For the sake of simplicity, we will ignore the centrifugal force in this unit even though it frequently also has a component tangent to the surface of Earth.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="20.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; determine how to express the Coriolis force in spherical coordinates (PHY); &#8226; numerically calculate the trajectory of an object constrained to the surface of a sphere under the influence of the Coriolis force (PHY);</p><p>&#8226; plot the trajectory on the surface of a sphere (PRO);</p><p>&#8226; test your code by examining special cases and conserved properties (PHY);</p><p>&#8226; and apply your code to real-world situations (PHY). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="20.2">Equations of Motion</head><p>Let us assume an object is fixed to the surface of a spherical Earth. In this case, the normal force due to the ground will perfectly balance the radial component of the net force, and there will be zero velocity or acceleration in the radial direction.</p><p>To take advantage of this lovely symmetry, we will work the problem in spherical coordinates. As happens with polar coordinates, the unit vectors change direction with position. In spherical coordinates:</p><p>Therefore, when taking the derivatives of the position vector we find &#8407; r = rr, &#8407; v = &#7769;r + r sin &#952; &#966; &#966; + r &#952; &#952;, and</p><p>Using these equations for acceleration, we can write down the equations of motion. For example, because we know that the object won't be accelerating in r, it is trivial to write the equation of motion in that direction:</p><p>To find the equations of motion in &#966; and &#952;, we need to be able to express the forces (in this case, the Coriolis force) that can have non-zero components in those directions. As you know, &#8407;</p><p>For an object on the surface of Earth, &#8407; = &#7825; and</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>On a separate sheet of paper, work out &#8407; F cor for an object on the surface of the Earth in spherical coordinates. You will need to use the following relations (use your right hand to verify the directions to yourself before solving for the Coriolis force).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Now use your equations for the Coriolis force and acceleration in a spherical coordinate system to write the equations of motion in &#966; and &#952;. Substitute in for v &#952; and v &#966; (if you haven't done so already), simplify as much as possible, and solve for &#966; and &#952;.</p><p>&#10148; Hint: What is &#7769;?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="20.3">Solving the Equations of Motion</head><p>Now that you have the equations of motion, it is time to compute the numerical solution. Define a function that will return the derivatives for your four variables, &#966;, &#966;, &#952;, and &#952;. Let be an optional parameter for the deriv() function. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="20.3.2">Solving the equations of motion for a set of initial conditions Exercise 4</head><p>Define the parameters of the problem and the initial conditions in the cell below. For now, set the radius of Earth to 1 and the angular speed of Earth, , to 0.1. Let your initial conditions be &#966; 0 = &#960;/4, &#966;0 = 0, &#952; 0 = &#960;/4, and &#952;0 = 0.1. Then use solve _ ivp() to solve for 0 &#8804; t &#8804; 2&#960;. Set the integration time to a reasonable amount (about the time it would take for the object to circumnavigate the world at its initial velocity is a good first estimate). In order to ensure an accurate calculation set rtol = 1.0e-11 and atol = 1.0e-11 in your call to solve _ ivp(). Set the max _ step parameter so that your integration encompasses at least 100 steps. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>Check your integration by first plotting &#952; (colatitude) in degrees vs. &#966; (longitude) in degrees.</p><p>Set the axes of your plot so that the entire possible range of &#966; and &#952; are visible.</p><p>Adjust your plot so that the values of &#966; wrap around. In other words, &#966; should be limited to the range -&#960; &lt; &#966; &#8804; &#960;. To do this, check out the modulus operator, %.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 5, Plot of theta vs. phi &gt; ### INCLUDE your plot of theta vs. phi</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="20.3.3">Plotting on the surface of a sphere</head><p>You can make a potentially more meaningful plot by plotting in 3-D on the surface of a sphere. The following section of code will walk you through how to do this.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6</head><p>First, write formulas to calculate the Cartesian coordinates of the object based on &#966; and &#952;:</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>The following code plots a wire-frame sphere in 3-D, to which you will add the trajectory.</p><p>As you saw in Three-Body Problem, three-dimensional plots may be created very similarly to two-dimensional plots with the following key differences: 1) when creating the axis object, set the keyword argument projection to '3d', similarly to how you set projection = 'polar' in Spherical Pendulum and Orbits, Keplerian and Not; 2) set data for the z-coordinate along with the x and y data like ax.plot(x, y, z).</p><p>Once the axis object has been defined with a 3-D projection, ax.view _ init() allows you to change the viewing angle. The arguments are the elevation and azimuth of the axes in degrees, respectively.</p><p>The wire-frame sphere is plotted using ax.plot _ wireframe(x, y, z), where x, y, and z are Cartesian coordinates defining the surface. The keyword arguments rstride and cstride are short for "row stride" and "column stride" and define the number of lines shown along each direction. In the block of code below, the sphere is defined by creating arrays of values of &#966; and &#952; and converting them to Cartesian coordinates, assuming a given radius of the sphere.</p><p>Add a command to plot the x, y, and z coordinates you calculated above on the sphere using the command ax.plot(x, y, z). If you would like to see the sphere from a different viewing angle, change the values of elev and azim in ax.view _ init(). # Alternatively, the figure and axes can be defined together, as in the # commented-out line below #fig, ax = plt.subplots(figsize=plt.figaspect(1) * 4, # subplot _ kw={'projection': '3d'}) ax.set _ box _ aspect(aspect = (1,1,1)) # Set the aspect ratio of the plot to be # the same in x, y, and z ax.view _ init(elev = 30, azim = 40) # change the orientation of the plot # Define and plot a spherical shape # Set of all spherical angles: u = np.linspace(0, 2 * np.pi, 100) v = np.linspace(0, np.pi, 100) # Cartesian coordinates that correspond to the spherical angles: # (this is the equation of a sphere): x _ sphere = radius * np.outer(np.cos(u), np.sin(v)) # np.outer computes the outer # product of two vectors, # e.g., # out[i, j] = a[i] * b[j] y _ sphere = radius * np.outer(np.sin(u), np.sin(v)) z _ sphere = radius * np.outer(np.ones _ like(u), np.cos(v)) # np.ones _ like returns # an array of ones # with the same # shape and type as # a given array ax.plot _ wireframe(x _ sphere, y _ sphere, z _ sphere, rstride = 4, cstride = 4, color = '0.5') # plot the sphere </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="20.7">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 16</head><p>Briefly summarize the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="20.8">Challenge Problems</head><p>If you have extra time, consider the following questions. These questions are all centered on adding the centrifugal force.</p><p>&#8226; Define a new deriv() function, and repeat your integration now including the tangential component of the centrifugal force.</p><p>&#8226; Redo your plots from Exercises 5-7 (with the same parameters and initial conditions), and compare the trajectories when the centrifugal force is included and not.</p><p>&#8226; Try starting your mass with zero velocity and calculating its trajectory when the centrifugal force is included.</p><p>A nalyzing rigid body motion involves lots of linear algebra. Luckily, computers are very good at solving matrices, which means that not every determinant needs to be determined by you. In this unit, you will find the principal axes of different shaped rectangular cuboids by computationally calculating the eigenvalues and eigenvectors of the inertia tensor. This unit reinforces the idea of principal axes by allowing you to visualize them for different shapes. It also introduces NumPy's linear algebra capabilities and makes use of Python classes.</p><p>This unit covers material from Ch. 10.2-10.8 of Classical Mechanics and references Problem 10.25.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; define an inertia tensor for a solid cuboid (PHY); &#8226; use Python to calculate the moments and principal axes of the inertia tensor (PRO); &#8226; create a class to represent a solid cuboid capable of rotation (PRO). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.2">Coding Techniques Preamble</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.2.1">Linear algebra with NumPy</head><p>Thus far, you have primarily used NumPy for one-dimensional arrays. But you have also seen that its capabilities easily extend to two-and higher-dimension matrices, for instance in Three-Body Problem. One powerful extension of this capability is that it is easy to work with tensors using the NumPy library. Therefore, we will start this unit with a brief reminder of two-dimensional NumPy arrays.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Defining two-dimensional NumPy arrays</head><p>To define a two-dimensional NumPy array with a specific set of elements, you can nest lists together and then convert them to a NumPy array, as shown: The result is a matrix of three rows and two columns, as can be checked using the numpy.shape command (in contrast, len() only returns the total number of elements). As you have seen before, specific elements can be accessed by providing the index of first the row, then the column. Other ways of creating two-dimensional NumPy arrays include using numpy.zeros or numpy.ones to request an array of a particular shape filled with either zeros or ones and using numpy.reshape to reshape a one-dimensional array into a multidimensional one.</p><p>The reverse of np.reshape is numpy.flatten, which will turn a multidimensional array into a single-dimensional one. By default, all elements of the first row will appear before all elements of the second row and so on (a.k.a. "row-major order"). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Basic matrix operations</head><p>To transpose a NumPy array, you can either use the function np.transpose(ndarray) or access the transpose property of the NumPy array object with either ndarray.transpose() or ndarray.T. Note that none of these methods actually changes the underlying array. To modify the array itself, you will need to assign it the value of the transposed array, as shown below. Technically, you cannot take the transpose of a one-dimensional NumPy array (it will just return the same array). To transpose a vector, you need to use a double set of square brackets to define it as a two-dimensional array that only has one row or column. Another basic matrix operation you will surely encounter is matrix multiplication. This can be done by providing numpy.matmul with the two matrices as the arguments of the function. The same task can be done using the @ operator. Examples of both follow. Be careful about the shape of the arrays you are trying to multiply, as the number of columns of the first must match the number of rows of the second.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># Example of multiplying two-dimensional arrays with matrix multiplication</head><p>print("a _ arr _ 43 ({}x{}):\n{}".format(np.shape(a _ arr _ 43)[0], np.shape(a _ arr _ 43) <ref type="bibr">[1]</ref>, a _ arr _ 43)) M _ arr = np.array([ <ref type="bibr">[1,</ref><ref type="bibr">2]</ref>, <ref type="bibr">[3,</ref><ref type="bibr">4]</ref>, [5, 6]]) print("M _ arr ({}x{}):\n{}".format(np.shape(M _ arr)[0], np.shape(M _ arr) <ref type="bibr">[1]</ref>, M _ arr)) # using np.matmul print("\na _ arr _ 43 x M _ arr:\n", np.matmul(a _ arr _ 43, M _ arr)) # using the @ operator print("Or equivalently, using the @ operator:\n", a _ arr _ 43 @ M _ arr) ### UNCOMMENT the following line to see the error message # print("\nM _ arr x a _ arr _ 43:\n", np.matmul(M _ arr, a _ arr _ 43))</p><p>Finally, many of the more advanced commands, including those to calculate the eigenvectors and eigenvalues of a matrix, are stored in the numpy.linalg library. (You have also used np.linalg.norm to find the magnitude of a vector in The Three-Body Problem.)</p><p>Examples of how to find the determinant, and eigenvector and eigenvalues for a particular array using numpy.linalg.det, and numpy.linalg.eig follow. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.2.2">Classes</head><p>Throughout these units you have written functions and strung them together to accomplish your goals. As such, you have primarily been working in a "functional programming" style, in which the functions themselves were the basic building blocks of your program.</p><p>Python, however, is also highly supportive of a different style of programming, known as "object-oriented" programming. In this style of programming, the basic units are "objects" that contain some combination of variables (a.k.a. "attributes") and/or functions (a.k.a. "methods"). A "class" is a template that contains descriptions for what information is to be stored and how it can be modified. An "object" is a particular instance of a class. For example, you could define a class, car, that allowed for information about gas mileage, amount of fuel, and present location to be stored along with methods for changing its location. Then you could create multiple instances of that class (e.g., car _ 1, car _ 2), each with its own unique mileage, amount of fuel, and location. In this example, car _ 1 and car _ 2 are both objects of the type class car.</p><p>You have actually been using objects throughout these units. For example, the NumPy arrays you have defined are all objects of class ndarray. Likewise, when you have used the familiar fig, ax = plt.subplots() command, you created an object ax of the matplotlib.axes.Axes class. You then modified that object using commands such as ax.plot(x, y).</p><p>One way you can recognize objects in your code is that a period is used to separate the name of the object (e.g., ax) from the reference to the attribute or method for that object (e.g., plot()). # Verify the types of these objects print("a _ arr is an instance of ", type(a _ arr)) print("b _ arr is an instance of ", type(a _ arr)) # Determine the shape of the array, either by using a function or by accessing # the shape information stored as an attribute of the object print("\nThe shape of a _ arr can be found by using np.shape(a _ arr)" " [{0}]\n\tor a _ arr.shape [{1}]".format(np.shape(a _ arr), a _ arr.shape))</p><p># Methods can be used to modify the object, as well as return specific # information about it.</p><p>print("\nThe method ndarray.max() returns the maximum value of the array. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.3">Moment of Inertia Tensor and Angular Momentum</head><p>In order to describe the motion of a solid rotating body, one must first be able to describe the body itself. Therefore, you will start by calculating the inertia tensor for a solid body rotating around different pivot points. Once the inertia tensor has been defined, you will be able to use Python's linear algebra capabilities to determine the angular momentum given a rotational vector.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.3.1">Defining the inertia tensor</head><p>For these exercises, you will be working with a uniform cuboid (a rectangular brick shapeperhaps your textbook?) centered on the origin. Let the lengths of the sides be 2a, 2b, and 2c in the x, y, and z directions, respectively, and the mass be m.</p><p>These variables are defined globally below. Later, you will learn how to use them to define a cuboid class. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>Determine all nine elements of the moment of inertia tensor for the cuboid with respect to its center of mass. You should be able to calculate the off-diagonal elements without doing any integration (this task corresponds to Problem 10.25 a from Classical Mechanics).</p><p>Then fix the code below to define the inertia tensor, keeping all elements in terms of the variables a _ side, b _ side, c _ side, and mass. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Now calculate the angular momentum of a cuboid with m = 3 kg, a = 2, b = 1, and c = 3 m spun around its center of mass with angular velocity &#8407; &#969; = 5 s -1 x. Justify to yourself that the answer makes sense.</p><p>You will want to make use of NumPy's matrix capabilities. Define an angular momentum vector using NumPy arrays, and then try multiplying (matmul) to find &#8407; L = I &#8407; &#969;.</p><p># &lt; Exercise 2, Calculating the angular momentum &gt; ### DEFINE the omega vector as a numpy array ### CALCULATE the angular momentum</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Suppose the cuboid were instead to be rotated around the x-axis from the far corner A at (a, b, c). Find the new moment of inertia tensor. (This exercise is equivalent to Problem 10.25 b from Classical Mechanics.)</p><p>&#10148; Hint: The "generalized parallel axis theorem" (Eq. 10.117 from Classical Mechanics) claims that if I cm denotes the moment of inertia tensor of a rigid body (mass M) about its center of mass, then the corresponding moment of inertia tensor I about a pivot point P displaced from the center of mass by = (&#958; , &#951;, &#950; ) is:</p><p>2 ); I yz = I cm yz -M&#951;&#950; and so on. 1. angular velocity &#8407; &#969; = 5 s -1 x 2. angular velocity &#8407; &#969; = 5 s -1 &#375;.</p><p>Again, make use of NumPy's matrix capabilities. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.3.2">Creating a class</head><p>It would be nice to be able to define different cuboids of different masses and dimensions and then manipulate them in the same way (e.g., find the moment of inertia or change the pivot point). Defining a class is a natural way to accomplish this goal and allows us to make use of some of Python's powerful object-oriented features.</p><p>In this situation, the object is an instance of a class representing uniformly solid cuboids capable of rotating with their center of mass at the origin. Once this class has been created, you can use it to define different individual cuboids and compare their properties. In particular, this class should allow the user to define the mass and side-lengths of the cuboid. It will then determine the moment of inertia for rotation around the center of mass or a userdefined pivot point. Later in this unit you will modify it to calculate the principal axes and moments.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>The code below defines such a class. Make the requested modifications using the code you wrote above for calculating the moments of inertia. </p><p>To use a class, you will want to define an object that is an instance of that class. The code block below shows how to define an instance of the SolidCuboid class "cube" that has the default side-length parameters (a = b = c = 1 m) and mass m = 3 kg. Once this object has been defined, the class can be used to calculate and access different attributes of this object, such as the center of mass and the moments of inertia around different pivot points. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.4">Finding the Principal Axes</head><p>Once the moment of inertia tensor has been created, you can find the principal axes by computing the eigenvectors. Remember, you can use numpy.linalg.eig to calculate the eigenvalues and vectors. For example:</p><p>w _ arr, v _ arr = linalg.eig(M _ arr)</p><p>returns an array of eigenvalues (w _ arr) and a two-dimensional array of the eigenvectors (v _ arr). Each eigenvalue corresponds to a different column in v _ arr (listed in order), so</p><p>Unfortunately, linalg.eig does not return the eigenvalues in any particular order. Therefore, you will also want to sort the eigenvalues and their corresponding eigenvectors for the sake of consistency. This sorting can be accomplished using the following lines of code after the eigenvalues and eigenvectors have been calculated: </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9</head><p>Now that you have calculated the principal axes, we have the opportunity to use Python's graphics capability to help visualize them. The second block of code below creates a 3-D projection plot of the rectangular cuboid.</p><p>At the end of the block of code, add ax.plot commands to plot each of the three principal axes. The easiest way to do this is to provide a set of two 3-D coordinates that will draw a line extending from the pivot point to a location along the vector some distance from the pivot point.</p><p>Once you have plotted the principal axes for the rectangular cuboid pivoted around the origin (rectangularCuboid _ centered), try uncommenting the second line of code and rerunning the cell to plot the principal axes for a rectangular cuboid pivoted around the far corner (rectangularCuboid _ offset).</p><p>This block of code uses a couple of new procedures to plot the surfaces of the cuboid. The command plot _ surface() from the Matplotlib library will draw a surface that intersects with the x, y, and z points provided. In order to calculate those points, the three-dimensional coordinates for each side must be provided. For example, to define the top of the cuboid, this code uses np.linspace() to create 1-D arrays of x and y locations. Then it uses numpy.meshgrid to return 2-D arrays of x and y values corresponding to the set of (x, y) coordinates (see the example immediately below). Finally, the corresponding z coordinates are defined to be a 2-D array with the same shape as the x and y coordinates, all set to the value of c. # Define the rectangles that will be three sides of the cuboid # Define the arrays that go from the minimum and maximum values of the # sides. x = np.linspace(-rectangularCuboid.a _ side, rectangularCuboid.a _ side, 6) y = np.linspace(-rectangularCuboid.b _ side, rectangularCuboid.b _ side, 6) z = np.linspace(-rectangularCuboid.c _ side, rectangularCuboid.c _ side, 6) # Use the plot _ surface command to plot a 3-D surface # Set the color to blue, the edgecolor to black, and make semi-transparent by # setting alpha to 0.4 X, Y = np.meshgrid(x, y) # define a grid of x and y coordinates for the top Z = X * 0 + rectangularCuboid.c _ side # the z coordinates of the top will all be # equal to c ax.plot _ surface(X, Y, Z, alpha = 0.2, color = 'b', edgecolor = 'b') # top ax.plot _ surface(X, Y, -1 * Z, alpha = 0.2, color = 'b', edgecolor = 'b') # bottom Y, Z = np.meshgrid(y, z) # define a grid of x and y coordinates for the top X = X * 0 + rectangularCuboid.a _ side # the z coordinates of the top will all be # equal to a ax.plot _ surface( X, Y, Z, alpha = 0.2, color = 'b', edgecolor = 'b') # right ax.plot _ surface(-1 * X, Y, Z, alpha = 0.2, color = 'b', edgecolor = 'b') # left X, Z = np.meshgrid(x, z) # define a grid of x and y coordinates for the top Y = X * 0 + rectangularCuboid.b _ side # the z coordinates of the top will all be # equal to b ax.plot _ surface(X, Y, Z, alpha = 0.2, color = 'b', edgecolor = 'b') # Front ax.plot _ surface(X, -1 * Y, Z, alpha = 0.2, color = 'b', edgecolor = 'b') # Back # Set the limits of the axes to be equal to the maximum side lim = max(rectangularCuboid.a _ side, rectangularCuboid.b _ side, rectangularCuboid.c _ side) ax.set _ xlim3d([-1.0 * lim, lim]) ax.set _ ylim3d([-1.0 * lim, lim]) ### ADD code here to plot the three principal axes for the object.</p><p># Add an offset so that the principal axes start at the pivot point. # You will likely also find it useful to include a constant scalefactor that # can be used to increase (or decrease) the length of the principal axes.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.5">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>Briefly summarize the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="21.6">Challenge Problem</head><p>If you have extra time, consider:</p><p>&#8226; Systematically changing the location of the pivot point and examining how the principal axes shift.</p><p>&#8226; Changing the shape of the cuboid and examining how the principal axes shift.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Precession of a Cuboid</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>22</head><p>P reviously in Principal Axes of a Cuboid you calculated the principal axes and moments of a rectangular cuboid rotated around different points. In this unit, you will see how those result in the rotation of the rectangular cuboid. Specifically, you will use the calculated principal moments in Euler's equations to computationally solve for the angular momentum over time.</p><p>This unit covers material from Ch. 10.2-10.8 of Classical Mechanics and references Problem 10.25.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="22.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; save your previously defined class representing a solid cuboid capable of rotation as a module and import it (PRO);</p><p>&#8226; model the evolution of the angular momentum vector over time for cuboids of different axis lengths spun around their centers of mass (PHY);</p><p>&#8226; analytically determine the frequency of precession if the cuboid has two equal-length sides (PHY);</p><p>&#8226; use events in solve _ ivp() to determine the frequency of precession (PRO). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="22.2">Creating and Importing Modules</head><p>In this unit, you will be working with the SolidCuboid class created in Exercise 5 of Principal Axes of a Cuboid. You can simply copy the code defining the class to a cell in this notebook. However, there is a more elegant solution, which is to copy the class into a Python file and import it into this notebook. Writing "modules" (files with code available for import) allows you to easily reuse code for different purposes and generally results in cleaner code.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>Create a new file named principal _ axes.py located in the same directory as this notebook. (This would require uploading it to CoCalc or to your "Colab Notebooks" directory on Google Drive if you are using Google Colab. If you are using Google Colab, you will also need to mount Google Drive by uncommenting the code in the cell below.) Then copy all of Exercise 5 of Principal Axes of a Cuboid into the file and save it. At the top of the file, be sure to include import numpy as np from numpy import linalg as these libraries are necessary for methods within the class. The next step is to import everything contained in this file (a.k.a. module). You could do this by running the following statement:</p><p>This line tells Python that you wish to import the code contained in "principal_axes.py".</p><p>Oftentimes, though, you will want to use a shortened nickname or "alias" for the module. For example, numpy is generally shortened to np. In that case, your code would look something like </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="22.3">Euler's Equations</head><p>Given the principal axes and principal moments for a body, one can use Euler's equations to determine the rotation of the body over time. If the body is subject to zero torque, Euler's equations reduce to: <ref type="formula">10</ref>.89 from Classical Mechanics)</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Write a deriv() function that will return an array of &#969;1 , &#969;2 , and &#969;3 . Provide the array of principal moments as one of the arguments in the deriv() function. Let the initial rotation be primarily aligned with the &#8407; e 2 principal axes (the one with the intermediate moment and aligned with the x-axis). Because your deriv() function assumes that the values of &#8407; &#969; are relative to the different principal axes, your values of omega _ init should be relative to the principal axes &#8407; &#969; e,0 = (&#969; e 1 , &#969; e 2 , &#969; e 3 ), rather than in Cartesian coordinates &#8407; &#969; 0 = (&#969; x , &#969; y , &#969; z ). Therefore, use &#8407; &#969; 0 = (0.01, 0.5, 0.0) and not &#8407; &#969; 0 = (0.5, 0.01, 0.0) when defining omega _ init, even though the rotation is happening primarily along the x-axis.</p><p>Integrate your solution for 100 seconds and set the maximum time step to 0.1 seconds. Plot &#969; 1 , &#969; 2 , and &#969; 3 versus time on the same axes.</p><p>&#10148; Hint: You will need to pass an array of the principal moments as an argument in solve _ ivp().</p><p>Make use of your previously defined object rectangularCuboid _ centered by using a call like args = (rectangularCuboid _ centered.w _ arr,). &#8407; &#969; 0 = (0, 0.01, 0.5) s -1 . Repeat the same steps as in Exercise 4.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Is rotation around this axis stable? How can you tell?</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Is rotation around this axis stable? How can you tell?</head><p>Following the derivation in "Motion of a Body with Two Equal Moments: Free Precession" from Section 10.8 of Classical Mechanics, when &#955; 1 = &#955; 2 , the first two Euler equations become</p><p>This constant angular frequency, b , is the angular frequency at which the magnitude of both &#969; 1 and &#969; 2 oscillate.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8</head><p>Calculate b for your solid body using the same initial angular velocity as before. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9</head><p>How well do the angular frequencies of oscillations of &#969; 1 and &#969; 2 in your computational solution compare with the analytically derived angular frequency you calculated in Exercise 8? To answer this question, you will need to be able to measure the period of oscillations of &#969; 1 (or &#969; 2 ).</p><p>In Simple Pendulum with Large Angle Release, you calculated the period of a pendulum using events in solve _ ivp() to record times at which it crossed zero degrees. In Motion on a Turntable, you also used events to terminate the integration when the puck left the edge of the turntable.</p><p>Recall, all you need to do to use events is define a function that goes to zero for the desired occurrence, then call it from the integrator with the keyword events =. The function call has to take the same arguments as for the deriv() function. For example, you could define an event like def event _ name(t _ now, r _ now, param1, param2, etc.): that triggers when the function returns zero. Then to use the event, add the parameter events = event _ name to your solve _ ivp() call. The time of the event triggering is then recorded in r _ soln.t _ events, assuming r _ soln is the name of the return from solve _ ivp(). The contents of r _ soln.t _ events will actually be a list of arrays, with each element in the list corresponding to a different event definition. So if you wanted to access those times the first event was triggered, you would use r _ soln.t _ events[0].</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="22.4">Check-out 259</head><p>In this case, you will want events to be triggered only if the zero point is crossed from a particular direction. To do this, set event _ name.direction to a positive float so that only negative-to-positive crossings are recorded.</p><p>&#10148; Hint: np.diff() will determine the difference between consecutive elements in an array. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="22.4">Check-out</head><p>Exercise 11</p><p>Briefly summarize the ideas in this unit. Masses Connected with Springs</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="22.5">Template</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>23</head><p>W hen two oscillators (masses and springs or pendula, for example) are brought into proximity, they can influence one another via the (coupling) force between them, causing a shift in their natural frequencies. Strong coupling leads to two distinct frequencies, wherein the two oscillators can move in phase or out of phase with each other. In the limit of weak coupling, we can observe slow energy flow from one oscillator to the other, causing a beat pattern.</p><p>Fourier analysis is introduced in the final section of this unit, and in Exercises 15 and 16 a fast Fourier transform algorithm is used to identify the normal modes and frequencies from the trajectory of an oscillator. Fourier analysis was introduced in Chapter 5.7 of Classical Mechanics as an optional topic and may be skipped here, if not previously covered. The first two exercises may also be completed ahead of time.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="23.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; solve analytically for two identical coupled oscillators, along the lines of Chapter 11 of Classical Mechanics, using the characteristic equation and solving the quadratic equation, yielding the normal mode frequencies and amplitude ratios (PHY);</p><p>&#8226; use solve _ ivp() to model the behavior of the oscillators and plot both normal modes and non-repeating patterns (PHY);</p><p>&#8226; use NumPy's linear algebra package to solve the eigenvalue problem (PRO);</p><p>&#8226; extend the general solution to three masses (PHY);</p><p>&#8226; examine the behavior of weakly coupled oscillators (PHY);</p><p>&#8226; create an animation showing the motion of the masses (PRO);</p><p>&#8226; use Fourier analysis to find the normal frequencies and reconstruct the trajectory of the oscillators (PRO). m showing the two normal mode frequencies, one (&#969; 1 ) at the normal mode frequency of the two oscillators without the center coupling spring k 2 , and the other (&#969; 2 ) pushed higher in frequency.</p><p>To complete the problem, substitute each solution, one at a time, into the matrix equation of motion to see that at the lower frequency the amplitudes of x have the same sign and hence are in phase, whereas the higher frequency mode requires that the two amplitudes are out of phase, as indicated by their opposite signs. We refer to these as even and odd solutions, respectively, just as cos(x) is an even function because cos(x) = cos(-x), and sin(x) is an odd function because sin(x) = -sin(-x).</p><p>Now that you have solved the symmetrical case by hand, we can plot the solutions by taking the real part (a cosine term) at the appropriate frequency.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>Plot the lower-frequency (even) solutions for both x 1 and x 2 from 0 to 100 seconds. Let k = 1 N/m and m = 1 kg. For best results, make your plots of x 1 and x 2 two different vertically stacked subplots of the same figure. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 4</head><p>Next, plot the higher-frequency (odd) solutions for both x 1 and x 2 from 0 to 100 seconds. As in Exercise 3, let k = 1 N/m and m = 1 kg. Again, for best results, make your plots of x 1 and x 2 two different vertically stacked subplots of the same figure. The general solution for this system is <ref type="formula">11</ref>.21 from Classical Mechanics).</p><p>In this equation, 1 1 and 1</p><p>-1</p><p>are the two normal modes (eigenvectors) that correspond to the normal frequencies, &#969; 1 and &#969; 2 (the eigenvalues). The constants, A 1 , &#948; 1 , A 2 , and &#948; 2 , are determined from the initial conditions. When finding these constants, though, it is often more convenient to rewrite the general solution as a sum of sines and cosines:</p><p>1 -1 (see Problem 11.7 from Classical Mechanics). In the following exercise, you will solve for these constants and plot the resulting motion.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 5</head><p>Suppose cart 1 starts from rest at x 1 = 1 and cart 2 starts from rest at x 2 = 0. This situation does not correspond to either of the two normal modes, so the resulting motion must be a linear combination of both modes.</p><p>Analytically solve for B 1 , C 1 , B 2 , and C 2 for these initial conditions. Then plot the x 1 and x 2 versus time for 0 &#8804; t &#8804; 100 s as two vertically stacked plots. On the x 1 versus time plot, also show the components of the two normal modes (B 1 cos &#969; 1 t + C 1 sin &#969; 1 t and B 2 cos &#969; 2 t + C 2 sin &#969; 2 t for this cart). Do the same for the x 2 versus time plot. By examining the curves, it should be clear that the motion of the carts results from summing the normal modes. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="23.3.2">Numerical solution</head><p>In Exercises 3 through 5, you manually plotted your analytical solutions. Here, you will instead numerically simulate the positions and velocities of each mass by integrating the equations of motion. (Later, that will allow you to add friction or other forces.)</p><p>To do this numerical integration, you will need a generalized derivative function (derivN) that applies Newton's second law to each of the masses. It takes in a square KM-matrix of any size N &#215; N and returns the 2N derivatives arranged in the appropriate v, dv dt order for each mass. You will call this function when running solve _ ivp(). </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6</head><p>Return to the KM-matrix from the two equal-mass, three equal-spring case (Exercise 2). Use solve _ ivp(), calling derivN() to simulate the positions of the masses from 0 to 100 seconds (let max _ step equal 0.1). Start the masses from rest with each displaced in such a way as to produce pure sinusoidal motion in the lower mode, &#969; 1 = k m . As before, plot both x 1 and x 2 in separate subplots in the same figure. Because the values of x 1 and x 2 recorded in the solution are the distances from equilibrium, you will have to add an offset. For instance, you can assume that the equilibrium position of cart 1 is at -1.5 m and the equilibrium position of cart 2 is at 1.5 m.</p><p>&#10148; Hint: The mechanics of this animation are very similar to Exercise 13 from Brachistochrone Problem, which also used circular patches to animate motion of masses. You can also read more about animation in Appendix 3: Creating Animations with FuncAnimation. ### USE patches.Circle to define circles representing carts 1 and 2; # call them ball1 and ball2 and let their radii be "ball _ radius" ball1 = 0 ### FIX ball2 = 0 ### FIX ### DEFINE an init function that uses ax.add _ patch to add each of the patches # to the axes def init(): return ball1, ball2 ### SET the center of each patch at a particular instance of time = t[i] using # ball1.set _ center and ball2.set _ center def animate(i): return ball1, ball2 ### CALL animation.FuncAnimation and display the resulting animation</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="23.3.3">Solving the eigenvalue problem numerically</head><p>While the previous case was easily solved analytically, as the number of oscillators increases or damping is included, we may wish to turn to numerical solutions of the eigenvalue problem:</p><p>Therefore, it is helpful to use one of Python's linear algebra packages to solve the eigenvalue problem numerically, yielding both the normal mode frequencies (from the square root of the eigenvalues) and the eigenvectors. There are many different solvers for eigenvalue problems in the SciPy and NumPy packages. In Principal Axes of a Cuboid, you used numpy.linalg.eig. In situations like this one, where the matrix is known to be symmetric, numpy.linalg.eigh is more efficient. It also has the additional advantage of returning the eigenvalues sorted in ascending order. Otherwise, it can be used the same way as numpy.linalg.eig().</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>An illustration of how to numerically solve for the frequencies from</head><p>Exercise 2 follows. Remember &#8730; 3 &#8764; 1.73205. # Example of using numpy.linalg.eigh # Define the km _ matrix: km _ matrix = np.array([[2,-1],[-1,2]]) print("Here is the KM-matrix: \n", km _ matrix) # Then we get eigenvalues and eigenvectors via print("\nSolving for the eigenvalues and eigenvectors: \n") eig _ vals, eig _ vecs = linalg.eigh(km _ matrix) # solve the problem 23.4 Weak Coupling, an Illustration of Beats 269 # Remember that we are actually solving for omega^2, so we need to take the # square root of the eigenvalues omegas2 = np.sqrt(eig _ vals) # here we put a '2' to indicate the two-mass # oscillator print(" Eigenfrequencies: \n", omegas2) # frequencies print("\n Eigenvectors: \n", eig _ vecs) # normal modes are columns print("First column of eigenvectors gives x1, x2 relative amplitudes: ", eig _ vecs[:,0]) print("Second column of eigenvectors gives x1, x2 relative amplitudes:", eig _ vecs[:,1])</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>Create a new KM-matrix km _ matrix3 for three identical masses (m = 1 kg) connected by four identical springs (k = 1 N/m), then find the eigenvalues and eigenvectors using the procedure above and answer these questions:</p><p>&#8226; What are the three eigenfrequencies?</p><p>&#8226; For the lowest mode (frequency), use arrows to sketch the relative motion of each mass.</p><p>&#8226; What can you say about the motion of m1, m2, and m3 for the middle mode? Make a sketch.</p><p>&#8226; What can you say about the motion of m1, m2, and m3 for the highest mode? Make a sketch. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="23.4">Weak Coupling, an Illustration of Beats</head><p>Now that you have examined the case with three identical springs, you will observe the behavior of the system when the middle spring is weakened. This situation is an example of two weakly coupled oscillators and, as you will see, produces the beat phenomenon.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 11*</head><p>Modify your 2 &#215; 2 K-matrix to permit a weak coupling spring, so that k 2 = k/10, and redo the eigenvalue problem. Be sure to print your KM-matrix, along with the normal mode frequencies and eigenvectors.</p><p># &lt; Exercise 11, Weak coupling, 2 masses, eigenfrequencies &gt; ### DEFINE your KM-matrix ### USE linalg.eigh to solve for the eigenvectors and eigenfrequencies ### PRINT the eigenfrequencies and eigenvectors Exercise 12* You should have found the same eigenvectors but different mode frequencies. Repeat Exercises 6, 7, and 8 to numerically calculate and plot the motion in both normal modes and when masses are started from rest at x 1 = 1 m and x 2 = 0 m. As before, for each set of initial conditions, plot both x 1 and x 2 in separate subplots of the same figure. Starting the masses from rest at x 1 = 1 m and x 2 = 0 was not an eigenmode. However, it did not produce a plot like that from Exercise 7, either. Instead, the weakness of the coupling produced a "beat" pattern in which the energy from the oscillations is passed back and forth between the two carts.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13*</head><p>To witness this transfer of energy, reexamine the final situation from Exercise 12 (m = 1 kg, k = 1 N/m, k 2 = k/10, x 1 = 1 m, and x 2 = 0). Compute the potential energies associated with each spring and the mechanical energy for each cart.</p><p>As separate lines on the same axes, plot the (a) kinetic energy of cart 1 + the potential energy of spring 1, (b) the kinetic energy of cart 2 + the potential energy of spring 3, and (c) the potential energy of the coupling spring.</p><p>Then, in a separate figure, plot the potential energy, kinetic energy, and total energy of the entire system versus time.</p><p>Describe how these plots illustrate conservation of energy within the entire system and energy transfer among the different components.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 13, Compute PE, KE, and TE for the weak coupling case &gt; ### COMPUTE kinetic, potential, and total energies ### PLOT energies versus time</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 14*</head><p>Repeat the animation creation from Exercise 9 for weak coupling. ### USE patches.Circle to define circles representing carts 1 and 2; # call them ball1 and ball2 ball1 = 0 ### FIX </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="23.5">Fourier Analysis</head><p>You have seen that the motion of the two carts is the linear combination of two normal modes of oscillation. The normal modes and their corresponding frequencies are determined by the system, while the amount of contribution from each of those modes is based on the initial conditions. In many physical situations, you will be able to measure the trajectory of the oscillators but will not know what the normal modes are. In these cases, Fourier analysis provides a powerful tool for determining the normal modes and their contribution to a given signal. (This topic was first introduced in Chapter 5.7 of Classical Mechanics in the context of driven oscillators.)</p><p>At the heart of Fourier analysis is Fourier's theorem, which states that any periodic function can be written as a sum of sines and cosines of different frequencies: <ref type="formula">5</ref>.82 from Classical Mechanics). The coefficients for each of the terms in the sum can be determined mathematically through</p><p>(Equations 5.83 and 5.84). (While the motion of carts in Exercises 5 and 8 is aperiodic, it is also, as we have established, a linear combination of sinusoidal terms, and the coefficients may be determined in a similar manner.) Rather than computing these integrals by hand, it is possible to use the fast Fourier transform algorithm to determine these coefficients. The implementation we will use is numpy.fft.fft. When provided with a periodic signal (in our case, x 1 ), this algorithm will return the coefficients. The corresponding frequencies for these coefficients are found through numpy.fft.fftfreq. The plot of the coefficients versus the frequencies is referred to as the Fourier spectrum. Peaks in this spectrum represent frequencies that provide much of the power to the summed signal. In the case of coupled oscillators, these peaks will correspond to the normal mode frequencies.</p><p>The two code cells below provide an example of how np.fft.fft() may be used to determine the normal frequencies and the relative contributions of the different normal modes given a particular trajectory (e.g., x 1 (t)). The peaks in the Fourier spectrum are identified using scipy.signal.peaks. The frequencies at which these peaks appear are the frequencies of the sinusoidal terms; their relative strength provides the relative contribution of the different terms. This information is then used to reconstruct the original trajectory by summing together the different sinusoidal terms.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 15</head><p>The next code cells numerically solve the case of two equal-mass carts attached by three equivalent springs wherein cart 1 is released from rest at x 1 = 1 and cart 2 is released from rest at x 2 = 0 (the same situation as in Exercises 5 and 8). The following code cell computes the Fourier transform for the position of cart 1 (x 1 ), identifies the peaks in the Fourier spectrum, and reconstructs the x 1 (t). Read through and run both blocks of code, making sure you understand what is going on. Explore the code by 1) changing the initial conditions, including trying at least one case that produces motion in entirely one normal mode, and 2) increasing the time span of the solution (t _ span) so that the Fourier transform can be computed over a longer time period (Note: The frequency resolution for this fast Fourier transform is inversely proportional to the time span of the signal).</p><p>Then answer the following questions.</p><p>How do the angular frequencies of the peaks in the Fourier spectrum compare to the normal frequencies?</p><p>How do the relative heights of the different peaks in the Fourier spectrum compare to your calculations of B</p><p>1 , C 1 , B 2 , and C 2 from Exercise 5? How does motion reconstructed from the Fourier analysis compare to the original motion? What is the impact of increasing the time span (t_end) over which the analysis is done ? # &lt; Exercise 15, Fourier analysis, determining the signal &gt; # This block of code numerically determines x _ 1(t) etc. for the case of 2 equal # masses and 3 equal springs. # Define the KM-matrix km _ matrix = np.array([[2,-1], [-1,2]]) # Set the initial conditions r _ init = np.array([1, 0, 0, 0]) ### ADJUST to explore different ICs print("r _ init: {:}".format(r _ init)) # Define a time array of evenly spaced times. Even temporal spacing simplifies # the Fast Fourier Transform t _ start = 0 t _ end = 100 ### INCREASE the time span to increase the accuracy of the Fourier # analysis t _ span = (0, t _ end) sample _ spacing = .01 # spacing of time steps t _ eval = np.arange(t _ span[0], t _ span[1], sample _ spacing) # Calculate the numerical solution r _ soln = solve _ ivp(derivN, t _ span, r _ init, args = (km _ matrix,), t _ eval = t _ eval) # &lt; Exercise 15, Computing the Fourier transform &gt; # This block of code runs the Fourier transform on x _ 1(t) and reconstructs # x _ 1(t) # Import the library from SciPy that contains find _ peaks from scipy import signal # Find the Fourier transform of the x1 position ft = np.abs(np.fft.fft(r _ soln.y[0])) # Determine the frequencies that correspond to the different elements in the # Fourier transform (ft) freq _ domain = np.fft.fftfreq(len(ft), d = sample _ spacing) # Find the peaks of Fourier transform # "height" filters out all peaks below this height, here set to be 10% of # the maximum height peaks, properties = signal.find _ peaks(ft, height = np.max(ft) * 0.1) # Because of numerical noise, find _ peaks may pick up on smaller, irrelevant # peaks. However, you know that the mass will be oscillating as the sum of # 2 modes (the number of masses and normal modes) or, if the oscillation is # entirely in the normal mode, 1 mode. The following line of code sets the # number of sine waves to consider to be the maximum of either the number of # rows in the KM-matrix or half the number of identified peaks. The reason # for half the peaks, as opposed to all of them, is that np.fft.fft actually # returns pairs of frequencies (both positive and negative), and we only # care about the positive values. num _ sines = min(np.shape(km _ matrix)[0], int(len(peaks)/2)) 23.5 Fourier Analysis 275 # Print the identified peaks as both frequencies and angular frequencies print("Frequencies: ", freq _ domain[peaks[:num _ sines]]) print("Angular Frequencies: ", 2 * np.pi * freq _ domain[peaks[:num _ sines]]) # Calculate the sum of the height of all peaks. # The fraction of this "total _ height" will be the fraction each frequency # curve contributes to the total motion total _ height = np.sum(properties['peak _ heights'][:num _ sines]) print("Fractional amplitudes: ", properties['peak _ heights'][:num _ sines]/total _ height) # Plots fig, axs = plt.subplots(2, 1) # Plot the Fourier transform and peaks axs[0].plot(freq _ domain, ft) # plotting the transform for i in range(num _ sines): axs[0].plot(freq _ domain[peaks[i]], ft[peaks[i]], color = 'r', marker = 'x', linestyle = 'None') # plotting the peaks axs[0].set _ xlim(0, 1) axs[0].set _ title("Fourier Spectrum w/ Peaks Plotted") axs[0].set _ xlabel('Frequency [Hz]') # Sum the contribution from the different modes to find the reconstructed # signal y = 0 for i in range(num _ sines): a _ n = properties['peak _ heights'][i] / total _ height y += a _ n * np.cos(2 * np.pi * freq _ domain[peaks[i]] * t _ eval) # Plot original x _ 1 axs[1].plot(t _ eval, y)</p><p># Plot the reconstructed x _ 1 curve using Fourier-identified frequencies and # amplitudes axs <ref type="bibr">[1]</ref>.plot(t _ eval, r _ soln.y[0]) axs <ref type="bibr">[1]</ref>.set _ xlim(0, 100) axs <ref type="bibr">[1]</ref>.set _ ylabel('$x _ 1$') axs <ref type="bibr">[1]</ref>.set _ xlabel('Time [s]') plt.show()</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 16*</head><p>Complete the Fourier analysis of the situation from Exercises 10 and 11. To do this, copy the code from Exercise 13 into the cells below but adjust the KM-matrix to reflect the weakcoupling case from Exercise 10. Once again, let both carts start from rest with x 1 = 1 m and x 2 = 0 m. Because the normal frequencies in this situation are so similar, you will need to provide the Fourier transform with a longer temporal baseline to achieve sufficiently high frequency resolution. Therefore, make t _ span extend to at least 500 seconds. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="23.6">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 17</head><p>Briefly summarize the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="23.7">Challenge Problems</head><p>If you have extra time, consider the following questions.</p><p>1. Modify your km _ array for the case in which there is no second wall on the right side, so the problem is one of two identical springs and two masses, and the third spring is missing. Find the normal mode frequencies and show plots of the oscillations of x 1 and x 2 in the two modes, and then with x 1 = 1 m, x 2 = 0 initial conditions. You should see that the amplitudes are no longer symmetrical, so you can plot both x 1 and x 2 on the same figure. This is Problem 11.5 in Classical Mechanics. 2. Solve the same problem as shown in the previous challenge problem, but this time analytically. Compare your analytical results with those above. 3. Make a function km _ matrix _ maker(N, k _ value) whose input is the number N of identical oscillators and single-spring constant k _ value, and whose output is a km_array that is suitable for solving the eigenvalue problem. You may want to experiment with np.diag(np.ones(N)), np.diag(np.ones(N-1, k = 1), and np.diag(N-1, k =-1). Test it on the N = 2 and N = 5 cases. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>24</head><p>C haotic behavior is highly sensitive to initial conditions. In chaotic systems, small differ- ences in initial conditions grow exponentially. Because we cannot know precisely the initial conditions of any real-world phenomena, chaotic systems are effectively impossible to predict even though they are deterministic.</p><p>For a system to exhibit chaos it must be nonlinear and sufficiently complex. Throughout this course, you have seen multiple examples of how to approach analytically solvable linear differential equations (so they must not be chaotic). Nonlinear ones frequently cannot be analytically solved, allowing them to be potentially chaotic. Not all nonlinear systems are chaotic, though. For instance, a simple pendulum is nonlinear but very predictable. For a pendulum to exhibit chaotic behavior complexity must be added -here in the form of driving and damping.</p><p>Across the next three units, you will be investigating the behavior of a damped driven pendulum (DDP). This system can, but does not always, exhibit chaotic behavior. In this unit, you will study the approach to chaos by modeling the DDP in the chaotic and nonchaotic regimes.</p><p>The DDP system is introduced in Ch. 12.2 of Classical Mechanics, and the topics in this unit extend through 12.5 of the text. Bifurcation Diagrams and State-Space Orbits and Poincar&#233; Sections build on this unit with additional analysis of the system.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.1">Objectives</head><p>In this unit, you will</p><p>&#8226; model a damped, driven pendulum (PHY); &#8226; identify period doubling from the pendulum's motion (PHY); &#8226; follow the approach to chaos (PHY); &#8226; show that solutions generated from very similar initial conditions diverge for chaotic systems (PHY);</p><p>&#8226; fit the rates of convergence/divergence between solutions (PRO); and</p><p>&#8226; identify peaks in an array of data (PRO).</p><p>import numpy as np import matplotlib.pyplot as plt from scipy.integrate import solve _ ivp # the differential equation integrator from scipy.optimize import curve _ fit # for fitting the exponential decrease/ # increase between ICs plt.rcParams["figure.figsize"] = (12,8) # make figures big for easier viewing # (width, height in inches) # To revert to the small default size, uncomment the following line # plt.rcParams["figure.figsize"] = plt.rcParamsDefault["figure.figsize"]</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.2">Equation of Motion</head><p>Exercise 1</p><p>You will need to find the equation of motion for a damped driven pendulum to model its behavior numerically. To do this, start by determining the equation of motion for a simple pendulum and then add in damping and driving. For this exercise, please fill in your answers to each step below.</p><p>1) Starting with Newton's second law in angular form, &#8407; &#964; = I &#8407; &#945;, with &#964; = &#8407; r &#215; &#8407; F, determine the restoring torque for a simple pendulum (just a point mass m on a frozen massless string of length L).</p><p>2) Write down the moment of inertia of a point mass at radius equal to the length of the pendulum.</p><p>3) Hence the second derivative of the angle is some function of the angle. That is the equation of motion for a simple pendulum. Write it here (don't divide out any L and m terms yet).</p><p>4) Modify your equation of motion to include damping by adding a term to the force (torque) side of the equation to represent linear air resistance.</p><p>5) Further modify your equation of motion to include driving by adding a sinusoidal varying torque: LF 0 cos &#969;t.</p><p>6) Determine the natural angular frequency of a simple pendulum, &#969; 0 .</p><p>7) Now simplify your equation of motion and rewrite it in terms of &#969; 0 , a damping constant (&#946; = b 2m ), and a dimensionless parameter &#947; representing the drive strength (&#947; = F 0 mg ).</p><p>When &#947; is small, the weight of the pendulum is more than the driving force, and the resulting force will be small. As we increase &#947; above 1, the pendulum will start to display chaotic motion.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.3">Numerical Solution</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.3.1">Defining the constants</head><p>Let the angular frequency of the driving force (&#969; or omega _ drive) equal 2&#960; . Set the natural frequency (&#969; 0 or omega _ 0) equal to 1.5&#969; = 3&#960;, and set the damping constant (&#946; or beta _ damp) to &#969; 0 4 . We will be changing the drive strength (&#947; or gamma _ drive).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>What do you expect the period of the DDP to be?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 3</head><p>In the cell below, set all the constants associated with the system except &#947; as global variables.</p><p># &lt; Exercise 3, Initializing the constants omega _ drive, omega _ 0, and beta _ damp &gt;</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.3.2">Numerically solving the equation of motion Exercise 4</head><p>Use the equation of motion from Exercise 1 to write a function to return the derivatives of your two variables, &#966; and &#966;. You will be changing the value of &#947; throughout the unit, so rather than setting &#947; as a global variable, allow it to be passed as a parameter to the deriv() function.</p><p>To find solutions, you will also need a set of initial conditions. For the most part, you will have the pendulum start from rest in line with the vertical: &#966;(t = 0) = 0 and &#966;(t = 0) = 0.</p><p>Use solve _ ivp() to solve the equations of motion for 0 &lt; t &lt; 10 s and &#966; versus time, assuming &#947; = 0.2. Let rtol = 1e-8 and atol = 1e-8.</p><p>Finally, plot &#966;(t) versus t, and check to make sure that your graph makes sense. Your plot should match Figure <ref type="figure">12</ref>.2 of Classical Mechanics. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.4.2">Intermediate and strong driving</head><p>As &#947; is increased slightly above 1 (the divide between weak and strong driving), strange long-term behavior starts to appear.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7*</head><p>First, solve for &#947; = 1.06 and include the plot of &#966;(t) versus t for 0 &lt; t &lt; 12 s (Figure <ref type="figure">12</ref>.4 of Classical Mechanics).</p><p>In the space below, describe the motion of the pendulum before t = 6. (Hint: What does it mean for &#966; to be greater than &#960;?) Also, describe the long-term behavior of the pendulum, including the period.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 7, DDP with gamma = 1.06 &gt; ### CALCULATE trajectory ### PLOT phi versus t</head><p>As we increase &#947; , we begin to see the emergence of harmonics and subharmonics. For instance, &#947; = 1.073 produces long-term oscillations with alternating values of the maxima and minima. It now takes twice the period of the driving force for the cycle to repeat, so this motion has period two (alternatively, we could say that this has a subharmonic of half the driving frequency).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8</head><p>Solve for &#947; = 1.073 from 0 &lt; t &lt; 30 s and include the plot of &#966;(t) versus t below. Your plot should match Figure <ref type="figure">12</ref>.5 of Classical Mechanics, so you will want to run your solver out to 30 seconds.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 8, DDP with gamma = 1.073 &gt; ### CALCULATE trajectory ### PLOT phi versus t</head><p>Increasing &#947; further yet will produce longer period oscillations in a process called a "period-doubling cascade".</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9*</head><p>Try several more values of &#947; between 1.073 and 1.090 and identify a value of &#947; that produces longer-period oscillations. You may need to increase max _ time (the time range) in order to reach the point after the transients have died off. Include a plot below. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>Solve for &#947; = 1.16 and include the plot below. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.5">Sensitivity to Initial Conditions</head><p>One of the most important features of chaos is how very slight differences in the initial conditions can lead to wildly different behavior, i.e., "sensitivity to initial conditions". This is why a deterministic system can be, in a practical sense, completely unpredictable. This chaotic behavior has a host of real-world implications, including the inability of forecasters to accurately predict weather days in advance.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 11</head><p>Let us start at the non-chaotic end. Verify that completely different initial conditions (&#966;(t = 0) &#8801; 0 and &#966;(t = 0) &#8801; &#960;/2) lead to the same long-term behavior if the driving force is weak (&#947; = 0.7) by plotting the two curves (soln1 and soln2) on the same graph for 0 &lt; t &lt; 30 s. Additionally, plot the logarithm of the absolute value of the difference between the solutions as a function of time. For convergence, the difference between the solutions will decrease exponentially, thus falling in a straight line on this log-linear ("semi-log") plot. One way to check whether the difference between the solutions is actually decreasing exponentially and to learn how fast that decrease is happening is to fit an exponential to it. You will do this by using scipy.optimize.curve_fit to fit a linear function to log(|&#966; 1 -&#966; 2 |) vs. t (while you could also directly fit an exponential, the method isn't as robust).</p><p>As you saw when introduced to curve _ fit() in Exercise 9 from Taylor Series with Loops and Functions and in Exercise 15 from Simple Pendulum Comparison to Data and Theory, the first step is to define the functional form of the fit. To do this, in the cell below, define a function that returns m * x + b when passed the arguments x, b, and m.</p><p>Then, use scipy.optimize.curve_fit (already imported in the first code cell of this notebook as curve _ fit) to find the best-fit values of m and b. The form of the function call is popt, pcov = curve _ fit(fit _ func, x, y, p0)</p><p>where fit _ func is the name of the fitting function you defined above, x is your independent variable (time), and y is your dependent variable (log(|&#966; 1 -&#966; 2 |)). The variable p0 is an array of initial guesses for the parameters in your fitting function, in this case, a guess for b and a guess for m. The returned values are 1) the array of fitted parameters (popt) optimized such that the square of the residuals is minimized and 2) the estimated approximate covariance of the fitted parameters (pcov), which is a measurement of the quality of the fit.</p><p>After you have found the best-fit curve, plot it atop your graph of log(|&#966; 1 -&#966; 2 |) versus t.</p><p>What is the exponential decay rate for the differences between your solutions? </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13*</head><p>A better fit can be obtained by fitting to the peaks (local maxima) of the curve, rather than the entirety of the data. To do this, though, you must first identify the peaks. Luckily, the SciPy package comes with a function, scipy.signal.find_peaks, which can identify peaks in data.</p><p>The function find _ peaks() takes as an argument the data it should identify the peaks of (in this case, log(|&#966; 1 -&#966; 2 |)). It also allows for a number of optional keyword arguments, such as height, width, threshold, and prominence, that can be used to set the characteristics of the identified peaks. These keyword arguments are especially useful when dealing with noisy data. For this situation, setting width = 10 is sufficient. Doing so ensures that each peak is the maximum within the surrounding 10 data points, equivalent to a 10&#948;t = 10 &#215; 0.01 s = 0.1 span of time.</p><p>The return of scipy.signal.find _ peaks() is a numpy array of peak indices and a dictionary of peak characteristics. To set a variable to only the peak indices, use a call like pk _ indices = find _ peaks(data) <ref type="bibr">[0]</ref> to select the first return of the function.</p><p>Find a fit to only the peaks of the differences and plot your best-fit line on top of the data, as in Exercise 12.</p><p># &lt; Exercise 13, Better fit of the exponential rate of convergence &gt; # Import find _ peaks from scipy.signal from scipy.signal import find _ peaks ### USE find _ peaks to identify the indices corresponding to peaks in the data ### CALL curve _ fit with only those data points that correspond to peaks ### PLOT your best-fit over the graph of log(|phi _ 1 -phi _ 2|) vs. t # On your plot, use a different symbol and color to mark the peaks, # e.g., ' * r' Exercise 14* Redo the above plots for slightly stronger driving (1.073 &lt; &#947; &lt; 1.09). How do your plots fit with the behavior you observed in Exercise 9? # &lt; Exercise 14, Response to ICs in the period-doubling regime &gt; ### SOLVE for both trajectories ### PLOT phi versus t and # log|(phi _ 1 -phi _ 2)| versus t Exercise 15 Now redo the above plots for the primarily chaotic regime (&#947; = 1.105). Because very slightly different initial conditions should produce very different results, change your initial angles so that they are very close together, e.g., &#966;(t = 0) &#8801; 0 and &#966;(t = 0) &#8801; 0.0001. Your figure should be similar to Figure 12.13 from Classical Mechanics, which we note is plotted using log10, not the natural log. Your solution will probably diverge more quickly because of taking coarser time steps. How are your results indicative of chaos? Why does the difference between the angles not increase forever? # &lt; Exercise 15, Sensitivity to ICs in the chaotic regime &gt; ### SOLVE for both trajectories ### PLOT phi versus t and # log|(phi _ 1 -phi _ 2)| versus t Exercise 16* Repeat the steps in Exercise 12 for this new value of &#947;, this time fitting to the exponential rate of increase in the difference between the two solutions over time for the chaotic regime. If desired, you can fit only to the peaks, as in Exercise 13. Only compute your fit for the first 7 seconds of the solutions in order to focus on the increasing portion of the graph. &#10148; Hint 1: The syntax to select the elements in an array that fit some criteria, such as elements less than 2, is arr _ select = arr[arr &lt; 2] &#10148; Hint 2: Because you are once again fitting a linear function to the log of your dependent variable, there is no need to redefine a fitting function for use in curve _ fit(). What is the (approximate) exponential rate increase for the differences between your solutions? # &lt; Exercise 16, Fitting the exponential rate of divergence &gt; ### DEFINE an array of initial guesses for b and m ### CALL curve _ fit for log(|phi _ 1 -phi _ 2|) vs. t, assuming a linear fit ### PLOT your best-fit over the graph of log(|phi _ 1 -phi _ 2|) vs. t</p><p>Unfortunately, this high degree of sensitivity to initial conditions means that it is also very difficult to model chaotic systems computationally. Essentially, small rounding or integration errors in code get hugely magnified. The result is that the same initial conditions simulated with different degrees of tolerance will produce very different behavior.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 17*</head><p>To see this sensitivity to simulation accuracy, plot &#966; versus t for 0 &lt; t &lt; 40 s with gamma = 1.16, using different values of rtol and atol in your solve _ ivp() call. Start with tolerance rtol = 1e-8 and atol = 1e-8, and decrease by factors of ~10 down to the minimum tolerance (atol = rtol &#8776; 1e-13). In all cases, set phi0 = 0 and dphi0 = 0. You can remind yourself of the meaning of atol and rtol by reading the solve_ivp() documentation. Also, recognize that you will not be able to use your solve_DDP function as written. It will probably be easier just to use solve _ ivp() here directly or add the tolerance to the parameter list.</p><p>The sad upshot is that because the trajectories diverge, we are unable to predict with any accuracy the state of the pendulum after some length of time. (This is the same reason why weather predictions a week out are so inaccurate.) Luckily, though, we can still use simulations to tell us about the statistical properties of a chaotic system. You will be examining this in the following units through bifurcation diagrams and Poincar&#233; plots.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 17, Accuracy of integration in chaotic regime &gt; ### SOLVE for all trajectories ### PLOT phi versus t</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.6">Check-out</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 18</head><p>Briefly summarize the ideas in this unit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.7">Challenge Problem</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="24.7.1">A parametrically driven pendulum leading to instability</head><p>Rather than driving the DDP along the line of travel (i.e., perpendicular to the string), a simple modification leads to a surprising result. We periodically vary a parameter of the pendulum, perhaps its length or, in this case, the local value of gravity, by pulling up and down at a particular phase of the motion. To motivate this problem, imagine holding a simple pendulum in your hand and letting it oscillate normally at small amplitude.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>At what part of the cycle is the tension in the string the greatest? (Remember, it has some velocity L &#966;.) At what part of the cycle is the string tension the least?</head><p>Now consider what happens if you raise the center of mass precisely when the tension is the greatest, and lower it when the tension is the lowest. Notice that you will be doing (net) work on the system over the periodic cycle, and the system must respond by increasing its energy -its amplitude will increase.</p><p>Consider that for best effect, you could raise the pendulum bob on both halves of the cycle, and so applying a driver at twice the natural frequency, 2&#969; 0 , is most advantageous, leading to the strongest growth rate. We then expect lower growth rate if we raise it and lower it only once per cycle, or once every 1.5 cycles, etc.:</p><p>an infinite series of solutions! For more background on the theory and an experimental test of this, see: W. Case, "Parametric instability: An elementary demonstration and discussion, " Am.J.Phys. 48, <ref type="bibr">218-221 (1980)</ref>. Famously, this parametric instability is exploited to impressive effect at the Santiago de Compostela Cathedral, in Spain; see: J. R. Sanmartin, "O Botafumeiro: Parametric pumping in the Middle Ages, " Am.J.Phys. 52, <ref type="bibr">937-945 (1984)</ref>. See the video here: <ref type="url">https://www.youtube.com/watch?v=rOoHyEEXxoA</ref> </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Challenge Problem, Part 1</head><p>Following Case's Eq. (3), we write the driving term as: &#964; mL = y 0 L &#969; 2 sin &#966; cos &#969;t where y 0 is the vertical displacement of the attachment point of the pendulum, &#966; is the angle from the vertical, and &#969; is the driving frequency. In brief, the vertical motion of the attachment point, y = y 0 cos &#969;t, adds a fictitious force in the reference frame of the pendulum equivalent to m&#255; = y 0 &#969; 2 cos &#969;t. To select for the tangential component of that force, we multiply it by sin &#966;, just as is done with the gravitational acceleration. (Note that in Case <ref type="bibr">[1980]</ref>, the small-angle substitution is used to replace sin &#966; with &#966;.) This driving term will replace &#964; mL = F 0 mL cos &#969;t from the previously analyzed damped driven pendulum system. One fundamental change is the introduction of &#966; to the driving term. Note that this introduction means that nothing happens if &#966; = &#966; = 0; that is, if the pendulum starts at the equilibrium location with no velocity in &#966;, it will not oscillate. But if &#966; is initially some vanishingly small angle but not quite zero, the driving term can get bigger, because its size depends upon &#966;. What happens next depends entirely on the frequency &#969; of the driver.</p><p>Create a new deriv() function, deriv_para, that describes this equation of motion, including damping. Define a new variable h 0 &#8801; y 0 L to characterize the strength of the driving. This variable will take the role of &#947; , and should be a parameter of deriv _ param, along with t _ now and r _ now. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Challenge Problem, Part 3</head><p>As you can see, for this particular set of &#969;, y 0 , and &#946;, the amplitude grows. The shape of the envelope appears to be exponential. Go ahead and see if this is the case by fitting an exponential to the amplitudes. The procedure to do this is very similar to what you used in Exercise 13: Identify the peaks, then fit a linear function to the log of the peaks. L &#969; 2 is not dimensionless but is &#969; 2 larger than h 0 above. At the peak of the instability in the small-angle regime, he predicts a growth rate &#955; max = h/(2&#969;) = h 0 &#969; 2 /(2&#969;) = h 0 &#969; 0 . Compare your value of the initial growth rate with h 0 &#969; 0 for these parameters:</p><p>beta _ damp = 0.0, h _ drive = 0.05 # &lt; CP 4, Comparison &gt; I n the previous unit, Damped Driven Pendulum, the damped driven pendulum was shown to demonstrate period-doubling and chaotic behavior under certain conditions. Specifically, you were asked to look at &#966; as a function of time for different values of forcing, &#947;. By examining the trajectory of the pendulum over time, it was clear for some values of &#947;, the pendulum exhibited periodic behavior with the same period as the forcing. For other values of &#947; (i.e., within the period-doubling regime), the pendulum exhibited periodic behavior with longer periods, and for others it exhibited no periodic motion at all, otherwise known as chaos.</p><p>This observation, however, raises the questions: for what values of the forcing amplitude &#947; is the system periodic, and how does the system transition between these different states? An elegant way to display how the behavior of the system changes for different values of &#947; is the bifurcation diagram. In a bifurcation diagram, the value of &#966; after every period of the forcing is plotted against &#947;. In this unit, you will recreate Figure <ref type="figure">12</ref>.17 from Classical Mechanics by generating the bifurcation diagram for the damped driven pendulum. Walking through the different steps should give you a better understanding of how the bifurcation diagram encodes information.</p><p>The material for this exercise is covered in Ch. 12.6 of Classical Mechanics.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="25.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; create and import a module that models the damped driven pendulum (DDP) (PRO);</p><p>&#8226; learn how to use a spline fit to interpolate between data points (PRO);</p><p>&#8226; generate the bifurcation diagram for the DDP (PHY);</p><p>&#8226; interpret the bifurcation diagram in the context of the approach to chaos (PHY);</p><p>&#8226; read and write data from files such that multiple groups can contribute to a shared bifurcation diagram (PRO).</p><p>import numpy as np import matplotlib.pyplot as plt plt.rcParams["figure.figsize"] = (12,8) # make figures big for easier viewing # (width, height in inches) # To revert to the small default size, uncomment the following line # plt.rcParams["figure.figsize"] = plt.rcParamsDefault["figure.figsize"]</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="25.2">Setup</head><p>In order to understand how a bifurcation diagram is generated, you will start by looking at both plots of &#966; versus t and the bifurcation diagram for individual values of &#947;. To do that, though, you first need your code to solve the DDP.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 1</head><p>As you may have encountered in Precession of a Cuboid, creating and importing modules is an elegant way to share code between notebooks. For this notebook and State-Space Orbits and Poincar&#233; Sections, you will want to use the code you wrote in Damped Driven Pendulum. Therefore, the first step is to copy the necessary code for modeling the damped driven pendulum system from Damped Driven Pendulum into a "module".</p><p>Create a new file named damped _ driven _ pendulum.py located in the same directory as this notebook (this would require uploading it to CoCalc or to your "Colab Notebooks" directory on Google Drive if you are using Google Colab). (If you are using Google Colab, you will also need to mount the Google Drive by uncommenting the code in the cell below.) At the top of the file, include import numpy as np from scipy.integrate import solve _ ivp as these libraries will be necessary for the functions (if you check the cell above, you'll see that solve _ ivp() is not imported in this notebook as it is only used within functions contained in your module). Then copy over the following:</p><p>&#8226; the constants defining the system from that notebook as in Exercise 3. Specifically, set the angular frequency of the driving force (&#969; or omega _ drive) to 2&#960;, producing a drive period of 2&#960;/&#969; = 1 s. Set the natural frequency (&#969; 0 or omega _ 0) to 1.5&#969;, and set the damping constant (&#946; or beta _ damp) to &#969; 0 4 . &#8226; the entirety of deriv() from Exercise 4.</p><p>&#8226; the entirety of solve _ ddp() from Exercise 5. The next step is to import everything contained in this file (a.k.a. module). You could do this by running the following statement: import damped _ driven _ pendulum as ddp where "as ddp" defines an alias for "damped_driven_pendulum" and saves you some typing.</p><p>After importing this module, you should be able to access the constants and functions defined in it by affixing "ddp. " to the start of it, e.g., ddp.solve _ ddp(). Test this by printing ddp.omega _ 0 after importing.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="25.3">One</head><p>-Point Bifurcation Diagram 295 # FOR GOOGLE COLAB ONLY # &lt; Exercise 1, Importing a module &gt; ### UNCOMMENT the following lines if you are using Google Colab #from google.colab import drive ## Mount your Google Drive #drive.mount('/content/drive/') #import sys # Append your Google Drive "Colab Notebooks" directory to your # path so that files contained in it may be found #sys.path.append('/content/drive/My Drive/Colab Notebooks/') # &lt; Exercise 1, Importing a module &gt; ### IMPORT your module</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 2</head><p>Now call ddp.solve _ ddp() to model a damped driven pendulum with the following characteristics:</p><p>&#8226; &#947; = 1.062,</p><p>&#8226; &#966;(t = 0) &#8801; -&#960;/2, and &#8226; &#966;(t = 0) &#8801; 0 for 0 &lt; t &lt; 600.</p><p>Then, check your work by plotting &#966; versus t. In order to make your plot legible, set your x-axis range from 0 to 30 s. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="25.3">One-Point Bifurcation Diagram</head><p>The next step is to add a single value of the forcing amplitude parameter &#947; to the bifurcation diagram by plotting the angle after each period of the driver at that value of &#947;. To do this, though, you will need to find the values of &#966; for any given time. You will be sifting through your &#966; solution, picking out values every 1 second, the period of the driver. Unfortunately, your integrator only returns &#966; and &#966; for the discrete times listed in your time array, which may not correspond to a multiple of the driver period. So, you will need to interpolate between those points if the time you want does not correspond to one of the values in your time array. A "spline" fit does exactly that. (A third-order spline fit may be familiar to you from Excel's scatter plotting option that connects points with a smooth curve.) markersize argument. Setting linestyle = 'None' ensures the points are not connected on the plot.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head># &lt; Exercise 5, Add a single value of gamma _ drive to the bifurcation diagram &gt; ### PLOT phi _ t _ ind versus gamma</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="25.4">Creating the Full Bifurcation Diagram</head></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6</head><p>The above bifurcation diagram is very boring because all 100 &#966; values that occur in sync with the driver period have the same value. If we steadily increase &#947;, we can explore more interesting regions, mapping out a complete bifurcation diagram. To make a full one, create a for loop to cycle through different values of &#947; for 1.06 &lt; &#947; &lt; 1.087. For each value of &#947;, plot the values of &#966;(t) at every t (period of the forcing) versus &#947;. At the end of the loop, call plt.show() to display all of the accumulated data sets. You should get something that looks like Figure <ref type="figure">12</ref>.17 from Classical Mechanics. You will see that the phase is no longer single-valued as the forcing amplitude gets higher.</p><p>&#10148; Hint 1: When you are first writing your for loop, increase the value of &#947; by relatively large steps (say, 0.001). Then, once you are sure your code is working properly, decrease the step size to fill in the diagram.</p><p>&#10148; Hint 2: Try using the pixel marker for this plot, e.g., using marker = ',' as a keyword argument in your plot command.</p><p>Be patient -these plots can take a long time to render. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>What ranges of &#947; produce chaotic behavior? What ranges produce period doubling?</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="25.6">Class Parallel Programming Project</head><p>One way to speed up the creation of your bifurcation diagram is to have multiple computers working at once. This type of problem is sometimes called an "embarrassingly parallel" (or "perfectly parallel") problem because the lack of interaction between the different calculations makes it exceedingly easy to compute in parallel. In other words, one computer could be working on solving the trajectory for one value of &#947; , and another computer working on a different value of &#947; with no need for them to share information beyond divvying up the values of &#947;. Next, each team should have its computer find the values for a bifurcation diagram that uses the same 0.001 spacing but with different starting values, as calculated above. The results should then be written into a file so that they can be combined for the final plot.</p><p>In Simple Pendulum with Large Angle Release, you wrote NumPy arrays of data to a CSV file using numpy.savetxt. The organization of the data is more complex this time; rather than having two NumPy arrays to write, you will have different arrays of data for each value of &#947;. Therefore, we will exert more control over the file writing by following these steps. where fname is the variable that stores the name of the file (e.g., fname = "bifurcationDiagram1.csv") and fh is the file handle and how you will refer to the file throughout the program. The "w" tells the computer that you wish to write to the file. is to plot d&#966;/dt rather than &#966;(t). The same physical locations can correspond to different values of &#966;(t) because the pendulum may have rolled over and increased its value by 2&#960;. The angular velocity, d&#966;/dt, however, does not share this degeneracy. Therefore, bifurcation diagrams using angular velocity rather than the angle will be cleaner.</p><p>If you have time, consider completing the following:</p><p>&#8226; Remake a bifurcation diagram plotting d&#966;/dt versus &#947; for 1.06 &lt; &#947; 1.53 (Figure <ref type="figure">12</ref>.18 from Classical Mechanics). To what extent does increasing &#947; result in greater amounts of chaos?</p><p>25.9 Template Code for damped_driven_pendulum.py </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>26</head><p>I n Damped Driven Pendulum, you were introduced to the damped driven pendulum (DDP) system and explored how its periodicity and sensitivity to initial conditions changed with the driving force. Then, in Bifurcation Diagrams you created and used a bifurcation diagram to further explore period doubling and the approach to chaos. In this unit, you will move from plotting &#966;(t) versus t to plotting &#966;(t) versus &#966;(t), i.e., the state-space orbit. State-space orbits offer a powerful and elegant way to display information about a system. Taking a cross section of state-space orbits, known as a Poincar&#233; section, offers a pared-down view of the system's motion and, in so doing, reveals a mysterious pattern within the chaotic motion known as a "strange attractor". After creating full state-space orbital plots, you will plot and interpret Poincar&#233; sections of the DDP.</p><p>The material for this unit is covered in Ch. 12.7 and Ch. 12.8 of Classical Mechanics.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="26.1">Objectives</head><p>In this unit, you will:</p><p>&#8226; construct state-space diagrams and Poincar&#233; sections for the damped driven pendulum system (PHY);</p><p>&#8226; identify signatures of the non-chaotic, period doubling, and chaotic behavior in statespace diagrams and Poincar&#233; sections (PHY);</p><p>&#8226; produce a Poincar&#233; section showing a strange attractor, and determine what parameters affect it (PHY). In general, simply plotting angular velocity versus angle will produce nice state-space diagrams for low values of &#947;. As we increase &#947;, though, we can run into problems.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 6</head><p>For example, try producing a state-space orbit for &#947; = 1.400 (using the same initial conditions as previously and only integrating from 0 &lt; t &lt; 6 s; Figure <ref type="figure">12</ref>.25 from Classical Mechanics).</p><p>Qualitatively describe the motion of the pendulum.</p><p># &lt; Exercise 6, Plot state-space orbit for gamma = 1.400 &gt; In order to get a more usable state-space diagram, we will want to limit &#966; to between -&#960; and +&#960;. For example, if our numerical solution gives us a value of &#966; = &#960; + 1, we would like our plot to interpret that as -&#960; + 1. Similarly, &#966; = 2&#960; + 1 should be interpreted as &#966; = 1.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 7</head><p>Adjust your code to produce a state-space plot so that the values plotted on the &#966;-axis range between -&#960; and +&#960;. Then verify your code by showing &#966; versus &#966; for &#947; = 1.400 (Figure <ref type="figure">12</ref>.26 from Classical Mechanics). Continue to use the same initial conditions and integrate from 0 &lt; t &lt; 30 s.</p><p>&#10148; Hint: The modulo operator designated by "%", will be particularly helpful for modifying the angle. What the modulo operator does is return the remainder of a division problem. For example,</p><p># &lt; Exercise 7, Plot state-space for gamma = 1.400 eliminating rollover &gt;</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 8*</head><p>What is the source of the horizontal lines across your state-space plot?</p><p>If they bother you, you can plot the data points alone without the connecting lines using something like ax.plot(x, y, marker = '.', linestyle = 'None').</p><p>It is also possible to eliminate the connecting lines by inserting np.nan into the &#966; and &#966; arrays at each "rollover" using numpy.insert. That is because Matplotlib will automatically create a break in the plot when it encounters not-a-number values. The challenge is to find the indices where the jump occurs. For that, remember that the Boolean "True" is evaluated as one and "False" is evaluated as zero. Therefore, if you can write a logical statement that will evaluate True or False if there is a jump, the numpy.nonzero can be used to return the indices corresponding to True.</p><p># &lt; Exercise 8, Plot state-space orbit for gamma = 1.400 without horizontal # lines &gt;</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="26.4">Poincar&#233; Sections</head><p>So far, our state-space diagrams have been very simple. However, once we enter the chaotic regime, they can get complicated fast. Therefore, we are going to introduce another new tool: Poincar&#233; sections. Rather than plotting the entire state-space orbit, Poincar&#233; sections only plot the position in state-space once every period. This is analogous to a bifurcation diagram where the &#966; value every period is plotted against &#947;. The difference is that here we will plot angular velocity versus angle every period, rather than just the angle every period versus the forcing parameter.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 9</head><p>Based on your previous state-space plot for &#947; = 0.600 (Exercise 2), predict what the Poincar&#233; section will look like.</p><p>In order to plot the Poincar&#233; section, you will need to know the values of the angle and angular velocity at specific times. Because the times you want may not have been included in your time array, you will need to interpolate across data points. As you found in Bifurcation, a spline function is the easiest way to do this.</p><p>As a reminder, to use scipy.interpolate.UnivariateSpline from scipy.interpolate library, the fit must first be created using a line of code similar to spl = UnivariateSpline(x, y, k = 5, s = 0) where x and y are the data arrays and the arguments k and s define qualities of the fit. Setting k to 5 tells it to compute a fifth-degree polynomial fit, while setting s to zero tells it to go through all the data points. Once the fit has been created, y _ fit = spl(x _ fit) will determine the y-values that correspond to an array of x locations contained in x _ fit.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 10</head><p>Create a Poincar&#233; section for &#947; = 0.600. To do this, use a spline fit to find the values of &#966;(t) and &#966;(t) across time. Then create an array of discrete times from 20 to 60 s in steps of the drive period: t = 2&#960;/&#969; D . Because the spline fits, phi _ fit and ang _ vel _ fit, are smoothed functions over your original time array, you can use the number array as indices to create a new array of the values at every drive period from t = 20 to t = 60 s, e.g., phi _ fit(time _ steps).</p><p>Starting at t = 20 rather than t = 0 means that transient behavior is eliminated. You still have to modify your phi _ fit(time _ steps) values to lie between -&#960; and &#960; as you did in Exercise 7. Then plot (as points rather than a line) your angular velocity spline versus your modulo &#966; spline at those discrete time steps. Set your axis range to -&#960; &lt; &#966; &lt; &#960; and -15 &lt; &#966; &lt; 15 so that it will be easily comparable to your plot from Exercise 2.</p><p>&#10148; Hint: When using the plot command, the smallest marker is a single pixel, designated by a ','.</p><p>Slightly larger markers are designated with a '.' and a keyword argument marker size (ms = 4, for example). Some options:</p><p>plot(x, y, ',k') # black dot (single pixel) plot(x, y, '.b') # blue dot, small plot(x, y, '.r', ms = 4) # red dot, medium plot(x, y, ',g', ms = 100) # green dot (single pixel; ms is ignored)</p><p>In the above example code, Python interprets the color and marker style arguments without your needing to write marker = ',' and color = 'k' as the equivalent. As an added bonus, writing the arguments in this way eliminates the need to use linestyle = 'None' to ensure the lines are not requested. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 12*</head><p>For each of &#947; = 1.078 (Figure <ref type="figure">12</ref>.28 from Classical Mechanics), 1.081, and 1.105, generate a state-space plot and predict what the Poincar&#233; section will look like. Then, generate the Poincar&#233; section. Include all of your plots and predictions below. Adjust your axes ranges and the maximum integration times to produce good plots.</p><p>&#10148; Hint: For this specific set of values of &#947;, you may prefer to not modify &#966; to lie between -&#960; and &#960; for your state-space diagrams. Please do make the modification before computing the Poincar&#233; section, though. </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="26.5">Strange Attractors</head><p>As you can imagine, things get very exciting in the chaotic regime. You will start by looking at the system for &#946;= &#969; 0 8 and &#947; = 1.500.</p><p>&#10148; Hint: The variables for &#969; 0 and &#946; in your damped_driven_pendulum module are accessible as ddp.omega _ 0 and ddp.beta _ damp, respectively.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Exercise 13</head><p>First plot &#966; versus t and the state-space diagram for the above values. # &lt; Exercise 14, Poincar&#233; section for gamma = 1.5 and beta _ damp = omega _ 0/8 &gt; ### INCLUDE code to plot Poincar&#233; section for gamma = 1.5 and # beta _ damp = omega _ 0/8</p><p>Initially, it seemed like there was no underlying structure to the motion of a chaotic pendulum. Now, however, we see that there is. The pattern you have produced is called a "strange attractor, " something typical of chaotic systems. Although not very evident here, strange attractors have a fractal structure. Now, regenerate your plot &#946; = &#969; 0 8 and &#947; = 1.500. However, start your series of time instances at a slightly different moment in time. For instance, rather than plotting angular velocity versus angle at t = 20.0, 21.0, 22.0, 23.0 s, and so forth, plot them for t = 20.05, 21.05, 22.05 s, and so on. Therefore, your plot will still generate a point once per period of forcing but at a slightly later time. Slowly increase your starting time in steps of 0.05 s to see how the sections of the Poincar&#233; plot evolve.</p><p>&#10148; Hint 1: You only need to find the numerical solution to the DDP once; it is only the spline fit to the new set of times that will need to be recalculated.</p><p>&#10148; Hint 2: You can compute each plot individually, but a better method is to loop through the offsets in time.</p><p>The following cell illustrates how solve _ ivp() may be called to solve the simple harmonic oscillator using the deriv() function defined above. The cell below will print the solution returned by solve _ ivp() and show how to access different portions of that solution, such as the times, positions, and velocities. In this example, the first row of r _ soln.y (accessible as r _ soln.y[0]) contains the positions, and the second (accessible as r _ soln.y [1]) contains the velocities. We can plot or otherwise analyze the results by accessing the appropriate rows in r _ soln.y and the time steps stored in r _ soln.t. print("Minimum time spacing: {0:3.2e} s\nMaximum time spacing: {1:3.2e} s". format(min _ step, max _ step)) Oscillations of a spring-mass system, t_step_min = 2.00e-03, t_step_max = 1.41e+00 Position Velocity</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="27.1.1">Time steps</head><p>The solution above appears choppy because the integrator is picking relatively large time step sizes to solve for the final solution. This strategy allows the integrator to achieve the desired level of accuracy for the solution at the final time using the minimum number of time steps (and, therefore, the fastest computational speed). If you look closely at the graph or the list of the first 10 times from the output above, you may notice that the separation between time steps is not constant. The integrator is making the decision to take smaller steps when the system is quickly changing (i.e., the derivatives are large) than when it is more stable. The integrator will use smaller time steps if the parameter max _ step is used in solve _ ivp() to set the maximum time step. An example of this is shown below. ax.plot(r _ soln.t, r _ soln.y[0],'-o') # position ax.plot(r _ soln.t, r _ soln.y[1],'-o') # velocity ax.legend(["Position", "Velocity"]) ax.set _ xlabel("Time [s]") ax.set _ ylabel("Position [m] and Velocity [m/s]") ax.set _ title("Oscillations of a spring-mass system, t _ step _ min = {:.2e}, " "t _ step _ max = {:.2e} ".format(min _ step, max _ step)) plt.show() Minimum time spacing: 2.00e-01 s Maximum time spacing: 2.00e-01 s 0.0 2.5 5.0 7.5 10.0 12.5 15.0 17.5 20.0 Time [s] 1.00 0.75 0.50 0.25 0.00 0.25 0.50 0.75 1.00 Position [m] and Velocity [m/s]</p><p>Oscillations of a spring-mass system, t_step_min = 2.00e-01, t_step_max = 2.00e-01 Position Velocity</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="27.1.2">Accuracy</head><p>By default, solve _ ivp() allows for a somewhat high level of inaccuracy in order to complete the calculations quickly. If you would like to require higher accuracy, the optional parameters rtol and atol in solve _ ivp() may be set. These define the maximum relative and absolute tolerance, respectively. Specifically, local error estimates are required to be less than atol + rtol * abs(y). The default values are 1e-3 for rtol and 1e-6 for atol.</p><p>Setting both to 1e-11 or 1e-13 will be more than sufficient for all of these exercises. When making this change, it is also helpful to set the integration method to LSODA, as that method is able to achieve high computational speeds in situations where high accuracy is desired.</p><p>Below is an example of how to call solve _ ivp() with these changes, and it shows how the accuracy is increased. # Plot the residuals on a logarithmic scale for the two different numerical # solutions compared to the analytical solution np.seterr(divide = 'ignore') # suppress error warning generated by taking # log(0) fig, ax = plt.subplots() ax.plot(r _ soln _ default.t, np.log10(np.abs(resid _ default))) # position residual ax.plot(r _ soln _ better.t, np.log10(np.abs(resid _ better))) # position residual ax.legend(["Error in default solution", "Error with lower tolerance solution"]) ax.set _ xlabel("Time [s]") ax.set _ ylabel("Log(|Computational position -Analytical position|)") ax.set _ title("Error in Computational Solution") plt.show() (If you are using Google Colab, you will also need to mount the Google Drive by uncommenting the code in the cell below.) path = './' # FOR GOOGLE COLAB ONLY ### UNCOMMENT the following lines if you are using Google Colab #from google.colab import drive ## Mount your Google Drive #drive.mount('/content/drive/') #import sys # Append your Google Drive "Colab Notebooks" directory to your # path so that files contained in it may be found #sys.path.append('/content/drive/My Drive/Colab Notebooks/') #path = '/content/drive/My Drive/Colab Notebooks/' # Create some data x _ arr = np.arange(0, 10, 0.3) y _ arr = np.sin(x _ arr) # Name the new file with an extension such as .txt, .dat, or .csv so that other # programs can open it fname = path + "myfile1.csv" # Open the newly named file for "w" writing or "a" appending fh = open(fname, "w") # Choose "w" for a new file. You could also choose "a" # to open an existing file and continue writing at # the end of it # Use a loop to step through the elements in the array. "zip" allows the user # to step through both arrays together for x _ data, y _ data in zip(x _ arr, y _ arr): # Write the formatted data file as comma-separated values (CSV) fh.write("{:5.3f},{:5.3f}\n".format(x _ data, y _ data)) # Include a newline # \n format # character # to mark the end # of each line # Close the file so that another application can use it fh.close()</p><p>If you open myfile1.csv in a text editor (look for it in your Google Drive "Colab Notebooks" folder, if you are using Google Colab), it will have the expected (x,y) format. Note that we achieved this by writing the file line-by-line with a for loop. If you had written the entire arrays (x_arr, y_arr) they would appear as all x-values, followed by all y-values. No one wants to read a file in that format.</p><p># Read in a data file using Pandas # Create an object called a 'DataFrame' by reading in a file df = pd.read _ csv(path + "myfile1.csv") # myfile1.csv was created using # "write()" in the first # section: "Read/Write # to a File Handle" print(df) 0.000 0.000.1 0.3 0.296 0.6 0.565 0.9 0.783 1.2 0.932 1.5 0.997 1.8 0.974 2.1 0.863 2.4 0.675 2.7 0.427 3.0 0.141 3.3 -0.158 3.6 -0.443 3.9 -0.688 4.2 -0.872 4.5 -0.978 4.8 -0.996 5.1 -0.926 5.4 -0.773 5.7 -0.551 6.0 -0.279 6.3 0.017 6.6 0.312 6.9 0.578 7.2 0.794 7.5 0.938 7.8 0.999 8.1 0.970 8.4 0.855 8.7 0.663 9.0 0.412 9.3 0.124 9.6 -0.174 9.9 -0.458</p><p>You will see that this is nice, but not perfect. Pandas assumes that the first line in the file contains header information, a label such as "x", "y", "time", or "distance". Note also that the index number 0 is shown on the second line. Subsequent lines give a row number (such as used in a spreadsheet) that is not part of the DataFrame itself.</p><p>to store floating-point numbers with limited precision, you can have a vastly greater storage compression. Briefly, here's how you store and retrieve binary data in Python.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="28.4.1">Writing binary data</head><p>Python writes and reads binary files with the .npy extension. It stores (single-precision) integers as 4 bytes (32 bits), which is 8 hexadecimal digits, because a hexadecimal digit takes 4 bits (representing 0-9, A-F). Double-precision integers, or so-called long integers, are stored in 8 bytes. Now try this code to create an array, display the contents in binary, and write the array to a file in binary format. On a 64-bit computer, the native size of integers is 8 bytes. If you have Notepad++ installed, you can take a closer look at the file. If you do not have Notepad++ installed, you will not be able to complete all of the following subsection, but you may still find it informative to learn how data is stored in binary and hexadecimal.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Examining a binary file</head><p>Open up binary.npy in Notepad++ and take a peek. (The bytes won't encode correctly in Notepad, so don't even try.) It should show you a header line of reasonable text explaining the format of the file, and something about its byte ordering, followed by a line of more or less ASCII-code representation of the raw bytes. (Google "ASCII chart" if you want to see a complete list of the 127 characters. ASCII stands for American Standard Code for Information Interchange. It is a 7-bit standard, where A = 65 decimal, B = 66 decimal, and so on. Anything below 32 decimal is unprintable, but meaningful to a printer, and includes such gems as 10 decimal = A hexadecimal = LF, "line feed", which in the Python or C world is designated as "\n", or newline, and 13 decimal = D hexadecimal = CR, "carriage return", which, don't laugh, comes from returning the typewriter-like carriage that holds the paper to its left-most position. Python understands this as "\r". All of the bytes consist of pairs of hexadecimal digits.) Select all of the characters on the next line, and convert them from ASCII codes into hexadecimal via the Plugins/Converter ASCII-&gt;HEX menu in Notepad++.</p><p>You should see something like this (on a 64-bit Intel computer):</p><p>You can verify that this is the same hexadecimal encoding that Python uses by printing the same data array in hexadecimal using the cell below. = 16, we need two hex digits to store it: 00011110</p><p>Starting from the right side, which is 2 0 = 1, the presence of a 1 indicates whether that bit is needed to represent the number. Add up all the bits and you have your answer: 128x0 + 64x0 +32x0 + 16x1 + 8x1 + 4x1 + 2x1 + 1x0 = 30 decimal.</p><p>This gets encoded as two hexadecimal digits (each one takes 4 bits), 1 and E, where the 1 represents 2 4 = 16, and E represents 14, hence the decimal result is 30.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="28.4">Binary Data 343</head><p>Try storing a decimal number bigger than 255 (but less than 65,535) in a binary file, by modifying your code above to store some bigger numbers, which will now take two bytes (4 hex digits) each.</p><p># Here we illustrate with the 10 integers from 300 to 309</p><p>x _ arr = np.arange(300,310,1) print('x _ arr:\n', x _ arr) np.save(path + "binary2.npy", x _ arr) print("x _ arr in binary format:") for index, x in enumerate(x _ arr): </p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="29.3">Call the Animator</head><p>Generating the animation requires repeatedly calling the animate() function and then determining how those generated frames will be looped through. The method to generate the animation is animation.FuncAnimation(). It takes as required arguments the figure object (fig) and the name of the function that generates the frames (animate). We also use the following keyword arguments in our example:</p><p>1) init _ func takes the name of a function that creates the base frame. If you would like to pass arguments to this function beyond the frame count, this can be done using functools.partial. The syntax is to replace the name of the function in the argument with a call to partial that contains the function name and all the arguments except the frame count: 2) When blit is set to True, blitting is used to determine which parts of the figure change, and then only those parts are updated. For the blitting algorithm to work, the function called to generate the frames and the function set by init _ func must return an iterable (e.g., a list or tuple) of the artists. 3) frames provides the source of the frame count to pass to the animate function to generate each frame. It may be an iterable, int, or generator function. If it is an iterable, then each of the values of it will be passed to the function to generate the complete set of frames. If it is an int, then the argument sets the number of frames to be generated; this is equivalent to passing range(frames). 4) interval sets the time delay between each frame in milliseconds. By default, it is set to 200. Typically, you will need a smaller number to create a visually smooth animation.</p><p>Frames per second, fps, is the inverse of interval. Therefore, a "cinematic" frame rate of 24 fps equates to an interval of about 42 milliseconds.</p><p>For a full list of keyword arguments, see the online documentation of animation.FuncAnimation. 29.3 Call the Animator 353 # Define the static plot elements # a support, consisting of a horizontal line ax.axhline(1) # a pivot point pivot = patches.Circle((0,1), 0.01, fc ='DarkSlateGray', ec = 'black', lw = 2 , zorder = 3) # "zorder = 3" ensures that it will # be plotted on top ax.add _ patch(pivot) # Define the plot elements that will change each frame: # a string line, = ax.plot([], [], lw = 2, color = 'k') # a mass at the end of the string bob = patches.Circle((pnd.x _ pos(0), pnd.y _ pos(0)), 0.02, fc ='red', ec = 'black', lw = 2 , zorder = 3) # a title title = ax.text(0.15, 0.9, '', horizontalalignment='left', verticalalignment='bottom') # ------NEW CODE------interval = 40 # [milliseconds], interval of time between frames # Calculate the number of frames needed to show five complete cycles of the # pendulum using 5 * period/(the interval of times between frames). Since # num _ frames needs to be an integer, we use int() to round down num _ frames = int(5 * pnd.period/(interval * 1e-3)) anim = animation.FuncAnimation(fig, animate, init _ func = init, blit = True, frames = num _ frames, interval = interval) plt.show()</p><p>When generating an animation, it is important to consider how long you would like the animation to run and what content you would like to display. For example, here we wanted to show five complete cycles of the pendulum, and we wanted it to run in real time (i.e., one second of the animation running should show one second of pendulum motion). To make the animation run in real time, the time we calculate the pendulum position for should correspond to the frame number times the interval between frames: ftime = i * interval * 1e-3, as seen in animate() (the factor of 1e-3 is because the time interval is measured in milliseconds). We chose the value of interval to result in visually smooth motion.</p><p>In other situations, you may wish to animate a range of behavior within a certain amount of time. For example, what if you wanted to fit five periods of the pendulum into five seconds? In that case, the maximum time you would want to calculate the pendulum position at would be five times the period (about 10.03 seconds), and you would want to fit it into five seconds of animation, resulting in a speedup of about a factor of two. For a frame interval of 40 milliseconds, we would need 125 frames: (length of animation) = (interval)&#215;(number of frames). Therefore, a frame count of 125 should correspond to 10.03 seconds for the pendulum calculation, requiring us to set ftime = i * 10.03/125 in animate().</p><p>For many of the exercises in this text, you will have an array of values generated by your numerical integrator that you would like to animate. In these situations, you can use the frame count as the index of the array, e.g., x _ pos = x _ arr[i]. Frequently, though, you will only want to generate frames for a subsample of the data array. Multiplying the frame count by a constant to get the index enables you to sample the data using a longer cadence, e.g., x _ pos = x _ arr[5 * i]. The same timing considerations outlined above must be taken into account here. So, if you want to use 125 frames to represent data from an array of 1000 elements, you would want to sample every eighth point, because 1000/125 = 8.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="29.3.1">Display and save your animation</head><p>Now that we have created an animation, the final step is to display and/or save it. Unfortunately, displaying and saving animations is highly system-dependent. If you experiment, though, you should be able to find a process that works for you using the guidance below.</p><p>If you are running Jupyter Notebook on your own computer and if you uncommented %matplotlib qt in the code cell above, your animation may have "just worked". This line of code allows a separate window for interactive plotting to be opened, wherein your animation should have appeared. This interactive plotting window gives you the opportunity to save your animation by selecting the floppy disk icon.</p><p>In other situations, though, you will want to have the animation display within the notebook. And if you are running on a web server such as Google Colab or CoCalc, you will need another option just to get the animation to show at all. Our preferred option throughout this text is to use to _ jshtml to generate an HTML representation of the animation and show it using display. A similar method is to convert it to an HTML5 &lt;video&gt; tag by using to _ html5 _ video(). This method is recommended for users of CoCalc and will also work with Google Colab. In all cases, the number of frames per second can be designated using the keyword argument fps. Index A absolute, numpy, 186 and, 46 append, numpy, 66 arange, numpy, 33 arcsin, numpy, 127 arctan2, numpy, 156 argument, functions, 53 arrays, 31 axhline, matplotlib, 81 B binary, 341 break, 49 C calculations, 27 chdir, os, 302 Circle, matplotlib.patches, 179, 221 close file, 301, 327 column_stack, numpy, 108, 198 complex number, 28 conditional statements, 47 cumsum, numpy, 51 curve_fit(), scipy.optimize, 59, 123, 285 D data types, 28 det, numpy.linalg, 242 diag, numpy, 276 diff, numpy, 83 docstring, 54 E eig, numpy.linalg, 242 eigh, numpy.linalg, 268 ellipk(), scipy.special, 116 else, 47 ExcelWriter, Pandas, 339 F factorial, numpy.math, 43 factorial, scipy.special, 43 False, 45 FFMpegWriter, 355 fft, numpy.fft, 272 fftfreq, numpy.fft, 272 figsize, 40 find_peaks, scipy.signal, 273, 286 flatten, numpy, 240 floating point, 28 for loop, 48 format, 29 formatting output, 29 FuncAnimation blit, 199, 352 frames, 180, 352 init_func, 180, 351 interval, 180, 352 FuncAnimation, matplotlib.animation, 180, 210, 347 function, 52 G generator expression, 162 getcwd, os, 302 global variables, 57 H help, 90 hexadecimal, 341 I if, 47 import, 254 indices, 34 insert, numpy, 309 integer, 28 isclose, numpy, 249 J Jupyter Notebook, 23 L Leapfrog integration, 70 legend, matplotlib, 39 len(), 35 libraries, 25 line continuation, 68 linspace, numpy, 33 listdir, os, 302 lists, 31 load, numpy, 345 loadtxt, numpy, 108, 214, 302, 332 local variables, 57 logic, 45 loop, 48 M Markdown, 24 matmul, numpy, 198, 241 Matplotlib, 37 meshgrid, numpy, 250 modulo, 308 multipanel subplots, matplotlib.pyplot, 207, 223 multiprocessing, 162 N nonzero, numpy, 309 norm, numpy.linalg, 56 not, 46 NumPy, 31 O ones, numpy, 240, 276 open file, 327 or, 46 order of operations, 28 os, 302 P Pandas, 333 parameter, functions, 53 PillowWriter, 355 plot_surface, matplotlib, 250 plot_wireframe, 235 plotting, 37 polar plot, 185, 208 procedures, 52 prod, numpy, 45 Q quad(), scipy.integrate, 115 R rcParams, 40 read, 329 read file, 329 read_excel, Pandas, 339 readline, 329 readlines, 329 reshape, numpy, 165, 240 return, functions, 53 root, scipy.optimize, 128, 171 round, 35 S save, numpy, 341 savetxt, numpy, 108, 331 scope, 57 set artist, 351 shape, numpy, 198 Simple Euler, 64 size, numpy, 53 solve_ivp(), 87, 315 atol, 143, 322 deriv(), 88 events, 105, 225, 258, 325 max_step, 91, 320 rtol, 143, 322 time_arr, 321 timearr, 92 stat, os, 346 step size, accuracy, 75 string, 29, 30 subplot, 37 sum, numpy, 83 T three-dimensional plot, 201, 234 time, 158 time, sleep, 158 timeit, 51 to_excel, 338 to_html5_video, 354 to_jshtml, 354 transpose, numpy, 198, 241 True, 45 Index 359 U UnivariateSpline, scipy.interpolate, 309 W where, numpy, 107, 176 while loop, 50 who, 58 write to file, 301, 327 Z zeros, numpy, 240</p></div><note xmlns="http://www.tei-c.org/ns/1.0" place="foot" n="1" xml:id="foot_0"><p>This is not to imply that calculations on arrays are done in parallel. Arrays are merely collections of numbers of the same type, which makes the underlying calculations particularly efficient because each element of the array can be stored sequentially in the computer's memory, allowing fast access.</p></note>
			<note xmlns="http://www.tei-c.org/ns/1.0" place="foot" n="2" xml:id="foot_1"><p>Technically, a function returns values, a procedure returns nothing, and a method is a function or procedure bound to a class. Because the syntax is the same, we will refer to functions and procedures interchangeably.</p></note>
			<note xmlns="http://www.tei-c.org/ns/1.0" place="foot" xml:id="foot_2"><p>If you look closely at the values for the time step, you will notice that they initially increase exponentially.</p></note>
			<note xmlns="http://www.tei-c.org/ns/1.0" place="foot" n="100" xml:id="foot_3"><p>Simple Pendulum with Large-Angle Release</p></note>
			<note xmlns="http://www.tei-c.org/ns/1.0" place="foot" n="1" xml:id="foot_4"><p>Y. Kostov, R. Morshed, B. Holing, R. Chen, and P.B. Siegel, "Period-speed analysis of a pendulum, " Am. J. Phys.76,956-962 (2008).</p></note>
			<note xmlns="http://www.tei-c.org/ns/1.0" place="foot" xml:id="foot_5"><p>r _ soln = solve _ ivp(deriv, t _ span, r _ init, args = (omega0,))</p></note>
		</body>
		</text>
</TEI>
