diff --git a/Physics-demo.ipynb b/Physics-demo.ipynb deleted file mode 100644 index 84839e5..0000000 --- a/Physics-demo.ipynb +++ /dev/null @@ -1,178 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# SEFI Workshop \"Teaching and Learning with Jupyter Notebooks\" \n", - "15 Sept. 2021 \n", - "C. Hardebolle \n", - "[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", - "\n", - "

" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Demonstration with the virtual lab\n", - "\n", - "The \"Suspended object\" virtual lab below allows to **experiment with different counterweights** to see how it affects the position of the jeans suspended on the cable. \n", - "\n", - "Step 1 \\[Predict\\]: Ask students to predict which counterweight allows to suspend the wet jeans (clicker question)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Step 2 \\[Observe\\]: Execute the code cell below (click on it then click on the \"play\" button in the tool bar above) to **launch the virtual lab**. \n", - "\n", - "Ask participants **which value(s) for the counterweight they would be interested to try out** (based on their answer to the clicker question). \n", - "Then test the values suggested by participants and ask them to **observe**: \n", - "* the position of the cable \n", - "* the angle that the cable makes with the horizon \n", - "* the **forces** that apply on the object (and how they evolve)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "(function(root) {\n", - " function embed_document(root) {\n", - " \n", - " var docs_json = {\"203bc558-0134-4c1b-a5b5-4f2694a43e16\":{\"defs\":[],\"roots\":{\"references\":[{\"attributes\":{\"children\":[{\"id\":\"1202\"},{\"id\":\"1144\"},{\"id\":\"1086\"}],\"sizing_mode\":\"scale_both\"},\"id\":\"1203\",\"type\":\"Row\"},{\"attributes\":{\"items\":[{\"id\":\"1143\"}],\"label_text_font_size\":\"12pt\",\"location\":\"bottom_right\"},\"id\":\"1142\",\"type\":\"Legend\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"gray\",\"line_dash\":[6],\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1083\",\"type\":\"Line\"},{\"attributes\":{\"data_source\":{\"id\":\"1080\"},\"glyph\":{\"id\":\"1082\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1083\"},\"view\":{\"id\":\"1085\"}},\"id\":\"1084\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"source\":{\"id\":\"1119\"}},\"id\":\"1123\",\"type\":\"CDSView\"},{\"attributes\":{\"axis_label\":\"Mass of the counterweight (kg)\",\"formatter\":{\"id\":\"1131\"},\"major_label_policy\":{\"id\":\"1132\"},\"ticker\":{\"id\":\"1098\"}},\"id\":\"1097\",\"type\":\"LinearAxis\"},{\"attributes\":{\"label\":{\"field\":\"height_text\"},\"renderers\":[{\"id\":\"1128\"}]},\"id\":\"1143\",\"type\":\"LegendItem\"},{\"attributes\":{\"source\":{\"id\":\"1080\"}},\"id\":\"1085\",\"type\":\"CDSView\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":0.5,\"fill_color\":\"lightgrey\",\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":1.0,\"line_color\":\"black\",\"line_dash\":[4,4],\"line_width\":2,\"right_units\":\"screen\",\"syncable\":false,\"top_units\":\"screen\"},\"id\":\"1027\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"below\":[{\"id\":\"1097\"}],\"center\":[{\"id\":\"1100\"},{\"id\":\"1104\"},{\"id\":\"1124\"},{\"id\":\"1142\"}],\"left\":[{\"id\":\"1101\"}],\"renderers\":[{\"id\":\"1122\"},{\"id\":\"1128\"}],\"sizing_mode\":\"scale_both\",\"title\":{\"id\":\"1087\"},\"toolbar\":{\"id\":\"1112\"},\"toolbar_location\":null,\"x_range\":{\"id\":\"1089\"},\"x_scale\":{\"id\":\"1093\"},\"y_range\":{\"id\":\"1091\"},\"y_scale\":{\"id\":\"1095\"}},\"id\":\"1086\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{\"formatter\":{\"id\":\"1134\"},\"major_label_policy\":{\"id\":\"1135\"},\"ticker\":{\"id\":\"1102\"}},\"id\":\"1101\",\"type\":\"LinearAxis\"},{\"attributes\":{\"align\":\"center\",\"text\":\"Height (m)\"},\"id\":\"1087\",\"type\":\"Title\"},{\"attributes\":{},\"id\":\"1246\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1098\",\"type\":\"BasicTicker\"},{\"attributes\":{},\"id\":\"1247\",\"type\":\"UnionRenderers\"},{\"attributes\":{},\"id\":\"1131\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{},\"id\":\"1110\",\"type\":\"HelpTool\"},{\"attributes\":{},\"id\":\"1095\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1222\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{},\"id\":\"1093\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1223\",\"type\":\"AllLabels\"},{\"attributes\":{\"axis\":{\"id\":\"1097\"},\"grid_line_color\":\"lightgray\",\"minor_grid_line_color\":\"gainsboro\",\"ticker\":null},\"id\":\"1100\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1132\",\"type\":\"AllLabels\"},{\"attributes\":{\"fill_color\":{\"value\":\"black\"},\"line_width\":{\"value\":2},\"size\":{\"value\":8},\"x\":{\"field\":\"m_counterweight\"},\"y\":{\"field\":\"alpha_degrees\"}},\"id\":\"1184\",\"type\":\"Circle\"},{\"attributes\":{\"formatter\":{\"id\":\"1192\"},\"major_label_policy\":{\"id\":\"1193\"},\"ticker\":{\"id\":\"1160\"}},\"id\":\"1159\",\"type\":\"LinearAxis\"},{\"attributes\":{\"align\":\"center\",\"text\":\"Angle \\u237a (\\u00b0)\"},\"id\":\"1145\",\"type\":\"Title\"},{\"attributes\":{\"axis\":{\"id\":\"1101\"},\"dimension\":1,\"grid_line_color\":\"lightgray\",\"minor_grid_line_color\":\"gainsboro\",\"ticker\":null},\"id\":\"1104\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1102\",\"type\":\"BasicTicker\"},{\"attributes\":{\"end\":102},\"id\":\"1147\",\"type\":\"Range1d\"},{\"attributes\":{\"below\":[{\"id\":\"1155\"}],\"center\":[{\"id\":\"1158\"},{\"id\":\"1162\"},{\"id\":\"1182\"},{\"id\":\"1200\"}],\"left\":[{\"id\":\"1159\"}],\"renderers\":[{\"id\":\"1180\"},{\"id\":\"1186\"}],\"sizing_mode\":\"scale_both\",\"title\":{\"id\":\"1145\"},\"toolbar\":{\"id\":\"1170\"},\"toolbar_location\":null,\"x_range\":{\"id\":\"1147\"},\"x_scale\":{\"id\":\"1151\"},\"y_range\":{\"id\":\"1149\"},\"y_scale\":{\"id\":\"1153\"}},\"id\":\"1144\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{\"data\":{\"x\":{\"__ndarray__\":\"AAAAAAAAAAB/pUCtXynwP3+lQK1fKQBAPvjggw8+CEB/pUCtXykQQN/OkJi3MxRAPvjggw8+GECeITFvZ0gcQH+lQK1fKSBAL7rooosuIkDfzpCYtzMkQI/jOI7jOCZAPvjggw8+KEDuDIl5O0MqQJ4hMW9nSCxATjbZZJNNLkB/pUCtXykwQNevFKj1KzFAL7rooosuMkCHxLydITEzQN/OkJi3MzRAN9lkk002NUCP4ziO4zg2QOftDIl5OzdAPvjggw8+OECWArV+pUA5QO4MiXk7QzpARhdddNFFO0CeITFvZ0g8QPYrBWr9Sj1ATjbZZJNNPkCmQK1fKVA/QH+lQK1fKUBAq6qqqqqqQEDXrxSo9StBQAO1fqVArUFAL7rooosuQkBbv1Kg1q9CQIfEvJ0hMUNAs8kmm2yyQ0DfzpCYtzNEQAvU+pUCtURAN9lkk002RUBj3s6QmLdFQI/jOI7jOEZAu+iiiy66RkDn7QyJeTtHQBPzdobEvEdAPvjggw8+SEBq/UqBWr9IQJYCtX6lQElAwgcffPDBSUDuDIl5O0NKQBoS83aGxEpARhdddNFFS0ByHMdxHMdLQJ4hMW9nSExAyiabbLLJTED2KwVq/UpNQCIxb2dIzE1ATjbZZJNNTkB6O0Ni3s5OQKZArV8pUE9A0kUXXXTRT0B/pUCtXylQQBWo9SsFalBAq6qqqqqqUEBBrV8pUOtQQNevFKj1K1FAbbLJJptsUUADtX6lQK1RQJm3MyTm7VFAL7rooosuUkDFvJ0hMW9SQFu/UqDWr1JA8cEHH3zwUkCHxLydITFTQB3HcRzHcVNAs8kmm2yyU0BJzNsZEvNTQN/OkJi3M1RAddFFF110VEAL1PqVArVUQKHWrxSo9VRAN9lkk002VUDN2xkS83ZVQGPezpCYt1VA+eCDDz74VUCP4ziO4zhWQCXm7QyJeVZAu+iiiy66VkBR61cK1PpWQOftDIl5O1dAffDBBx98V0AT83aGxLxXQKn1KwVq/VdAPvjggw8+WEDU+pUCtX5YQGr9SoFav1hAAAAAAAAAWUA=\",\"dtype\":\"float64\",\"order\":\"little\",\"shape\":[100]},\"y\":[30.96375653207352,30.96375653207352,30.96375653207352,29.669750337402906,21.79272868989431,17.277504952145858,14.32962423473438,12.24795877800581,10.697573432357999,9.497234468517048,8.540009363963428,7.758619888586814,7.108575593467493,6.559258996197503,6.088899645992875,5.6815889505198,5.325428962788641,5.011341894945422,4.732280378958207,4.482689296218185,4.258130314199082,4.055014446723428,3.8704080113109756,3.7018894951747736,3.5474423907213284,3.4053738728430356,3.2742523249585984,3.152858804046428,3.0401489447456354,2.9352227725817244,2.837300573935052,2.7457034503230244,2.6598375289594234,2.579181051648375,2.5032737477169347,2.431708032948405,2.364121678560225,2.300191671444479,2.239629045731718,2.182174510964944,2.1275947371941686,2.0756791846184153,2.026237386851151,1.9790966138348436,1.9340998539056633,1.891104065284166,1.8499786559303375,1.8106041577021361,1.7728710664424012,1.7366788242586735,1.701934924063625,1.6685541195747844,1.6364577265604854,1.605573003267094,1.5758325997521698,1.5471740673445704,1.519539420708093,1.4928747460423195,1.4671298498471825,1.442257943434278,1.4182153590108342,1.3949612937102076,1.372457578411083,1.3506684685889094,1.3295604547879678,1.3091020905995137,1.2892638362879665,1.270017916429146,1.2513381901171623,1.2332000324640526,1.2155802262621744,1.1984568628067775,1.1818092509876463,1.165617833856433,1.1498641119621167,1.1345305728225552,1.1196006259666567,1.1050585430404887,1.0908894025226397,1.0770790386402154,1.063613994117737,1.0504814764275492,1.0376693172426923,1.0251659348220228,1.012960299083137,1.001041899141641,0.9894007131159406,0.9780271800151946,0.9669121735446579,0.9560469776775513,0.9454232638560081,0.9350330696957394,0.9248687790799617,0.9149231035379827,0.9051890648127459,0.8956599785296966,0.8863294388866385,0.8771913042908763,0.8682396838759605,0.8594689248358217]},\"selected\":{\"id\":\"1196\"},\"selection_policy\":{\"id\":\"1197\"}},\"id\":\"1177\",\"type\":\"ColumnDataSource\"},{\"attributes\":{},\"id\":\"1106\",\"type\":\"WheelZoomTool\"},{\"attributes\":{},\"id\":\"1153\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1105\",\"type\":\"PanTool\"},{\"attributes\":{\"end\":31.96375653207352,\"start\":-1},\"id\":\"1149\",\"type\":\"Range1d\"},{\"attributes\":{\"overlay\":{\"id\":\"1111\"}},\"id\":\"1107\",\"type\":\"BoxZoomTool\"},{\"attributes\":{\"axis_label\":\"Mass of the counterweight (kg)\",\"formatter\":{\"id\":\"1189\"},\"major_label_policy\":{\"id\":\"1190\"},\"ticker\":{\"id\":\"1156\"}},\"id\":\"1155\",\"type\":\"LinearAxis\"},{\"attributes\":{},\"id\":\"1108\",\"type\":\"SaveTool\"},{\"attributes\":{},\"id\":\"1151\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1109\",\"type\":\"ResetTool\"},{\"attributes\":{},\"id\":\"1168\",\"type\":\"HelpTool\"},{\"attributes\":{},\"id\":\"1156\",\"type\":\"BasicTicker\"},{\"attributes\":{\"axis\":{\"id\":\"1155\"},\"grid_line_color\":\"lightgray\",\"minor_grid_line_color\":\"gainsboro\",\"ticker\":null},\"id\":\"1158\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1134\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"line_cap\":\"round\",\"line_width\":8,\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1042\",\"type\":\"Line\"},{\"attributes\":{\"data\":{\"x\":[5,5],\"y\":[0,1.5]},\"selected\":{\"id\":\"1238\"},\"selection_policy\":{\"id\":\"1239\"}},\"id\":\"1041\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"1036\"},\"glyph\":{\"id\":\"1037\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1038\"},\"view\":{\"id\":\"1040\"}},\"id\":\"1039\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"axis\":{\"id\":\"1159\"},\"dimension\":1,\"grid_line_color\":\"lightgray\",\"minor_grid_line_color\":\"gainsboro\",\"ticker\":null},\"id\":\"1162\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1135\",\"type\":\"AllLabels\"},{\"attributes\":{\"line_alpha\":0.1,\"line_cap\":\"round\",\"line_width\":8,\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1038\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"1160\",\"type\":\"BasicTicker\"},{\"attributes\":{\"source\":{\"id\":\"1041\"}},\"id\":\"1045\",\"type\":\"CDSView\"},{\"attributes\":{\"data\":{\"x\":[0,0],\"y\":[0,1.5]},\"selected\":{\"id\":\"1236\"},\"selection_policy\":{\"id\":\"1237\"}},\"id\":\"1036\",\"type\":\"ColumnDataSource\"},{\"attributes\":{},\"id\":\"1164\",\"type\":\"WheelZoomTool\"},{\"attributes\":{\"source\":{\"id\":\"1036\"}},\"id\":\"1040\",\"type\":\"CDSView\"},{\"attributes\":{},\"id\":\"1163\",\"type\":\"PanTool\"},{\"attributes\":{\"overlay\":{\"id\":\"1169\"}},\"id\":\"1165\",\"type\":\"BoxZoomTool\"},{\"attributes\":{},\"id\":\"1166\",\"type\":\"SaveTool\"},{\"attributes\":{\"level\":\"glyph\",\"source\":{\"id\":\"1052\"},\"text\":{\"field\":\"alpha_text\"},\"x\":{\"value\":0},\"x_offset\":{\"value\":50},\"y\":{\"value\":1.5},\"y_offset\":{\"value\":-20}},\"id\":\"1071\",\"type\":\"LabelSet\"},{\"attributes\":{},\"id\":\"1167\",\"type\":\"ResetTool\"},{\"attributes\":{\"line_alpha\":0.1,\"line_cap\":\"round\",\"line_width\":8,\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1043\",\"type\":\"Line\"},{\"attributes\":{\"fill_color\":{\"value\":\"white\"},\"hatch_color\":{\"value\":\"gray\"},\"hatch_pattern\":{\"value\":\"/\"},\"height\":{\"value\":0.1},\"left\":{\"value\":-0.2},\"line_color\":{\"value\":\"white\"},\"right\":{\"value\":5.2},\"y\":{\"value\":-0.05}},\"id\":\"1048\",\"type\":\"HBar\"},{\"attributes\":{\"data\":{},\"selected\":{\"id\":\"1240\"},\"selection_policy\":{\"id\":\"1241\"}},\"id\":\"1047\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"1041\"},\"glyph\":{\"id\":\"1042\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1043\"},\"view\":{\"id\":\"1045\"}},\"id\":\"1044\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"source\":{\"id\":\"1052\"}},\"id\":\"1129\",\"type\":\"CDSView\"},{\"attributes\":{\"data_source\":{\"id\":\"1047\"},\"glyph\":{\"id\":\"1048\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1049\"},\"view\":{\"id\":\"1051\"}},\"id\":\"1050\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"location\":0},\"id\":\"1046\",\"type\":\"Span\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":0.5,\"fill_color\":\"lightgrey\",\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":1.0,\"line_color\":\"black\",\"line_dash\":[4,4],\"line_width\":2,\"right_units\":\"screen\",\"syncable\":false,\"top_units\":\"screen\"},\"id\":\"1111\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"fill_alpha\":{\"value\":0.1},\"fill_color\":{\"value\":\"white\"},\"hatch_color\":{\"value\":\"gray\"},\"hatch_pattern\":{\"value\":\"/\"},\"height\":{\"value\":0.1},\"left\":{\"value\":-0.2},\"line_alpha\":{\"value\":0.1},\"line_color\":{\"value\":\"white\"},\"right\":{\"value\":5.2},\"y\":{\"value\":-0.05}},\"id\":\"1049\",\"type\":\"HBar\"},{\"attributes\":{\"fill_color\":{\"value\":\"black\"},\"line_width\":{\"value\":2},\"size\":{\"value\":8},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1054\",\"type\":\"Circle\"},{\"attributes\":{},\"id\":\"1193\",\"type\":\"AllLabels\"},{\"attributes\":{\"source\":{\"id\":\"1047\"}},\"id\":\"1051\",\"type\":\"CDSView\"},{\"attributes\":{\"line_color\":\"gray\",\"line_dash\":[6],\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1082\",\"type\":\"Line\"},{\"attributes\":{\"data\":{\"alpha_degrees\":[30.96375653207352],\"alpha_text\":[\"\\u237a = 30.96 \\u00b0\"],\"height_text\":[\"h = 0.00 m\"],\"m_counterweight\":[0.0],\"x\":[2.5],\"y\":[0.0]},\"selected\":{\"id\":\"1140\"},\"selection_policy\":{\"id\":\"1141\"}},\"id\":\"1052\",\"type\":\"ColumnDataSource\"},{\"attributes\":{},\"id\":\"1192\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"fill_alpha\":{\"value\":0.1},\"fill_color\":{\"value\":\"black\"},\"line_alpha\":{\"value\":0.1},\"line_width\":{\"value\":2},\"size\":{\"value\":8},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1055\",\"type\":\"Circle\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":0.5,\"fill_color\":\"lightgrey\",\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":1.0,\"line_color\":\"black\",\"line_dash\":[4,4],\"line_width\":2,\"right_units\":\"screen\",\"syncable\":false,\"top_units\":\"screen\"},\"id\":\"1169\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"data\":{\"x\":[2.74525,2.5,2.25475],\"y\":[0.14715,0.2943,0.14715]},\"selected\":{\"id\":\"1246\"},\"selection_policy\":{\"id\":\"1247\"}},\"id\":\"1080\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"source\":{\"id\":\"1052\"}},\"id\":\"1057\",\"type\":\"CDSView\"},{\"attributes\":{},\"id\":\"1138\",\"type\":\"Selection\"},{\"attributes\":{\"data_source\":{\"id\":\"1177\"},\"glyph\":{\"id\":\"1178\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1179\"},\"view\":{\"id\":\"1181\"}},\"id\":\"1180\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"1052\"},\"glyph\":{\"id\":\"1054\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1055\"},\"view\":{\"id\":\"1057\"}},\"id\":\"1056\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"line_alpha\":0.1,\"line_cap\":\"round\",\"line_width\":2,\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1063\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"1233\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1139\",\"type\":\"UnionRenderers\"},{\"attributes\":{},\"id\":\"1234\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"level\":\"glyph\",\"source\":{\"id\":\"1052\"},\"text\":{\"field\":\"height_text\"},\"x\":{\"field\":\"x\"},\"x_offset\":{\"value\":8},\"y\":{\"field\":\"y\"},\"y_offset\":{\"value\":-20}},\"id\":\"1058\",\"type\":\"LabelSet\"},{\"attributes\":{\"end\":102},\"id\":\"1089\",\"type\":\"Range1d\"},{\"attributes\":{\"data_source\":{\"id\":\"1060\"},\"glyph\":{\"id\":\"1062\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1063\"},\"view\":{\"id\":\"1065\"}},\"id\":\"1064\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"line_cap\":\"round\",\"line_width\":2,\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1062\",\"type\":\"Line\"},{\"attributes\":{\"data\":{\"x\":[0,2.5,5],\"y\":[1.5,0.0,1.5]},\"selected\":{\"id\":\"1242\"},\"selection_policy\":{\"id\":\"1243\"}},\"id\":\"1060\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"active_multi\":null,\"tools\":[{\"id\":\"1105\"},{\"id\":\"1106\"},{\"id\":\"1107\"},{\"id\":\"1108\"},{\"id\":\"1109\"},{\"id\":\"1110\"}]},\"id\":\"1112\",\"type\":\"Toolbar\"},{\"attributes\":{\"source\":{\"id\":\"1060\"}},\"id\":\"1065\",\"type\":\"CDSView\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"gray\",\"line_dash\":[2,2],\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1068\",\"type\":\"Line\"},{\"attributes\":{\"line_color\":\"green\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1120\",\"type\":\"Line\"},{\"attributes\":{\"line_color\":\"gray\",\"line_dash\":[2,2],\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1067\",\"type\":\"Line\"},{\"attributes\":{\"data_source\":{\"id\":\"1119\"},\"glyph\":{\"id\":\"1120\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1121\"},\"view\":{\"id\":\"1123\"}},\"id\":\"1122\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data\":{\"x\":[0.44999999999999996,0.25724787771376323],\"y\":[1.5,1.345651273371742]},\"selected\":{\"id\":\"1244\"},\"selection_policy\":{\"id\":\"1245\"}},\"id\":\"1066\",\"type\":\"ColumnDataSource\"},{\"attributes\":{},\"id\":\"1141\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"level\":\"glyph\",\"source\":{\"id\":\"1073\"},\"text\":{\"field\":\"name\"},\"text_color\":{\"field\":\"color\"},\"x\":{\"field\":\"x_start\"},\"x_offset\":{\"field\":\"x_offset\"},\"y\":{\"field\":\"y_start\"},\"y_offset\":{\"field\":\"y_offset\"}},\"id\":\"1078\",\"type\":\"LabelSet\"},{\"attributes\":{\"line_color\":\"gray\",\"line_dash\":[6],\"location\":1.5},\"id\":\"1124\",\"type\":\"Span\"},{\"attributes\":{\"data_source\":{\"id\":\"1066\"},\"glyph\":{\"id\":\"1067\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1068\"},\"view\":{\"id\":\"1070\"}},\"id\":\"1069\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data\":{\"x\":{\"__ndarray__\":\"AAAAAAAAAAB/pUCtXynwP3+lQK1fKQBAPvjggw8+CEB/pUCtXykQQN/OkJi3MxRAPvjggw8+GECeITFvZ0gcQH+lQK1fKSBAL7rooosuIkDfzpCYtzMkQI/jOI7jOCZAPvjggw8+KEDuDIl5O0MqQJ4hMW9nSCxATjbZZJNNLkB/pUCtXykwQNevFKj1KzFAL7rooosuMkCHxLydITEzQN/OkJi3MzRAN9lkk002NUCP4ziO4zg2QOftDIl5OzdAPvjggw8+OECWArV+pUA5QO4MiXk7QzpARhdddNFFO0CeITFvZ0g8QPYrBWr9Sj1ATjbZZJNNPkCmQK1fKVA/QH+lQK1fKUBAq6qqqqqqQEDXrxSo9StBQAO1fqVArUFAL7rooosuQkBbv1Kg1q9CQIfEvJ0hMUNAs8kmm2yyQ0DfzpCYtzNEQAvU+pUCtURAN9lkk002RUBj3s6QmLdFQI/jOI7jOEZAu+iiiy66RkDn7QyJeTtHQBPzdobEvEdAPvjggw8+SEBq/UqBWr9IQJYCtX6lQElAwgcffPDBSUDuDIl5O0NKQBoS83aGxEpARhdddNFFS0ByHMdxHMdLQJ4hMW9nSExAyiabbLLJTED2KwVq/UpNQCIxb2dIzE1ATjbZZJNNTkB6O0Ni3s5OQKZArV8pUE9A0kUXXXTRT0B/pUCtXylQQBWo9SsFalBAq6qqqqqqUEBBrV8pUOtQQNevFKj1K1FAbbLJJptsUUADtX6lQK1RQJm3MyTm7VFAL7rooosuUkDFvJ0hMW9SQFu/UqDWr1JA8cEHH3zwUkCHxLydITFTQB3HcRzHcVNAs8kmm2yyU0BJzNsZEvNTQN/OkJi3M1RAddFFF110VEAL1PqVArVUQKHWrxSo9VRAN9lkk002VUDN2xkS83ZVQGPezpCYt1VA+eCDDz74VUCP4ziO4zhWQCXm7QyJeVZAu+iiiy66VkBR61cK1PpWQOftDIl5O1dAffDBBx98V0AT83aGxLxXQKn1KwVq/VdAPvjggw8+WEDU+pUCtX5YQGr9SoFav1hAAAAAAAAAWUA=\",\"dtype\":\"float64\",\"order\":\"little\",\"shape\":[100]},\"y\":[0.0,0.0,0.0,0.07577395922358243,0.5004393479690332,0.7224133291556544,0.8613812245902405,0.9572900542010168,1.0277297543278097,1.0817675245317802,1.1245875801245115,1.159381844636124,1.1882285459743205,1.2125414428326033,1.2333169395076509,1.2512781372706439,1.2669628695288067,1.2807796610463977,1.2930445020726795,1.3040057152656372,1.3138611934221514,1.3227706116876807,1.3308642477167265,1.3382494623408896,1.3450155352714814,1.3512373239535134,1.3569780671742144,1.362291558228339,1.3672238472714293,1.371814587857079,1.376098111600774,1.3801042929948102,1.3838592507217882,1.3873859204624353,1.3907045258794521,1.3938329683048356,1.3967871510573335,1.3995812508451824,1.4022279460670786,1.404738609796818,1.407123473669265,1.4093917676641832,1.4115518398268425,1.4136112592084564,1.4155769047092224,1.4174550420272416,1.419251390531458,1.420971181565758,1.422619209439013,1.424199876150063,1.4257172307280703,1.427175003930005,1.4285766389224435,1.4299253184798388,1.4312239891522922,1.4324753827897223,1.4336820357538773,1.4348463061029573,1.4359703889942168,1.4370563305165396,1.4381060401366355,1.43912130191835,1.4401037846539422,1.4410550510285098,1.4419765659235504,1.4428697039525706,1.443735756310371,1.4445759370078548,1.4453913885557486,1.4461831871532462,1.4469523474311818,1.4476998267937293,1.4484265293977316,1.449133309804468,1.4498209763348975,1.4504902941560975,1.4511419881236995,1.4517767454025348,1.4523952178854258,1.4529980244280372,1.4535857529159006,1.4541589621781428,1.4547181837610141,1.4552639235730644,1.455796663412671,1.456316862387623,1.4568249582355566,1.4573213685532307,1.457806491941901,1.4582807090754006,1.4587443836969451,1.4591978635501501,1.4596414812492737,1.4600755550932623,1.460500389827789,1.4609162773591216,1.4613234974233331,1.4617223182140862,1.4621129969719462,1.4624957805379524]},\"selected\":{\"id\":\"1138\"},\"selection_policy\":{\"id\":\"1139\"}},\"id\":\"1119\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"source\":{\"id\":\"1066\"}},\"id\":\"1070\",\"type\":\"CDSView\"},{\"attributes\":{},\"id\":\"1140\",\"type\":\"Selection\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"green\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1121\",\"type\":\"Line\"},{\"attributes\":{\"data_source\":{\"id\":\"1052\"},\"glyph\":{\"id\":\"1126\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1127\"},\"view\":{\"id\":\"1129\"}},\"id\":\"1128\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"line_width\":{\"value\":2},\"size\":{\"value\":12}},\"id\":\"1074\",\"type\":\"OpenHead\"},{\"attributes\":{\"end\":{\"id\":\"1074\"},\"line_color\":{\"field\":\"color\"},\"line_width\":{\"value\":2},\"source\":{\"id\":\"1073\"},\"start\":null,\"x_end\":{\"field\":\"x_end\"},\"x_start\":{\"field\":\"x_start\"},\"y_end\":{\"field\":\"y_end\"},\"y_start\":{\"field\":\"y_start\"}},\"id\":\"1075\",\"type\":\"Arrow\"},{\"attributes\":{\"active_multi\":null,\"tools\":[{\"id\":\"1163\"},{\"id\":\"1164\"},{\"id\":\"1165\"},{\"id\":\"1166\"},{\"id\":\"1167\"},{\"id\":\"1168\"}]},\"id\":\"1170\",\"type\":\"Toolbar\"},{\"attributes\":{\"fill_alpha\":{\"value\":0.1},\"fill_color\":{\"value\":\"black\"},\"line_alpha\":{\"value\":0.1},\"line_width\":{\"value\":2},\"size\":{\"value\":8},\"x\":{\"field\":\"m_counterweight\"},\"y\":{\"field\":\"y\"}},\"id\":\"1127\",\"type\":\"Circle\"},{\"attributes\":{\"fill_color\":{\"value\":\"black\"},\"line_width\":{\"value\":2},\"size\":{\"value\":8},\"x\":{\"field\":\"m_counterweight\"},\"y\":{\"field\":\"y\"}},\"id\":\"1126\",\"type\":\"Circle\"},{\"attributes\":{\"data\":{\"color\":[\"blue\",\"red\",\"gray\",\"red\"],\"dash\":[\"solid\",\"solid\",[2,2],\"solid\"],\"name\":[\"F\",\"T\",\"Tr\",\"T\"],\"x_end\":[2.5,2.74525,2.5,2.25475],\"x_offset\":[8,25,8,-35],\"x_start\":[2.5,2.5,2.5,2.5],\"y_end\":[-0.2943,0.14715,0.2943,0.14715],\"y_offset\":[-45,6,45,6],\"y_start\":[0.0,0.0,0.0,0.0]},\"selected\":{\"id\":\"1233\"},\"selection_policy\":{\"id\":\"1234\"}},\"id\":\"1073\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"line_color\":\"green\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1178\",\"type\":\"Line\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"green\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1179\",\"type\":\"Line\"},{\"attributes\":{\"source\":{\"id\":\"1177\"}},\"id\":\"1181\",\"type\":\"CDSView\"},{\"attributes\":{},\"id\":\"1236\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1237\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"line_color\":\"gray\",\"line_dash\":[6],\"location\":0},\"id\":\"1182\",\"type\":\"Span\"},{\"attributes\":{\"data_source\":{\"id\":\"1052\"},\"glyph\":{\"id\":\"1184\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1185\"},\"view\":{\"id\":\"1187\"}},\"id\":\"1186\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"value\":0.1},\"fill_color\":{\"value\":\"black\"},\"line_alpha\":{\"value\":0.1},\"line_width\":{\"value\":2},\"size\":{\"value\":8},\"x\":{\"field\":\"m_counterweight\"},\"y\":{\"field\":\"alpha_degrees\"}},\"id\":\"1185\",\"type\":\"Circle\"},{\"attributes\":{\"source\":{\"id\":\"1052\"}},\"id\":\"1187\",\"type\":\"CDSView\"},{\"attributes\":{\"label\":{\"field\":\"alpha_text\"},\"renderers\":[{\"id\":\"1186\"}]},\"id\":\"1201\",\"type\":\"LegendItem\"},{\"attributes\":{\"items\":[{\"id\":\"1201\"}],\"label_text_font_size\":\"12pt\"},\"id\":\"1200\",\"type\":\"Legend\"},{\"attributes\":{},\"id\":\"1238\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1239\",\"type\":\"UnionRenderers\"},{\"attributes\":{},\"id\":\"1189\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"children\":[{\"id\":\"1002\"}],\"sizing_mode\":\"stretch_height\"},\"id\":\"1202\",\"type\":\"Column\"},{\"attributes\":{},\"id\":\"1190\",\"type\":\"AllLabels\"},{\"attributes\":{\"line_cap\":\"round\",\"line_width\":8,\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1037\",\"type\":\"Line\"},{\"attributes\":{\"below\":[{\"id\":\"1013\"}],\"center\":[{\"id\":\"1016\"},{\"id\":\"1020\"},{\"id\":\"1035\"},{\"id\":\"1046\"},{\"id\":\"1058\"},{\"id\":\"1071\"},{\"id\":\"1075\"},{\"id\":\"1078\"}],\"left\":[{\"id\":\"1017\"}],\"outline_line_color\":null,\"renderers\":[{\"id\":\"1039\"},{\"id\":\"1044\"},{\"id\":\"1050\"},{\"id\":\"1056\"},{\"id\":\"1064\"},{\"id\":\"1069\"},{\"id\":\"1084\"}],\"sizing_mode\":\"stretch_height\",\"title\":{\"id\":\"1003\"},\"toolbar\":{\"id\":\"1028\"},\"toolbar_location\":null,\"x_range\":{\"id\":\"1005\"},\"x_scale\":{\"id\":\"1009\"},\"y_range\":{\"id\":\"1007\"},\"y_scale\":{\"id\":\"1011\"}},\"id\":\"1002\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{},\"id\":\"1240\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1241\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"line_color\":\"gray\",\"line_dash\":[6],\"location\":1.5},\"id\":\"1035\",\"type\":\"Span\"},{\"attributes\":{\"end\":1.55,\"start\":-0.05},\"id\":\"1007\",\"type\":\"Range1d\"},{\"attributes\":{\"align\":\"center\",\"text\":\"Suspended object (3 kg)\"},\"id\":\"1003\",\"type\":\"Title\"},{\"attributes\":{\"end\":5.2,\"start\":-0.2},\"id\":\"1005\",\"type\":\"Range1d\"},{\"attributes\":{\"axis_label\":\"Distance (m) \",\"formatter\":{\"id\":\"1219\"},\"major_label_policy\":{\"id\":\"1220\"},\"ticker\":{\"id\":\"1014\"}},\"id\":\"1013\",\"type\":\"LinearAxis\"},{\"attributes\":{},\"id\":\"1026\",\"type\":\"HelpTool\"},{\"attributes\":{\"axis_label\":\"Height (m)\",\"formatter\":{\"id\":\"1222\"},\"major_label_policy\":{\"id\":\"1223\"},\"ticker\":{\"id\":\"1018\"}},\"id\":\"1017\",\"type\":\"LinearAxis\"},{\"attributes\":{\"end\":1.55,\"start\":-0.05},\"id\":\"1091\",\"type\":\"Range1d\"},{\"attributes\":{},\"id\":\"1009\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1011\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1014\",\"type\":\"BasicTicker\"},{\"attributes\":{\"axis\":{\"id\":\"1013\"},\"ticker\":null,\"visible\":false},\"id\":\"1016\",\"type\":\"Grid\"},{\"attributes\":{\"axis\":{\"id\":\"1017\"},\"dimension\":1,\"ticker\":null,\"visible\":false},\"id\":\"1020\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1219\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{},\"id\":\"1018\",\"type\":\"BasicTicker\"},{\"attributes\":{},\"id\":\"1242\",\"type\":\"Selection\"},{\"attributes\":{\"active_multi\":null,\"tools\":[{\"id\":\"1021\"},{\"id\":\"1022\"},{\"id\":\"1023\"},{\"id\":\"1024\"},{\"id\":\"1025\"},{\"id\":\"1026\"}]},\"id\":\"1028\",\"type\":\"Toolbar\"},{\"attributes\":{},\"id\":\"1243\",\"type\":\"UnionRenderers\"},{\"attributes\":{},\"id\":\"1220\",\"type\":\"AllLabels\"},{\"attributes\":{},\"id\":\"1022\",\"type\":\"WheelZoomTool\"},{\"attributes\":{},\"id\":\"1021\",\"type\":\"PanTool\"},{\"attributes\":{\"overlay\":{\"id\":\"1027\"}},\"id\":\"1023\",\"type\":\"BoxZoomTool\"},{\"attributes\":{},\"id\":\"1024\",\"type\":\"SaveTool\"},{\"attributes\":{},\"id\":\"1025\",\"type\":\"ResetTool\"},{\"attributes\":{},\"id\":\"1196\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1197\",\"type\":\"UnionRenderers\"},{\"attributes\":{},\"id\":\"1244\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1245\",\"type\":\"UnionRenderers\"}],\"root_ids\":[\"1203\"]},\"title\":\"Bokeh Application\",\"version\":\"2.3.3\"}};\n", - " var render_items = [{\"docid\":\"203bc558-0134-4c1b-a5b5-4f2694a43e16\",\"notebook_comms_target\":\"1248\",\"root_ids\":[\"1203\"],\"roots\":{\"1203\":\"77bfdfd5-5e25-42d9-b30b-d642ec19019d\"}}];\n", - " root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n", - "\n", - " }\n", - " if (root.Bokeh !== undefined) {\n", - " embed_document(root);\n", - " } else {\n", - " var attempts = 0;\n", - " var timer = setInterval(function(root) {\n", - " if (root.Bokeh !== undefined) {\n", - " clearInterval(timer);\n", - " embed_document(root);\n", - " } else {\n", - " attempts++;\n", - " if (attempts > 100) {\n", - " clearInterval(timer);\n", - " console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n", - " }\n", - " }\n", - " }, 10, root)\n", - " }\n", - "})(window);" - ], - "application/vnd.bokehjs_exec.v0+json": "" - }, - "metadata": { - "application/vnd.bokehjs_exec.v0+json": { - "id": "1203" - } - }, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "62975937e8cc419b8c2ec16436040cf1", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(VBox(children=(HBox(children=(Label(value='Mass of the counterweight ($kg$):', layout=Layout(ma…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from lib.suspendedobject import *\n", - "SuspendedObjectLab(m_object = 3, height = 1.5, distance = 5).launch();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "*If you wonder how the virtual lab works, you can have a look at the code in [this python file](lib/suspendedobjects.py).*\n", - "\n", - "
\n", - "\n", - "Step 3 \\[Explain\\]:\n", - "\n", - "Highlight: \n", - "* how vectors sum graphically\n", - "* how forces compensate for static equilibrium \n", - "* how the angle influences the vertical and horizontal components of the two tensions\n", - "\n", - "Use two specific values to illustrate the differences:\n", - "1. With a counterweight of 6 kg.\n", - "2. With a counterweight of 12kg\n", - "\n", - "\n", - "Step 4 \\[Check\\]: Ask the suspended jeans question again and a the second clicker question with a different but related physics question for checking understanding (clicker questions).\n", - "\n", - "\n", - "
\n", - "\n", - "# Resources\n", - "\n", - "This demonstration implements partly the **Predict-Observe-Explain** pattern: \n", - "White, R. T., & Gunstone, R. F. (1992). Probing Understanding. Great Britain: Falmer Press. \n", - "https://arbs.nzcer.org.nz/predict-observe-explain-poe\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python", - "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/Physics-exercises.ipynb b/Physics-exercises.ipynb index 394460c..fcc942d 100644 --- a/Physics-exercises.ipynb +++ b/Physics-exercises.ipynb @@ -1,1078 +1,1093 @@ { "cells": [ { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": true }, "source": [ "\n", "# SEFI Workshop \"Teaching and Learning with Jupyter Notebooks\" \n", "15 Sept. 2021 \n", "C. Hardebolle \n", "[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", "

Figure 1: The suspended jeans situation

\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": [ "# Activity 1: Experimenting with the virtual lab\n", "\n", "The \"Suspended object\" virtual lab will allow you to **experiment with different counterweights** to see how it affects the position of the jeans suspended on the cable. \n", + "We will follow a 3-step process highlighting the important steps in experimenting, whether in the lab or with a virtual lab like today.\n", "\n", "## 1. Plan\n", - "Before starting, it is useful to think about which value(s) for the counterweight you would be interested to try out (based on your answer to the clicker question).\n", + "Before starting, it is useful to think about which value(s) for the counterweight you would be interested to try out (based on your answer to the clicker question). \n", + "In any experiment, the planning step is essential and involves identifying testing scenarios as well as the results you expect from them.\n", "\n", "
\n", "\n", - "In the cell below, **list the different values** you would like to test for the counterweight.\n", + "In the cell below, **list the different values** you would like to test for the counterweight. \n", + "This cell is for just note taking, you can refer to these values when you will carry out the experiment in the next step.\n", " \n", "
" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "- value 1:\n", "- value 2:\n", "- value 3:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Experiment\n", - "Now is time to experiment! \n", + "Now is time to experiment! \n", + "A key to this exciting step of the process is to be as systematic as possible with your investigations and to carefully observe the results.\n", "\n", "
\n", "\n", "**Execute the code cell below** (click on it then click on the \"play\" button in the tool bar above) to launch the virtual lab. \n", - "Try out the different values you have listed above and observe: \n", + "Try out the different values you have listed above and **observe**: \n", "* the position of the cable \n", "* the angle that the cable makes with the horizon \n", - "* the **forces** that apply on the object (and how they evolve)\n", + "* the **forces** that apply on the object and **how they evolve** as you change the counterweight\n", " \n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# Let's import the custom library with the virtual lab\n", "from lib.suspendedobject import *\n", - "SuspendedObjectLab(m_object = 3, height = 1.5, distance = 5).launch();" + "\n", + "# The virtual lab sets up a situation with an object of mass 3kg, suspended on a cable between poles 1.5m high and 5m apart\n", + "SuspendedObjectLab(mass_object = 3, height = 1.5, distance = 5).launch();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Reflect\n", "\n", - "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", + "The goal of the reflection step is for you to look back and identify your key take-aways from the experiment. \n", + "In other words this is when you learn!\n", + "\n", + "So let's come back to our original question: \n", + "\"Which counterweight do you think would allow to suspend the jeans (3kg) on the cable in the position illustrated below?\" \n", "More importantly, **can you explain why**?\n", "\n", "\"suspended\n", "\n", "\n", "
\n", "\n", "Execute the code cell below and **post a message in the chat** with:\n", "* the value you found for the mass of the counterweight \n", "* a brief explanation of **why** the mass of the counterweight differs from the mass of the jeans. \n", "\n", - "You can compare your answer with what others have written and vote for the best explanation using the \"thumb up\" icon.\n", + "**Compare your answer with the others** 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", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import IFrame\n", "IFrame('https://speakup.epfl.ch/room/12989', 400, 500)" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ + "

\n", + "\n", + "Congrats, you have finished Activity 1! \n", + "Optionnally, you can continue with Activity 2 and programming activities below if you want.\n", + "\n", "

\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Activity 2: Looking more closely at the tension in the cable\n", "\n", "In the lecture, we have seen that, in this situation, the value of the tension in the cable (represented by the plain red arrows in the virtual lab above) is defined by:\n", "\n", "$\n", "\\begin{align}\n", "\\lvert\\vec{T}\\rvert = \\frac{\\frac{1}{2}.m.g}{sin(\\alpha)}\n", "\\end{align}\n", "$, where $m$ is the mass of the suspended object, $\\alpha$ the angle that the cable makes with the horizon and $g$ the gravity of earth.\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$**. \n", "For this, we need to import some useful Python libraries. " ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "
\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", + "# We will use a custom library with visualizations and tools 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": { "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", "* 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 custom 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", + "# First choose an angle alph, e.g. 2 degrees\n", "alpha = 2\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", "**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", "You can experiment with different values for $\\alpha$.\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", "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": { "tags": [] }, "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": [ "

\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 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", + "Congratulations, you have completed Activity 2! \n", "Below are some optional exercises if you want to continue.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Additional exercises (optional)" ] }, { "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": [ "



\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", "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/figs/counterweight-sketch-forces.png b/figs/counterweight-sketch-forces.png deleted file mode 100644 index ecdba65..0000000 Binary files a/figs/counterweight-sketch-forces.png and /dev/null differ diff --git a/figs/jeans-sketch-forces.png b/figs/jeans-sketch-forces.png deleted file mode 100644 index c88468a..0000000 Binary files a/figs/jeans-sketch-forces.png and /dev/null differ diff --git a/figs/jeans-sketch.png b/figs/jeans-sketch.png deleted file mode 100644 index 90ca52b..0000000 Binary files a/figs/jeans-sketch.png and /dev/null differ diff --git a/lib/suspendedobject.py b/lib/suspendedobject.py index 7380fc8..268fdb0 100644 --- a/lib/suspendedobject.py +++ b/lib/suspendedobject.py @@ -1,559 +1,557 @@ import numpy as np from operator import add from ipywidgets import interact, interactive, fixed, interact_manual from ipywidgets import HBox, VBox, Label, Layout import ipywidgets as widgets -import matplotlib.pyplot as plt -plt.style.use('seaborn-whitegrid') # global style for plotting - from IPython.display import IFrame -from IPython.display import set_matplotlib_formats, display, Math, Markdown, Latex, HTML -set_matplotlib_formats('svg') +# from IPython.display import set_matplotlib_formats, display, Math, Markdown, Latex, HTML +# set_matplotlib_formats('svg') +from IPython.display import display, Math, Markdown, Latex, HTML from bokeh.io import push_notebook, show, output_notebook, curdoc from bokeh.plotting import figure from bokeh.models import Legend, ColumnDataSource, Slider, Span, LegendItem from bokeh.models import Arrow, OpenHead, NormalHead, VeeHead, LabelSet -from bokeh.models.glyphs import Wedge, Bezier +#from bokeh.models.glyphs import Wedge, Bezier from bokeh.layouts import gridplot, row, column output_notebook(hide_banner=True) class SuspendedObjectLab: """ 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.5, x_origin = 0, y_origin = 0): + def __init__(self, mass_object = 3, distance = 5, height = 1.5, 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) ''' ###--- Static parameters of the situation - self.m_object = m_object # mass of the wet object, in kg + self.m_object = mass_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 # Parameters for drawing forces self.gravity = 9.81 self.force_scaling = .01 self.forces_nb = 4 # parameters for sliders self.m_counterweight_min = 0.0 self.m_counterweight_max = 100.0 self.m_counterweight = self.m_counterweight_min # initial mass of the counterweight (0 by default, no counterweight at the beginning) # parameter to draw the angle self.radius=0.3 def launch(self): ###--- Elements of the ihm: # IHM input elements self.m_counterweight_label = Label('Mass of the counterweight ($kg$):', layout=Layout(margin='0px 5px 0px 0px')) self.m_counterweight_widget = widgets.FloatSlider(min=self.m_counterweight_min,max=self.m_counterweight_max,step=0.5,value=self.m_counterweight, layout=Layout(margin='0px')) self.m_counterweight_note = Label('[Note: once you have clicked on the slider (the circle becomes blue), you can use the arrows from your keyboard to make it move.]', layout=Layout(margin='0px 0px 15px 0px')) self.m_counterweight_input = VBox([HBox([self.m_counterweight_label, self.m_counterweight_widget], layout=Layout(margin='0px')), self.m_counterweight_note]) # Linking widgets to handlers self.m_counterweight_widget.observe(self.m_counterweight_event_handler, names='value') # IHM output element for moodle quiz self.quiz_output = widgets.Output() ###--- Compute variables dependent with the counterweight selected by the user alpha = self.get_angle(self.m_counterweight) alpha_degrees = radians_to_degrees(alpha) alpha_text = '⍺ = {:.2f} °'.format(alpha_degrees) coord_object = self.get_object_coords(alpha) height_text = 'h = {:.2f} m'.format(coord_object[1]) ###--- Create the figure ---### # LIMITATIONS of Bokeh (BokehJS 1.4.0) # - labels: impossible to use LaTeX formatting in labels # - arc: impossible to take into account figure ratio when dynamic rescaling is activated # - forces/vectors: impossible to adjust the line_color and line_dash of OpenHead according to datasource ###--- First display the clothesline # Fix graph to problem boundaries ymargin = .05 xmargin = .2 fig_object = figure(title='Suspended object ({} kg)'.format(self.m_object), #plot_width=600, plot_height=400, sizing_mode='stretch_height', y_range=(self.y_origin-ymargin,self.y_origin+self.height+ymargin), x_range=(self.x_origin-xmargin,self.x_origin+self.distance+xmargin), background_fill_color='#ffffff', toolbar_location=None) fig_object.title.align = "center" fig_object.yaxis.axis_label = 'Height (m)' # Customize graph style so that it doesn't look too much like a graph fig_object.ygrid.visible = False fig_object.xgrid.visible = False fig_object.outline_line_color = None # trick to keep the three graphs aligned, while having auto scaling fig_object.xaxis.axis_label = "Distance (m) " #fig_object.xaxis.axis_line_color = "white" #fig_object.xaxis.axis_label_text_color = "white" #fig_object.xaxis.major_label_text_color = "white" #fig_object.xaxis.major_tick_line_color = "white" #fig_object.xaxis.minor_tick_line_color = "white" # Draw the horizon line fig_object.add_layout(Span(location=self.height, dimension='width', line_color='gray', line_dash='dashed', line_width=1)) # Draw the poles fig_object.line([self.x_origin, self.x_origin], [self.y_origin, self.y_origin+self.height], color="black", line_width=8, line_cap="round") fig_object.line([self.x_origin+self.distance, self.x_origin+self.distance], [self.y_origin, self.y_origin+self.height], color="black", line_width=8, line_cap="round") # Draw the ground fig_object.add_layout(Span(location=self.x_origin, dimension='width', line_color='black', line_width=1)) fig_object.hbar(y=self.y_origin-ymargin, height=ymargin*2, left=self.x_origin-xmargin, right=self.x_origin+self.distance+xmargin, color="white", line_color="white", hatch_pattern="/", hatch_color="gray") # --DYN-- Draw the point at which the object is suspended (this data source also used for the other graphs) self.object_source = ColumnDataSource(data=dict( x=[coord_object[0]], y=[coord_object[1]], m_counterweight=[self.m_counterweight], alpha_degrees=[alpha_degrees], height_text=[height_text], alpha_text=[alpha_text] )) fig_object.circle(source=self.object_source, x='x', y='y', size=8, fill_color="black", line_color='black', line_width=2) fig_object.add_layout(LabelSet(source=self.object_source, x='x', y='y', text='height_text', level='glyph', x_offset=8, y_offset=-20)) # --DYN-- Draw the hanging cable self.cable_source = ColumnDataSource(data=dict( x=[self.x_origin, coord_object[0], self.x_origin+self.distance], y=[self.y_origin+self.height, coord_object[1], self.y_origin+self.height] )) fig_object.line(source=self.cable_source, x='x', y='y', color="black", line_width=2, line_cap="round") # --DYN-- Draw the angle between the hanging cable and horizonline # Trick here: we use a straight line because the Arc glyph doesn't support dynamic figure ratio ratio=1.5 x0=self.x_origin+ratio*self.radius y0=self.y_origin+self.height x1=(self.x_origin+self.radius*np.cos(alpha)) y1=(self.y_origin+self.height-self.radius*np.sin(alpha)) self.alpha_arc = fig_object.line([x0, x1], [y0, y1], color="gray", line_width=1, line_dash=[2,2]) fig_object.add_layout(LabelSet(source=self.object_source, x=self.x_origin, y=self.y_origin+self.height, text='alpha_text', level='glyph', x_offset=50, y_offset=-20)) # --DYN-- Draw the force vectors # Weight Fy = self.m_object*self.gravity*self.force_scaling # Tension Tx = ((self.m_object*self.gravity) / (2*np.tan(alpha)))*self.force_scaling Ty = .5*self.m_object*self.gravity*self.force_scaling self.forces_x_start = [coord_object[0]]*self.forces_nb self.forces_y_start = [coord_object[1]]*self.forces_nb self.forces_x_mag = [0, Tx, 0, -Tx] self.forces_y_mag = [-Fy, Ty, Fy, Ty] self.forces_source = ColumnDataSource(data=dict( x_start=self.forces_x_start, y_start=self.forces_y_start, x_end=list(map(add, self.forces_x_start, self.forces_x_mag)), y_end=list(map(add, self.forces_y_start, self.forces_y_mag)), name=["F", "T", "Tr", "T"], color=["blue", "red", "gray", "red"], dash=["solid", "solid", [2,2], "solid"], x_offset=[8, 25, 8, -35], y_offset=[-45, 6, 45, 6] )) # Draw the arrows forces_arrows = Arrow(source=self.forces_source, x_start='x_start', y_start='y_start', x_end='x_end', y_end='y_end', line_color='color', line_width=2, end=OpenHead(line_width=2, size=12, line_color='black')) fig_object.add_layout(forces_arrows) # Add the labels forces_labels = LabelSet(source=self.forces_source, x='x_start', y='y_start', text='name', text_color='color', level='glyph', x_offset='x_offset', y_offset='y_offset', render_mode='canvas') fig_object.add_layout(forces_labels) # --DYN-- Draw the tension projection lines self.proj_source = ColumnDataSource(data=dict( x=self.forces_source.data["x_end"][1:4], y=self.forces_source.data["y_end"][1:4] )) fig_object.line(source=self.proj_source, x='x', y='y', color="gray", line_width=1, line_dash="dashed") ###--- Then display the angle and the height as functions from the mass of the counterweight # Create all possible values of the mass of the counterweight m_cw_list = np.linspace(self.m_counterweight_min, self.m_counterweight_max, 100) # Compute the angle (in degrees) and height for all these values angle_list = [] height_list = [] for m in m_cw_list: a = self.get_angle(m) angle_list.append(radians_to_degrees(a)) c = self.get_object_coords(a) height_list.append(c[1]) ### Create the graph for height fig_height = figure(title='Height (m)', #plot_height=400, plot_width=200, y_range=(-ymargin,self.height+ymargin), x_range=(0,102), background_fill_color='#ffffff', toolbar_location=None) fig_height.title.align = "center" fig_height.xaxis.axis_label = 'Mass of the counterweight (kg)' fig_height.grid.grid_line_color = 'lightgray' fig_height.grid.minor_grid_line_color = 'gainsboro' # Draw the curve of the function fig_height.line(m_cw_list, height_list, color="green", line_width=1) # Draw the horizon line fig_height.add_layout(Span(location=self.height, dimension='width', line_color='gray', line_dash='dashed', line_width=1)) # --DYN-- Add the current height from the counterweight selected by the user fig_height.circle(source=self.object_source, x='m_counterweight', y='y', size=8, fill_color="black", line_color='black', line_width=2, legend_field="height_text") fig_height.legend.location = "bottom_right" fig_height.legend.label_text_font_size = '12pt' ### Create the graph for angle fig_alpha = figure(title='Angle ⍺ (°)', #plot_height=400, plot_width=400, y_range=(-1,angle_list[0]+1), x_range=(0,102), background_fill_color='#ffffff', toolbar_location=None) fig_alpha.title.align = "center" fig_alpha.xaxis.axis_label = 'Mass of the counterweight (kg)' fig_alpha.grid.grid_line_color = 'lightgray' fig_alpha.grid.minor_grid_line_color = 'gainsboro' # Draw the curve of the function c = fig_alpha.line(m_cw_list, angle_list, color="green", line_width=1) # Draw the horizon line fig_alpha.add_layout(Span(location=0, dimension='width', line_color='gray', line_dash='dashed', line_width=1)) # --DYN-- Add the current angle from the counterweight selected by the user fig_alpha.circle(source=self.object_source, x='m_counterweight', y='alpha_degrees', size=8, fill_color="black", line_color='black', line_width=2, legend_field="alpha_text") fig_alpha.legend.location = "top_right" fig_alpha.legend.label_text_font_size = '12pt' ###--- Add a quiz from Moodle iframe_quiz = ''' ''' widget_quiz = widgets.HTML(value=iframe_quiz) ###--- Display the whole interface show(row(column(children=[fig_object], sizing_mode='stretch_height'), fig_alpha, fig_height, sizing_mode='scale_both'), notebook_handle=True) #display(VBox([self.m_counterweight_input, widget_quiz])) display(VBox([self.m_counterweight_input])) # Event handlers def m_counterweight_event_handler(self, change): # get new value of counterweight mass self.m_counterweight = change.new # compute all problem values alpha = self.get_angle(self.m_counterweight) alpha_degrees = radians_to_degrees(alpha) alpha_text = '⍺ = {:.2f} °'.format(alpha_degrees) coord_object = self.get_object_coords(alpha) height_text = 'h = {:.2f} m'.format(coord_object[1]) self.forces_y_start = [coord_object[1]]*self.forces_nb Tx = ((self.m_object*self.gravity) / (2*np.tan(alpha)))*self.force_scaling self.forces_x_mag = [0, Tx, 0, -Tx] # update the object representation on all graphs (coordinates+labels) self.object_source.data = dict( x=[coord_object[0]], y=[coord_object[1]], m_counterweight=[self.m_counterweight], alpha_degrees=[alpha_degrees], height_text=[height_text], alpha_text=[alpha_text] ) # update line representing the angle alpha self.alpha_arc.data_source.patch({ 'x' : [(1, self.x_origin+self.radius*np.cos(alpha))], 'y' : [(1, self.y_origin+self.height-self.radius*np.sin(alpha))] }) # update the point where the object is attached on cable self.cable_source.patch({ 'x' : [(1, coord_object[0])], 'y' : [(1, coord_object[1])] }) # update point of start for all forces, update Tx and Ty for T self.forces_source.patch({ 'y_start' : [(slice(self.forces_nb), self.forces_y_start)], 'x_end' : [(slice(self.forces_nb), list(map(add, self.forces_x_start, self.forces_x_mag)))], 'y_end' : [(slice(self.forces_nb), list(map(add, self.forces_y_start, self.forces_y_mag)))], }) # update the tension projection lines self.proj_source.patch({ 'x' : [(slice(3), self.forces_source.data["x_end"][1:4])], 'y' : [(slice(3), self.forces_source.data["y_end"][1:4])] }) push_notebook() def visualize_counterweight(self, m_counterweight = 0): ### first let's validate the counterweight # it cannot be negative if m_counterweight < 0: print("\033[1m\x1b[91m The mass of the counterweight cannot be negative. \x1b[0m\033[0m") return # update attribute with new value self.m_counterweight = m_counterweight # compute the angle given by this counterweight alpha = self.get_angle(self.m_counterweight) alpha_degrees = radians_to_degrees(alpha) # visualize the situation self.visualize_angle(alpha_degrees) def visualize_angle(self, angle_degrees): ### first let's validate the angle # it cannot be null (i.e. cable horizontal) or negative if angle_degrees <= 0: print("\033[1m\x1b[91m The angle cannot be null or negative. \x1b[0m\033[0m") return # it cannot be more than the default angle given the parameters of the situation alpha_default = np.arctan(self.height / (self.distance / 2)) alpha_default_degrees = radians_to_degrees(alpha_default) if angle_degrees > alpha_default_degrees: print(f"\033[1m\x1b[91m The angle cannot be greater than {alpha_default_degrees:.2f} degrees given the parameters of this situation (poles of {self.height} meters, distant by {self.distance} meters). \x1b[0m\033[0m") angle_degrees = alpha_default_degrees # compute variables dependent with the angle selected by the user alpha_degrees = angle_degrees alpha = degrees_to_radians(alpha_degrees) alpha_text = '⍺ = {:.2f} °'.format(alpha_degrees) coord_object = self.get_object_coords(alpha) height_text = 'h = {:.2f} m'.format(coord_object[1]) ###--- Create the figure ---### # LIMITATIONS of Bokeh (BokehJS 1.4.0) # - labels: impossible to use LaTeX formatting in labels # - arc: impossible to take into account figure ratio when dynamic rescaling is activated # - forces/vectors: impossible to adjust the line_color and line_dash of OpenHead according to datasource ###--- First display the clothesline # Fix graph to problem boundaries ymargin = .05 xmargin = .2 fig_object = figure(title='Suspended object ({} kg)'.format(self.m_object), plot_width=800, plot_height=400, #sizing_mode='scale_both', y_range=(self.y_origin-ymargin,self.y_origin+self.height+ymargin), x_range=(self.x_origin-xmargin,self.x_origin+self.distance+xmargin), background_fill_color='#ffffff', toolbar_location=None) fig_object.title.align = "center" fig_object.yaxis.axis_label = 'Height (m)' fig_object.xaxis.axis_label = "Distance (m)" # Customize graph style so that it doesn't look too much like a graph fig_object.ygrid.visible = False fig_object.xgrid.visible = False fig_object.outline_line_color = None # Draw the horizon line fig_object.add_layout(Span(location=self.height, dimension='width', line_color='gray', line_dash='dashed', line_width=1)) # Draw the poles fig_object.line([self.x_origin, self.x_origin], [self.y_origin, self.y_origin+self.height], color="black", line_width=8, line_cap="round") fig_object.line([self.x_origin+self.distance, self.x_origin+self.distance], [self.y_origin, self.y_origin+self.height], color="black", line_width=8, line_cap="round") # Draw the ground fig_object.add_layout(Span(location=self.x_origin, dimension='width', line_color='black', line_width=1)) fig_object.hbar(y=self.y_origin-ymargin, height=ymargin*2, left=self.x_origin-xmargin, right=self.x_origin+self.distance+xmargin, color="white", line_color="white", hatch_pattern="/", hatch_color="gray") # --DYN-- Draw the point at which the object is suspended (this data source also used for the other graphs) self.object_source = ColumnDataSource(data=dict( x=[coord_object[0]], y=[coord_object[1]], m_counterweight=[self.m_counterweight], alpha_degrees=[alpha_degrees], height_text=[height_text], alpha_text=[alpha_text] )) fig_object.circle(source=self.object_source, x='x', y='y', size=8, fill_color="black", line_color='black', line_width=2) fig_object.add_layout(LabelSet(source=self.object_source, x='x', y='y', text='height_text', level='glyph', x_offset=8, y_offset=-35)) # --DYN-- Draw the hanging cable self.cable_source = ColumnDataSource(data=dict( x=[self.x_origin, coord_object[0], self.x_origin+self.distance], y=[self.y_origin+self.height, coord_object[1], self.y_origin+self.height] )) fig_object.line(source=self.cable_source, x='x', y='y', color="black", line_width=2, line_cap="round") # --DYN-- Draw the angle between the hanging cable and horizonline # Trick here: we use a straight line because the Arc glyph doesn't support dynamic figure ratio ratio=1.5 x0=self.x_origin+ratio*self.radius y0=self.y_origin+self.height x1=(self.x_origin+self.radius*np.cos(alpha)) y1=(self.y_origin+self.height-self.radius*np.sin(alpha)) self.alpha_arc = fig_object.line([x0, x1], [y0, y1], color="gray", line_width=1, line_dash=[2,2]) fig_object.add_layout(LabelSet(source=self.object_source, x=self.x_origin, y=self.y_origin+self.height, text='alpha_text', level='glyph', x_offset=50, y_offset=-20)) # --DYN-- Draw the force vectors # Weight Fy = self.m_object*self.gravity*self.force_scaling # Tension Tx = ((self.m_object*self.gravity) / (2*np.tan(alpha)))*self.force_scaling Ty = .5*self.m_object*self.gravity*self.force_scaling self.forces_x_start = [coord_object[0]]*self.forces_nb self.forces_y_start = [coord_object[1]]*self.forces_nb self.forces_x_mag = [0, Tx, 0, -Tx] self.forces_y_mag = [-Fy, Ty, Fy, Ty] self.forces_source = ColumnDataSource(data=dict( x_start=self.forces_x_start, y_start=self.forces_y_start, x_end=list(map(add, self.forces_x_start, self.forces_x_mag)), y_end=list(map(add, self.forces_y_start, self.forces_y_mag)), name=["F", "T", "Tr", "T"], color=["blue", "red", "gray", "red"], dash=["solid", "solid", [2,2], "solid"], x_offset=[8, 15, 8, -25], y_offset=[-64, -16, 45, -16] )) # Draw the arrows ### Bokeh issue here: with a datasource, it is not possible to specify the color of the openhead so it remains black forces_arrows = Arrow(source=self.forces_source, x_start='x_start', y_start='y_start', x_end='x_end', y_end='y_end', line_color='color', line_width=2, end=OpenHead(line_width=2, size=12)) fig_object.add_layout(forces_arrows) # Add the labels forces_labels = LabelSet(source=self.forces_source, x='x_start', y='y_start', text='name', text_color='color', level='glyph', x_offset='x_offset', y_offset='y_offset', render_mode='canvas') fig_object.add_layout(forces_labels) # --DYN-- Draw the tension projection lines self.proj_source = ColumnDataSource(data=dict( x=self.forces_source.data["x_end"][1:4], y=self.forces_source.data["y_end"][1:4] )) fig_object.line(source=self.proj_source, x='x', y='y', color="gray", line_width=1, line_dash="dashed") ###--- Display the whole interface show(row(children=[fig_object]), notebook_handle=True) #, sizing_mode="scale_both" # 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 = 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 considered on the ground for all values of the angle which give a delta height higher than the height of the poles :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] def degrees_to_radians(angle_degrees): return angle_degrees * np.pi / 180 def radians_to_degrees(angle_radians): return angle_radians * 180 / np.pi # EOF