diff --git a/TeachingExamples/lib/fallingobjects.py b/TeachingExamples/lib/fallingobjects.py index dee8056..b34b1e5 100644 --- a/TeachingExamples/lib/fallingobjects.py +++ b/TeachingExamples/lib/fallingobjects.py @@ -1,348 +1,349 @@ import numpy as np import pandas from ipywidgets import interact, interactive, fixed, interact_manual from ipywidgets import Box, HBox, VBox, Label, Layout import ipywidgets as widgets from IPython.display import set_matplotlib_formats set_matplotlib_formats('svg') import matplotlib.pyplot as plt plt.style.use('seaborn-whitegrid') # global style for plotting +g = 9.81 ###--- Equation functions implementing a model without air resistance (free fall) def accel_time(o, g, h_0, v_0, t): ''' Computes the acceleration of the object o as a function of time Implements a constantly accelerated vertical motion (free fall) of equation: a(t) = -g :o: object considered :g: gravitational acceleration constant :h_0: initial height of the object :v_0: initial velocity of the object :t: time scale (array of time points at which to compute the equation) :returns: array of values for acceleration at each point of the time scale ''' return [-g] * t.size # returning a list of same length as the time interval filled with -g def veloc_time(o, g, h_0, v_0, t): ''' Computes the velocity of the object o as a function of time Implements a constantly accelerated vertical motion (free fall) of equation: v(t) = -g.t + v_0 :o: object considered :g: gravitational acceleration constant :h_0: initial height of the object :v_0: initial velocity of the object :t: time scale (array of time points at which to compute the equation) :returns: array of values for velocity at each point of the time scale ''' return -g * t + v_0 def height_time(o, g, h_0, v_0, t): ''' Computes the height of the object o as a function of time Implements a constantly accelerated vertical motion (free fall) of equation: h(t) = -1/2.g.t^2 + v_0.t + h_0 :o: object considered :g: gravitational acceleration constant :h_0: initial height of the object :v_0: initial velocity of the object :t: time scale (array of time points at which to compute the equation) :returns: array of values for height at each point of the time scale ''' return -0.5 * g * (t **2) + v_0 * t + h_0 ###--- Equation functions implementing a model with linear friction from air def accel_time_withair(obj, g, h_0, v_0, t): ''' Computes the height of the object o as a function of time Implements a vertical motion taking into account a linear friction from air :o: object considered -- must have a mass and a friction coefficient k :g: gravitational acceleration constant :h_0: initial height of the object :v_0: initial velocity of the object :t: time scale (array of time points at which to compute the equation) :returns: array of values for height at each point of the time scale ''' coeff = obj.k / obj.mass return -g * np.exp(- coeff * t) def veloc_time_withair(obj, g, h_0, v_0, t): ''' Computes the height of the object o as a function of time Implements a vertical motion taking into account a linear friction from air :o: object considered -- must have a mass and a friction coefficient k :g: gravitational acceleration constant :h_0: initial height of the object :v_0: initial velocity of the object :t: time scale (array of time points at which to compute the equation) :returns: array of values for height at each point of the time scale ''' coeff = obj.k / obj.mass return (v_0 + (g / coeff)) * np.exp(- coeff * t) - (g / coeff) def height_time_withair(obj, g, h_0, v_0, t): ''' Computes the height of the object o as a function of time Implements a vertical motion taking into account a linear friction from air :o: object considered -- must have a mass and a friction coefficient k :g: gravitational acceleration constant :h_0: initial height of the object :v_0: initial velocity of the object :t: time scale (array of time points at which to compute the equation) :returns: array of values for height at each point of the time scale ''' coeff = obj.k / obj.mass return (1 / coeff) * (v_0 + (g / coeff)) * (1 - np.exp(- coeff * t)) - (g / coeff) * t + h_0 ###--- Static list of objects with which we can experiment. # Objects come with a name, a mass (in kg), a friction coefficient and a color for identifying them in the graphical display. objects = [{ 'name':'Bowling ball', 'mass':5.0, 'k': (6*np.pi*0.11) * 1.8*10**-5, 'color':'#DC143C' },{ 'name':'Tennis ball', 'mass':0.0567, 'k': (6*np.pi*0.032) * 1.8*10**-5, 'color':'#2E8B57' },{ 'name':'Ping-pong ball', 'mass':0.0027, 'k': (6*np.pi*0.02) * 1.8*10**-5, 'color':'#FF4500' },{ 'name':'Air-inflated balloon', 'mass':0.013, 'k': 0.02,#(6*np.pi*0.28) * 1.8*10**-5, 'color':'#000080' }] def object_string(obj, show_withair = True): '''Utility function to print objects nicely''' return '{!s}:\n mass = {} kg'.format(obj.name, obj.mass)+(' \n friction coeff. = {:.2e} kg/s'.format(obj.k) if show_withair else '') ###--- Virtual lab class FallingObjectsLab: """ This class embeds all the necessary code to create a virtual lab to study the movement of falling objects. """ def __init__(self, objects = objects, g = 9.81, h_0 = 1.5, v_0 = 0, t = np.linspace(0,1.5,30), accel_time = accel_time, veloc_time = veloc_time, height_time = height_time, accel_time_withair = accel_time_withair, veloc_time_withair = veloc_time_withair, height_time_withair = height_time_withair, show_v_0 = False, show_withair = False): ''' Initiates and displays the virtual lab on falling objects. :objects: nested dictionnary with the objects to display, which should come with at least the following properties: a name, a mass (in kg) and a color (HEX code) :g: gravitational acceleration constant :h_0: initial height of the objects :v_0: initial velocity of the objects :t: time scale (array of time points at which to compute the equation) :accel_time: function to compute the acceleration of an object as a function of time -- without air resistance -- a(o, g, h_0, v_0, t) :veloc_time: function to compute the velocity of an object as a function of time -- without air resistance -- v(o, g, h_0, v_0, t) :height_time: function to compute the height of an object as a function of time -- without air resistance -- h(o, g, h_0, v_0, t) :accel_time_withair: function to compute the acceleration of an object as a function of time -- WITH air resistance -- a(o, g, h_0, v_0, t) :veloc_time_withair: function to compute the velocity of an object as a function of time -- WITH air resistance -- v(o, g, h_0, v_0, t) :height_time_withair: function to compute the height of an object as a function of time -- WITH air resistance -- h(o, g, h_0, v_0, t) :show_v_0: when True, a slider to change the initial velocity of objects is displayed in the interface :show_withair: when True, a checkbox allows to plot the equations which include air resistance ''' ###--- We define the parameters of our problem: # Create indexed list of objects self.objects_list = pandas.DataFrame(objects) self.objects_list.set_index('name', inplace=True) # We index objects by their name to find them easily after # Initialize list of currently selected objects with first element of the list self.selected_objs_names = [self.objects_list.index[0],] # The standard acceleration due to gravity self.g = g # gravity in m/s2 # The initial conditions of our problem self.h_0 = h_0 # initial height in m self.v_0 = v_0 # initial velocity in m/s # To plot the movement of our objects in time we need to define a time scale. self.t = t # time interval from 0 to x seconds, with n points in the interval # Functions to compute movement equations - free fall (no air resistance) self.accel_time = accel_time self.veloc_time = veloc_time self.height_time = height_time # Functions to compute movement equations - with air resistance (linear friction) self.withair = False self.accel_time_withair = accel_time_withair self.veloc_time_withair = veloc_time_withair self.height_time_withair = height_time_withair ###--- Then we define the elements of the IHM: # parameters for sliders self.h_min = 0 self.h_max = 3 self.v_min = 0 self.v_max = 3 # IHM input elements input_layout=Layout(margin='2px 6px') self.h_label = Label('Initial height ($m$):', layout=input_layout) self.h_widget = widgets.FloatSlider(min=self.h_min,max=self.h_max,value=self.h_0, layout=input_layout) self.h_input = HBox([self.h_label, self.h_widget]) self.v_label = Label('Initial velocity ($m.s^{-1}$):', layout=input_layout) self.v_widget = widgets.FloatSlider(min=self.v_min,max=self.v_max,value=self.v_0, layout=input_layout) self.v_input = HBox([self.v_label, self.v_widget]) self.obj_label = Label('Choice of object(s):', layout=input_layout) self.obj_widget = widgets.SelectMultiple( options = self.objects_list.index, value = self.selected_objs_names, disabled = False, layout=input_layout ) self.obj_input = HBox([self.obj_label, self.obj_widget]) self.air_label = Label('Include air friction:', layout=input_layout) #self.air_widget = widgets.Checkbox(value=self.withair, layout=input_layout) #self.situation_widget = widgets.ToggleButtons(options=[('In vacuum', False), ('In the air', True)], description='Situation:', layout=input_layout) self.air_widget = widgets.ToggleButton(value=self.withair, description="ok", layout=input_layout) self.air_input = HBox([self.air_label, self.air_widget]) # IHM output elements self.obj_output = widgets.Output(layout=input_layout) self.graph_output = widgets.Output(layout=input_layout) # Linking widgets to handlers self.h_widget.observe(self.h_event_handler, names='value') self.v_widget.observe(self.v_event_handler, names='value') self.obj_widget.observe(self.obj_event_handler, names='value') self.air_widget.observe(self.air_event_handler, names='value') # Organize layout opt_inputs = [self.h_input] if show_v_0: opt_inputs.append(self.v_input) if show_withair: opt_inputs.append(self.air_input) self.ihm = VBox([self.graph_output, HBox([ VBox([self.obj_input, self.obj_output]), VBox(opt_inputs) ]) ]) ###--- 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 h_event_handler(self, change): self.h_0 = change.new self.update_lab() def v_event_handler(self, change): self.v_0 = change.new self.update_lab() def obj_event_handler(self, change): self.selected_objs_names = change.new self.update_lab() def air_event_handler(self, change): self.withair = change.new self.update_lab() # Update output function def update_lab(self): # Clear outputs self.graph_output.clear_output(wait=True) self.obj_output.clear_output(wait=True) # Create the figure fig, ax = plt.subplots(1, 3, sharex='col', figsize=(14, 4)) # for each object currently selected for o_name in self.selected_objs_names: # Get the selected object obj = self.objects_list.loc[o_name] # Recompute equations with parameters set by the user h_t = self.height_time(obj, self.g, self.h_0, self.v_0, self.t) v_t = self.veloc_time(obj, self.g, self.h_0, self.v_0, self.t) a_t = self.accel_time(obj, self.g, self.h_0, self.v_0, self.t) # Plot equations ax[0].set_title('Height ($m$)') ax[0].plot(self.t, h_t, color=obj.color, label=o_name) ax[0].set_ylim(bottom = 0, top = self.h_max+(self.v_max/2 if self.v_0 > 0 else 0.2)) ax[1].set_title('Velocity ($m.s^{-1}$)') ax[1].plot(self.t, v_t, color=obj.color, label=o_name) ax[1].set_ylim(top = self.v_max+1) ax[2].set_title('Acceleration ($m.s^{-2}$)') ax[2].plot(self.t, a_t, color=obj.color, label=o_name) ax[2].set_ylim(top = 0, bottom = - self.g - 1) # If air resistance is activated, then compute the equations and plot them on top if self.withair: h_t_withair = self.height_time_withair(obj, self.g, self.h_0, self.v_0, self.t) ax[0].plot(self.t, h_t_withair, color=obj.color, linestyle='dashed', label=o_name+" with air friction") v_t_withair = self.veloc_time_withair(obj, self.g, self.h_0, self.v_0, self.t) ax[1].plot(self.t, v_t_withair, color=obj.color, linestyle='dashed', label=o_name+" with air friction") a_t_withair = self.accel_time_withair(obj, self.g, self.h_0, self.v_0, self.t) ax[2].plot(self.t, a_t_withair, color=obj.color, linestyle='dashed', label=o_name+" with air friction") # Display characteristics of object selected with self.obj_output: print(object_string(obj, self.withair)) # Add time axis and legend for a in ax: a.set_xlabel('Time (s)') a.legend() fig.tight_layout() # Display graph with self.graph_output: plt.show(); # EOF diff --git a/TeachingHowTos/Docs/NotebookSurvey.pdf b/TeachingHowTos/Docs/NotebookSurvey.pdf new file mode 100644 index 0000000..d04a4ad Binary files /dev/null and b/TeachingHowTos/Docs/NotebookSurvey.pdf differ diff --git a/TeachingHowTos/EmbedFeedbackSurvey.ipynb b/TeachingHowTos/EmbedFeedbackSurvey.ipynb index b69953b..2281925 100644 --- a/TeachingHowTos/EmbedFeedbackSurvey.ipynb +++ b/TeachingHowTos/EmbedFeedbackSurvey.ipynb @@ -1,74 +1,100 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# How to get feedback from students on your notebooks?\n", "\n", + "We offer two ways in which you can get feedback from students on your notebooks:\n", + "1. Through a **[short generic online survey](#generic)** integrated into your notebooks\n", + "2. Through a more **[detailed survey](#specific)** which can be **customized to your needs** and administered online or on paper in your class\n", + "\n", + "## Short generic survey  \n", + "\n", "We provide a generic survey that you can embed into your notebooks to **collect anonymous feedback** from those who use your notebooks. \n", "The survey is designed to be short and easy to fill out, with three questions (both in English and French) focusing on helpfulness, usability and a free text field for comments. You can see the survey form below. \n", "The survey will automatically collect data for you. We then process the data and provide you with a summary of the feedback regularly. \n", "\n", - "## How to use it?\n", + "How to use it?\n", "\n", - "**Step 1:** Add a cell to your notebook and copy the following piece of code in it:\n", + "**Step 1:** Add a cell to your notebook and copy the following piece of Python code in it (below is the code to use in R):\n", "\n", " ```python\n", "from IPython.display import IFrame\n", "IFrame('https://www.surveymonkey.com/r/NOTOSURVEY?notebook_set=COURSENAME¬ebook_id=NOTEBOOKNAME', 600, 800)\n", "```\n", "\n", "**Step 2:** In the code, replace the two following elements (attention: DO NOT USE SPACES, use dash or underscore to replace spaces):\n", "* `COURSENAME`: replace by name of the project or course, e.g. `MecaDRIL` or `PHYS-101`\n", "* `NOTEBOOKNAME`: replace by the name or code of the notebook in the course, e.g. `01-Logan` \n", "\n", "\n", "Example of complete URL: \n", "`https://www.surveymonkey.com/r/NOTOSURVEY?notebook_set=MecaDRIL¬ebook_id=01-Logan`\n", "\n", "**Optional:** It can be useful to add one line of text above the code cell with the survey to indicate to your students that you would appreciate their feedback on your notebook. \n", - "You could use something like:\n", - "\n", - "```\n", - "Did you like this Notebook? Why don't you give us some feedback using the completely anonymous form below (just execute the cell to see it)? Thank you!\n", - "```\n", + "You could use something like: *Did you like this Notebook? Why don't you give us some feedback using the completely anonymous form below (just execute the cell to see it)? Thank you!*\n", "\n", - "## Demo\n", + "**Demo:**\n", "\n", "Execute the cell below to see the survey:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import IFrame\n", "IFrame('https://www.surveymonkey.com/r/NOTOSURVEY?notebook_set=noto-poc-notebooks¬ebook_id=survey-monkey-demo', 600, 900)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you use R, here is the equivalent code:\n", + "\n", + "```R\n", + "library(IRdisplay)\n", + "IRdisplay::display_html(' ') \n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Detailed customized survey  \n", + "\n", + "If you would like to have **more detailed feedback** or if these questions do not match your context, then we can **design a survey** for you. \n", + "You can see [in this file an example of a questionnaire](Docs/NotebookSurvey.pdf) with customized questions, which can be administered either on paper in your class or online. We can help you with designing the questions, administering the questionnaire as well as analyzing the data.\n", + "\n", + "Just **drop us an email at [noto-support@groupes.epfl.ch](mailto:noto-support@groupes.epfl.ch)** if you would like to use one of those questionnaires." + ] } ], "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 }