{
"cells": [
{
"cell_type": "raw",
"id": "0",
"metadata": {
"editable": true,
"raw_mimetype": "text/restructuredtext",
"slideshow": {
"slide_type": ""
},
"tags": []
},
"source": [
".. important::\n",
" Jupyter widgets cannot be run within in documentation. To be able to interact with the widget you must run a mybinder instance. To run a mybinder instance of this notebook please use this link https://mybinder.org/v2/gh/osscar-org/scicode-widgets/HEAD?labpath=docs%2Fsrc%2Fexercises.ipynb."
]
},
{
"cell_type": "markdown",
"id": "1",
"metadata": {
"tags": []
},
"source": [
"# Writing exercises"
]
},
{
"cell_type": "markdown",
"id": "2",
"metadata": {},
"source": [
"The scicode-widgets mainly offers a flexible code widget that allows instant feedback to evaluate the code for interactive plots."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from scwidgets import (\n",
" CodeExercise,\n",
" CodeInput,\n",
" CueFigure,\n",
" CueOutput,\n",
" CueObject,\n",
" ExerciseRegistry,\n",
" ParametersPanel,\n",
" TextExercise,\n",
")\n",
"\n",
"from ipywidgets import FloatSlider, IntSlider\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "4",
"metadata": {},
"source": [
"## ExerciseRegistry to store and load answers to exercises"
]
},
{
"cell_type": "markdown",
"id": "5",
"metadata": {},
"source": [
"Due to limitations of jupyter notebooks in saving the widgets state, we store and load the widget state by ourself using the `ExerciseRegstry`. It allows you specify a filename for the json file to store all the answers to all registered exercises. The exercises are registered by passing the `ExerciseRegstry` instance as input argument."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"exercise_registry = ExerciseRegistry()\n",
"exercise_registry"
]
},
{
"cell_type": "markdown",
"id": "7",
"metadata": {},
"source": [
"## TextExercise to create text exercises"
]
},
{
"cell_type": "markdown",
"id": "8",
"metadata": {},
"source": [
"A simple textarea to save students answers. Note that the save and load buttons only appear when a exercise key and registry is given"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"TextExercise(\n",
" title=\"Exercise 01: Derive a solution for weights\",\n",
" description=\"\"\"\n",
" We can define ridge regression by extending the ordinary least\n",
" square solution by a penalization $\\lambda\\|\\mathbf{w}\\|_2^2$. Please derive the solution\n",
" for the weights from the optimization problem:
\n",
" $$\\hat{\\mathbf{w}} = \\min_\\mathbf{w} \\|\\mathbf{y}-\\mathbf{X}\\mathbf{w}\\|^2 + \\lambda\\|\\mathbf{w}\\|^2$$\"\"\",\n",
" key=\"ex01\", # the key it is stored under in the json file\n",
" exercise_registry=exercise_registry\n",
")"
]
},
{
"cell_type": "markdown",
"id": "10",
"metadata": {},
"source": [
"## Interactive coding exercises"
]
},
{
"cell_type": "markdown",
"id": "11",
"metadata": {},
"source": [
"This is an example how to create a simple exercise."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12",
"metadata": {},
"outputs": [],
"source": [
"# This is what the students sees and can adapt\n",
"def sin(x: int, omega=1.0):\n",
" \"\"\"\n",
" Implements ridge regression\n",
"\n",
" :param x: An array of data points\n",
" :param omega: The frequency\n",
" \"\"\"\n",
" import numpy as np\n",
" return np.sin(x*omega)\n",
"\n",
"def update_func(code_ex):\n",
" x = np.linspace(-2*np.pi, 2*np.pi, 100)\n",
" y = code_ex.code(x, code_ex.parameters[\"omega\"])\n",
" ax = code_ex.figure.gca()\n",
" ax.plot(x, y)\n",
" \n",
"code_ex_description = \"\"\"\n",
"Implements a sinus function $\\sin(x\\omega)$.\n",
"\"\"\"\n",
"code_ex = CodeExercise(\n",
" code=sin,\n",
" parameters={'omega': (0.5, 3.14, 0.1)},\n",
" outputs=plt.figure(),\n",
" update=update_func,\n",
" update_mode=\"continuous\", # we also support [\"manual\", \"release\"]\n",
" title=\"Sinus function\",\n",
" description=code_ex_description,\n",
" key=\"sin_local\",\n",
" exercise_registry=exercise_registry\n",
")\n",
"\n",
"code_ex.run_update() # For the demonstration we run the widget one time\n",
"display(code_ex)"
]
},
{
"cell_type": "markdown",
"id": "13",
"metadata": {},
"source": [
"### Creating widget components beforehand"
]
},