diff --git a/Banner/Noto.ipynb b/Banner/Noto.ipynb deleted file mode 100644 index 568a525..0000000 --- a/Banner/Noto.ipynb +++ /dev/null @@ -1,115 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# The physics of suspended objects" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import math\n", - "from IPython.display import IFrame\n", - "IFrame('https://h5p.org/h5p/embed/583522', 800, 450)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "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 is equally distributed $\\vec T$, which combine into a vertical resulting tention $\\vec T_r = 2.\\vec T$\n", - "\n", - "\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", - "Using the fact that the tension is equal on both sides of the jeans we get: $\\vec F_j + 2.\\vec T = 0$ \n", - "[...] \n", - "With equations $(1)$ and $(2)$ we obtain: $\\left\\{\\begin{matrix}T = \\frac{m_j.g}{2.sin(\\alpha)} \\\\ T = m_{cw}.g\\end{matrix}\\right.$\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 line makes with the horizon*: \n", - "\n", - "$\n", - "\\begin{align}\n", - "m_{cw} = \\frac{m_j}{2.sin(\\alpha)}\n", - "\\end{align}\n", - "$\n", - "\n", - "Here is the equivalent Python function:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def counterweight_mass(jean_mass, alpha):\n", - " return jean_mass / (2 * math.sin(alpha))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To pull the cable taut to an angle of $\\frac{\\pi}{120}=1.5^{\\circ}$ with jeans of 3 kg suspended on it, the counterweight necessary is:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "mcw = counterweight_mass(3, math.pi/120)\n", - "print('{:.03f} kg'.format(mcw))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The virtual lab below allows to vary the mass of the counterweight and visualize the cable as well as the angle $\\alpha$ it makes with the horizong and the height at which the jeans get suspended:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "from lib.suspendedobjects import *\n", - "SuspendedObjectsLab();" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/Banner/figs/jean-solution.png b/Banner/figs/jean-solution.png deleted file mode 100644 index 7d8c742..0000000 Binary files a/Banner/figs/jean-solution.png and /dev/null differ diff --git a/Banner/lib/suspendedobjects.py b/Banner/lib/suspendedobjects.py deleted file mode 100644 index 8c231a2..0000000 --- a/Banner/lib/suspendedobjects.py +++ /dev/null @@ -1,242 +0,0 @@ -import numpy as np - -from ipywidgets import interact, interactive, fixed, interact_manual -from ipywidgets import HBox, VBox, Label, Layout -import ipywidgets as widgets - -from IPython.display import set_matplotlib_formats, display, Math, Markdown, Latex -set_matplotlib_formats('svg') - -import matplotlib.pyplot as plt -import matplotlib.patches as pat -plt.style.use('seaborn-whitegrid') # global style for plotting - - -class SuspendedObjectsLab: - """ - This class embeds all the necessary code to create a virtual lab to study the static equilibrium of an object suspended on a clothesline with a counterweight. - """ - - def __init__(self, m_object = 3, distance = 5, height = 1, x_origin = 0, y_origin = 0): - ''' - Initiates and displays the virtual lab on suspended objects. - - :m_object: mass of the suspended object - :distance: horizontal distance between the two poles - :height: height of the poles (same height for both) - :x_origin: x coordinate of the bottom of the left pole (origin of the coordinate system) - :y_origin: y coordinate of the bottom of the left pole (origin of the coordinate system) - ''' - - ###--- Parameters of the situation - self.m_object = m_object # mass of the wet object, in kg - - self.distance = distance # distance between the poles, in m - self.height = height # height of the poles, in m - - self.x_origin = x_origin # x coordinate of point of origin of the figure = x position of the left pole, in m - self.y_origin = y_origin # y coordinate of point of origin of the figure = y position of the lower point (ground), in m - - - ###--- Then we define the elements of the ihm: - # parameters for sliders - self.m_counterweight_min = 0 - self.m_counterweight_max = 100 - self.m_counterweight = 12 # initial mass of the counterweight (0 by default, no counterweight at the beginning) - - # IHM input elements - input_layout=Layout(margin='5px 10px') - self.m_counterweight_label = Label('Mass of the counterweight ($kg$):')#, layout=input_layout - self.m_counterweight_widget = widgets.FloatSlider(min=self.m_counterweight_min,max=self.m_counterweight_max,step=1,value=self.m_counterweight)#, layout=input_layout - self.m_counterweight_input = HBox([self.m_counterweight_label, self.m_counterweight_widget]) - - # IHM output elements - self.title_output = widgets.Output()#layout=input_layout - with self.title_output: - display(Markdown(r'Mass of the suspended object: $m = {} kg$'.format(self.m_object))) - self.graph_output = widgets.Output()#layout=input_layout - - # Linking widgets to handlers - self.m_counterweight_widget.observe(self.m_counterweight_event_handler, names='value') - - # Organize layout - self.ihm = VBox([self.graph_output, self.m_counterweight_input]) - - - ###--- Finally, we display the whole interface and we update it right away so that it plots the graph with current values - display(self.ihm); - self.update_lab() - - - # Event handlers - def m_counterweight_event_handler(self, change): - self.m_counterweight = change.new - self.update_lab() - - - # Utility functions - def get_angle(self, m_counterweight): - """ - Computes the angle that the cable makes with the horizon depending on the counterweight chosen: - - if the counterweight is sufficient: angle = arcsin(1/2 * m_object / m_counterweight) - - else (object on the ground): alpha = np.arctan(height / (distance / 2)) - - :m_counterweight: mass of the chosen counterweight - - :returns: angle that the cable makes with the horizon (in rad) - """ - # Default alpha value i.e. object is on the ground - alpha_default = np.arctan(self.height / (self.distance / 2)) - alpha = alpha_default - - # Let's check that there is actually a counterweight - if m_counterweight > 0: - - # Then we compute the ratio of masses - ratio = 0.5 * self.m_object / m_counterweight - - # Check that the ratio of masses is in the domain of validity of arcsin ([-1;1]) - if abs(ratio) < 1: - alpha = np.arcsin(ratio) - - return min(alpha_default, alpha) - - - def get_object_coords(self, angle): - """ - Computes the position of the object on the cable taking into account the angle determined by the counterweight and the dimensions of the hanging system. - By default: - - the object is supposed to be suspended exactly in the middle of the cable - - the object is on considered the ground for all values of the angle - - :angle: angle that the cable makes with the horizon - - :returns: coordinates of the point at which the object are hanged - """ - # the jean is midway between the poles - x_object = self.x_origin + 0.5 * self.distance - - # default y value: the jean is on the ground - y_object = self.y_origin - - # we check that the angle is comprised between horizontal (greater than 0) and vertical (smaller than pi/2) - if angle > 0 and angle < (np.pi / 2): - # we compute the delta between the horizon and the point given by the angle - delta = (0.5 * self.distance * np.tan(angle)) - # we check that the delta is smaller than the height of the poles (otherwise it just means the jean is on the ground) - if delta <= self.height: - y_object = self.y_origin + self.height - delta - - return [x_object, y_object] - - - # Create visualisation - def update_lab(self): - # Clear outputs - self.graph_output.clear_output(wait=True) - - # Compute new values with the counterweight selected by the user - alpha = self.get_angle(self.m_counterweight) - alpha_degrees = alpha*180/np.pi - alpha_text = r'$\alpha$ = {:.2f} $^\circ$'.format(alpha_degrees) - - coord_object = self.get_object_coords(alpha) - height_text = r'h = {:.2f} $m$'.format(coord_object[1]) - - - # Create the figure - fig = plt.figure(figsize=(12, 4)) - ax1 = plt.subplot(131) - ax2 = plt.subplot(132) - ax3 = plt.subplot(133, sharex = ax2, sharey = ax1) - - - ###--- First display the clothesline - ax1.set_title('Suspended object ({} kg)'.format(self.m_object)) - - # Fix graph to problem boundaries - ax1.set_ylim(bottom = self.y_origin) # limit bottom of y axis to ground - ax1.set_ylim(top = self.y_origin + self.height + .1) # limit top of y axis to values just above height - - # Customize graph style so that it doesn't look like a graph - #ax1.get_xaxis().set_visible(False) # hide the x axis - ax1.grid(False) # hide the grid - ax1.set_ylabel("Height ($m$)") # add a label on the y axis - ax1.set_xlabel("Distance ($m$)") # add a label on the x axis - ax1.spines['top'].set_visible(False) # hide the frame except bottom line - ax1.spines['right'].set_visible(False) - ax1.spines['left'].set_visible(False) - - # Draw poles - x_pole1 = np.array([self.x_origin, self.x_origin]) - y_pole1 = np.array([self.y_origin, self.y_origin+self.height]) - ax1.plot(x_pole1, y_pole1, "k-", linewidth=7) - x_pole2 = np.array([self.x_origin+self.distance, self.x_origin+self.distance]) - y_pole2 = np.array([self.y_origin, self.y_origin+self.height]) - ax1.plot(x_pole2, y_pole2, "k-", linewidth=7) - - # Draw the hanging cable - x = np.array([self.x_origin, coord_object[0], self.x_origin+self.distance]) - y = np.array([self.y_origin+self.height, coord_object[1], self.y_origin+self.height]) - ax1.plot(x, y, linewidth=3, linestyle = "-", color="green") - - # Draw the horizon line - ax1.axhline(y=self.y_origin+self.height, color='gray', linestyle='-.', linewidth=1, zorder=1) - - # Draw the angle between the hanging cable and horizonline - ellipse_radius = 0.2 - fig_ratio = self.height / self.distance - ax1.add_patch(pat.Arc(xy = (self.x_origin, self.y_origin+self.height), width = ellipse_radius/fig_ratio, height = ellipse_radius, theta1 = -1*alpha_degrees, theta2 = 0, color="gray", linestyle='-.')) - ax1.annotate(alpha_text, xy=(self.x_origin, self.y_origin+self.height), xytext=(30, -15), textcoords='offset points', bbox=dict(boxstyle="round", facecolor = "white", edgecolor = "white", alpha = 0.8)) - - # Draw the point at which the object is suspended - ax1.scatter(coord_object[0], coord_object[1], s=80, c="r", zorder=15) - ax1.annotate(height_text, xy=(coord_object[0], coord_object[1]), xytext=(10, -10), textcoords='offset points', bbox=dict(boxstyle="round", facecolor = "white", edgecolor = "white", alpha = 0.8)) - - - - ###--- Then display the angle and the height as functions from the mass of the counterweight - ax2.set_title(r'Angle $\alpha$ ($^\circ$)') - ax3.set_title(r'Height ($m$)') - - # Create all possible values of the mass of the counterweight - m_cw = np.linspace(self.m_counterweight_min, self.m_counterweight_max, 100) - - # Compute the angle (in degrees) and height for all these values - angle = [] - height = [] - for m in m_cw: - a = self.get_angle(m) - #a = self.get_angle_from_masses(self.m_object, m, self.distance, self.height) - angle.append(a*180/np.pi) - c = self.get_object_coords(a) - #c = self.get_object_coordinates(a, self.x_origin, self.y_origin, self.distance, self.height) - height.append(c[1]) - - # Display the functions on the graphs - ax2.set_xlabel('Mass of the counterweight (kg)') - ax2.plot(m_cw, angle, "b") - - ax3.set_xlabel('Mass of the counterweight (kg)') - ax3.plot(m_cw, height, "b") - - # Draw the horizon lines - ax2.axhline(y=self.y_origin, color='gray', linestyle='-.', linewidth=1, zorder=1) - ax3.axhline(y=self.y_origin+self.height, color='gray', linestyle='-.', linewidth=1, zorder=1) - - # Add the current angle from the counterweight selected by the user - ax2.scatter(self.m_counterweight, alpha_degrees, s=80, c="r", zorder=15) - ax2.annotate(alpha_text, xy=(self.m_counterweight, alpha_degrees), xytext=(10, 5), textcoords='offset points', bbox=dict(boxstyle="round", facecolor = "white", edgecolor = "white", alpha = 0.8)) - - # Add the current height from the counterweight selected by the user - ax3.scatter(self.m_counterweight, coord_object[1], s=80, c="r", zorder=15) - ax3.annotate(height_text, xy=(self.m_counterweight, coord_object[1]), xytext=(10, -10), textcoords='offset points', bbox=dict(boxstyle="round", facecolor = "white", edgecolor = "white", alpha = 0.8)) - - - # Display graph - with self.graph_output: - plt.show(); - - - -# EOF