diff --git a/GettingStarted-01-NarrativeAndCode.ipynb b/GettingStarted-01-NarrativeAndCode.ipynb index b03ddd3..d37ffc3 100644 --- a/GettingStarted-01-NarrativeAndCode.ipynb +++ b/GettingStarted-01-NarrativeAndCode.ipynb @@ -1,266 +1,268 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "# Workshop \"Teaching Sciences and Engineering with Jupyter Notebooks\" 2022\n", "C. Hardebolle, [CC-BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)\n", "\n", "
\n", " How to use this notebook?
\n", " This notebook is made of text cells and code cells. The code cells have to be executed to see the result of the program.
To execute a cell, simply select it and click on the \"play\" button () in the tool bar just above the notebook, or type shift + enter.
It is important to execute the code cells in their order of appearance in the notebook.\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Getting started: narrative and code\n", "\n", "A notebook is made of text cells (\"Markdown\") and code cells.\n", "\n", "## Text cells\n", "\n", "The text you are reading is contained in a text cell, which can be edited by *double clicking* on it.\n", "\n", "You can **format the text** in a notebook using the Markdown syntax. For instance, you can put a sentence in bold by adding double stars (\\*\\*) before and after, **like this.** \n", "The double stars are actually not visible, what you see is the result of the formatting once the text cell has been **rendered**. \n", "To render a text cell, simply select it and click on the \"play\" button () in the tool bar just above the notebook, or type shift + enter.\n", "\n", "Here is an [amazing cheat sheet about the Markdown syntax in Jupyter notebooks](https://medium.com/analytics-vidhya/the-ultimate-markdown-guide-for-jupyter-notebook-d5e5abf728fd) and you can also find the [full reference of the Mardown syntax on John Gruber's website](https://daringfireball.net/projects/markdown/syntax).\n", "\n", "To **add a text cell** to a notebook, click on the + icon in the toolbar at the top, then change the type of the cell to \"Markdown\" using the dropdown menu in the same toolbar.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Code cells\n", "\n", "Code cells make it possible to **embed a program** or pieces of program into a notebook, to execute them and **see the results** of its execution right away directly in the notebook.\n", "\n", "The code cell below contains a line of Python code that displays the text \"Hello!\". \n", "To **execute this cell**, simply select it and click on the \"play\" button () in the tool bar just above the notebook, or type shift + enter.
\n", "You should then see the text \"Hello!\" appear just below the code cell." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Hello!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You might have noticed that a *number between square brackets* has appeared at the left of the code cell after its execution. \n", "This number indicates the **order in which the code cell has been executed**, with respect to the other code cells or to previous executions of the same code cell.\n", "\n", "The order in which different pieces of code are executed affects the execution results, which is why these numbers are important information to consider.\n", "\n", "To **add a code cell** to a notebook, simply click on the + icon in the toolbar at the top.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "---\n", "\n", "# Useful text formatting tools\n", "\n", "Below, we discuss some additional useful tools to format your narrative in notebooks.\n", "\n", "
\n", "\n", "## Spacing / structuring text\n", "\n", "You can space out your text in your notebooks using blank lines. \n", "Unfortunately Markdown has no syntax for blank lines therefore you have to use the HTML syntax `
` each time you want to introduce one.\n", "\n", "To separate paragraphs you can also use horizontal lines, which are easily generated in Markdown with the syntax `---` (triple minus). \n", "The HMTL equivalent is `
`.\n", "\n", "**Example:** Edit the current cell to see how the following blank line is generated, and find the symbols that generate the horizontal line at the top of the section.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Hyperlinks\n", "\n", "To create any type of link in Markdown, the syntax is the following: `[text that will appear](URL)` \n", "If you wish to create a link to a local file in the current folder, for instance another notebook, simply replace the URL with the path of the file, relative to the notebook in which the link is: `[text that will appear](subfolder/file.extension)`\n", "\n", "**Example:** Here is a link to our [Getting Started notebook on interactive visualizations](GettingStarted-02-InteractiveVisualization.ipynb).\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Highlighting important information\n", "\n", "Text cells also support more elaborate [HTML](https://html.com/) and [CSS](https://html.com/css/) formatting, for instance to style paragraphs or titles with colors and backgrounds.\n", "\n", "**Example:** The two cells below illustrate how you can highlight the instructions or solution of an activity. \n", "Feel free to reuse these examples (simply edit the text cell and copy-paste the content)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Activity
\n", "\n", "Add a line to the plot that draws a door to the house. \n", "Then create a slider that will let the user choose the width of the door. \n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Solution** - You check your answer with the solution by clicking on the \"...\" below.\n", " \n", "
\n", "\n", "

" ] }, { "attachments": { "a3a2f418-e391-442c-99b9-c118480d23e9.png": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAACwAAAAzCAYAAADsBOpPAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5gEfDzsECVBQWgAACKtJREFUaN7tmW1sU9cZx//PudfkzQ5QKOJlox0EA+NFAkqZgCkZoH6g+ISEuVvHmjJgYR+mjg0ShzDaWzotTYBCk0qo2ZJqG21XpSN2LKRV0C1dgTLKwqo2g4QS6EAJBQIJdhI79r3PPsShxjiOkxLIhx3Jkn3Oc+753XOe87yZMIxafn7+NwBMB9BQUlJyOZqMGC6weXmO5wG6ANARgC7k5TkKo8nRcIB1OByLmXEsopsBXlxSUnJi2O2wYSAj+maK9MhO9V4vzlqG2tH9xRjDb9ItexpbCeD+Z1FzNDFmbukTODc3d2QgENhERAmqqv6+vLy8pb9l2grmjFbgfwKMJQAWA0jzdjWPBEwgFfA6rOwBWkE4Q8BRnflYalLgfdIu+u4EC1YRKfkAZoZ116ekJL3bpw7/ZP36Y6FFAaLz5uTkWWVlZf5ooLe2TV8C5k3EsANIHOAhtDLwRwiUpxY1nu3tLCgoGM3MWwFMY6bG7m7f7n379rVFBV63bt14EqIlYmBhZWXlqfC+9oKpaQor+xh48l6oLhPepKC+1bL7/NV4JwkA6OjouAYg3O61qap67iu9hPA4rC8KVurvESwACGI8A0U563VYc+KddFslcjZunKvo+m9ApBLwUmVl5UcAcC1/uiWR+E8AMofUVDDKzW2Wn1P5vwJxAUdr3rxZ41kE/g5gRhxLdgFoAeAH0BmiSAVTIgjjAZj6ZWb81dJlrKayz/0DBm7V0lJHdIlaAPOiDF8G40MAx1mh0yKonjfvqr/SJ4gdSvsj0yerKk81DCwUhCUMLAEwKspOv2NObvwRaTDiBmY7FO8U63sAlod1NxP4ALGoSilpOPW1NSB3galjdMcyQH+KQU8BMIe5uFdSixu3xA3sLbBuYcbu0Dl9RqCSlGTTO6TVdw+F+t7c/OgoJSEhl8CbAUwAwCBaYXm54W/9At/aNsNKhvFvAD4CvZjS1PAaVUG/Hy76yta5KSmqLw+MAgJaOpNMc8Zp9d6YwB6H1QnCQ8KgH6aUNDQ/iNiiIy9tviFEFYA/WIobd/YZ/NzaZp0B5hZz4sRlDwoWAFJ2fV4X9HcvAHgOaxOT+xRsz7c+zsMk5OxVEU/hzNn4fxtAy87OnmCz2Z4YtD+/38C6rmcLIfIHO/+2vtpstkIhxBmXy1UtpZxHRN93uVzbe8ellPuFEMVOp/OilPLHzNxKRBMAZDPzZUVRdjmdzvOZmZkrmDm9pqZmR+/czMzMAgCnmdkA8AKARwEcAvBuTU3NYbvdnuT3+zeHvOoFIUSR0+ls0zRN1NXV7WfmUiLarijKS7d3mIiWGobRG0B/i5mfjni5jbquPxz6/h0iep2IvktE+4goaBjGiaysrDGGYZwHUGiz2WaENmIsM+8UQvw3GAw2EtEnRHQTwBFFUb7QNE34/f7DzGwQ0RYAnbquvwkAmqYZADYQUTkRfQzg6qBSJGbuJqJrLpdrfY8nxREp5SLDMHLcbvdeKeVhIsoBUEhE2QBOVldXnwmdVCMRTa+pqanqyeeMlQDgdruLQ4/fKaVsllJOnD9//pW6ujoGUOZyuf486JyOiJiZP8GdidgpZp4aGn+dmcvsdvsOv9//AwCVMR43F8BCKeWNOy6XEBPq6+u/BKDquv5ZzCSUmb1EZOn9nZGRkRjlgiZHzJkE9KTq7e3t7tTU1DKfz7eGiOYnJCRkRrwsfZUxGx0Ajrvd7u9FctjtdgUAVFU1YloJk8n0HwCjpZTZubm5ptTU1N7Lp4ctvHL16tULAGDVqlXLiWiZqqrVAFBbWxtk5goi2gvg7aqqKm/Yi7UDmG6320cAgKIobiJ6TEq5tlcmKytrXDxmLQVANwAcPHjwMjNvB/DWlStXbhCRD0AdACVM/qhhGHullNeEEL9j5rXV1dWNYeNvAxgP4NXwBRMSEpwAWv1+/w0pZbbT6bxIRFkAdkgpb0gprweDwdKwKU3MfHeUKKU8b7PZ7rAMubm5pt5jiZDdI6WsCB3biGg7IaV8QUrp7GunNE2763RtNltyRkZGzHulrly58hFVVZ8DYPJ4PNXhg+Xl5YH+LmBVVVV3hN4l+Xy+50NmcFEM4LsyCrfb3dnfeqqqqo8ws1fX9cdra2t9cRqKE0SUFDX493pNJpNpNhGlHzp0qGlI3WZ73sxpwynuYA2iLS9tap+xBKmBsV6H9VfDAzZD9XZZS4WqtsXOOPKtR1mgyZLo/RlpzZ0PAtazLe1hGOItgOstxec2x4zWmEQ+MdZ6u8ynvPnW5fd1VwHy5lufhSE+BTCPlGBRXFnzLYf1FQJ+GfrpNBhFI0saTw4lqCdv2pNC0K8ZWNQDRs+YixsOxFeX0CYme7vMHwP4drijYKI32K86R+6tv3EvQLscUybrpK5hxk8jSq0HLcWNawZU+elyTJkchHocwKSIoQCAf4DxIQkcM0icTi062xoPYOd266RggBYIYCmI0xlYeBcD00fmZM+Kvu5PzITTUzhzNnT9AwAP9VfbBqgJwHWA2wnoMAgKMScBNArM40FiCsDJ/TynnoVIj7UB/WbIbXlpUxUhXABmDfGVe0+nxKdHvfzpzUFXL3tbT8kVbwC8ZijSPALvSWk6VxhPhWlANQhvgXUFM16NuIyDbgSc1AU/N7Lo3D8HnITG74FmjfB0BZ4FkEvAY4PgNAA+zBD7LcUNNfH9yxQDuKKiwgKggIhszMxEdExVVS0nJ+dq1BqYIiQYS9FjP819aRWA4wQ+GtTFX0btbrjwtdN8ACgtLU0wm80fhBYPb5cCgcD8TZs2XY/l+zs7W8axgbEsMAZk6BB8nUhvNf/2wpf3SuHvCJbNZvP6KLAA8E2TybQDwC/6fHOtNgigOfQZshYZS2TEkE0fDlGciMh8/TFku4cdMIDqGLUI57AD3rBhQzURVUSRq/V4PHuGA3BUO1xRUSGJaDUAEzO/f+nSpQOapgWHA/D/AEh/gI+aea01AAAAAElFTkSuQmCC" } }, "cell_type": "markdown", "metadata": {}, "source": [ "## Images\n", "\n", "Images play an important role for understanding explanations, so don't be afraid to embed images (photos, diagrams, sketches, etc.) into your notebooks.\n", "\n", "A first option to include an image in a text cell is to use the Markdown syntax, where you indicate either the path to the image file in your workspace (relative to the notebook you are currently editing) or a URL if you use an online image: `![](relative/path/to/image/file.png)`\n", "\n", "One drawback of the Markdown syntax is that it does not allow you so specify the size of the image, which means your image appears *as is*. \n", "To include an image while modifying the size at which it is displayed, you can use the HTML syntax: ``. If you specify only the width or the height, the ratio of the image will be conserved. If you specify both values then the ratio will be adapted to what you specify.\n", "\n", "If you want to quickly include an image into a notebook (e.g. a screen capture) without going through the steps of saving the image into your workspace then writing the necessary code to embed it, then you can also simply drag-and-drop or copy-paste an image right onto your text cell and Jupyter will take care of everything! (Note: the image is then stored in the metadata of your notebook, therefore you will not be able to retrieve it afterwards, and it increases the size of your notebook...).\n", "\n", "**Examples:** Edit the cell to see the code of the different examples. \n", "![](figs/epfl-logo.png) ![main-logo.png](attachment:a3a2f418-e391-442c-99b9-c118480d23e9.png)\n", + "\n", + "
\n", "
" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Hiding cells\n", "\n", "Hidding cells can be used to make a notebook more readable, to hide supplementary content or to hide exercise solutions.\n", "\n", "To hide a cell, click on the blue bar that appears on its left when you select or edit it. \n", "A hidden cell appears as a series of 3 dots `...` on which you can simply click to show the cell again. \n", "If you save your notebook with a hidden cell, the cell will remain hidden when you re-open your notebook (therefore if you share a notebook with hidden cells to students, the cells will be hidden for them).\n", "\n", "\n", "\n", "\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Math equations\n", "\n", "You can use LaTeX to write mathematical equations in your Markdown cells, both inline like this: $\\lvert\\vec{T}\\rvert$ or in blocks like this:\n", "\n", "$$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = \\frac{\\frac{1}{2}.m.g}{sin(\\alpha)}\n", "\\end{align}\n", "$$\n", "\n", "Of course you need to be familiar with the LaTeX syntax and symbols. \n", "You can find a quick intro and overview in the [LaTeX chapter](https://personal.math.ubc.ca/~pwalls/math-python/jupyter/latex/) of the online [\"Mathematical Python\"](https://personal.math.ubc.ca/~pwalls/math-python/) textbook (made with notebooks!).\n", "\n", "Note that notebooks support only a subset of the LaTeX mathematical syntax, more specifically it relies on the MathJax subset of LaTeX, more information in the [official documentation of MathJax](https://docs.mathjax.org/en/latest/input/tex/)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# References\n", "\n", "The full documentation of JupyterLab: \n", "https://jupyterlab.readthedocs.io/en/1.2.x/ \n", "\n", "A useful cheat sheet on the Mardown syntax: \n", "https://medium.com/analytics-vidhya/the-ultimate-markdown-guide-for-jupyter-notebook-d5e5abf728fd\n", "\n", "The full reference for the Markdown syntax in Jupyter notebooks: \n", "https://daringfireball.net/projects/markdown/syntax\n" ] } ], "metadata": { "kernelspec": { - "display_name": "Python", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/GettingStarted-02-InteractiveVisualization.ipynb b/GettingStarted-02-InteractiveVisualization.ipynb index a6ea046..aad575a 100644 --- a/GettingStarted-02-InteractiveVisualization.ipynb +++ b/GettingStarted-02-InteractiveVisualization.ipynb @@ -1,427 +1,427 @@ { "cells": [ { "cell_type": "markdown", "id": "42235b78-7365-4921-8034-a7139a839952", "metadata": { "toc-hr-collapsed": true }, "source": [ "# Workshop \"Teaching Sciences and Engineering with Jupyter Notebooks\" 2022\n", "C. Hardebolle, [CC-BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)\n", "\n", "
\n", " How to use this notebook?
\n", " This notebook is made of text cells and code cells. The code cells have to be executed to see the result of the program.
To execute a cell, simply select it and click on the \"play\" button () in the tool bar just above the notebook, or type shift + enter.
It is important to execute the code cells in their order of appearance in the notebook.\n", "
" ] }, { "cell_type": "markdown", "id": "a2774ae7-2192-4acc-9b04-39a894706ab5", "metadata": {}, "source": [ "# Tutorial: creating interactive visualizations with Matplotlib\n", "\n", "This very short tutorial walks you through the steps for creating an interactive visualization with matplotlib. \n", "Please note that there are *multiple ways* to achieve the same result. This tutorial is just one possible example." ] }, { "cell_type": "markdown", "id": "a7ecbc64-f2c7-4066-ab9c-eeabe7675d06", "metadata": {}, "source": [ "## Technical setup" ] }, { "cell_type": "markdown", "id": "2ca6f8ff-3012-47f2-8f91-f0c05c4ec0a7", "metadata": {}, "source": [ "We need to import two libraries: \n", "* matplotlib for plotting\n", "* ipywidgets for interaction elements such as sliders" ] }, { "cell_type": "code", "execution_count": null, "id": "178a7a7c-86dd-4bad-80ee-70c250708b82", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import ipywidgets as widgets" ] }, { "cell_type": "markdown", "id": "8322fb56-37f2-45b0-9c40-2ddfa29eb4b6", "metadata": {}, "source": [ "Before starting to construct a figure, we need to tell matplotlib what we want to generate:\n", "* static images: `%matplotlib inline`\n", "* or dynamic interactive visuals: `%matplotlib widget`" ] }, { "cell_type": "code", "execution_count": null, "id": "2f42895d-8a0a-41b9-aca7-118ed0c476cd", "metadata": {}, "outputs": [], "source": [ "# In this tutorial, we will create an interactive figure\n", "%matplotlib widget" ] }, { "cell_type": "markdown", "id": "c7a1a710-02f9-4ce0-b546-6422f2db7549", "metadata": {}, "source": [ "This setting changes the type of `backend` that matplotlib will use. \n", "With the static backend, matplotlib will generate a new image each time we make a modification. \n", "With the interactive backend, only one image will be generated and all the actions and drawings will update the image. \n", "\n", "*Watchout: code written with one backend usually does not work with another backend!*" ] }, { "cell_type": "markdown", "id": "568a1cf2-9745-4396-8575-4d245dfa125a", "metadata": {}, "source": [ "## Data to generate the visualization\n", "Let's generate some data to plot. \n", "In this example we will draw a house and its roof, using sets of x and y coordinates to define points connected by a line." ] }, { "cell_type": "code", "execution_count": null, "id": "29922ff8-37d4-4b1e-9cf6-9fa5d25975c1", "metadata": {}, "outputs": [], "source": [ "# We will plot a rectangle to model a house\n", "house_x = [2, 2, 4, 4]\n", "house_y = [0, 2, 2, 0]\n", "\n", "# We will plot a triangle to model the roof\n", "roof_x = [2, 3, 4]\n", "roof_y = [2, 5, 2]" ] }, { "cell_type": "markdown", "id": "fea1da5d-f18d-48f4-8b4f-e24d3d1cb745", "metadata": {}, "source": [ "## Building the visualization" ] }, { "cell_type": "markdown", "id": "b0530b84-7d3d-4e65-9960-e222f434686f", "metadata": {}, "source": [ "Before starting to build the figure, we first tell matplolib to wait until we tell him to show the figure where we want in the notebook. \n", "If we don't do that, it will start to display the figure right after the first instructions we write." ] }, { "cell_type": "code", "execution_count": null, "id": "9d65ead3-efc7-40f5-823a-306980328261", "metadata": {}, "outputs": [], "source": [ "# Turn the output off for the moment\n", "plt.ioff()" ] }, { "cell_type": "markdown", "id": "22c696c5-f1bb-48cc-b68a-286bea11396e", "metadata": {}, "source": [ "Now let's build the plot. It has two main components:\n", "* a figure, which is an overall container\n", "* one or more \"axes\" i.e. plots" ] }, { "cell_type": "code", "execution_count": null, "id": "f99f07ce-f750-493e-bac0-76427ac83ffb", "metadata": {}, "outputs": [], "source": [ "# Creation of the figure\n", "fig = plt.figure(num='Interactive figure', figsize=(6,4))\n", "\n", "# Creation of one subplot/axe - it will take position index number 1 in a grid of 1 row and 1 column, as described by (nrows, ncols, index)\n", "ax = fig.add_subplot(1,1,1)" ] }, { "cell_type": "markdown", "id": "19d36c78-ecb3-4f17-8199-a48437331805", "metadata": {}, "source": [ "Once we have created these components, we can plot our data. \n", "The `plot` method of the `axe` object plots y versus x as lines and/or markers. It returns the resulting `line` object(s)." ] }, { "cell_type": "code", "execution_count": null, "id": "a061a2e0-1775-4752-8884-88bf00639903", "metadata": {}, "outputs": [], "source": [ "# Plot the house\n", "ax.plot(house_x, house_y)\n", "\n", "# Plot the roof, and get the resulting line, on which we will add interactivity later - NOTICE the syntax with the comma \"roof_line, =\"\n", "roof_line, = ax.plot(roof_x, roof_y)" ] }, { "cell_type": "markdown", "id": "69412705-47f2-40b3-9cd0-95422a0b6309", "metadata": {}, "source": [ "Now let's show the resulting figure! \n", "For that we need to turn on again the interactive mode of matplotlib." ] }, { "cell_type": "code", "execution_count": null, "id": "d940f221-813c-4f52-8fa3-766fd0fa9b2f", "metadata": {}, "outputs": [], "source": [ "# Turn the output on\n", "plt.ion()\n", "\n", "# Show the figure\n", "display(fig.canvas)" ] }, { "cell_type": "markdown", "id": "425f9a44-f36b-417b-a9de-35e855bd5487", "metadata": {}, "source": [ "## Adding some interactivity \n", "Now we can add some interactivity to this plot. For that, we proceed in four steps:\n", "1. We create a slider" ] }, { "cell_type": "code", "execution_count": null, "id": "eadae473-19a9-4f18-b453-4aec54012015", "metadata": {}, "outputs": [], "source": [ "# We create a slider with values ranging from 2 to 10 in steps of .5, by default on value 5\n", "roof_widget = widgets.FloatSlider(min=2, max=10, step=0.5, value=5, description='Roof height:')" ] }, { "cell_type": "markdown", "id": "37549dfa-026c-4ee2-9c45-5e55b06f84b8", "metadata": {}, "source": [ "2. We create a function that will be called when the slider is moved and will update the figure" ] }, { "cell_type": "code", "execution_count": null, "id": "93c29bff-d13f-477f-ab8d-9723856472b1", "metadata": {}, "outputs": [], "source": [ "# This function will be called when the slider is moved\n", "def roof_event_handler(change):\n", " # It allows us to retrieve the new value of the slider\n", " newposition = change.new\n", "\n", " # Then we can update the points of the roof line\n", " roof_line.set_ydata([2, newposition, 2])\n", " \n", " # If the interactive mode of matplotlib is on (as it actually is in our case), we don't need to tell the figure to update the drawing\n", " # But if it is off, just uncomment the line below so that the figure is updated\n", " #fig.canvas.draw()" ] }, { "cell_type": "markdown", "id": "bfc19f67-fd2a-4d99-a138-3e84c25a962b", "metadata": {}, "source": [ "3. We link the slider to the callback function" ] }, { "cell_type": "code", "execution_count": null, "id": "c7155220-f5f2-40f4-aad6-38d055441646", "metadata": {}, "outputs": [], "source": [ "# Finally we link the widget to the callback function \n", "roof_widget.observe(roof_event_handler, names='value')" ] }, { "cell_type": "markdown", "id": "3242d42a-706b-4040-bf08-a2d118829ab4", "metadata": {}, "source": [ "4. And then we display both the figure and the slider" ] }, { "cell_type": "code", "execution_count": null, "id": "9ebd1106-589f-46ad-b7cd-bfce1db0744a", "metadata": {}, "outputs": [], "source": [ "# Display the figure again, and the widget below (no need to turn the interactive mode on again since we did it earlier)\n", "display(fig.canvas, roof_widget)" ] }, { "cell_type": "markdown", "id": "03b9a626-ee0a-4bec-ba57-d2691252d881", "metadata": {}, "source": [ "Note that the slider udpates both the figure here and also the figure above. \n", "This is the work of the interactive backend!" ] }, { "cell_type": "markdown", "id": "e6c31f99-06b2-42a5-988f-a8e81951b6e2", "metadata": {}, "source": [ "---\n", "\n", "# Your turn now!" ] }, { "cell_type": "markdown", "id": "b4c41719-fe79-4c76-ba62-68e5fbb3edc1", "metadata": {}, "source": [ "
\n", " Activity
\n", "\n", "Add a line to the plot that draws a door to the house. \n", "Then create a slider that will let the user choose the width of the door. \n", " \n", "
" ] }, { "cell_type": "markdown", "id": "206b7ae1-02b9-44d0-9af9-4472df30adbe", "metadata": {}, "source": [ "
\n", "\n", "**Solution** - You can see *one possible solution* by clicking on the \"...\" below.\n", " \n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "15d0e85e-abc0-4ff1-af59-47eb4c19869b", "metadata": { "jupyter": { "source_hidden": true }, "tags": [] }, "outputs": [], "source": [ "# First define the x and y coordinates of the door\n", "door_x = [2.25, 2.25, 2.50, 2.50]\n", "door_y = [0, 1, 1, 0]\n", "\n", "# Draw the door on the plot and get the resulting line (to be able to update it with the slider)\n", "door_line, = ax.plot(door_x, door_y)\n", "\n", "# Create a widget for the width of the door\n", "door_widget = widgets.FloatSlider(min=0.25, max=1.5, step=0.05, value=0.25, description='Door width:')\n", "\n", "# This function will be called when the door slider is moved\n", "def door_event_handler(change):\n", " # It allows us to retrieve the new value of the slider\n", " newwidth = change.new\n", "\n", " # Then we can change the points of the door line - the figure will update automatically thanks to the interactive mode!\n", " door_line.set_xdata([2.25, 2.25, 2.25+newwidth, 2.25+newwidth])\n", " \n", "\n", "# Finally we link the widget to the callback function \n", "door_widget.observe(door_event_handler, names='value')\n", "\n", "# Let's display again the whole figure with the two sliders\n", "display(fig.canvas, roof_widget, door_widget)" ] }, { "cell_type": "markdown", "id": "9690331e-446d-4062-97d0-069276e335bf", "metadata": {}, "source": [ "---\n", "\n", "# Additional resources\n", "\n", "More on figures and axes of Matplotlib: \n", "https://medium.com/@kapil.mathur1987/matplotlib-an-introduction-to-its-object-oriented-interface-a318b1530aed\n", "\n", "Using the widgets to add interactivity: \n", "https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html\n", "\n", "Details about the backends of matplotlib: \n", "https://matplotlib.org/3.4.3/users/interactive.html" ] } ], "metadata": { "kernelspec": { - "display_name": "Python", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 5 } diff --git a/GettingStarted-03-InteractiveVisualizationHiddenCode.ipynb b/GettingStarted-03-InteractiveVisualizationHiddenCode.ipynb index a90919a..8bc7e4c 100644 --- a/GettingStarted-03-InteractiveVisualizationHiddenCode.ipynb +++ b/GettingStarted-03-InteractiveVisualizationHiddenCode.ipynb @@ -1,171 +1,171 @@ { "cells": [ { "cell_type": "markdown", "id": "4a8ba901-956b-49da-88f3-7a41036c4dbc", "metadata": { "toc-hr-collapsed": true }, "source": [ "# Workshop \"Teaching Sciences and Engineering with Jupyter Notebooks\" 2022\n", "C. Hardebolle, [CC-BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)\n", "\n", "
\n", " How to use this notebook?
\n", " This notebook is made of text cells and code cells. The code cells have to be executed to see the result of the program.
To execute a cell, simply select it and click on the \"play\" button () in the tool bar just above the notebook, or type shift + enter.
It is important to execute the code cells in their order of appearance in the notebook.\n", "
" ] }, { "cell_type": "markdown", "id": "37abfac0-d5d8-44be-8572-425abf875a19", "metadata": {}, "source": [ "# Demo: hiding the code of your interactive visualizations\n", "\n", "This notebook demonstrates how the code from an interactive visualization can be hidden from a notebook. \n", "Again there are different ways to achieve the same result, this is only one possible solution.\n", "\n", "In this demo, we use the same \"interactive house\" visualization example as [in this notebook](GettingStarted-02-InteractiveVisualization.ipynb)." ] }, { "cell_type": "markdown", "id": "c0c4751d-38b6-449b-8db2-8699e51a8eba", "metadata": {}, "source": [ "## Putting the code in an external Python file\n", "The main principle is to put the code that generates the interactive visualization into an external Python file. \n", "In such a file, code can take the form of functions or classes.\n", "\n", "Here is an exemple: [interactivevisualization.py (click here to open the file)](lib/interactivevisualization.py). \n", "In this demo, we have simply created a function that generates the interactive house visualization and displays it. " ] }, { "cell_type": "markdown", "id": "c0e26a53-bc03-429f-aeb0-71abb560f549", "metadata": { "tags": [] }, "source": [ "## Using the file\n", "To be able to use the code defined in our Python file, we simply use the built-in `import` feature of Python." ] }, { "cell_type": "code", "execution_count": null, "id": "8aa8717e-44e5-4df6-b79f-0f6e01488d9c", "metadata": {}, "outputs": [], "source": [ "# Let's import the content of our Python file\n", "from lib.interactivevisualization import *" ] }, { "cell_type": "markdown", "id": "b0c5c5ec-6a13-4bd7-aa39-d2531010c0bf", "metadata": {}, "source": [ "Then we can simply call the function we have defined." ] }, { "cell_type": "code", "execution_count": null, "id": "a15eab91-850b-4402-b8fa-a244fbce9f70", "metadata": {}, "outputs": [], "source": [ "# Now we can use the functions defined in the file\n", "displayInteractiveHouse()" ] }, { "cell_type": "markdown", "id": "1c44abd2-1641-42e7-b7eb-c5e14c77251c", "metadata": {}, "source": [ "---\n", "\n", "## Differences between the code in the notebook and in the external file\n", "\n", "### Choosing the matplotlib backend\n", "\n", "We cannot use the instruction `%matplotlib widget` to choose the backend of matplotlib in a Python file because it works only when in a [notebook](02-InteractiveVisualization.ipynb). \n", "Instead we need to use the following two lines, which do exactly the same thing:\n", "```python\n", "from IPython import get_ipython\n", "get_ipython().run_line_magic('matplotlib', 'widget')\n", "\n", "```\n", "\n", "### Turning the interactive mode on/off\n", "\n", "Since the code is in one piece only - compared to split over several cells in a [notebook](02-InteractiveVisualization.ipynb) - we don't need to switch the interactive mode of matplotlib off and back on again. \n", "Matplotlib will simply render the full visualization in one shot when the function is called.\n", "\n", "Therefore, we don't need to:\n", "- call `plt.ioff()` to turn the interactive mode off\n", "- call `plt.ion()` to turn it back on\n", "- display the figure explicitely (`display(fig.canvas)`) - matplotlib will display it automatically while it is drawn\n", "\n", "NB: try it out, if you display the figure, you will see the figure twice..." ] }, { "cell_type": "markdown", "id": "1bd36fc9-b9cd-464a-acd1-d2b574cc59a7", "metadata": {}, "source": [ "---\n", "\n", "## Function or class?\n", "\n", "Note that, in this design, the event handler function becomes a *nested* function, which some would argue is not a great design. \n", "Another way to achieve the same thing is to create a class with the different variables of the visualization as attributes and the event handler function as a method. \n", "For this, you need to be familiar with [object-oriented programming in Python](https://realpython.com/python3-object-oriented-programming/). \n", "\n", "Here is an example of a more complex visualization example implemented as a Python class: [SuspendedObject.py (click here to open the file)](SuspendedObject.py). \n", "\n", "Execute the code cell below to see the result. \n", "Note that once you have executed this cell, the previous visualization will not work anymore since the interactive backend renders only one visualization at a time. \n", "This is actually one limitation of the interactive backend." ] }, { "cell_type": "code", "execution_count": null, "id": "f9c64f8d-33a6-4156-86d8-ad34598e2b8d", "metadata": {}, "outputs": [], "source": [ "from lib.suspendedobjectinteractive import *\n", "SuspendedObjectLab();" ] } ], "metadata": { "kernelspec": { - "display_name": "Python", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 5 } diff --git a/LearningPhysics.ipynb b/LearningPhysics.ipynb index 3a06af1..37047bc 100644 --- a/LearningPhysics.ipynb +++ b/LearningPhysics.ipynb @@ -1,1319 +1,1320 @@ { "cells": [ { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": true }, "source": [ "\n", "# Workshop \"Teaching Sciences and Engineering with Jupyter Notebooks\" 2022\n", "C. Hardebolle, [CC-BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)\n", "\n", "
\n", " How to use this notebook?
\n", " This notebook is made of text cells and code cells. The code cells have to be executed to see the result of the program.
To execute a cell, simply select it and click on the \"play\" button () in the tool bar just above the notebook, or type shift + enter.
It is important to execute the code cells in their order of appearance in the notebook.\n", "
" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": true }, "source": [ "
\n", "\n", "# Learning goals\n", "\n", "After using this notebook, you should be able to:\n", "* Analyze how the position of the cable influences the tension force in the jeans problem\n", "* Describe how the counterweight influences the position of the cable in the jeans problem\n", "* Use Python to make mathematical calculations and write simple functions\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# The problem\n", "\n", "The question we are trying to answer in this notebook is the following: \n", "**Estimate which counterweight allows to suspend wet jeans (3kg) on the cable in the position illustrated below.**\n", "\n", "\"suspended\n", "\n", "\n", "\n", "The activities below allow you to find out the answer to this question by exploring how the counterweight affects the position of the jeans suspended on the cable. " ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "

\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 1. Analyzing the problem\n", "\n", "Our first step, as with any problem, should be to analyze the question into more details and this includes:\n", "* Identifying assumptions we can make to simplify the question\n", "* Making a sketch \n", "* Identifying the parameters of the problem" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "## How can we simplify the problem?\n", "We can make the assumption that the jeans do not move on the cable, i.e. the whole system is in static equilibrium. \n", "In that case, it is like the cable is attached to fixed points on both sides, we can ignore the pulley. Let's make a sketch.\n", "\n", "\"sketch\"\n", "

Figure 1: Simplified suspended jeans situation

\n", "\n", "\n", "Therefore we are in a situation which is **identical to the one seen in the mini-lecture**.
\n", "The only difference is that the cable is attached to poles instead of the ceiling but this doesn't change anything in terms of the forces applied to the jeans, which are:\n", "* the weight $\\vec{F}$ \n", "* the tensions $\\vec{T}$ on both sides of the cable, which are identical in norm if we assume that the jeans are suspended right in the middle of the cable\n", "\n", "However we need to figure out where the angle $\\alpha$ is. \n" ] }, { "cell_type": "markdown", "metadata": { "tags": [], "toc-hr-collapsed": false }, "source": [ "## Where is the angle $\\alpha$?\n", "If we draw a line that represents the horizon, then **the angle $\\alpha$ is the angle between the cable and the horizon**.
\n", "Let's add the forces and the angle to our sketch.\n", "\n", "\"sketch\"\n", "

Figure 2: Angle and forces in the suspended jeans situation

\n", "\n", "\n", "We can therefore **use the equation seen in the mini-lecture, which gives the tension in the cable** depending on the mass $m$ of the suspended object, the angle $\\alpha$ that the cable makes with the horizon and the gravity of earth $g$:\n", "\n", "$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = \\frac{\\frac{1}{2}.m.g}{sin(\\alpha)}\n", "\\end{align}\n", "$\n", "\n", "Computing the tension in the cable will help us figure out the counterweight necessary to maintain the cable taught: intuitively, we can predict that **the higher the tension in the cable, the heavier the counterweight needed** to keep the cable taught. " ] }, { "cell_type": "markdown", "metadata": { "tags": [], "toc-hr-collapsed": false }, "source": [ "## How does the angle $\\alpha$ influence the forces on the jeans?\n", "\n", "\n", "Now, we know that the mass of the jeans is $m =$ 3kg and that the gravity of earth is $g =$ 9.81m.s$^{-2}$, but we don't know the value of $\\alpha$. \n", "From the sketch in the original question, which we show again below, we can guess that $\\alpha$ is probably quite small, but how small?\n", "\n", "\"suspended\n", "

Figure 3: The suspended jeans situation in the original question

\n", "\n", "In the following, we are going to use the `suspendedobjects` library to visualize concretely what a particular value for $\\alpha$ means. \n", "For this, we need to import some useful Python libraries. \n", "\n", "
\n", " Activity
\n", "\n", "**Execute the code cell below** so that the necessary libraries get imported.\n", " \n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# We need Numpy for some mathematical functions (e.g. sinus)\n", "import numpy as np\n", "\n", "# We will do some plotting with Bokeh\n", "from bokeh.plotting import figure\n", "\n", "# We will use a custom library with visualizations developed for this exercise\n", "from lib.suspendedobject import *\n", "\n", "# And display a message once all libraries are imported\n", "print(\"Libraries imported.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can start to use Python for visualizing our angle $\\alpha$. \n", "The `suspendedobjects` library generates a situation with poles 1.5 meters high, separated by a 5 meter distance, and shows the angle $\\alpha$ as well as the forces on the jeans. \n", "**Which value should $\\alpha$ be to reflect appropriately the angle in the original question (see [Figure 3 above](#fig1) above)?**" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "
\n", " Activity
\n", " \n", "Execute the code cell below to generate the visualization with the jeans suspended on the cable.
\n", "**Make a guess for the value of $\\alpha$** that would reflect appropriately the angle in [Figure 3 above](#fig1) then **replace the value of ``alpha`` with the value you have chosen**.
\n", "If your guess does not seem to reflect completely the angle in the original question (see [Figure 3 above](#fig1)), feel free to change it until you are satisfied.\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Choose a value for alpha, in degrees\n", "alpha = 20 # CHANGE THE VALUE HERE\n", "\n", "# Build a concrete situation where poles measure 1.5 meters and are distant from 5 meters\n", "lab = SuspendedObjectLab(m_object = 3, height = 1.5, distance = 5)\n", "\n", "# Visualize the angle\n", "lab.visualize_angle(alpha);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that you have chosen a value for $\\alpha$, the question is: **how does it affect the forces on the jeans?**
\n", "\n", "
\n", " Activity
\n", "\n", "Compare the forces on the jeans with $\\alpha$ = 20$^\\circ$ and with the value you have chosen for $\\alpha$. \n", "**What do you observe?** \n", "Execute the code cell below and **post a message in the chat** with the value you suggest for $\\alpha$ and a description of how you think the angle influences the forces on the jeans.

\n", "You can compare with what others have written and vote for the best explanation using the \"thumb up\" icon.\n", "\n", "*This chat is completely anonymous.* \n", "*You might need to accept the cookies to see the text box where to post your message.*\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import IFrame\n", "IFrame('https://speakup.epfl.ch/room/76017', 400, 500)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 2. Computing the tension in the cable with Python\n", "\n", "To get a more precise view of what is happening, we will now compute the tension in the cable using the equation from the mini-lecture:\n", "\n", "$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = \\frac{\\frac{1}{2}.m.g}{sin(\\alpha)}\n", "\\end{align}\n", "$\n", "\n", "In the following we are going to use Python to **compute the tension in the cable** for **different values of the angle $\\alpha$**." ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Defining the constants of our physics problem\n", "\n", "In the above equation we have two constants:\n", "- $g$ the gravity of earth, which is 9.81m.s$^{-2}$\n", "- $m$ the mass of the suspended object, which is 3 kg in the case of the jeans\n", "\n", "
\n", " Activity
\n", "\n", "**Execute the code cell below** so that these two constants get defined in Python.\n", " \n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's define the value of gravity\n", "g = 9.81\n", "\n", "# And the mass of the jeans\n", "m = 3\n", "\n", "# Display the value of the constants to check they are well defined\n", "print(\"gravity:\", g, \", jeans_mass:\", m)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating a function to compute the tension in the cable\n", "\n", "Let's define a Python function that represents the equation of the tension. \n", "Its input parameters are:\n", "* `g`: the gravity of earth\n", "* `m`: the mass of the jeans\n", "* `alpha`:the angle that the cable makes with the horizon\n", "\n", "It returns the value of the mass of the counterweight as computed with the equation $\n", "\\begin{align}\n", "\\frac{\\frac{1}{2}.m.g}{sin(\\alpha)}\n", "\\end{align}\n", "$\n", "\n", "
\n", " Activity
\n", "\n", "In the code cell below, **complete the code of the function `tension_norm`** by implementing the equation above. \n", "Here is some syntax you will need:\n", + "* variables: you can use `g`, `m` and `alpha`\n", "* multiplication: `*`\n", "* division: `/`\n", "* sinus function $sin(x)$: `np.sin(x)`\n", "\n", "You can also use parentheses to indicate the order of operations.\n", " \n", "Then **execute the code cell** so that this function gets defined in Python.\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Then let's define the function\n", "def tension_norm(g, m, alpha):\n", " tension = 1 # REPLACE \"1\" BY YOUR EQUATION HERE\n", " return tension\n", "\n", "# And display a message once it is defined\n", "print(\"Function defined.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Solution** - you can check your answer with the solution by clicking on the \"...\" below.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": { "jupyter": { "source_hidden": true }, "tags": [] }, "source": [ "
\n", " Solution
\n", "\n", "Your function should look like the following:\n", "\n", "def tension_norm(g, m, alpha):\n", " tension = (1/2 * m * g) / np.sin(alpha)\n", " return tension\n", "\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Computing the tension in the cable using our function\n", "\n", "Now let's use this Python function to compute the norm of the tension in the cable for a given angle of $\\alpha$.\n", "\n", "Because our function takes the sinus of our angle $\\alpha$, we first need to convert it from degrees into radians. \n", "This is where the library we have imported gets useful as it provides us with a function `degrees_to_radians` which we can use to convert our angle.\n", "\n", "The code cell below defines a value for $\\alpha$ in degrees, converts it to radians, then computes the tension on the cable using our previously defined function with `g` = 9.81 m.s$^{-2}$ and `m` = 3 kg and prints the result." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# First choose an angle alpha\n", "alpha = 20 # CHANGE THE VALUE HERE\n", "\n", "# Then convert it to radians\n", "alpha_radians = degrees_to_radians(alpha)\n", "\n", "# Then compute the tension using our equation with g = 9.81 m.s-2 and m = 3 kg\n", "T = tension_norm(g, m, alpha_radians)\n", "\n", "# And print the result\n", "print(\"Norm of the tension in the cable: T =\", T, \"N\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Activity
\n", "\n", "**Change the value of $\\alpha$ with the value you have chosen, then execute the code cell** to see the value of the corresponding tension force $\\lvert\\vec{T}\\rvert$, expressed in Newtons. \n", " \n", "You can compare the value you obtain to the force exerted by earth gravity on the jeans, which weight 3 kg: $\\lvert\\vec{F}\\rvert = $ 29 $N$ \n", "Is the tension in the cable bigger or smaller than the weight of the jeans on the cable? Why?\n", " \n", "Note down your answer in the cell below, then you can check the solution by clicking on the \"...\" below.\n", "\n", "
" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "Just note down your answer here (this cell is for note taking):\n", "..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Solution** - you can check your answer with the solution by clicking on the \"...\" below.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": { "jupyter": { "source_hidden": true }, "tags": [] }, "source": [ "
\n", " Solution
\n", "\n", "If you have chosen an angle $\\alpha$ smaller than 30$^\\circ$ (which is quite likely), then you will notice that the tension $\\lvert\\vec{T}\\rvert$ is bigger than the weight of the jeans.\n", " \n", "This is because such small angles make the cable more horizontal whereas the weight of the jeans is purely vertical, therefore a higher tension will be necessary to create the vertical resulting force that compensates for the weight of the jeans.\n", " \n", "\"sketch\"\n", "\n", "Mathematically speaking, remember that the tension is defined by the following equation: \n", "$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = \\frac{\\frac{1}{2}.m.g}{sin(\\alpha)}\n", "\\end{align}\n", "$\n", "\n", "Because the weight of the jeans is $\\lvert\\vec{F}\\rvert = m.g$, the tension can also be written: \n", "$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = \\frac{\\frac{1}{2}}{sin(\\alpha)}.\\lvert\\vec{F}\\rvert\n", "\\end{align}\n", "$\n", "\n", "As a consequence:\n", "* For angles between 0$^\\circ$ and 30$^\\circ$, $sin(\\alpha)$ will be between 0 and $\\frac{1}{2}$ and therefore we will have $\\lvert\\vec{T}\\rvert > \\lvert\\vec{F}\\rvert$\n", "* For angles between 30$^\\circ$ and 90$^\\circ$ (although such high values do not really make sense in the real situation), $sin(\\alpha)$ will be between $\\frac{1}{2}$ and 1 and therefore we will have $\\lvert\\vec{T}\\rvert < \\lvert\\vec{F}\\rvert$\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 3. From the tension to the counterweight\n", "\n", "Now how can we relate this tension to the counterweight in our original question? \n", "A key here is to understand that **the tension is transmitted in the cable to the counterweight**, and that **the pulley only changes the direction of the force**, it does not affect its value.\n", "\n", "Therefore the weight $\\vec{F_{cw}}$ of the counterweight will have to compensate exactly the tension $\\vec{T}$ in the cable, as indicated on the sketch below.\n", "\n", "\"sketch\"\n", "

Figure 4: Forces on the counterweight

\n", "\n", "We can write this as an equation: \n", "$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = \\lvert\\vec{F_{cw}}\\rvert\n", "\\end{align}\n", "$\n", "\n", "Since the weight of the counterweight is $\\lvert\\vec{F_{cw}}\\rvert = m_{cw}.g$, we can therefore write: \n", "$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = m_{cw}.g\n", "\\end{align}\n", "$\n", "\n", "From there we can find the mass of the counterweight $m_{cw}$: \n", "$\n", "\\begin{align}\n", "m_{cw} = \\frac{\\lvert\\vec{T}\\rvert}{g}\n", "\\end{align}\n", "$\n", "\n", "\n", "Again we can use python to make this simple computation, using the values `T` and `g` computed earlier." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Activity
\n", "\n", "**Complete the code cell below** to compute the mass of the counterweight according to the equation above. \n", "Then **execute the code cell** to see which counterweight would allow the jeans to be suspended with the angle $\\alpha$ you have chosen earlier.\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's compute the mass of the counterweight to compensate for the tension in the cable\n", "m_cw = 1 # REPLACE \"1\" WITH YOUR EQUATION HERE\n", "\n", "# And print the result\n", "print(\"Mass of the counterweight: m =\", m_cw, \"kg\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Solution** - you can check your answer with the solution by clicking on the \"...\" below.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": { "jupyter": { "source_hidden": true }, "tags": [] }, "source": [ "
\n", " Solution
\n", "\n", "Your code should look like the following:\n", "\n", "m_cw = T / g\n", " \n", "Of course, if you are at ease with Python you can write this as a function, which could also call the ``tension_norm`` function defined earlier instead of using ``T``...\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So now let's come back to our original question: \"which counterweight do you think would allow to suspend the jeans (3kg) on the cable in the position illustrated below?\" \n", "How does the result you obtained just above compare to your estimation before using this notebook?
\n", "More importantly, **can you explain why**?\n", "\n", "\"suspended\n", "\n", "\n", "
\n", " Activity
\n", "\n", "Execute the code cell below and **post a message in the chat** with the value you found for the mass of the counterweight and a brief explanation of why the mass of the counterweight differs from the mass of the jeans. \n", "\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import IFrame\n", "IFrame('https://speakup.epfl.ch/room/82969', 400, 500)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 4. What have you learned so far?\n", "\n", "
\n", " Activity
\n", "\n", "Write **2 things you have learned** about the **tension force** in a cable that is used to suspend an object:\n", " \n", "
" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "- \n", "- " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "Can you identify **other real-life situations** in which cables are used to suspend objects or in which cables are taut between poles? \n", "Write 2 ideas:\n", "\n", "
" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "- \n", "- " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "Congratulations, you have completed the activities!\n", "\n", "\n", "Execute the code cell below and click on the \"A\" button to indicate to the instructor that you have completed all the activities.
\n", "You can then continue with the optional exercises below if you want.\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import IFrame\n", "IFrame('https://speakup.epfl.ch/room/45263', 400, 400)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Additional exercises (optional)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Looking at how the tension in the cable evolves with the angle $\\alpha$\n", "\n", "Now we want to look at **how the tension evolves** when the angle $\\alpha$ changes. \n", "Thanks to our previously defined Python function, we are going to compute the tension in the cable for 100 different values of $\\alpha$, from 20$^\\circ$ to 0$^\\circ$, and plot the result on a graph. \n", "\n", "
\n", " Activity
\n", "\n", "**Execute the code cell below** to see the resulting graph.\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's define the boundaries of the graph\n", "a_start = 20 # start the angle at 20°\n", "a_stop = 0 # stop at O°\n", "\n", "# Now we generate 100 possible values for the angle alpha between a_start and a_stop (excluded)\n", "a_deg = np.linspace(start=a_start, stop=a_stop, num=100, endpoint = False)\n", "print(\"The 100 different values for the angle alpha:\\n\", a_deg, \"\\n\")\n", "\n", "# Since our previously defined function works with angles expressed in radians, we first convert our list of angles from degrees to radians\n", "a_rad = degrees_to_radians(a_deg)\n", "\n", "# Then we compute the value of the tension for all the 100 possible angles alpha using our previously defined function\n", "t = tension_norm(g, m, a_rad)\n", "print(\"The corresponding values for the tension in the cable:\\n\", t)\n", "\n", "# Finally we generate the plot and customize its appearance\n", "fig = figure(title='Tension in the cable', x_axis_label = 'Angle ⍺ (°)', y_axis_label = 'Tension T (N)', x_range=(a_deg[0],0), width=500, height=400, toolbar_location=None)\n", "fig.line(a_deg, t, color=\"red\", line_width=2)\n", "show(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Activity
\n", "\n", "What happens to the tension when the angle $\\alpha$ gets closer to 0$^\\circ$? \n", "Could we ever have $\\alpha$ = 0$^\\circ$, i.e. **could we ever pull the cable taught completely horizontally**?\n", " \n", "Note down your answer in the cell below, then you can check the solution by clicking on the \"...\" below.\n", "
\n" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "Just note down your answer here (this cell is for note taking)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Solution** - you can check your answer with the solution by clicking on the \"...\" below.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": { "jupyter": { "source_hidden": true }, "tags": [] }, "source": [ "
\n", " Solution
\n", "\n", "The curve on the graph indicates that the tension in the cable is getting infinitely big as the angle $\\alpha$ gets closer to 0$^\\circ$. \n", "This means that we will never be able to pull the cable taught horizontally as this would mean an infinite tension (the cable would break before that).\n", "\n", " \n", "From a mathematical point of view, remember that the tension is defined by the following equation: \n", "$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = \\frac{\\frac{1}{2}.m.g}{sin(\\alpha)}\n", "\\end{align}\n", "$\n", "\n", "When $\\alpha$ tends toward 0, $sin(\\alpha)$ also tends toward 0, therefore the limit of the expression defining the tension in the cable is:\n", "\n", "$\n", "\\begin{align}\n", "\\lim_{\\alpha\\to0}\\; \\lvert\\vec{T}\\rvert = \\lim_{\\alpha\\to0}\\;\\frac{\\frac{1}{2}.m.g}{sin(\\alpha)} = +\\infty\n", "\\end{align}\n", "$\n", "\n", "You can find out the detailed answer to this question below, in the [solution section of the notebook](#Solution:-Is-it-possible-to-pull-the-cable-taut-completely-horizontally?).\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How does our Python function behave with $\\alpha$ = 0?\n", "\n", "What happens with our piece of Python code when we say that the cable it taut completely horizontal, i.e. the angle $\\alpha$ = 0$^\\circ$, which is actually impossible in real life? \n", "\n", "
\n", " Activity
\n", "\n", "In the cell below, call your ``tension_norm`` function with a value of $0$ for `alpha` and execute the cell. \n", "What happens? \n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Compute the tension with g = 9.81 m.s-2, m = 3 kg and alpha = 0°\n", "T = 1 # REPLACE 1 WITH THE CALL TO YOUR FUNCTION tension_norm HERE\n", "\n", "# And print the result\n", "print(\"Norm of the tension in the cable: T =\", T, \"N\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Solution** - you can check your answer with the solution by clicking on the \"...\" below.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": { "jupyter": { "source_hidden": true }, "tags": [] }, "source": [ "
\n", " Solution
\n", "\n", "Here is how to call your function:\n", "T = tension_norm(g, m, 0)\n", " \n", "When you execute this code, two things happen:\n", "* You get a warning message which says `RuntimeWarning: divide by zero encountered in double_scalars`\n", "* The function nonetheless returns a value which is `inf`\n", "\n", "When the angle $\\alpha$ = 0 then $sin(\\alpha)$ = 0 and therefore we divide `(.5 * jeans_mass * gravity)` by 0 so on one hand, mathematically speaking, we know that the result should be $+\\infty$. On the other hand, we also know that usually division by 0 is not well supported by computers.\n", "\n", "Actually, division by 0 is not supported in standard Python. \n", "You can create a code cell (click on the `+` icon in the toolbar above) and try to execute the following computation to see what happens: ``(0.5 * 3 * 9.81)/0``\n", " \n", "Now, because in the calculation we use the function `np.sin()` from the Numpy library, our data is automatically converted to Numpy types, which support division by zero and returns the \"real\" result which is $+\\infty$. \n", "By convention, Numpy also generates a warning message but this can be deactivated when not necessary. \n", "If you are curious, you can take a look at [the errors generated by Numpy for floating-point calculations](https://docs.scipy.org/doc/numpy/reference/generated/numpy.seterr.html#numpy.seterr).\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What are the consequences?\n", "\n", "It is important to know that high tension in a cable is very dangerous and can lead to serious accidents, especially when the cable is not well adapted for its intended use (i.e. not strong enough) or when the cable is progressively deteriorating with use. \n", "\n", "
\n", " Activity
\n", "\n", "Execute the cell below to see the video and watch the first minute (sound is not necessary). \n", "What happens at time 0:53? Why?\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import YouTubeVideo\n", "YouTubeVideo('KIbd5zBek5s', 600, 337, start=45, mute=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Solution** - you can check your answer with the solution by clicking on the \"...\" below.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": { "jupyter": { "source_hidden": true }, "tags": [] }, "source": [ "
\n", " Solution
\n", "\n", "At time 0:53, the cable desintegrates completely. This is probably because it has grown weaker and weaker after repeated use until it cannot withstand the high tension anymore.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Improving the `tension_norm` function\n", "\n", "As you have noticed, each time you want to call your `tension_norm` function with an angle expressed in degrees, you have first to convert your angle from degrees to radians. \n", "A better design would be to do the conversion of the angle inside the function so that you can pass it angles in degrees directly.\n", "\n", "
\n", " Activity
\n", "\n", "Define a new function `tension_norm_degrees` which converts `alpha` from degrees to radians using the `degrees_to_radians` function, then calls the `tension_norm` function to compute the norm of the tension and returns the result. \n", " \n", "Then **execute the code cell** so that this function gets defined and then tested with an angle $\\alpha$ of 20$^\\circ$. \n", "If your function works correctly, the result of the execution should be `Tension norm: T = 43.023781748399834 N`.
\n", "You can further check your answer with the solution by clicking on the \"...\" below. \n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define your function\n", "def tension_norm_degrees(g, m, alpha):\n", " # WRITE YOUR CODE HERE\n", " return\n", "\n", "# Let's test it: the execution of these lines should give T = 43.023781748399834 N\n", "alpha = 20\n", "T = tension_norm_degrees(g, m, alpha) \n", "print(\"Tension norm: T =\", T, \"N\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Solution** - you can check your answer with the solution by clicking on the \"...\" below.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": { "jupyter": { "source_hidden": true }, "tags": [] }, "source": [ "
\n", " Solution
\n", "\n", "Your function could look like the following:\n", "\n", "def tension_norm_degrees(g, m, alpha):\n", " apha_rad = degrees_to_radians(alpha)\n", " tension = tension_norm(g, m, apha_rad)\n", " return tension\n", "\n", "\n", "Of course the three lines in this function can be combined into just one: \n", "\n", "def tension_norm_degrees(g, m, alpha):\n", " return tension_norm(g, m, degrees_to_radians(alpha))\n", "\n", " \n", "
Note: Another way to test such a function would be to use [Python assertions](https://zetcode.com/python/assert/). \n", "
However, comparing float values can get tricky. These can be made simpler by the use of more elaborate libraries such as ``pytest``, which support [approximations](https://randycoulman.com/blog/2018/06/19/comparing-floats-in-tests/).\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A function that computes the mass of the counterweight\n", "\n", "From the exercises above, we have learned that \n", "$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = \\frac{\\frac{1}{2}.m.g}{sin(\\alpha)}\n", "\\end{align}\n", "$ and that\n", "$\n", "\\begin{align}\n", "m_{cw} = \\frac{\\lvert\\vec{T}\\rvert}{g}\n", "\\end{align}\n", "$\n", "\n", "From there, we can write the equation for the mass of the counterweight as a function of $\\alpha$:\n", "\n", "$\n", "\\begin{align}\n", "m_{cw} = \\frac{\\frac{1}{2}.m}{sin(\\alpha)}\n", "\\end{align}\n", "$\n", "\n", "
\n", " Activity
\n", "\n", "In the code cell below, write a function `counterweight_mass` that takes `m` and `alpha` as an inputs and returns the mass of the counterweight as computed by the equation above. \n", "Of course you can reuse code from the previous activities as a model. \n", " \n", "Then use this function to compute the counterweight necessary so that the cable makes an angle of 1.5$^\\circ$ with the horizon. \n", "You should find a counterweight of approximately 57 kg.\n", "\n", "You can find the complete solution in the [detailed solution section below](#Numerical-application).\n", "\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Write your code here\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "



\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": true, "toc-nb-collapsed": true }, "source": [ "# Solution: Is it possible to pull the cable taut completely horizontally?\n", "\n", "In this section we demonstrate the physical and mathematical reasoning to answer this question.\n", "\n", "### Goal\n", "\n", "For the cable to be taut completely horizontally means that the angle $\\alpha$ that the cable makes with the horizon is 0$^\\circ$. \n", "In other words, the question could be reframed as: which mass $m_{cw}$ should we put as a counterweight to get $\\alpha$ = 0$^\\circ$?\n", "If we can find a 'reasonable' value for the counterweight then it will mean that it is possible to pull the cable taut completely horizontally.\n", "\n", "To answer this question, we therefore look for an expression relating the **mass of the counterweight $m_{cw}$** and the **angle $\\alpha$** that the cable makes with the horizon.\n", "\n", "\n", "### Method\n", "\n", "Given that the system is in static equilibrium, the sum of external forces exerted on the system will be zero, so using Newton's second law should be easy. The force that the counterweight exerts on the system will involve the mass of the counterweight so we should be able to rewrite Newton's second law to get an expression of the form $m_{cw} = ...$.\n", "\n", "### Hypotheses and simplifications\n", "\n", "We make the following assumptions and simplifications:\n", "* the jeans are considered as positioned exactly mid-way between the poles so the tension is equal on both sides of the cable\n", "* we represent the jeans by the point at which they are suspended\n", "* the cable is considered as rigid (not bended), with a negligible mass\n", "* the pulley is considered as perfect, without mass nor friction\n", "* we consider the static equilibrium obtained after changing the weight, once the system is stabilized\n", "\n", "### Resolution\n", "\n", "First, let's draw a diagram and represent the different forces involved.\n", "\n", "\n", "The *forces applied on the jeans* are:\n", "* the weight: $\\vec F_j = m_j \\vec g$ \n", "* the force exerted by the cable on each side of the jeans: assuming the jeans are suspended at the exact center of the cable, then the tension applied on each of the two sides is equally distributed with two tensions of equal magnitude $T$ on each side but with opposite directions on the $x$ axis, which combine into a vertical resulting tension $\\vec T_r$\n", "\n", "From Newton's second law in a static equilibrium we can write: $\\sum \\vec F_j = \\vec 0$ \n", "With the forces on the jeans we get: $\\vec F_j + \\vec T_r = 0$ \n", "\n", "If we project on $x$ and $y$ axes, we get: \n", "$\\left\\{\\begin{matrix} F_{jx} + T_{rx} = 0 \\\\ F_{jy} + T_{ry} = 0\\end{matrix}\\right. $\n", "\n", "Since $\\vec T_r$ is the result of two tensions of same magnitude $T$ on both sides of the jeans but of opposite directions on the $x$ axis, we can decompose $T_{rx}$ and $T_{ry}$ into the $x$ and $y$ components of $T$: \n", "$\\left\\{\\begin{matrix} T_{rx} = T_{x} - T_{x} \\\\ T_{ry} = 2.T_{y} \\end{matrix}\\right. $\n", "\n", "\n", "Now we can use this in our previous equations, which gives: \n", "$\\left\\{\\begin{matrix} F_{jx} + T_{x} - T_{x} = 0 \\\\ F_{jy} + 2.T_{y} = 0\\end{matrix}\\right. $\n", "\n", "\n", "Since the two $T_{x}$ cancel out we cannot know their value yet and so we focus on the second equation: \n", "$\\begin{align} F_{jy} + 2.T_y = 0 \\end{align}$\n", "\n", "The component of the weight on the y axis is $F_{jy} = - m_j.g$, which gives us: \n", "$\\begin{align} - m_j.g + 2.T_y = 0 \\end{align}$\n", "\n", "Using the angle $\\alpha$ we can get the tension $T_y$ expressed as a function of T since $sin(\\alpha) = \\frac{T_y}{T}$, therefore $T_y = T.sin(\\alpha)$\n", "\n", "By replacing $T_y$ by this expression in the above equation we get: \n", "$\\begin{align} - m_j.g + 2.T.sin(\\alpha) = 0 \\end{align}$\n", "\n", "From there we can get $T$, and this is equation number $(1)$: \n", "\n", "$\n", "\\begin{align}\n", "T = \\frac{m_j.g}{2.sin(\\alpha)}\n", "\\end{align}\n", "$\n", "\n", " \n", "\n", "We now want to make the mass of the counterweight appear in this expression. \n", "So we will now look at the forces applied on the *counterweight*.\n", "\n", " \n", "\n", "The forces applied on the *counterweight* are:\n", "* the weight: $\\vec F_{cw} = m_{cw} \\vec g$ \n", "* the force exerted by the cable: a simple pulley simply changes the direction of the tension so the tension applied on the counterweight is therefore of the same magnitude $T$ as before but with a different direction - we will note it $\\vec T$\n", "\n", "From Newton's second law in a static equilibrium we can write: $\\sum \\vec F_{cw} = \\vec 0$ \n", "With the forces involved in our problem : $\\vec F_{cw} + \\vec T = \\vec 0$ \n", "\n", "All forces being vertical, there is no need to project on $x$ so we get: $- F_{cw} + T = 0$ \n", "We replace the weight by its detailed expression: $-m_{cw}.g + T = 0$ \n", "Now we can express $T$ as a function of the other parameters, which is equation number $(2)$: $T = m_{cw}.g$ \n", "\n", " \n", "\n", "Let's now summarize what we have so far with equations $(1)$ and $(2)$: \n", "\n", "$\n", "\\begin{align}\n", "\\left\\{\\begin{matrix}T = \\frac{m_j.g}{2.sin(\\alpha)} \\\\ T = m_{cw}.g\\end{matrix}\\right. \n", "\\end{align}\n", "$\n", "\n", "These two equations combined give us:\n", "\n", "$\n", "\\begin{align}\n", "\\frac{m_j.g}{2.sin(\\alpha)} = m_{cw}.g\n", "\\end{align}\n", "$\n", "\n", "This allow us to find the mass of the counterweight as a function of the *mass of the jeans* and of the *angle that the cable makes with the horizon*: \n", "\n", "
\n", "\n", "$$m_{cw} = \\frac{m_j}{2.sin(\\alpha)}$$\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Conclusion\n", "\n", "When the cable approaches the horizon, $\\alpha$ is really small i.e. really close to zero. \n", "This means that $sin(\\alpha)$ is also close to zero, which means in turn that $m_{cw}$ is very big.\n", "Therefore, **the more we want the cable to be close to the horizon, the bigger $m_{cw}$ we will need!**\n", "\n", "Mathematically speaking, the limit of the expression defining the mass of the counterweight when alpha tends to 0 is:\n", "\n", "$\n", "\\begin{align}\n", "\\lim_{\\alpha\\to0}m_{cw} = \\lim_{\\alpha\\to0}\\frac{m_j}{2.sin(\\alpha)} = +\\infty\n", "\\end{align}\n", "$\n", "\n", "So basically, we would need to put an **infinitely heavy counterweight** to make the angle null and that is why it is impossible to get the cable taut so that it is absolutely straight (the cable would break before that!)..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Numerical application\n", "\n", "We have already defined above the constant `m` which represents the mass of our jeans (3 kg).\n", "\n", "Let's define a Python function that represents the above equation. \n", "The function takes two input parameters, `m` which represents the mass of our jeans and `alpha`, the angle that the cable makes with the horizon. \n", "It returns the mass of the counterweight as computed with the equation above." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define the function\n", "def counterweight_mass(m, alpha):\n", " return m / (2 * np.sin(alpha))\n", "\n", "# And display a message once it is defined\n", "print(\"Function defined.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For an angle $\\alpha$ of $1.5^\\circ$, we need to put a counterweight of:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Choose a value for angle alpha\n", "alpha = 1.5\n", "\n", "# Then convert it to radians\n", "alpha_radians = degrees_to_radians(alpha)\n", "\n", "# Compute the mass of the counterweight using our function\n", "m_cw = counterweight_mass(m, alpha_radians)\n", "\n", "# And print the result\n", "print(\"Mass of the counterweight: m_cw =\", m_cw, \"kg\")" ] } ], "metadata": { "kernelspec": { - "display_name": "Python", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" }, "toc-autonumbering": false, "toc-showcode": false, "toc-showmarkdowntxt": false }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/examples/Demonstration-Physics.ipynb b/examples/Demonstration-Physics.ipynb index faebdbf..04e6889 100644 --- a/examples/Demonstration-Physics.ipynb +++ b/examples/Demonstration-Physics.ipynb @@ -1,645 +1,645 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Example extracted from the Mécanique Générale repository (MecaDRIL) by Cécile Hébert, more on: https://github.com/c-hebert/MecaDRIL.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# EXPERIENCE DE LA BILLE SUR GLISSIÈRE" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import urllib\n", "import os\n", "#from notebook import notebookapp\n", "from jupyter_server import serverapp\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import pandas as pd\n", "import math\n", "\n", "import io\n", "import base64\n", "import ipywidgets as widgets\n", "from ipywidgets import interact, interactive, fixed, interact_manual, Label, VBox\n", "from IPython.display import HTML, Video, IFrame\n", "\n", "from bokeh.plotting import figure, curdoc\n", "from bokeh.models.widgets import Slider, CheckboxButtonGroup, PreText\n", "from bokeh.layouts import row, column, widgetbox\n", "from bokeh.models import ColumnDataSource, Slider, Button, TextInput, Arrow, OpenHead, NormalHead, VeeHead\n", "from bokeh.plotting import figure, show, ColumnDataSource\n", "\n", "from bokeh.io import output_notebook, show, export_png\n", "\n", "output_notebook()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Le problème :" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Une bille est lâchée dans une glissière qui forme un looping. Si elle n'est pas lâchée d'une hauteur suffisante, elle ne complète pas le looping. De quelle hauteur faut-il la lâcher pour qu'elle complète le looping ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Détail des calculs et de la modélisation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"expérience" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On lâche la bille d'une hauteur h et on observe son comportement au point P en supposant qu'elle suit jusqu'en P la trajectoire imposée par la glissière. On prend $P$ dans le quart supérieur droit de la glissière ($\\theta >0$)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1. On cherche la norme de $\\vec v$ au point P: $v_p$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "La conservation de l'énergie donne: \n", "$$mgh = mgy_{p} + \\frac{1}{2}mv_{p}^{2}$$\n", "\n", "$$y_p = R ( 1 + \\sin\\theta)$$\n", "\n", "$$v_p = \\sqrt{2g[h - R ( 1 + \\sin\\theta)]}$$\n", "\n", "Un calcul plus juste tient compte de l'énergie de rotation de la bille. Dans ce cas la conservation de l'énergie devient: \n", "$$mgh = mgy_p + \\frac{1}{2}mv_{p}^{2} + \\frac{1}{2}Iw_{p}^{2}$$\n", "\n", "$$I = \\frac{2}{5}mr^2$$\n", "\n", "$$w_p = \\frac{v_p}{r}$$ avec r le rayon de la bille \n", "\n", "$$mgh = mgy_p + \\frac{1}{2}mv_{p}^{2} + \\frac{1}{2}\\frac{2}{5}mr^2\\frac{v_p^2}{r^2}$$\n", "\n", "$$gh = gR(1 + \\sin\\theta) + \\frac{7}{10}v_p^2$$\n", "\n", "$$v_p = \\sqrt{\\frac{10}{7}g[h - R(1 + \\sin\\theta)]}$$\n", "\n", "de manière générale, on peut donc écrire\n", "\n", "$$v_p = \\sqrt{A g[h - R(1 + \\sin\\theta)]}$$\n", "\n", "Avec $A$ un facteur qui vaut 2 pour un objet qui glisse et $\\frac{10}{7}$ pour une sphère homogène qui roule sans glisser.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2. On cherche les composantes du vecteur $\\vec v_p$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "La direction du vecteur vitesse en P s'obtient par le fait qu'il est tangent à la glissière\n", "\n", "$$\\vec v=\\begin{pmatrix}-v_p\\sin\\theta\\\\v_p\\cos\\theta \\end{pmatrix}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3. On calcule la trajectoire parabolique qu'aurait une bille libre dans le champ de pesanteur si elle était lancée en P avec $\\vec v_p$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\\vec r_P=\\begin{pmatrix}-v_p\\sin\\theta \\, t + R \\cos \\theta \\\\-\\frac{1}{2} g t^2 + v_p\\cos\\theta \\, t \n", "+ R (1+\\sin \\theta)\n", "\\end{pmatrix}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4. Analyse des forces exercées sur la bille en P." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Les forces sont le poids $\\vec P$ et la réaction de la glissière $\\vec N$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$ \\vec P = m \\vec g = - mg \\vec e_y $$ \n", "\n", "$$ \\vec N = N \\vec n$$ \n", "avec $\\vec n$ vecteur normal au support pointant vers le centre du cercle. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$\\vec N$ est forcément dirigée vers le centre du cercle donc $N \\geq 0$. \n", "\n", "$N <0$ impliquerait que la glissière attire la bille. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Au point P, \n", "$$ \\sum \\vec F = m \\vec a = m \\frac{v_p^2}{R}\\vec n + m \\left ( \\frac{\\mathrm{d} v}{\\mathrm{d} t} \\right )\\vec \\tau = -mg\\vec e_y + N\\vec n$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Projeté sur $\\vec n $, on obtient, \n", "\n", "$$m g \\sin \\theta + N = m \\frac{v_P^2}{R}$$\n", "\n", "$$N= m(\\frac{v_P^2}{R} - g \\sin \\theta)$$\n", "\n", "$$N= m g \\Big(A \\frac{h}{R} - A (1 + \\sin\\theta) - \\sin \\theta \\Big)$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On remarque donc que si $h$ n'est pas assez grand, $N$ va devenir négatif pour un $\\theta < \\frac{\\pi}{2}$. Cela signifie que au point P, la bille devrait être attiré par la glissière pour rester dessus... elle décolle. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tracé de la figure " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Paramètres pour la figure" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Définition de l' image de fond\n", "import base64\n", "\n", "def file2src(filename, data_type='image/png'):\n", " with open(filename, \"rb\") as image_file:\n", " data = base64.b64encode(image_file.read())\n", " image_file.close()\n", " return 'data:{};base64,{}'.format(data_type, data.decode())\n", "\n", "# Définition de l'image de fond\n", "img_path = file2src(\"resources/background.png\")\n", "\n", "# Import des données du video\n", "df = pd.read_csv('resources/data-05-04-19.csv', index_col=0)\n", "df = df.rename({'VideoAnalysis: X (cm)': 'x', 'VideoAnalysis: Y (cm)':'y',\n", " 'VideoAnalysis: X Velocity (cm/s)':'v_x', 'VideoAnalysis: Y Velocity (cm/s)':'v_y'}, axis='columns')\n", "\n", "# Calculer les limites \n", "y_min = df['y'].min()\n", "x_min = df['x'].min()\n", "x_max = df['x'].max()\n", "x_lowest = df.loc[df['y'] == y_min]['x'].iloc[0]\n", "\n", "# Calculer le rayon de la boucle\n", "df_h = df.loc[df['x'] < 20]\n", "df_h = df_h.loc[df_h['x'] > -20]\n", "R = df_h['y'].max() - df_h['y'].min()\n", "idx = df_h.loc[df_h['y'] == df_h['y'].min()]\n", "R = R/2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Équations et paramètres initiaux" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Choissir A: 2 ou 10/7\n", "A = 10/7\n", "\n", "# Définir parametres initiaux\n", "g = 9.81 # pesanteur\n", "h = 2*R # diamètre\n", "theta = 20 # angle par rapport à l'horizontale\n", "to_rad = lambda x: np.pi*x/180.0\n", "thetarad = to_rad(theta)\n", "\n", "t = np.linspace(0, 2, 100) # temps après que la bille est arrivéee en P (tracé parabole)\n", "neg_t = np.linspace(0, -1, 100) # temps avant que la bille soit arrivéee en P (tracé parabole)\n", "\n", "# Eq. parametrique de la parabole\n", "x_p = lambda v, ang, t: -v*np.sin(ang)*t + R*np.cos(ang)\n", "y_p = lambda v, ang, t: -0.5*g*t*t + v*np.cos(ang)*t + R*(1 + np.sin(ang))\n", "\n", "# Définition du vecteur vitesse (tracé depuis P)\n", "vx = lambda v, ang: np.linspace(x_p(v, ang, t)[0], x_p(v, ang, t)[0] - 0.5*v*np.sin(ang), 10) \n", "vy = lambda v, ang: np.linspace(y_p(v, ang, t)[0], y_p(v, ang, t)[0] + 0.5*v*np.cos(ang), 10) \n", "\n", "# Define equations\n", "vitesse_p = lambda h, A, theta_rad: math.sqrt(A*g*R*(h - (1 + np.sin(theta_rad)))) #Vitesse au point A\n", "normale_p = lambda hsurR, A, angle: g*(A*hsurR-A*(1+np.sin(angle))-np.sin(angle)) #Force normale au point A\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Figure" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## PLOT\n", "\n", "def modify_doc(doc):\n", " \n", " # Définir information à visualiser\n", " x = -df['x']\n", " y = df['y'] - y_min\n", " x0 = x.iloc[0]\n", " y0 = y.iloc[0]\n", "\n", " # Définir glissière\n", " slope2 = (y.iloc[6] - y.iloc[5]) / (x.iloc[6] - x.iloc[5])\n", " get_x = lambda y_ini: x0 - (y0 - y_ini)*(1/slope2)\n", "\n", " line_ini_y = np.linspace(2, y.iloc[0], 20)\n", " line_ini_x = get_x(line_ini_y)\n", "\n", " slope = np.arctan((y.iloc[6] - y.iloc[5]) / (x.iloc[6] - x.iloc[5]))\n", " line_end_x = x.iloc[-1] - np.linspace(0, 40, 20)*np.cos(-slope-0.02)\n", " line_end_y = y.iloc[-1] - np.linspace(0, 40, 20)*np.sin(-slope -0.02)\n", "\n", " angle = np.linspace(0, 2*np.pi, 100)\n", " \n", " # Conditions initiales \n", " g = 9.81\n", " h = 2.0\n", " A = 10/7\n", " theta = 45\n", " thetarad = to_rad(theta)\n", " t = np.linspace(0, 2, 100)\n", " neg_t = np.linspace(0, -1, 100)\n", " vit_P = vitesse_p(h,A, thetarad)\n", " lwidth = 2.5 \n", "\n", "\n", " def initialize(h_init, thetarad):\n", " # Calculer position, vitesse et forces au point P\n", " vit_P = vitesse_p(h_init, A, thetarad) # vitesse en P\n", " n_P = normale_p(h_init, A, thetarad) # force normale en P\n", " \n", " # coordonnées parabole après point P\n", " x_parab = x_p(vit_P, thetarad, t) \n", " y_parab = y_p(vit_P, thetarad, t)\n", " \n", " # coordonnées parabole avant point P\n", " x_parab_neg = x_p(vit_P, thetarad, neg_t)\n", " y_parab_neg = y_p(vit_P, thetarad, neg_t)\n", " \n", " # vecteur force normale\n", " norm_end_x = x_parab[0] - n_P*np.cos(thetarad)\n", " norm_end_y = y_parab[0] - n_P*np.sin(thetarad)\n", " \n", " # vecteur vitesse\n", " v_x, v_y = vx(vit_P, thetarad), vy(vit_P, thetarad)\n", " \n", " P = {\n", " 'x_init': [get_x(h_init*R)], 'y_init':[h_init*R],\n", " 'x_point_P': [x_parab[0]], 'y_point_P': [y_parab[0]]\n", " }\n", " \n", " CURV = {\n", " 'x_point_P_parab_pos': x_parab, 'y_point_P_parab_pos': y_parab,\n", " 'x_point_P_parab_neg': x_parab_neg, 'y_point_P_parab_neg': y_parab_neg\n", " }\n", " \n", " ANG = {\n", " 'l_hor_x':np.linspace(0,R, 100), 'l_hor_y':[R]*100, \n", " 'l_ver_x': np.linspace(0, x_parab[0], 100), 'l_ver_y': np.linspace(R,y_parab[0],100)\n", " \n", " }\n", " \n", " FORCES = {\n", " 'grav_x': [x_parab[0]]*10, 'grav_y': np.linspace(y_parab[0], -9.8+y_parab[0], 10),\n", " 'arrow_up_grav_x': np.linspace(x_parab[0], 0.5 + x_parab[0], 10), \n", " 'arrow_up_grav_y': np.linspace(-9.8 + y_parab[0], 0.6 -9.8 + y_parab[0], 10),\n", " 'arrow_down_grav_x': np.linspace(x_parab[0], -0.5 + x_parab[0], 10), \n", " 'arrow_down_grav_y': np.linspace(-9.8 + y_parab[0], 0.6 -9.8 + y_parab[0], 10),\n", " 'norm_x': np.linspace(x_parab[0], norm_end_x, 10), 'norm_y': np.linspace(y_parab[0], norm_end_y, 10),\n", " 'arrow_up_norm_x': norm_end_x + np.linspace(0, 0.1*n_P*np.cos(thetarad - np.pi*20/180), 10), \n", " 'arrow_up_norm_y': norm_end_y + np.linspace(0, 0.1*n_P*np.sin(thetarad - np.pi*20/180), 10), \n", " 'arrow_down_norm_x': norm_end_x + np.linspace(0, 0.1*n_P*np.cos(thetarad + np.pi*20/180), 10), \n", " 'arrow_down_norm_y': norm_end_y + np.linspace(0, 0.1*n_P*np.sin(thetarad + np.pi*20/180), 10)\n", " }\n", " \n", " VEL = {\n", " 'v_x': v_x,\n", " 'v_y': v_y, \n", " 'arrow_up_vel_x': v_x[-1] + np.linspace(0, 0.1*vit_P*np.cos(thetarad - np.pi*70/180), 10),\n", " 'arrow_up_vel_y': v_y[-1] + np.linspace(0, 0.1*vit_P*np.sin(thetarad - np.pi*70/180), 10), \n", " 'arrow_down_vel_x': v_x[-1] + np.linspace(0,- 0.1*vit_P*np.cos(thetarad + np.pi*70/180), 10),\n", " 'arrow_down_vel_y': v_y[-1] + np.linspace(0,- 0.1*vit_P*np.sin(thetarad + np.pi*70/180), 10) \n", "\n", " }\n", " \n", " return P, CURV, ANG, FORCES, VEL\n", " \n", " \n", " # Calculer les valeurs initiales\n", " P, C, ANG, FORCES, VEL = initialize(h, thetarad)\n", "\n", " # Créer dictionnaire pour acceder aux données\n", " source_P = ColumnDataSource(data = P)\n", " source_C = ColumnDataSource(data = C)\n", " source_ANGLE = ColumnDataSource(data = ANG)\n", " source_FORCES = ColumnDataSource(data = FORCES)\n", " source_VITESSE = ColumnDataSource(data= VEL)\n", " \n", " \n", " # Figure\n", " p = figure(title=\"Trajectory\", plot_height=425, plot_width=950, x_range=(-57,53), y_range=(-5,45),\\\n", " background_fill_color='#ffffff')\n", " \n", " # Définir image de fond\n", " p.image_url(url=[img_path], x=-56.5, y=45, w=110, h=45, alpha=0.2, angle=-np.pi*1/180)\n", "\n", "\n", " # Partie fixe de la figure:\n", " # Glissière\n", " p.line(line_ini_x, line_ini_y, color='#635c74', alpha=0.7, line_width=2.5, legend_label='Piste')\n", " p.line(R*np.sin(angle), R + R*np.cos(angle), color='#635c74', alpha=0.6, line_width=2.5, legend_label='Piste')\n", " # Centre de la boucle\n", " p.circle(0,R,size=3, fill_color='#635c74', line_color='#635c74', legend_label='Centre')\n", "\n", "\n", " # Variables:\n", " # Point initial\n", " p_ini = p.circle('x_init', 'y_init', source=source_P, size=6, fill_color='#e32020', line_color='#e32020', legend_label='Point Initial')\n", "\n", " # Point A\n", " p_a = p.circle('x_point_P','y_point_P', source=source_P, size=6, \\\n", " legend_label='A', line_color='#e32020', fill_color='#e32020')\n", "\n", " # Parabole\n", " parab = p.line('x_point_P_parab_pos', 'y_point_P_parab_pos', source=source_C, color='#e32020', line_width=1.5, alpha=0.8, \\\n", " legend_label='Parabole')\n", "\n", " parab_neg = p.line('x_point_P_parab_neg', 'y_point_P_parab_neg', source=source_C, color='#e32020', line_width=1.5, alpha=0.8, \\\n", " legend_label='Parabole')\n", " \n", "\n", " # Angle theta\n", " l_h = p.line('l_hor_x', 'l_hor_y', source=source_ANGLE, line_width=lwidth, color='#000000', alpha=0.2)\n", " l_v = p.line('l_ver_x', 'l_ver_y', source=source_ANGLE, line_width=lwidth ,color='#000000', alpha=0.2)\n", "\n", " # Force gravité\n", " f_g = p.line('grav_x', 'grav_y', source=source_FORCES, line_width=lwidth, color='#178717', alpha=0.8, legend_label='mg')\n", " a_u_f = p.line('arrow_up_grav_x', 'arrow_up_grav_y', source=source_FORCES, line_width=lwidth, color='#178717', alpha=0.8, legend_label='mg')\n", " a_d_f = p.line('arrow_down_grav_x', 'arrow_down_grav_y', source=source_FORCES, line_width=lwidth, color='#178717', alpha=0.8, legend_label='mg')\n", "\n", " # Force normale\n", " f_n = p.line('norm_x', 'norm_y', source=source_FORCES, line_width=lwidth*1.2, color='#064d06', alpha=0.8, legend_label='N')\n", " a_u_n = p.line('arrow_up_norm_x', 'arrow_up_norm_y', source=source_FORCES, line_width=lwidth, color='#064d06', alpha=0.8, legend_label='N')\n", "\n", " a_d_n = p.line('arrow_down_norm_x', 'arrow_down_norm_y', source=source_FORCES, line_width=lwidth, color='#064d06', alpha=0.8, legend_label='N')\n", "\n", " # Vitesse\n", " v = p.line('v_x', 'v_y', source=source_VITESSE, color='#01B9FF', legend_label='Vitesse', line_width=lwidth) \n", " a_u = p.line('arrow_up_vel_x','arrow_up_vel_y', source=source_VITESSE, line_width=lwidth)\n", " a_d = p.line('arrow_down_vel_x','arrow_down_vel_y', source=source_VITESSE, line_width=lwidth)\n", " \n", " \n", " # Définir sliders \n", " slider_h = Slider(start=2, end=3, step=0.05, value=2, title='Hauteur initiale')\n", " slider_om = Slider(start=0, end=90, step=1, value=45, title='Theta')\n", " \n", " pre = PreText(text='Point initiale.\\tx:{:3.2f} \\ty:{:3.2f} \\nPoint A.\\tx: {:3.2f} \\ty: {:3.2f} \\nVitesse.\\t{:3.2f}[cm/s]\\tx:{:3.2f}[cm/s]\\ty:{:3.2f}[cm/s] \\nForce normale. \\t{:3.2f} [N]'\\\n", " .format(source_P.data['x_init'][0], source_P.data['y_init'][0],source_P.data['x_point_P'][0], source_P.data['y_point_P'][0], \\\n", " vit_P, -vit_P*np.cos(thetarad), vit_P*np.sin(thetarad), normale_p(h, A, thetarad)),\n", " width=500, height=100)\n", " \n", " \n", " def refresh_source(attrname, old, new):\n", " \n", " # Mettre à jour les valeurs à afficher \n", " om = slider_om.value\n", " h = slider_h.value\n", " #h = h*R # hauteur par rapport au rayon\n", " omrad = to_rad(om)\n", "\n", " P, C, ANG, FORCES, VEL = initialize(h, omrad)\n", " \n", " source_P.data = P\n", " source_C.data = C\n", " source_ANGLE.data = ANG\n", "\n", " source_FORCES.data = FORCES\n", " source_VITESSE.data = VEL\n", " \n", " vit_P = vitesse_p(h, A, omrad)\n", " x_p = source_C.data['x_point_P_parab_pos'][0]\n", " y_p = source_C.data['y_point_P_parab_pos'][0]\n", " \n", " pre.text='Point initiale.\\tx:{:3.2f} \\ty:{:3.2f} \\nPoint A.\\tx: {:3.2f} \\ty: {:3.2f} \\nVitesse.\\t{:3.2f}[cm/s]\\tx:{:3.2f}[cm/s]\\ty:{:3.2f}[cm/s] \\nForce normale. \\t{:3.2f} [N]'\\\n", " .format(source_P.data['x_init'][0], source_P.data['y_init'][0],source_P.data['x_point_P'][0], source_P.data['y_point_P'][0], \\\n", " vit_P, -vit_P*np.sin(omrad), vit_P*np.cos(omrad), normale_p(h, A, omrad))\n", " \n", " \n", " slider_h.on_change('value', refresh_source)\n", " slider_om.on_change('value', refresh_source)\n", " \n", " layout = column(\n", " row(p),\n", " row(slider_h, slider_om),\n", " row(pre)\n", " )\n", "\n", " \n", " doc.add_root(layout)\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def remote_jupyter_proxy_url(port):\n", " \"\"\"\n", " Callable to configure Bokeh's show method when a proxy must be\n", " configured.\n", "\n", " If port is None we're asking about the URL\n", " for the origin header.\n", " \"\"\"\n", " \n", " base_url = os.environ['EXTERNAL_URL']\n", " host = urllib.parse.urlparse(base_url).netloc\n", "\n", " # If port is None we're asking for the URL origin\n", " # so return the public hostname.\n", " if port is None:\n", " return host\n", "\n", " service_url_path = os.environ['JUPYTERHUB_SERVICE_PREFIX']\n", " proxy_url_path = 'proxy/%d' % port\n", "\n", " user_url = urllib.parse.urljoin(base_url, service_url_path)\n", " full_url = urllib.parse.urljoin(user_url, proxy_url_path)\n", " return full_url\n", "\n", "\n", "\n", "def show_document(doc):\n", " servers = list(serverapp.list_running_servers())[0]\n", " if servers['hostname'] == 'localhost':\n", " show(doc) \n", " else:\n", " show(doc, notebook_url=remote_jupyter_proxy_url)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Utilisation de la figure" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On varie h et $\\theta$ et on observe: \n", "\n", "- l'intensité et la direction de $\\vec N$, calculé par la relation $N= m g \\Big(A \\frac{h}{R} - A (1 + \\sin\\theta) - \\sin \\theta \\Big)$\n", "\n", "- et l'allure de la parabole qu'aurait une bille en chute libre avec $\\vec v_p$ en P. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***Comment se place la parabole par rapport à la glissière quand N=0 ? Et quand N>0 ? Et N<0 ?***" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "show_document(modify_doc);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Feedback" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "IFrame('https://www.surveymonkey.com/r/NOTOSURVEY?notebook_set=MecaDRIL¬ebook_id=bille_sur_glissiere', 600, 800)" ] } ], "metadata": { "hide_input": false, "kernelspec": { - "display_name": "Python", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/examples/TextbookA-SignalProcessing.ipynb b/examples/TextbookA-SignalProcessing.ipynb index e6d10eb..6bc9f50 100644 --- a/examples/TextbookA-SignalProcessing.ipynb +++ b/examples/TextbookA-SignalProcessing.ipynb @@ -1,397 +1,397 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Example extracted from the Signal Processing repository (COM303) by Paolo Prandoni, more on: https://github.com/prandoni/COM303.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Hearing the phase of a sound\n", "\n", "In this notebook we will investigate the effect of phase on the perceptual quality of a sound. It is often said that the human ear is largely insensitive to phase and that's why most of the equalization in commercial-grade audio equipment takes place in the magnitude domain only.\n", "\n", "But is it really so? Let's find out." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "import matplotlib\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import IPython\n", "from scipy.io import wavfile" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.rcParams[\"figure.figsize\"] = (14,4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will be synthesizing audio clips so let's set the sampling rate for the rest of the notebook:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Fs = 16000 # sampling freqency\n", "TWOPI = 2 * np.pi" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will be synthesizing and playing audio clips so let's define a convenience function to \"beautify\" the resulting sound: basically, we want a gentle fade-in and fade-out to avoid abrupt \"clicks\" when the waveform begins and ends. \n", "\n", "Also, there is a \"bug\" in the current version of IPython whereby audio data is forcibly normalized prior to playing (see [here](https://github.com/ipython/ipython/issues/8608) for details; this may have been solved in the meantime). On the other hand, we want to avoid normalization in order to keep control over the volume of the sound. A way to do so is to make sure that all audio clips have at least one sample at a pre-defined maximum value, and this value is the same for all clips. To do so we add a slow \"tail\" to the data which will not result in an audible sound but will set a common maximum value in all clips." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def prepare(x, max_value = 3):\n", " N = len(x)\n", " # fade-in and fade-out times max 0.2 seconds\n", " tf = min(int(0.2 * Fs), int(0.1 * N))\n", " for n in range(0, int(tf)):\n", " s = float(n) / float(tf)\n", " x[n] *= s\n", " x[N-n-1] *= s\n", " # let's append an anti-normalization tail; drawback is one second of silence in the end\n", " x = np.concatenate((x, np.linspace(0, max_value, int(Fs/2)), np.linspace(max_value, 0, int(Fs/2))))\n", " return x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1) Sustained sounds\n", "\n", "The first experiment will use sustained sounds, i.e. waveform whose global envelope does not change in time. A periodic sustained waveform is simply the sum of harmonically-related sinusoidal components, i.e. a set of sines or cosines with different amplitudes and phases at frequencies multiple of a fundamental. The fundamental frequency is also known as the pitch of the sound.\n", "\n", "A class of instruments that produce good sustained sounds is the woodwinds (clarinet, saxophone, etc). The mechanics of sound generation in woodwinds are beyond the scope of this notebook and plenty of references can be found on the internet. For our purposes we will choose to simulate a clarinet according to the analysis described [here](http://www.phy.mtu.edu/~suits/clarinet.html).\n", "\n", "![title](figs/clarinet.png)\n", "\n", "A clarinet-like sustained sound will contain frequencies at odd multiples of the fundamental. We will just keep the fundamental and five harmonics and we be able to specify the initial phase offset for each component:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def clarinet(f, phase = []):\n", " # length in seconds of audio clips\n", " T = 3\n", " \n", " # we will keep 5 harmonics and the fundamental\n", " # amplitude of components: \n", " ha = [0.75, 0.5, 0.14, 0.5, 0.12, 0.17]\n", " \n", " # phase\n", " phase = np.concatenate((phase, np.zeros(len(ha)-len(phase))))\n", "\n", " x = np.zeros((T * Fs))\n", " # clarinet has only odd harmonics\n", " n = np.arange(len(x))\n", " for k, h in enumerate(ha):\n", " x += h * np.sin(phase[k] + TWOPI * (2*k + 1) * (float(f)/Fs) * n)\n", " return x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# fundamental frequency: D4\n", "D4 = 293.665\n", "x = clarinet(D4)\n", "\n", "# let's look at the waveform, nice odd-harmonics shape: \n", "plt.plot(x[0:300])\n", "plt.show()\n", "\n", "# and of course we can play it (using our preparing function):\n", "IPython.display.Audio(prepare(x), rate=Fs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ok, so it's not the best clarinet sound in the universe but it's not bad for just a few lines of code. Now let's see how changing the phase affects the sound. Let's just use random phase offsets for the components: we can see that the waveform doesn't look too nice anymore:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xrp = clarinet(D4, [3.84, 0.90, 3.98, 4.50, 4.80, 2.96])\n", "\n", "plt.plot(xrp[0:300])\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# but if we play it, it sounds the same! \n", "IPython.display.Audio(prepare(xrp), rate=Fs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "OK, so it seems that phase is not important after all. To check once again, run the following notebook cell as many times as you want and see if you can tell the difference between the original zero-phase and a random-phase sustained note (the phases will be different every time you run the cell):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xrp = clarinet(D4, np.random.rand(6) * TWOPI)\n", "plt.plot(xrp[0:300])\n", "plt.show() \n", "IPython.display.display(IPython.display.Audio(prepare(x), rate=Fs))\n", "IPython.display.display(IPython.display.Audio(prepare(xrp), rate=Fs))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2) Dynamic sounds\n", "\n", "In the second experiment we will use real-world dynamic sounds, i.e. sounds that display time-varying characteristics. Typically, a physical musical instrument will produce sounds whose envelope displays four subsequent portions:\n", "\n", "* the **attack** time is the time taken for the sound to go from silence to max amplitude\n", "* the **decay** time is the time taken for the sound to decrease to sustain level\n", "* the **sustain** time is the time during the sound is kept at the same amplitude\n", "* the **release** time is the time taken for sound to go to zero after the stimulation is stopped.\n", "\n", "![title](figs/piano.jpg)\n", "\n", "Consider for instance a piano note: the attack time is very quick (the hammer hits the string); the decay is quite rapid as the string settles into harmonic equilibrium but there is no sustain since once the hammer hits, the stimulation ends. So a piano note has a distinct volume envelope that rises very fast and then releases slowly:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scipy.io import wavfile\n", "Fs, x = wavfile.read(\"resources/piano.wav\")\n", "plt.plot(x)\n", "plt.show() \n", "IPython.display.Audio(x, rate=Fs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By now we know that the \"shape\" of a waveform is largely encoded in the phase. It is no surprise, therefore, that if we mess up with the phase of the piano sample above, we will get something that looks very different.\n", "\n", "To see this, let's take the DFT of the audio data, set the phase to zero and take the IDFT:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# first some prep work; let's make sure that\n", "# the length of the signal is even \n", "# (it will be useful later)\n", "if len(x) % 2 != 0:\n", " x = x[:-1]\n", "\n", "# let's also store the maximum value for our \n", "# \"prepare\" function \n", "mv = int(max(abs(x)) * 1.2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's take the Fourier transform\n", "X = np.fft.fft(x)\n", "\n", "# we can plot the DFT and verify we have a nice \n", "# harmonic spectrum\n", "plt.plot(np.abs(X[0:int(len(X)/2)]))\n", "plt.show() " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# now we set the phase to zero; we just need to\n", "# take the magnitude of the DFT\n", "xzp = np.fft.ifft(np.abs(X))\n", "\n", "# in theory, xzp should be real; however, because\n", "# of numerical imprecision, we're left with some imaginary crumbs:\n", "print (max(np.imag(xzp)) / max(np.abs(xzp)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# the imaginary part is negligible, as expected, \n", "# so let's just get rid of it\n", "xzp = np.real(xzp)\n", "\n", "# and now we can plot:\n", "plt.plot(xzp)\n", "plt.show() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Gee, what happened?!? Well, by removing the phase, we have destroyed the timing information that, for instance, made the sharp attack possible (mathematically, note that by creating a zero-phase spectrum we did obtain a symmetric signal in the time domain!).\n", "\n", "If we play the waveform, we can hear that the pitch and some of the timbral quality have been preserved (after all, the magnitude spectrum is the same), but the typical piano-like envelope has been lost." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "IPython.display.Audio(prepare(xzp, mv), rate=Fs)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can amuse ourselves with even more brutal phase mangling: let's for instance set a random phase for each DFT component. The only tricky thing here is that we need to preserve the Hermitian symmetry of the DFT in order to have a real-valued time-domain signal:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# we know the signal is even-length so we need to build\n", "# a phase vector of the form [0 p1 p2 ... pM -pM ... -p2 -p1]\n", "# where M = len(x)/2\n", "ph = np.random.rand(int(len(x) / 2) ) * TWOPI * 1j\n", "# tricky but cute Python slicing syntax...\n", "ph = np.concatenate(([0], ph, -ph[-2::-1]))\n", "\n", "# now let's add the phase offset and take the IDFT\n", "xrp = np.fft.ifft(X * np.exp(ph))\n", "\n", "# always verify that the imaginary part is only roundoff error\n", "print (max(np.imag(xrp))/max(np.abs(xrp)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xrp = np.real(xrp)\n", "plt.plot(xrp)\n", "plt.show()\n", "\n", "IPython.display.Audio(prepare(xrp, mv), rate=Fs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pretty bad, eh? So, in conclusion, phase is very important to the temporal aspects of the sound, but not so important for sustained sounds. In fact, the brain processes the temporal and spectral cues of sound very differently: when we concentrate on attacks and sound envelope, the brain uses time-domain processing, whereas for pitch and timbre, it uses primarily the magnitude of the spectrum!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" }, "toc-autonumbering": false }, "nbformat": 4, "nbformat_minor": 4 }