
{
"cell_type": "markdown",
"id": "14",
"metadata": {},
"source": [
"More complex widgets might need creation beforehand to allow full customization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from ipywidgets import HTML\n",
"\n",
"# One can pass a function also by\n",
"code_input = CodeInput(\n",
" function_name=\"sin\",\n",
" function_parameters=\"x: int, omega=1.0\",\n",
" function_body=\"import numpy as np\\nreturn np.sin(x*omega)\",\n",
" docstring=\"Implements ridge regression\\n\\n:param x: An array of data points\\n:param omega: The frequency\"\n",
")\n",
"# customization of figure toolbar, only important in widget mode (use %matplotlib widget)\n",
"figure = CueFigure(plt.figure(), show_toolbars=True)\n",
"# to use a custom output for own widgets\n",
"output = CueOutput()\n",
"# to use display custom widgets\n",
"table = CueObject(HTML(value=\"\"))\n",
"\n",
"\n",
"# to customize sliders, one can directy\n",
"parameter_panel = ParametersPanel(\n",
" omega=FloatSlider(value=1, min=0.5, max=3.14, step=0.1, description=\"$\\\\omega$\")\n",
")\n",
"# alternatively if passed to CodeExercise this also works\n",
"#parameter_panel = dict(\n",
"# omega=FloatSlider(value=1, min=0.5, max=3.14, step=0.1, description=\"$\\\\omega$\")\n",
"#)\n",
"\n",
"\n",
"def update_func(code_ex):\n",
" x = np.linspace(-2*np.pi, 2*np.pi, 100)\n",
" y = code_ex.code(x, code_ex.parameters[\"omega\"])\n",
" ax = code_ex.outputs[0].figure.gca()\n",
" ax.plot(x, y)\n",
" with code_ex.outputs[1]:\n",
" print(\"Some text after the figure\")\n",
"\n",
" code_ex.outputs[2].object.value = \"| x | y |
\" + \\\n",
" \"\".join([f\"| {x[i]:.2f} | {y[i]:.2f} |
\" for i in range(0, len(x), 20)]) + \\\n",
" \"
\"\n",
" # the captured text in the function is always printed before any other output\n",
" print(\"Some text before the figure\")\n",
" \n",
" \n",
"\n",
"code_ex = CodeExercise(\n",
" code=code_input,\n",
" parameters=parameter_panel,\n",
" outputs=[figure, output, table],\n",
" update=update_func,\n",
")\n",
"\n",
"code_ex.run_update() # For the demonstration we run the widget one time\n",
"display(code_ex)"
]
},
{
"cell_type": "markdown",
"id": "16",
"metadata": {},
"source": [
"### Include imports to code input"
]
},
{
"cell_type": "markdown",
"id": "17",
"metadata": {},
"source": [
"So far we always added the imports required for the code inside the code input. We need to do this because the widget is its creates its own environment (own globals) so no function from the notebook is accidently used. This however does not solve the problem when one wants to include typehints in the function signature that require imports. For that one can add the library to the globals."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18",
"metadata": {},
"outputs": [],
"source": [
"def sin(x: np.ndarray, omega=1.0): # using np.ndarray requires import numpy before the function body\n",
" \"\"\"\n",
" Implements ridge regression\n",
"\n",
" :param x: An array of data points\n",
" :param omega: The frequency\n",
" \"\"\"\n",
" return np.sin(x*omega)\n",
"\n",
"def update_func(code_ex):\n",
" x = np.linspace(-2*np.pi, 2*np.pi, 100)\n",
" y = code_ex.code(x, code_ex.parameters[\"omega\"])\n",
" ax = code_ex.figure.gca()\n",
" ax.plot(x, y)\n",
" \n",
"code_ex = CodeExercise(\n",
" code=CodeInput(sin, builtins={'np': np}),\n",
" parameters={'omega': (0.5, 3.14, 0.1)},\n",
" outputs=plt.figure(),\n",
" update=update_func,\n",
")\n",
"\n",
"code_ex.run_update() # For the demonstration we run the widget one time\n",
"display(code_ex)"
]
},
{
"cell_type": "markdown",
"id": "19",
"metadata": {},
"source": [
"### Interactive coding exercises with globals variables"
]
},
{
"cell_type": "markdown",
"id": "20",
"metadata": {},
"source": [
"This is an example how to create a simple exercise using globals in the update function. This can be more convenient in certain cases but a bit more prone to errors as when creating multiple exercises the global names can easily conflict with each other and result in unwanted behavior. Therefore we recommend use the code demo instance that is passed through the update function."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# This is what the students sees and can adapt\n",
"def sin(x, omega):\n",
" \"\"\"\n",
" Implements ridge regression\n",
"\n",
" :param x: An array of data points\n",
" :param omega: The frequency\n",
" \"\"\"\n",
" import numpy as np\n",
" return np.sin(x*omega)\n",
"\n",
"code_input = CodeInput(sin)\n",
"cue_figure = CueFigure(plt.figure())\n",
"parameter_panel = ParametersPanel(\n",
" omega=FloatSlider(value=1, min=0.5, max=3.14, step=0.1, description=\"$\\\\omega$\")\n",
")\n",
"\n",
"x = np.linspace(-2*np.pi, 2*np.pi, 100)\n",
"def update_func():\n",
" global x, code_input, cue_figure, parameter_panel \n",
" y = code_ex.code(x, parameter_panel.parameters[\"omega\"])\n",
" ax = cue_figure.figure.gca()\n",
" ax.plot(x, y)\n",
" \n",
" \n",
"code_ex = CodeExercise(\n",
" code=code_input,\n",
" parameters=parameter_panel,\n",
" outputs=cue_figure,\n",
" update=update_func,\n",
")\n",
"\n",
"\n",
"code_ex.run_update() # For the demonstration we run the widget one time\n",
"display(code_ex)"
]
},
{
"cell_type": "markdown",
"id": "22",
"metadata": {},
"source": [
"## ParametersPanel short constructors"
]
},
{
"cell_type": "markdown",
"id": "23",
"metadata": {},
"source": [
"The `ParametersPanel` can be also used with the same shorthand constructors as [interact](https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html). Here some examples"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from ipywidgets import fixed\n",
"ParametersPanel(\n",
" frequency=(0.5, 2*np.pi, 0.1),\n",
" amplitude=(1, 5, 1),\n",
" inverted=True,\n",
" type=[\"sin\", \"cos\"],\n",
" plot_title=\"trigonometric curve\",\n",
" const=fixed(1) # this argument will be passed but is not changeable and therefore not displayed\n",
")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}