diff --git a/PredictionQuestion/FreeFall-withCode.ipynb b/PredictionQuestion/FreeFall-withCode.ipynb
new file mode 100644
index 0000000..78e8c48
--- /dev/null
+++ b/PredictionQuestion/FreeFall-withCode.ipynb
@@ -0,0 +1,474 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook is about demonstrating:\n",
+ "* prediction questions\n",
+ "* observation questions\n",
+ "\n",
+ "The example chosen is voluntarily *simple* so that anyone can understand what is illustrated and focus the pedagogical features of the example."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Free fall\n",
+ "\n",
+ "An object is dropped from a given height with no initial velocity. We are interested in the movement from the object in the case where *resistance from the air is ignored*.\n",
+ "\n",
+ "1. Answer the following prediction questions - be sure to write down your answer on a piece of paper:\n",
+ " * Which object would reach the ground first: a bowling ball (5 kg) or a tennis ball (0.05 kg)?\n",
+ " * Why? Describe in words your explanation for this behavior.\n",
+ " * Sketch your prediction for the *height* of the object as a function of time. Describe in words what this graph means.\n",
+ " * Sketch your prediction for the *velocity* of the object as a function of time. Describe in words what this graph means.\n",
+ " * Sketch your prediction for the *acceleartion* of the object as a function of time. Describe in words what this graph means.\n",
+ "\n",
+ "\n",
+ "2. Run the demo and answer the following observation questions :\n",
+ " * When does the bowling ball reaches the ground (time in seconds)?\n",
+ " * When does the tennis ball reaches the ground (time in seconds)?\n",
+ " * When does the ostrich feather reaches the ground (time in seconds)?\n",
+ "\n",
+ "\n",
+ "3. Compare the explanation provided in this notebook with your own explanation.\n",
+ "\n",
+ "To be fully convinced, look at the video at the end of this notebook!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Demo"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import pandas\n",
+ "\n",
+ "from ipywidgets import interact, interactive, fixed, interact_manual\n",
+ "from ipywidgets import HBox, VBox, Label\n",
+ "import ipywidgets as widgets\n",
+ "\n",
+ "import matplotlib.pyplot as plt\n",
+ "%matplotlib inline\n",
+ "plt.style.use('seaborn-whitegrid') # global style for plotting"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, let's create the list of objects with which we can experiment.\n",
+ "\n",
+ "Objects come with a name, a mass (in $kg$) and a color for identifying them in the graphical display."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
mass
\n",
+ "
color
\n",
+ "
\n",
+ "
\n",
+ "
name
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
Bowling ball
\n",
+ "
5.000
\n",
+ "
#DC143C
\n",
+ "
\n",
+ "
\n",
+ "
Tennis ball
\n",
+ "
0.050
\n",
+ "
#2E8B57
\n",
+ "
\n",
+ "
\n",
+ "
Ostrich feather
\n",
+ "
0.005
\n",
+ "
#483D8B
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " mass color\n",
+ "name \n",
+ "Bowling ball 5.000 #DC143C\n",
+ "Tennis ball 0.050 #2E8B57\n",
+ "Ostrich feather 0.005 #483D8B"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Types of objects we have with their mass\n",
+ "objects = [{\n",
+ " 'name':'Bowling ball',\n",
+ " 'mass':5.0,\n",
+ " 'color':'#DC143C'\n",
+ " },{\n",
+ " 'name':'Tennis ball',\n",
+ " 'mass':0.05,\n",
+ " 'color':'#2E8B57'\n",
+ " },{ \n",
+ " 'name':'Ostrich feather',\n",
+ " 'mass':0.005,\n",
+ " 'color':'#483D8B'\n",
+ "}]\n",
+ "objects_list = pandas.DataFrame(objects, columns = ['name', 'mass', 'color'])\n",
+ "objects_list.set_index('name', inplace=True) # Index objects by their name to find them easily after\n",
+ "objects_list"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Then let's define the parameters of our problem:\n",
+ "* gravity\n",
+ "* initial conditions: initial height (in $m$) and initial velocity (in $m.s^{-1}$)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Parameters of the problem\n",
+ "g = 9.81 # gravity in m/s2\n",
+ "\n",
+ "# Initial conditions\n",
+ "h_0 = 5 # initial height in m\n",
+ "v_0 = 0 # initial velocity in m/s"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To plot the movement of our objects in time we need to define a time interval. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Time space\n",
+ "t = np.linspace(0,3,25) # time scale from 0 to x seconds, with n points in the interval"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Then we define three functions which compute the movement of our objects in free fall on our time interval."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Functions representing the equations of the movement as functions of time and the problem parameters\n",
+ "def accel_time (t, m, h_0, v_0):\n",
+ " return [-g]*t.size # returning a list of same length as the time list filled with -g\n",
+ "\n",
+ "def veloc_time (t, m, h_0, v_0):\n",
+ " return -g*t+v_0 \n",
+ "\n",
+ "def height_time (t, m, h_0, v_0):\n",
+ " return -0.5*g*(t**2)+v_0*t+h_0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now we create an interactive graph which will plot the equations of the objects depending on the parameters that the user selects."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "3ea7624081bf4df29df0be5711fe38f4",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "VBox(children=(Output(), HBox(children=(VBox(children=(HBox(children=(Label(value='Choice of object(s):'), Sel…"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# parameters for sliders\n",
+ "h_min = 0\n",
+ "h_max = 10\n",
+ "v_min = 0\n",
+ "v_max = 10\n",
+ "\n",
+ "\n",
+ "# IHM input elements\n",
+ "h_label = Label('Initial height ($m$):')\n",
+ "h_widget = widgets.FloatSlider(min=h_min,max=h_max,step=1,value=h_0)\n",
+ "h_input = HBox([h_label, h_widget])\n",
+ "\n",
+ "v_label = Label('Initial velocity ($m.s^{-1}$):')\n",
+ "v_widget = widgets.FloatSlider(min=v_min,max=v_max,step=1,value=v_0)\n",
+ "v_input = HBox([v_label, v_widget])\n",
+ "\n",
+ "obj_m_label = Label('Choice of object(s):')\n",
+ "obj_m_widget = widgets.SelectMultiple(\n",
+ " options = objects_list.index,\n",
+ " value = [objects_list.index[0],], # tuple(objects_list['name'])\n",
+ " disabled = False\n",
+ ")\n",
+ "obj_m_input = HBox([obj_m_label, obj_m_widget])\n",
+ "\n",
+ "# IHM output elements\n",
+ "obj_m_output = widgets.Output()\n",
+ "graph_output = widgets.Output()\n",
+ "\n",
+ "# Display updated output function\n",
+ "def display_updated_output(h_0, v_0, objs):\n",
+ " # Clear outputs\n",
+ " graph_output.clear_output(wait=True)\n",
+ " obj_m_output.clear_output(wait=True)\n",
+ " \n",
+ " # Create the figure\n",
+ " fig, ax = plt.subplots(1, 3, sharex='col', figsize=(16, 4))\n",
+ "\n",
+ " # for each object selected (by name)\n",
+ " for o in objs:\n",
+ " # get mass\n",
+ " m = objects_list.loc[objects_list.index == o]['mass'].item()\n",
+ " # get color\n",
+ " c = objects_list.loc[objects_list.index == o]['color'].item()\n",
+ " \n",
+ " \n",
+ " # Recompute equations with parameters set by the user\n",
+ " h_t = height_time(t, m, h_0, v_0)\n",
+ " v_t = veloc_time(t, m, h_0, v_0)\n",
+ " a_t = accel_time(t, m, h_0, v_0)\n",
+ "\n",
+ " # Plot equations\n",
+ " ax[0].set_title('Height ($m$)')\n",
+ " ax[0].plot(t, h_t, color=c, label=o)\n",
+ " ax[0].set_ylim(bottom = 0) # limit y axis to values >= 0\n",
+ "\n",
+ " ax[1].set_title('Velocity ($m.s^{-1}$)')\n",
+ " ax[1].plot(t, v_t, color=c, label=o)\n",
+ "\n",
+ " ax[2].set_title('Acceleration ($m.s^{-2}$)')\n",
+ " ax[2].plot(t, a_t, color=c, label=o)\n",
+ " \n",
+ " # Display weight of object selected\n",
+ " with obj_m_output:\n",
+ " print(\"Weight of the object selected (kg): \", m)\n",
+ "\n",
+ " # Add time axis and legend\n",
+ " for a in ax:\n",
+ " a.set_xlabel('Time (s)')\n",
+ " a.legend()\n",
+ "\n",
+ " fig.tight_layout()\n",
+ " \n",
+ " # Display graph \n",
+ " with graph_output:\n",
+ " plt.show()\n",
+ " \n",
+ "\n",
+ "# Event handlers\n",
+ "def h_event_handler(change):\n",
+ " display_updated_output(change.new, v_widget.value, obj_m_widget.value)\n",
+ "\n",
+ "def v_event_handler(change):\n",
+ " display_updated_output(h_widget.value, change.new, obj_m_widget.value)\n",
+ "\n",
+ "def obj_m_event_handler(change):\n",
+ " display_updated_output(h_widget.value, v_widget.value, change.new) # obj_m_transform(change.new)\n",
+ "\n",
+ "\n",
+ "# Linking widgets to handlers\n",
+ "h_widget.observe(h_event_handler, names='value')\n",
+ "v_widget.observe(v_event_handler, names='value')\n",
+ "obj_m_widget.observe(obj_m_event_handler, names='value')\n",
+ "\n",
+ "#ihm = VBox([m_box, output_object, h_box, v_box, output_graph])\n",
+ "# Organize layout \n",
+ "ihm = VBox([graph_output,\n",
+ " HBox([\n",
+ " VBox([obj_m_input, obj_m_output]),\n",
+ " VBox([h_input, v_input])])\n",
+ " ])\n",
+ "\n",
+ "# Display whole interface with the graph already plotted\n",
+ "display(ihm)\n",
+ "display_updated_output(h_0, v_0, [objects_list.index[0],]) # objects_list['name']\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Additional observation questions: role of the initial velocity\n",
+ "\n",
+ "Choose one object. Which initial velocity is it necessary to give it so that it reaches the ground at $t = 2.5 s$?\n",
+ "\n",
+ "Choose one object and give it an initial velocity.\n",
+ "* When does it reaches its maximum height?\n",
+ "* What is its velocity at that moment? Why?\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Explanation\n",
+ "\n",
+ "Let's draw a free body diagram and represent the forces applied on our object.\n",
+ "\n",
+ "![Free Body Diagram](Images/FBD-ball.png)\n",
+ "\n",
+ "If we ignore the friction from air, the only force applied on the object is the weight: $\\vec p = m \\vec g$\n",
+ "\n",
+ "From Newton's second law we can write: $\\sum \\vec F = m \\vec a$\n",
+ "\n",
+ "With the weight the only force we have we get: $\\vec p = m \\vec a$\n",
+ "\n",
+ "Using the expression of the weight: $m \\vec g = m \\vec a$\n",
+ "\n",
+ "Therefore the movement of the object is described by $\\vec a = \\vec g$.\n",
+ "\n",
+ "To get the equation of acceleration as a function of time, let's project onto our coordinate system: $a = -g$, therefore $a(t) = -g$.\n",
+ "This means the ball is under **constant acceleration**\n",
+ "\n",
+ "From there we can get the equations for velocity and height by integrating successively:\n",
+ "\n",
+ "$\\left\\{\\begin{matrix} a(t) = -g \\\\ v(t) = -g\\,t + v_0 \\\\ h(t) = -\\frac{1}{2}\\,g\\,t^2 + v_0\\,t + h_0\\end{matrix}\\right. $\n",
+ "\n",
+ "With the following parameters:\n",
+ "* the initial height $h_0$ from which the object is dropped\n",
+ "* the initial velocity of the object $v_0$, with $v_0 = 0$ when the object is dropped with no initial velocity\n",
+ "* and of course the acceleration due to gravity $g$\n",
+ "\n",
+ "We see clearly that **the mass $m$ of the object plays no role at all in the movement**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# And how does that look in real life?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%%HTML\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Other documents\n",
+ "\n",
+ "https://www.rentech.com.tr/wp-content/uploads/2017/09/PWV-06-ball_toss.pdf\n",
+ "\n",
+ "https://opentextbc.ca/physicstestbook2/chapter/falling-objects/"
+ ]
+ }
+ ],
+ "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": 2
+}
diff --git a/PredictionQuestion/lib/virtuallab.py b/PredictionQuestion/lib/virtuallab.py
new file mode 100644
index 0000000..325195e
--- /dev/null
+++ b/PredictionQuestion/lib/virtuallab.py
@@ -0,0 +1,164 @@
+import numpy as np
+import pandas
+
+from ipywidgets import interact, interactive, fixed, interact_manual
+from ipywidgets import HBox, VBox, Label
+import ipywidgets as widgets
+
+import matplotlib.pyplot as plt
+plt.style.use('seaborn-whitegrid') # global style for plotting
+
+
+###--- First, we create the list of objects with which we can experiment.
+
+# Objects come with a name, a mass (in kg) and a color for identifying them in the graphical display.
+objects = [{
+ 'name':'Bowling ball',
+ 'mass':5.0,
+ 'color':'#DC143C'
+ },{
+ 'name':'Tennis ball',
+ 'mass':0.05,
+ 'color':'#2E8B57'
+ },{
+ 'name':'Ostrich feather',
+ 'mass':0.005,
+ 'color':'#483D8B'
+}]
+objects_list = pandas.DataFrame(objects, columns = ['name', 'mass', 'color'])
+objects_list.set_index('name', inplace=True) # We index objects by their name to find them easily after
+
+
+###--- Then we define the parameters of our problem:
+
+# The standard acceleration due to gravity
+g = 9.81 # gravity in m/s2
+
+# The initial conditions of our problem
+h_0 = 5 # initial height in m
+v_0 = 0 # initial velocity in m/s
+
+# To plot the movement of our objects in time we need to define a time scale.
+t = np.linspace(0,3,25) # time interval from 0 to x seconds, with n points in the interval
+
+
+###--- Then we define three functions which compute the movement of our objects in free fall on our time interval.
+
+# Functions representing the equations of the movement as functions of time and the problem parameters
+def accel_time (t, m, h_0, v_0):
+ return [-g]*t.size # returning a list of same length as the time interval filled with -g
+
+def veloc_time (t, m, h_0, v_0):
+ return -g*t+v_0
+
+def height_time (t, m, h_0, v_0):
+ return -0.5*g*(t**2)+v_0*t+h_0
+
+###--- Now we create an interactive graph which will plot the equations of the objects depending on the parameters that the user selects.
+
+# parameters for sliders
+h_min = 0
+h_max = 10
+v_min = 0
+v_max = 10
+
+
+# IHM input elements
+h_label = Label('Initial height ($m$):')
+h_widget = widgets.FloatSlider(min=h_min,max=h_max,step=1,value=h_0)
+h_input = HBox([h_label, h_widget])
+
+v_label = Label('Initial velocity ($m.s^{-1}$):')
+v_widget = widgets.FloatSlider(min=v_min,max=v_max,step=1,value=v_0)
+v_input = HBox([v_label, v_widget])
+
+obj_m_label = Label('Choice of object(s):')
+obj_m_widget = widgets.SelectMultiple(
+ options = objects_list.index,
+ value = [objects_list.index[0],], # tuple(objects_list['name'])
+ disabled = False
+)
+obj_m_input = HBox([obj_m_label, obj_m_widget])
+
+# IHM output elements
+obj_m_output = widgets.Output()
+graph_output = widgets.Output()
+
+# Display updated output function
+def display_updated_output(h_0, v_0, objs):
+ # Clear outputs
+ graph_output.clear_output(wait=True)
+ obj_m_output.clear_output(wait=True)
+
+ # Create the figure
+ fig, ax = plt.subplots(1, 3, sharex='col', figsize=(16, 4))
+
+ # for each object selected (by name)
+ for o in objs:
+ # get mass
+ m = objects_list.loc[objects_list.index == o]['mass'].item()
+ # get color
+ c = objects_list.loc[objects_list.index == o]['color'].item()
+
+
+ # Recompute equations with parameters set by the user
+ h_t = height_time(t, m, h_0, v_0)
+ v_t = veloc_time(t, m, h_0, v_0)
+ a_t = accel_time(t, m, h_0, v_0)
+
+ # Plot equations
+ ax[0].set_title('Height ($m$)')
+ ax[0].plot(t, h_t, color=c, label=o)
+ ax[0].set_ylim(bottom = 0) # limit y axis to values >= 0
+
+ ax[1].set_title('Velocity ($m.s^{-1}$)')
+ ax[1].plot(t, v_t, color=c, label=o)
+
+ ax[2].set_title('Acceleration ($m.s^{-2}$)')
+ ax[2].plot(t, a_t, color=c, label=o)
+
+ # Display weight of object selected
+ with obj_m_output:
+ print("Weight of the object selected (kg): ", m)
+
+ # Add time axis and legend
+ for a in ax:
+ a.set_xlabel('Time (s)')
+ a.legend()
+
+ fig.tight_layout()
+
+ # Display graph
+ with graph_output:
+ plt.show();
+
+
+# Event handlers
+def h_event_handler(change):
+ display_updated_output(change.new, v_widget.value, obj_m_widget.value)
+
+def v_event_handler(change):
+ display_updated_output(h_widget.value, change.new, obj_m_widget.value)
+
+def obj_m_event_handler(change):
+ display_updated_output(h_widget.value, v_widget.value, change.new) # obj_m_transform(change.new)
+
+
+# Linking widgets to handlers
+h_widget.observe(h_event_handler, names='value')
+v_widget.observe(v_event_handler, names='value')
+obj_m_widget.observe(obj_m_event_handler, names='value')
+
+# Organize layout
+ihm = VBox([graph_output,
+ HBox([
+ VBox([obj_m_input, obj_m_output]),
+ VBox([h_input, v_input])])
+ ])
+
+
+
+###--- Finally, we display the whole interface and we update it right away so that it plots one default object (the first in our lis)
+
+display(ihm);
+display_updated_output(h_0, v_0, [objects_list.index[0],])