diff --git a/.jupyterlite.doit.db b/.jupyterlite.doit.db deleted file mode 100644 index d60e365..0000000 Binary files a/.jupyterlite.doit.db and /dev/null differ diff --git a/book/_contents/interactive-wave.ipynb b/book/_contents/interactive-wave.ipynb deleted file mode 100644 index c3be4a8..0000000 --- a/book/_contents/interactive-wave.ipynb +++ /dev/null @@ -1,91 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "%gui asyncio\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from ipywidgets import interact" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "4570ae9da644449a87c3fea9c4d7f919", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(FloatSlider(value=3.0, description='omega', max=5.0, min=1.0), Output()), _dom_classes=(…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x = np.linspace(0, 2*np.pi, 100)\n", - "fig, ax = plt.subplots()\n", - "line, = ax.plot(x, np.sin(x))\n", - "\n", - "def plot_sine_wave(omega):\n", - " y = np.sin(omega*x)\n", - " line.set_ydata(y)\n", - " fig.canvas.draw_idle()\n", - "\n", - "interact(plot_sine_wave, omega=(1, 5, 0.1))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "base", - "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.9.13" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/book/_contents/wave.ipynb b/book/_contents/wave.ipynb deleted file mode 100644 index 4ec2b57..0000000 --- a/book/_contents/wave.ipynb +++ /dev/null @@ -1,108 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Sine wave" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Packages" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Coordinates" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "x = np.linspace(0, 2*np.pi, 100)\n", - "y = np.sin(x)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plotting" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.plot(x, y)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "fast", - "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.2" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/book/_toc.yml b/book/_toc.yml index f6cbb07..abfc447 100644 --- a/book/_toc.yml +++ b/book/_toc.yml @@ -34,7 +34,25 @@ parts: sections: - file: features/jupyterlite/configuration - file: features/jupyterlite/including-jupyterlite + - file: features/custom_toc.md + - file: features/custom_download.md + - file: features/interactive_element + sections: + - file: feutures/h5p_examples + - file: features/theory_questions.md - file: features/h5p.md + - file: features/live_code.md + sections: + - file: features/widgets.ipynb + - file: features/exercise_checking.ipynb + + - caption: Examples chapters: - - file: examples/overview \ No newline at end of file + - file: examples/overview + - file: examples/interactive_templates.md + sections: + - file: examples/example_gumbel.ipynb + - file: examples/example_quiz_interactive.ipynb + - file: examples/coding_theory_widgets.ipynb + - file: examples/parametric_questions.ipynb \ No newline at end of file diff --git a/book/examples/coding_theory_widgets.ipynb b/book/examples/coding_theory_widgets.ipynb new file mode 100644 index 0000000..81c8255 --- /dev/null +++ b/book/examples/coding_theory_widgets.ipynb @@ -0,0 +1,333 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Combining coding theory with interactive questions and potential live coding\n", + "\n", + "This page shows an example of how to combine the introduction of a certain coding implementation with interactive questions. Automatic executation of the thebe live coding has been enables and the download button has been replaced by a custom notebook file." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{custom_download_link} ./Unconstrained_optimization_stripped.ipynb\n", + ":replace_default: \"True\"\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example unconstrained optimization\n", + "\n", + "In this chapter, we'll cover how to apply `scipy.optimize.minimize` to unconstrained optimization problems. As a reminder, unconstrained optimization considers:\n", + "\n", + "```{math}\n", + ":label: unconstrained_optimizatino\n", + "\n", + "\\mathop {\\min }\\limits_x f\\left( x \\right)\n", + "```\n", + "with $x$ the design variable of length $n$ and $f$ the objective function.\n", + "\n", + "\n", + "### Method\n", + "In this course, we're making use of the function `scipy.optimize.minimize`. The documentation of this function is available here: \n", + "https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html. In this course we'll cover only the relevant parts.\n", + "\n", + "For unconstrained optimization we need to run at least `scipy.optimize.minimize(fun, x0, ...)` with:\n", + "- `fun`, the objective function $f(x)$ to be minimized. `fun` is a callable. The `scipy.optmize.minimize` function takes care of defining and inputing our design variable $x$.\n", + "- `x0`, the initial guess for our design variable $x$. It needs to be a `ndarray` with length $n$\n", + "\n", + "The function `scipy.optimize.minimize` outputs an object `scipy.optimize.OptimizeResult`. with:\n", + "- `scipy.optimize.OptimizeResult.x` the optimized solution of the design variable `x`. It is a `ndarray` with length $n$\n", + "- `scipy.optimize.OptimizeResult.success`, a indication whether or not the optimizer was executed succesfully. It is a `bool`, indicating `True` or `False`\n", + "- `scipy.optimize.OptimizeResult.message`, a message describing the cause of termination of the optimizatino algorithm. It is a `str`.\n", + "- `scipy.optimize.OptimizeResult.fun`, the values of the optimized objective function $f$. It is a `int` or `float`\n", + "- `scipy.optimize.OptimizeResult.nit`, the number of iteration performed by the optimizer. It is a `int`\n", + "\n", + ":::{card}\n", + "\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 1\n", + "\n", + "#### Problem\n", + "It is desired to determine the number of bathymetry maps $n$ of a local area that should be produced to maximize the profit of a company. The total cost of production and distribution is €$75$ per unit $n$. The revenues are proportional to the number of units multiplied by its price: $Revenues = n \\cdot Price $\n", + "\n", + "The demand depends on the price ($Price = 150 - 0.01n^2$), as shown in the graph:\n", + "\n", + "```{figure} ./coding_theory_widgets_images/output.png\n", + "---\n", + "width: 600px\n", + "name: demand with respect to pice\n", + "---\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The profit can be estimated as the revenues minus the total costs." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Model\n", + "The function for the profit can be found by combining the relations in the problem statement. However, this is the profit which should be maximized. To turn this into a minimization problem, the profit can be multiplied with $-1$. The final model of this problem results in:\n", + "\n", + "```{math}\n", + ":label: unconstrained_optimization_problem\n", + "\n", + "\\mathop {\\min }\\limits_n \\left(75n - \\left( 150 - 0.01n^2 \\right) n \\right)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{card}\n", + "\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Method\n", + "\n", + "This model is described using `scipy.optimize.minimize`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Importing libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "tags": [ + "auto-execute-page", + "thebe-init" + ] + }, + "outputs": [], + "source": [ + "import scipy as sp \n", + "import numpy as np\n", + "import matplotlib.pylab as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Defining the variables\n", + "There are very few variables in this problem. In fact, the only variable we have to specify is the initial guess for the optimization algorithm. The objective function will be treated later.\n", + "The length of $n$ doesn't have to be specified." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [ + "thebe-init" + ] + }, + "outputs": [], + "source": [ + "n0 = 20" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{card}\n", + "\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Defining the objective function\n", + "In the objective function, the formula given in the model description can be inserted. Or, each individual step can be calculated on a seperate line. Again, note that the profit is multiplied with $-1$ to maximize the profit in the minimization formulation.\n", + "This results in:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "tags": [ + "thebe-init" + ] + }, + "outputs": [], + "source": [ + "def negprofit(n):\n", + " price = 150 - 0.01 * n**2\n", + " revenues = price * n\n", + " totalcost = 75 * n\n", + " profit = revenues - totalcost\n", + " return -profit" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Solving the problem\n", + "Now, the problem can be solved. The result is stored in the variables `result` which is printed." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "tags": [ + "thebe-init" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " message: Optimization terminated successfully.\n", + " success: True\n", + " status: 0\n", + " fun: -2499.9999999998727\n", + " x: [ 5.000e+01]\n", + " nit: 8\n", + " jac: [ 0.000e+00]\n", + " hess_inv: [[ 3.503e-01]]\n", + " nfev: 22\n", + " njev: 11\n" + ] + } + ], + "source": [ + "result = sp.optimize.minimize(negprofit,n0)\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{card}\n", + "\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Postprocessing\n", + "As this case is only one-dimensional and the potential range of values is limited, we can easily check this answer by an exhaustive search, evaluating all possible values for $n$. The plot below shows the $\\text{negative profit}$ for $0" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "n_range = np.linspace(0,100,100)\n", + "negprofit_result = negprofit(n_range)\n", + "plt.plot(n_range,negprofit_result)\n", + "plt.plot(result.x,result.fun,'o');\n", + "plt.xlabel('$n$')\n", + "plt.ylabel('Negative profit');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{card}\n", + "\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + ":::{card}\n", + "Adapt the code to answer the following questions.\n", + "\n", + "\n", + "\n", + "\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "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.9.18" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/book/examples/example_gumbel.ipynb b/book/examples/example_gumbel.ipynb new file mode 100644 index 0000000..9095c4c --- /dev/null +++ b/book/examples/example_gumbel.ipynb @@ -0,0 +1,259 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "# Programming assignment\n", + "\n", + "This page shows how you can convert the classical Jupyter Notebook assignment to a jupyter book format with live coding. A widget has been implemented to check the answers, of which the data is included in a separate python file. The page automatically loads and a custom download page has been added\n", + "\n", + "```{custom_download_link} ./example_gumbel_stripped.ipynb\n", + ":replace_default: \"True\"\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Gumbel Distribution Exercise\n", + "\n", + "Imagine you are concerned with the concentration of an airborne contaminant, $X$, measured in ppm. A previous benchmark study estimates that there is a 10% and 1% chance, respectively, of exceeding 4 and 10 ppm, respectively. You have been asked by the regulatory agency to estimate the contaminant concentration with 0.1% probability of being exceeded. Prior studies suggest that a Gumbel distribution can be used to model contaminant concentration, given by the CDF:\n", + "\n", + "$$\n", + "F_X(x) = \\exp{\\Bigg( -\\exp{\\Big(-\\frac{x-\\mu}{\\beta}\\Big)}\\Bigg)}\n", + "$$\n", + "\n", + "Using the cell blocks below as a guide (and also to check your analysis): Task 1) find the parameters of a Gumbel distribution that matches the information provided above, then, Tasks 2-3) use it to estimate the concentration with exceedance probability 0.1%.\n", + "\n", + "To complete this assignment, you can use `numpy`, `matplotlib` and from the `math` library, `log` and `e` (these are imported for you when you inialize the notebook)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "import micropip\n", + "await micropip.install(\"ipywidgets\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "from math import log, e\n", + "import numpy as np\n", + "from example_gumbel import check_example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Task 0:** fill in the appropriate fitting points:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "auto-execute-page" + ] + }, + "outputs": [], + "source": [ + "x_1 = _\n", + "p_1 = _\n", + "x_2 = _\n", + "p_2 = _" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Task 1:** derive the distribution parameters:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def gumbel_2_points(x_1, p_1, x_2, p_2): \n", + " \"\"\"Compute Gumbel distribution parameters from two points of the CDF.\n", + " Arguments:\n", + " x_1 (float): point one\n", + " p_1 (float): cumulative probability for point 1\n", + " x_2 (float): point two\n", + " p_2 (float): cumulative probability for point 2\n", + " \"\"\"\n", + "\n", + " # YOUR CODE GOES HERE #\n", + " beta = _\n", + " mu = _\n", + " #######################\n", + "\n", + " return mu, beta" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cells below will print your parameter values and create a plot to help confirm you have the right implementation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "def plot_distribution(mu, beta,\n", + " x_1, p_1, x_2, p_2):\n", + " plt.title(\"Gumbel Distribution, $1-F_X(x)$\")\n", + " plt.xlabel(\"Contaminant Concentration, $X$ [ppm]\")\n", + " plt.ylabel(\"Exceedance Probability [--]\")\n", + " plt.grid(color='black', linestyle='-', linewidth=0.3)\n", + " x_axis = np.arange(0, 20, 0.1)\n", + " plt.plot(x_axis, np.vectorize(gumbel_distribution)(x_axis), linewidth=2)\n", + " plt.plot(x_1, 1 - p_1, 'ro')\n", + " plt.annotate(\"Point 1\", (x_1 + 0.4, 1 - p_1 + 0.02))\n", + " plt.plot(x_2, 1 - p_2, 'ro')\n", + " plt.annotate(\"Point 2\", (x_2 + 0.4, 1 - p_2 + 0.001))\n", + " plt.yscale(\"log\") \n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mu, beta = gumbel_2_points(x_1, p_1, x_2, p_2)\n", + "\n", + "print(f\"Your mu: {mu:0.5f}\\nYour beta: {beta:0.5f}\")\n", + "\n", + "gumbel_distribution = lambda x: 1 - e**(-e**(-(x - mu)/beta))\n", + "\n", + "plot_distribution(mu, beta, x_1, p_1, x_2, p_2)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Task 2:** write a function to solve for the random variable value (it will be tested for you with the Check answer button, along with the distribution parameters)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def find_x_with_probability_p(p): \n", + " \"\"\" Compute point in the gumbel distribution for which the CDF is p\n", + " Use the variables mu and beta defined above!\n", + " Hint: they have been defined globally, so you don't need to \n", + " include them as arguments.\n", + " \"\"\"\n", + " # YOUR CODE GOES HERE #\n", + " x = _\n", + " #######################\n", + "\n", + " return x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Task 3:** use the function `find_x_with_probability_p` to evaluate the random variable value with exceedance probabiltiy of 0.001." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# YOUR CODE GOES HERE #\n", + "x = _\n", + "#######################\n", + "\n", + "print(f\"The value of x with probability of exceedance 0.001 os {x:0.5f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "check_example(globals())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/book/examples/example_quiz_interactive.ipynb b/book/examples/example_quiz_interactive.ipynb new file mode 100644 index 0000000..3cc70d9 --- /dev/null +++ b/book/examples/example_quiz_interactive.ipynb @@ -0,0 +1,359 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Combining theory with interactive questions and extensive coding assignment\n", + "This page shows an example of how to combine theory, quizzes and code on one page. With headings and boxes, the interactive parts can be indicated (for now only if part of one cell). Automatic execution has been activated and a custom ipynb.file has replaced the download button.\n", + "\n", + "```{custom_download_link} ./example_quiz_interactive_stripped.ipynb\n", + ":replace_default: \"True\"\n", + "```\n", + "\n", + "\n", + "## Weighted least-squares estimation\n", + "In ordinary least-squares estimation, we assume that all observations are equally important. In many cases this is not realistic, as observations may be obtained by different measurement systems, or under different circumstances. We want our methodology for least-squares estimation to be able to take this into account.\n", + "\n", + "We achieve this goal by introducing a weight matrix in the minimization problem:\n", + "\n", + "$$\n", + "\\underset{\\mathrm{x}}{\\min} {\\mathrm{(y-Ax)^T W(y-Ax)}}\n", + "$$\n", + "\n", + "In the unweighted least-squares approach, we arrive at the normal equation by pre-multiplying both sides of $\\mathrm{y=Ax}$ with the transpose of the design matrix $\\mathrm{A^T}$:\n", + "\n", + "$$ \n", + "\\mathrm{y=Ax} \\; \\rightarrow \\; \\mathrm{A^T\\; y = A^T\\; A x }\n", + "$$\n", + "\n", + "In the weighted least-squares approach, we now need to add weight matrix $W$ to this pre-multiplication factor, i.e., $ \\mathrm{A^T W}$, to obtain the normal equation:\n", + "\n", + "$$ \n", + "\\mathrm{y=Ax} \\; \\rightarrow \\; \\mathrm{A^T W \\; y = A^TW \\; A x}\n", + "$$\n", + "\n", + "The normal matrix is now defined as $\\mathrm{N=A^T W A}$. From this, assuming that the normal matrix $N$ is invertible (non-singular) we find the weighted least-squares estimate $ \\mathrm{\\hat{x}} $, \n", + "\n", + "$$ \n", + "\\begin{align*}\n", + "\\mathrm{\\hat{x}} &= \\mathrm{(A^T W A)^{-1} A^T W y} \\\\\n", + " &= \\arg \\underset{\\mathrm{x}}{\\min} {\\mathrm{(y-Ax)^T W(y-Ax)}}\n", + "\\end{align*}\n", + "$$\n", + "\n", + "We also find the derived estimate $ \\mathrm{\\hat{y}} $ and $ \\mathrm{\\hat{\\epsilon}} $:\n", + "\n", + "$$\n", + "\\mathrm{\\hat{y} = A \\hat{x} = A (A^T W A )^{-1} A^T W y}\n", + "$$\n", + "\n", + "$$\n", + " \\mathrm{\\hat{\\epsilon} = y - \\hat{y}= y - A \\hat{x} = y-A (A^T W A )^{-1} A^T W y = (I- A(A^T W A )^{-1} A^T W) y}\n", + "$$\n", + "\n", + "### Quiz question\n", + ":::{card}\n", + "\n", + ":::\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "### Video\n", + "```{eval-rst}\n", + ".. raw:: html\n", + "\n", + " \n", + "```\n", + "\n", + "### Discussion on the weight matrix\n", + "The weight matrix $\\mathrm{W}$ expresses the (relative) weights between the observations. It is always a square matrix. The size of the weight matrix depends on the number of observations, $m$. The size of the weight matrix is $m\\times m$. \n", + "\n", + "If it is a unit matrix ($\\mathrm{W=I}$), this implies that all observations have equal weight. Note that in this case the equations are equal to the ordinary least-squares solution. \n", + "\n", + "If it is a diagonal matrix, with different values on the diagonal, this implies that entries with a higher value correspond to the observations which are considered to be of more importance. If the weight matrix has non-zero elements on the off-diagonal positions, this implies that (some) observations are correlated.\n", + "\n", + "### Weighted least-squares estimator: properties\n", + "\n", + "Until now, we have looked at the weighted least-squares solution of a single *realization* of the observations, where generally we assume that it is a realization of the *random* observable vector $Y$, since measurements are affected by random errors. As such it follows the the weighted least-squares *estimator* is given by:\n", + "\n", + "$$\n", + "\\hat{X} = \\mathrm{(A^T W A )^{-1} A^T W} Y\n", + "$$\n", + "\n", + "This estimator has two important properties: it is *linear* and *unbiased*.\n", + "\n", + "The linearity property is due to the fact that $\\hat{X}$ is a linear function of the observables $Y$.\n", + "\n", + "The unbiased property means that the expectation of $\\hat{X}$ is equal to the true (but unknown) $\\mathrm{x}$. This can be shown as follows:\n", + "\n", + "$$\n", + "\\mathbb{E}(\\hat{X}) = \\mathrm{(A^T W A )^{-1} A^T W} \\mathbb{E}(Y) = \\mathrm{(A^T W A )^{-1} A^T W Ax = x}\n", + "$$\n", + "\n", + "This a very desirable property. It applies that if we would repeat the measurements many times to obtain a new estimate, the *average of the estimated* values would be equal to the truy values." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + ":::{card}\n", + "\n", + "You have a time series of 8 measurements and fit a model assuming a linear trend (constant velocity).\n", + "\n", + "Times of observations, observed values and number of observations are given as the variables `t`, `y` and `m`, as well as `numpy` and `matplotlib.pyplot` abbreviated as `np` and `plt`\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "# this will print all float arrays with 3 decimal places\n", + "import numpy as np\n", + "float_formatter = \"{:.2f}\".format\n", + "np.set_printoptions(formatter={'float_kind':float_formatter})\n", + "\n", + "import micropip\n", + "await micropip.install(\"ipywidgets\")\n", + "import ipywidgets as widgets\n", + "from IPython.display import display\n", + "import operator\n", + "\n", + "def check_answer(variable_name, expected, comparison = operator.eq):\n", + " output = widgets.Output()\n", + " button = widgets.Button(description=\"Check answer\")\n", + " def _inner_check(button):\n", + " with output:\n", + " if comparison(globals()[variable_name], expected):\n", + " output.outputs = [{'name': 'stdout', 'text': 'Correct!', 'output_type': 'stream'}]\n", + " else:\n", + " output.outputs = [{'name': 'stdout', 'text': 'Incorrect!', 'output_type': 'stream'}]\n", + " button.on_click(_inner_check)\n", + " display(button, output)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "# times of observation [months]\n", + "t = np.arange(8)\n", + "\n", + "# observed heights [m]\n", + "y = [1.39, 1.26, 1.48, 4.03, 5.89, 5.14, 6.47, 7.64]\n", + "\n", + "# number of observations\n", + "m = len(t)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{card}\n", + "Fill in the correct $\\mathrm{A}$-matrix.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "auto-execute-page" + ] + }, + "outputs": [], + "source": [ + "# design matrix\n", + "A = np.column_stack((np.ones(m), t))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "check_answer(\"A\", np.column_stack((np.ones(m), t)), np.array_equiv)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{card}\n", + "Define the weight matrix for $\\mathrm{W}=I_m$\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "W_1 = 1 #added to have initial input" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Weight matrix for case 1\n", + "W_1 = ?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "check_answer(\"W_1\",np.eye(m), np.array_equiv)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{card}\n", + "Define the weight matrix with the weight of first 3 observations is five times as large as the rest\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "W_2 = 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Weight matrix for case 2\n", + "W_2 = ?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "# Weight matrix for case 2\n", + "w=5\n", + "W_2_ans = np.eye(m)\n", + "W_2_ans[0,0] = w\n", + "W_2_ans[1,1] = w\n", + "W_2_ans[2,2] = w\n", + "check_answer(\"W_2\",W_2_ans, np.array_equiv)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{card}\n", + "Define the weight matrix with the weight of 4th and 5th observation is five times as large as the rest\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Weight matrix for case 3\n", + "W_3 = ?" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "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.9.18" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/book/examples/interactive_templates.md b/book/examples/interactive_templates.md new file mode 100644 index 0000000..266b10c --- /dev/null +++ b/book/examples/interactive_templates.md @@ -0,0 +1,16 @@ +# Templates for interactive coding / non-coding elements + +These pages show examples of how to use interactive coding / non-coding elements in a Jupyter Book. + +You can make use of: +- Admonitions +- JupyterQuiz +- H5p +- Grasple-exercises +- Interactive widgets +- Live coding exercise checking with widgets +- Any combination of the above + +The following templates are given: +```{tableofcontents} +``` \ No newline at end of file diff --git a/book/examples/parametric_questions.ipynb b/book/examples/parametric_questions.ipynb new file mode 100644 index 0000000..d61f7cd --- /dev/null +++ b/book/examples/parametric_questions.ipynb @@ -0,0 +1,318 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Parametric question\n", + "\n", + "On this page an example is given for parametric questions, in which an equation is checked on equivalence with the correct answer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "import sympy as sp\n", + "import numpy as np\n", + "from sympy import pi, latex\n", + "from sympy.printing.mathml import mathml\n", + "\n", + "import operator\n", + "import ipywidgets as widgets\n", + "from IPython.display import display, Latex, display_jpeg, Math, Markdown\n", + "sp.init_printing(use_latex=True) \n", + "\n", + "check_equation = lambda eq1, eq2: sp.simplify(eq1-eq2) == 0\n", + "\n", + "def check_answer(variable_name, expected, comparison = operator.eq):\n", + " output = widgets.Output()\n", + " button = widgets.Button(description=\"Check answer\")\n", + " def _inner_check(button):\n", + " with output:\n", + " if comparison(globals()[variable_name], expected):\n", + " output.outputs = [{'name': 'stdout', 'text': 'Correct!',\n", + " 'output_type': 'stream'}]\n", + " else:\n", + " output.outputs = [{'name': 'stdout', 'text': 'Incorrect!',\n", + " 'output_type': 'stream'}]\n", + " button.on_click(_inner_check)\n", + " display(button, output)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercises on Taylor expansion\n", + "\n", + "This page shows some exercises on calculation Taylor expansion. If you reload this page, you'll get new values.\n", + "\n", + "### Exercise 1\n", + "\n", + "Calculate the taylor series expension of:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true, + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "x, y = sp.symbols('x, y')\n", + "a_1 = sp.Integer(np.random.randint(2,6))\n", + "b_1 = sp.Integer(np.random.randint(-10,10))\n", + "c_1 = sp.Integer(np.random.randint(-5,5))\n", + "eq1_original = a_1 * x**2 + b_1*x\n", + "eq1_correct = sp.series(eq1_original,x,c_1)\n", + "eq1_answer = 0\n", + "display(eq1_original)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "around:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "display(sp.Eq(x,c_1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Fill in your answer and run the cell before clicking 'Check answer'. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "auto-execute-page", + "disable-download-page" + ] + }, + "outputs": [], + "source": [ + "eq1_answer = " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "check_answer(\"eq1_answer\",eq1_correct, check_equation)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 2\n", + "\n", + "Calculate the taylor series expension of:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "a_2 = sp.Integer(np.random.randint(1,7))\n", + "c_2 = sp.Integer(np.random.randint(-5,5))\n", + "eq2_original = a_2*sp.tan(x)\n", + "display(eq2_original)\n", + "eq2_correct = sp.series(eq2_original,x,c_2*sp.pi,3).removeO()\n", + "#display(eq2_correct)\n", + "eq2_answer = 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "around:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "display(sp.Eq(x,c_2*sp.pi))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "discard any $O(x^3)$ terms.\n", + "\n", + "Fill in your answer and run the cell before clicking 'Check answer'. Furthermore, use `pi` for $\\pi$:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eq2_answer = " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "check_answer(\"eq2_answer\",eq2_correct, check_equation)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 3\n", + "\n", + "Calculate the taylor series expension of:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "a_3 = sp.Integer(np.random.randint(1,10))\n", + "c_3 = sp.Integer(np.random.randint(-1,1))\n", + "eq3_original = a_3 / (1 - x)\n", + "display(eq3_original)\n", + "eq3_correct = sp.series(eq3_original,x,c_3,3).removeO()\n", + "#display(eq3_correct)\n", + "eq3_answer = 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "around:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "display(sp.Eq(x,c_3))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "discard any $O(x^3)$ terms.\n", + "\n", + "Fill in your answer and run the cell before clicking 'Check answer':" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eq3_answer =" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "check_answer(\"eq3_answer\",eq3_correct, check_equation)" + ] + } + ], + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/book/features/custom_download.md b/book/features/custom_download.md new file mode 100644 index 0000000..f71a52f --- /dev/null +++ b/book/features/custom_download.md @@ -0,0 +1,41 @@ +# Page download options + +You can control the download option of the book in two ways: + - Disabling downloading using cell tags `disable-download-page` + - Add / replace download link with custom link using `spinhx-download-link-replacer` + +## Disabling download +If you add `disable-download-page` as a cell tag to a cell in a python notebook, the download button ({fa}`download`) will disappear from the topright corner. The cell tag can be added to any code cell in the notebook. This function might be handy if your page includes code which you don't want the students to see. Be aware that this also removes the option to download a PDF of the page. + +## Add/replace download link +The download link {fa}`download` --> {fa}`file`{guilabel}`.ipynb` can be replaced by using the following code in any markdown / notebook file: +````md +```{custom_download_link} +:text: "Custom text" +:replace_default: "True" +``` +```` + +An explanation of the options of this command is given on https://gitlab.tudelft.nl/mude/sphinx-download-link-replacer. This extension can be downloaded by running + +`pip install git+https://gitlab.tudelft.nl/mude/sphinx-download-link-replacer` + +A potential application of this functionality is when creating a page in which students have to do some coding. Downloading the page allows the student to save their work and work with their local environment. However, the original source file might include code (jupyterbook formatting, widgets, answers) which is not necessary for students. You can make a copy of the notebook file without these elements and replace the {fa}`file`{guilabel}`.ipynb` link to this custom notebook file. Furthermore, you can add any additional data as an additional {fa}`file`{guilabel}`.zip` file. + + +### Installation instructions +To make use of this functionality, you have to install the `download_link_replace` package by running the following line in your command line: +``` +git+https://gitlab.tudelft.nl/mude/sphinx-download-link-replacer +``` + +Furthermore, you should add the following line to `requirements.txt` +``` +download_link_replacer @ git+https://gitlab.tudelft.nl/mude/sphinx-download-link-replacer +``` +and to `_config.yml`: +``` +sphinx: + extra_extensions: + - download_link_replacer +``` \ No newline at end of file diff --git a/book/features/custom_toc.md b/book/features/custom_toc.md new file mode 100644 index 0000000..005c970 --- /dev/null +++ b/book/features/custom_toc.md @@ -0,0 +1,22 @@ +# Exclude parts of book from published book + +A new feature is added to enable testing of all interactive features on the web server (i.e., in the draft book) while preventing students from seeing it. Pages in the draft book (`main` branch) can be manually stripped out of the table of contents when a merge request from `main` to `publish` is made using a tag `REMOVE-FROM-PUBLISH`. Pages marked with this feature are still visible in the draft book and books build from source on personal computers. The tag is applied as follows: + +``` +format: jb-book +root: intro + +parts: + - caption: ... + chapters: + - file: ... + ... +# START REMOVE-FROM-PUBLISH + - file: files_to_remove +# END REMOVE-FROM-PUBLISH +``` +There is no limit to the number of stripped sections, they can be sequential and indentation does not matter. The action is implemented in shell script `build-book.sh` and is executed at run time (see line in script [here](https://gitlab.tudelft.nl/mude/book/-/blob/main/build-book.sh#L13)). + +```{note} +The `REMOVE-FROM-PUBLISH` feature was developed to retain testing of all interactive features on the web server and we recommended incorporating this in all books that rely on these features. It avoids problems associated with building the book on various platforms and virtual environments, rather than the actualy webserver where the book will be deployed. +``` \ No newline at end of file diff --git a/book/features/exercise_checking.ipynb b/book/features/exercise_checking.ipynb new file mode 100644 index 0000000..83445c7 --- /dev/null +++ b/book/features/exercise_checking.ipynb @@ -0,0 +1,120 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exercise checking using check-answer button\n", + "\n", + "Using the python-enabled interactivity, exercise checking can be intuitively incorporated using a widgets which checks the value of certain variables with respect to the answer. These examples show different approaches to achieving this goal. \n", + "\n", + "A function has been made to check the value of a variable. As input values it takes the variable name, the expected value (correct answer) and how these values should be compared. You need to add this function to each page, including the imports, and add the `thebe-remove-input-init` tag to the cell.\n", + "```python\n", + "import micropip\n", + "await micropip.install(\"ipywidgets\")\n", + "import ipywidgets as widgets\n", + "from IPython.display import display\n", + "import operator\n", + "\n", + "def check_answer(variable_name, expected, comparison = operator.eq):\n", + " output = widgets.Output()\n", + " button = widgets.Button(description=\"Check answer\")\n", + " def _inner_check(button):\n", + " with output:\n", + " if comparison(globals()[variable_name], expected):\n", + " output.outputs = [{'name': 'stdout', 'text': 'Correct!', 'output_type': 'stream'}]\n", + " else:\n", + " output.outputs = [{'name': 'stdout', 'text': 'Incorrect!', 'output_type': 'stream'}]\n", + " button.on_click(_inner_check)\n", + " display(button, output)\n", + "```\n", + "\n", + "To check a float value, you could use `math.isclose` as a function. For checking the equivalence of arrays, you might consider `numpy.array_equiv`\n", + "\n", + "You can choose to include the correct answers in the same notebook. If students download the page and you didn't specify a custom url, they can find the correct answers. You can also place the correct answers in a seperate .py file which you import in the notebook.\n", + "\n", + "## Example" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "import micropip\n", + "await micropip.install(\"ipywidgets\")\n", + "import ipywidgets as widgets\n", + "from IPython.display import display\n", + "import operator\n", + "\n", + "def check_answer(variable_name, expected, comparison = operator.eq):\n", + " output = widgets.Output()\n", + " button = widgets.Button(description=\"Check answer\")\n", + " def _inner_check(button):\n", + " with output:\n", + " if comparison(globals()[variable_name], expected):\n", + " output.outputs = [{'name': 'stdout', 'text': 'Correct!', 'output_type': 'stream'}]\n", + " else:\n", + " output.outputs = [{'name': 'stdout', 'text': 'Incorrect!', 'output_type': 'stream'}]\n", + " button.on_click(_inner_check)\n", + " display(button, output)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-init", + "auto-execute-page" + ] + }, + "outputs": [], + "source": [ + "# This example has the user type in the answer as a Python variable, but they need to run the cell to update the answer checked!\n", + "pi = 3.14" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "import math\n", + "check_answer(\"pi\", 3.14, math.isclose)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "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.10.6" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/book/features/h5p_examples.md b/book/features/h5p_examples.md new file mode 100644 index 0000000..5c75bfc --- /dev/null +++ b/book/features/h5p_examples.md @@ -0,0 +1,23 @@ +# Examples H5p + +This page shows some examples of H5p questions. + +Some best-practices on the use of H5p: + - To startup your H5p-account, first login to H5p via Brightspace (follow instructions as under [https://www.tudelft.nl/teaching-support/educational-tools/h5p](https://www.tudelft.nl/teaching-support/educational-tools/h5p)). After that, direct login is possible via [tudelft.h5p.com](tudelft.h5p.com) + - Place your H5p-elements in a shared folder in H5p. + - Disable the display options "Toolbar Below Content" and "Display author's name to public (anonymous users) (only relevant when content's status is set to Public )" for each + +## Complex fill in the blanks +This can be used to evaluate numerical answers: + + +## Dialog cards +This can be used to ask open questions: + + +## Question set +This can be used to create a set of Multiple Choice, Drag and Drop, Fill in the blanks, True/False questions and Image choice questions: + + +## True/False Question + \ No newline at end of file diff --git a/book/features/interactive_element.ipynb b/book/features/interactive_element.ipynb new file mode 100644 index 0000000..2563004 --- /dev/null +++ b/book/features/interactive_element.ipynb @@ -0,0 +1,752 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "hideCode": true, + "hidePrompt": true + }, + "source": [ + "# Interactive elements not including Python\n", + "\n", + "To add interactivity to the book, you can make use of interactive elements which are embedded in the book. You've four options:\n", + "\n", + " - Admonition: question provided in normal markdown language with answers in a drowdown menu. There's no feedback delivered to the student, only the correct answer no matter what the student's thinking/work.\n", + " - JupyterQuiz: code-based implementation of multiple-choiche questions and numerical answer questions. This is a basic open course tool which requires you to write your quiz questions in Python language as a `dict` or to a `.json` file.\n", + " - H5p: TU-Delft-licensed tool to create questions (and many other interactive elements) using an [online GUI](https://tudelft.h5p.com/). These elements are embedded into the book using an iframe which requires very little coding. This elements are easier to implement than the JupyterQuiz and have more features (although proper numerical answer questions are missing). More information on this is provided in the [CiTG Jupyter Book introduction book](https://interactivetextbooks.citg.tudelft.nl/jupyter-book-manual/cookbook/h5p.html)\n", + " - Grasple-exercises: TU-Delft-licensed tool to creating math-based quiz questions. Again, these elements are embedded into the book using an iframe which requiries very little coding. These elements are very well suited to math-based problems as it allows analytical evaluation of numerical values and formulas. Furthermore, Grasple allows parameterization (not required for formative exercises). More information on this is provided in the [CiTG Jupyter Book introduction book](https://interactivetextbooks.citg.tudelft.nl/jupyter-book-manual/cookbook/grasple.html)\n", + " - Non-kernel-based widgets: You can create interactive widgets with Python which doesn't need a kernel to run online. The use of the [plotly](https://plotly.com/python/getting-started/) package is an example which allows this use.\n", + "\n", + "Examples of each type is shown below based on the following problem:\n", + "\n", + "A pre-fabricated bridge design is being considered for river crossings in a remote region of the world, as shown in Figure 1. Cities 1, 2 and 3 are labelled C1, C2 and C3, with bridges labelled B1-B4.\n", + "\n", + "```{figure} ../figures/pd/simple-city.png\n", + "---\n", + "width: 600px\n", + "name: exercise-simple-city\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": true, + "hidePrompt": true + }, + "source": [ + "## Admonition\n", + "If the probability of failure for an individual bridge is 0.1 per year, compute the probability that you cannot drive from City 1 to City 2\n", + "\n", + "\n", + "```{admonition} Answer\n", + ":class: tip, dropdown\n", + "\n", + "$$\n", + " P_{f_{C1-C2}} = P_{f_{B1}} * P_{f_{B4-B3-B2}} = P_{f_{B1}} * (1 - (1 - P_{f_{B4}})(1 - P_{f_{B3}}*P_{f_{B2}}))\n", + "$$\n", + "$$\n", + " P_{f_{C1 - C2}} = 0.1 * (1 - (1 - 0.1)(1 - 0.1 * 0.1)) = 0.0109\n", + "$$\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": true, + "hidePrompt": true + }, + "source": [ + "## JupyterQuiz" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "hideCode": true, + "hidePrompt": true, + "tags": [ + "disable-execution-page", + "disable-download-page", + "remove-input" + ] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "var questionsabVDrvSsNeVg=[{\"question\": \"If the probability of failure for an individual bridge is 0.1 per year, compute the probability that you cannot drive from City 1 to City 2:\", \"type\": \"numeric\", \"answers\": [{\"type\": \"range\", \"value\": 0.0109, \"correct\": true, \"feedback\": \"Correct.\"}, {\"type\": \"default\", \"feedback\": \"Try again\"}]}];\n // Make a random ID\nfunction makeid(length) {\n var result = [];\n var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n var charactersLength = characters.length;\n for (var i = 0; i < length; i++) {\n result.push(characters.charAt(Math.floor(Math.random() * charactersLength)));\n }\n return result.join('');\n}\n\n// Choose a random subset of an array. Can also be used to shuffle the array\nfunction getRandomSubarray(arr, size) {\n var shuffled = arr.slice(0), i = arr.length, temp, index;\n while (i--) {\n index = Math.floor((i + 1) * Math.random());\n temp = shuffled[index];\n shuffled[index] = shuffled[i];\n shuffled[i] = temp;\n }\n return shuffled.slice(0, size);\n}\n\nfunction printResponses(responsesContainer) {\n var responses=JSON.parse(responsesContainer.dataset.responses);\n var stringResponses='IMPORTANT!To preserve this answer sequence for submission, when you have finalized your answers:
  1. Copy the text in this cell below \"Answer String\"
  2. Double click on the cell directly below the Answer String, labeled \"Replace Me\"
  3. Select the whole \"Replace Me\" text
  4. Paste in your answer string and press shift-Enter.
  5. Save the notebook using the save icon or File->Save Notebook menu item



  6. Answer String:
    ';\n console.log(responses);\n responses.forEach((response, index) => {\n if (response) {\n console.log(index + ': ' + response);\n stringResponses+= index + ': ' + response +\"
    \";\n }\n });\n responsesContainer.innerHTML=stringResponses;\n}\nfunction check_mc() {\n var id = this.id.split('-')[0];\n //var response = this.id.split('-')[1];\n //console.log(response);\n //console.log(\"In check_mc(), id=\"+id);\n //console.log(event.srcElement.id) \n //console.log(event.srcElement.dataset.correct) \n //console.log(event.srcElement.dataset.feedback)\n\n var label = event.srcElement;\n //console.log(label, label.nodeName);\n var depth = 0;\n while ((label.nodeName != \"LABEL\") && (depth < 20)) {\n label = label.parentElement;\n console.log(depth, label);\n depth++;\n }\n\n\n\n var answers = label.parentElement.children;\n\n //console.log(answers);\n\n\n // Split behavior based on multiple choice vs many choice:\n var fb = document.getElementById(\"fb\" + id);\n\n\n\n\n if (fb.dataset.numcorrect == 1) {\n // What follows is for the saved responses stuff\n var outerContainer = fb.parentElement.parentElement;\n var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n if (responsesContainer) {\n //console.log(responsesContainer);\n var response = label.firstChild.innerText;\n if (label.querySelector(\".QuizCode\")){\n response+= label.querySelector(\".QuizCode\").firstChild.innerText;\n }\n console.log(response);\n //console.log(document.getElementById(\"quizWrap\"+id));\n var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n console.log(\"Question \" + qnum);\n //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n var responses=JSON.parse(responsesContainer.dataset.responses);\n console.log(responses);\n responses[qnum]= response;\n responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n printResponses(responsesContainer);\n }\n // End code to preserve responses\n \n for (var i = 0; i < answers.length; i++) {\n var child = answers[i];\n //console.log(child);\n child.className = \"MCButton\";\n }\n\n\n\n if (label.dataset.correct == \"true\") {\n // console.log(\"Correct action\");\n if (\"feedback\" in label.dataset) {\n fb.textContent = jaxify(label.dataset.feedback);\n } else {\n fb.textContent = \"Correct!\";\n }\n label.classList.add(\"correctButton\");\n\n fb.className = \"Feedback\";\n fb.classList.add(\"correct\");\n\n } else {\n if (\"feedback\" in label.dataset) {\n fb.textContent = jaxify(label.dataset.feedback);\n } else {\n fb.textContent = \"Incorrect -- try again.\";\n }\n //console.log(\"Error action\");\n label.classList.add(\"incorrectButton\");\n fb.className = \"Feedback\";\n fb.classList.add(\"incorrect\");\n }\n }\n else {\n var reset = false;\n var feedback;\n if (label.dataset.correct == \"true\") {\n if (\"feedback\" in label.dataset) {\n feedback = jaxify(label.dataset.feedback);\n } else {\n feedback = \"Correct!\";\n }\n if (label.dataset.answered <= 0) {\n if (fb.dataset.answeredcorrect < 0) {\n fb.dataset.answeredcorrect = 1;\n reset = true;\n } else {\n fb.dataset.answeredcorrect++;\n }\n if (reset) {\n for (var i = 0; i < answers.length; i++) {\n var child = answers[i];\n child.className = \"MCButton\";\n child.dataset.answered = 0;\n }\n }\n label.classList.add(\"correctButton\");\n label.dataset.answered = 1;\n fb.className = \"Feedback\";\n fb.classList.add(\"correct\");\n\n }\n } else {\n if (\"feedback\" in label.dataset) {\n feedback = jaxify(label.dataset.feedback);\n } else {\n feedback = \"Incorrect -- try again.\";\n }\n if (fb.dataset.answeredcorrect > 0) {\n fb.dataset.answeredcorrect = -1;\n reset = true;\n } else {\n fb.dataset.answeredcorrect--;\n }\n\n if (reset) {\n for (var i = 0; i < answers.length; i++) {\n var child = answers[i];\n child.className = \"MCButton\";\n child.dataset.answered = 0;\n }\n }\n label.classList.add(\"incorrectButton\");\n fb.className = \"Feedback\";\n fb.classList.add(\"incorrect\");\n }\n // What follows is for the saved responses stuff\n var outerContainer = fb.parentElement.parentElement;\n var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n if (responsesContainer) {\n //console.log(responsesContainer);\n var response = label.firstChild.innerText;\n if (label.querySelector(\".QuizCode\")){\n response+= label.querySelector(\".QuizCode\").firstChild.innerText;\n }\n console.log(response);\n //console.log(document.getElementById(\"quizWrap\"+id));\n var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n console.log(\"Question \" + qnum);\n //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n var responses=JSON.parse(responsesContainer.dataset.responses);\n if (label.dataset.correct == \"true\") {\n if (typeof(responses[qnum]) == \"object\"){\n if (!responses[qnum].includes(response))\n responses[qnum].push(response);\n } else{\n responses[qnum]= [ response ];\n }\n } else {\n responses[qnum]= response;\n }\n console.log(responses);\n responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n printResponses(responsesContainer);\n }\n // End save responses stuff\n\n\n\n var numcorrect = fb.dataset.numcorrect;\n var answeredcorrect = fb.dataset.answeredcorrect;\n if (answeredcorrect >= 0) {\n fb.textContent = feedback + \" [\" + answeredcorrect + \"/\" + numcorrect + \"]\";\n } else {\n fb.textContent = feedback + \" [\" + 0 + \"/\" + numcorrect + \"]\";\n }\n\n\n }\n\n if (typeof MathJax != 'undefined') {\n var version = MathJax.version;\n console.log('MathJax version', version);\n if (version[0] == \"2\") {\n MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n } else if (version[0] == \"3\") {\n MathJax.typeset([fb]);\n }\n } else {\n console.log('MathJax not detected');\n }\n\n}\n\nfunction make_mc(qa, shuffle_answers, outerqDiv, qDiv, aDiv, id) {\n var shuffled;\n if (shuffle_answers == \"True\") {\n //console.log(shuffle_answers+\" read as true\");\n shuffled = getRandomSubarray(qa.answers, qa.answers.length);\n } else {\n //console.log(shuffle_answers+\" read as false\");\n shuffled = qa.answers;\n }\n\n\n var num_correct = 0;\n\n\n\n shuffled.forEach((item, index, ans_array) => {\n //console.log(answer);\n\n // Make input element\n var inp = document.createElement(\"input\");\n inp.type = \"radio\";\n inp.id = \"quizo\" + id + index;\n inp.style = \"display:none;\";\n aDiv.append(inp);\n\n //Make label for input element\n var lab = document.createElement(\"label\");\n lab.className = \"MCButton\";\n lab.id = id + '-' + index;\n lab.onclick = check_mc;\n var aSpan = document.createElement('span');\n aSpan.classsName = \"\";\n //qDiv.id=\"quizQn\"+id+index;\n if (\"answer\" in item) {\n aSpan.innerHTML = jaxify(item.answer);\n //aSpan.innerHTML=item.answer;\n }\n lab.append(aSpan);\n\n // Create div for code inside question\n var codeSpan;\n if (\"code\" in item) {\n codeSpan = document.createElement('span');\n codeSpan.id = \"code\" + id + index;\n codeSpan.className = \"QuizCode\";\n var codePre = document.createElement('pre');\n codeSpan.append(codePre);\n var codeCode = document.createElement('code');\n codePre.append(codeCode);\n codeCode.innerHTML = item.code;\n lab.append(codeSpan);\n //console.log(codeSpan);\n }\n\n //lab.textContent=item.answer;\n\n // Set the data attributes for the answer\n lab.setAttribute('data-correct', item.correct);\n if (item.correct) {\n num_correct++;\n }\n if (\"feedback\" in item) {\n lab.setAttribute('data-feedback', item.feedback);\n }\n lab.setAttribute('data-answered', 0);\n\n aDiv.append(lab);\n\n });\n\n if (num_correct > 1) {\n outerqDiv.className = \"ManyChoiceQn\";\n } else {\n outerqDiv.className = \"MultipleChoiceQn\";\n }\n\n return num_correct;\n\n}\nfunction check_numeric(ths, event) {\n\n if (event.keyCode === 13) {\n ths.blur();\n\n var id = ths.id.split('-')[0];\n\n var submission = ths.value;\n if (submission.indexOf('/') != -1) {\n var sub_parts = submission.split('/');\n //console.log(sub_parts);\n submission = sub_parts[0] / sub_parts[1];\n }\n //console.log(\"Reader entered\", submission);\n\n if (\"precision\" in ths.dataset) {\n var precision = ths.dataset.precision;\n // console.log(\"1:\", submission)\n submission = Math.round((1 * submission + Number.EPSILON) * 10 ** precision) / 10 ** precision;\n // console.log(\"Rounded to \", submission, \" precision=\", precision );\n }\n\n\n //console.log(\"In check_numeric(), id=\"+id);\n //console.log(event.srcElement.id) \n //console.log(event.srcElement.dataset.feedback)\n\n var fb = document.getElementById(\"fb\" + id);\n fb.style.display = \"none\";\n fb.textContent = \"Incorrect -- try again.\";\n\n var answers = JSON.parse(ths.dataset.answers);\n //console.log(answers);\n\n var defaultFB = \"\";\n var correct;\n var done = false;\n answers.every(answer => {\n //console.log(answer.type);\n\n correct = false;\n // if (answer.type==\"value\"){\n if ('value' in answer) {\n if (submission == answer.value) {\n if (\"feedback\" in answer) {\n fb.textContent = jaxify(answer.feedback);\n } else {\n fb.textContent = jaxify(\"Correct\");\n }\n correct = answer.correct;\n //console.log(answer.correct);\n done = true;\n }\n // } else if (answer.type==\"range\") {\n } else if ('range' in answer) {\n //console.log(answer.range);\n if ((submission >= answer.range[0]) && (submission < answer.range[1])) {\n fb.textContent = jaxify(answer.feedback);\n correct = answer.correct;\n //console.log(answer.correct);\n done = true;\n }\n } else if (answer.type == \"default\") {\n defaultFB = answer.feedback;\n }\n if (done) {\n return false; // Break out of loop if this has been marked correct\n } else {\n return true; // Keep looking for case that includes this as a correct answer\n }\n });\n\n if ((!done) && (defaultFB != \"\")) {\n fb.innerHTML = jaxify(defaultFB);\n //console.log(\"Default feedback\", defaultFB);\n }\n\n fb.style.display = \"block\";\n if (correct) {\n ths.className = \"Input-text\";\n ths.classList.add(\"correctButton\");\n fb.className = \"Feedback\";\n fb.classList.add(\"correct\");\n } else {\n ths.className = \"Input-text\";\n ths.classList.add(\"incorrectButton\");\n fb.className = \"Feedback\";\n fb.classList.add(\"incorrect\");\n }\n\n // What follows is for the saved responses stuff\n var outerContainer = fb.parentElement.parentElement;\n var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n if (responsesContainer) {\n console.log(submission);\n var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n //console.log(\"Question \" + qnum);\n //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n var responses=JSON.parse(responsesContainer.dataset.responses);\n console.log(responses);\n if (submission == ths.value){\n responses[qnum]= submission;\n } else {\n responses[qnum]= ths.value + \"(\" + submission +\")\";\n }\n responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n printResponses(responsesContainer);\n }\n // End code to preserve responses\n\n if (typeof MathJax != 'undefined') {\n var version = MathJax.version;\n console.log('MathJax version', version);\n if (version[0] == \"2\") {\n MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n } else if (version[0] == \"3\") {\n MathJax.typeset([fb]);\n }\n } else {\n console.log('MathJax not detected');\n }\n return false;\n }\n\n}\n\nfunction isValid(el, charC) {\n //console.log(\"Input char: \", charC);\n if (charC == 46) {\n if (el.value.indexOf('.') === -1) {\n return true;\n } else if (el.value.indexOf('/') != -1) {\n var parts = el.value.split('/');\n if (parts[1].indexOf('.') === -1) {\n return true;\n }\n }\n else {\n return false;\n }\n } else if (charC == 47) {\n if (el.value.indexOf('/') === -1) {\n if ((el.value != \"\") && (el.value != \".\")) {\n return true;\n } else {\n return false;\n }\n } else {\n return false;\n }\n } else if (charC == 45) {\n var edex = el.value.indexOf('e');\n if (edex == -1) {\n edex = el.value.indexOf('E');\n }\n\n if (el.value == \"\") {\n return true;\n } else if (edex == (el.value.length - 1)) { // If just after e or E\n return true;\n } else {\n return false;\n }\n } else if (charC == 101) { // \"e\"\n if ((el.value.indexOf('e') === -1) && (el.value.indexOf('E') === -1) && (el.value.indexOf('/') == -1)) {\n // Prev symbol must be digit or decimal point:\n if (el.value.slice(-1).search(/\\d/) >= 0) {\n return true;\n } else if (el.value.slice(-1).search(/\\./) >= 0) {\n return true;\n } else {\n return false;\n }\n } else {\n return false;\n }\n } else {\n if (charC > 31 && (charC < 48 || charC > 57))\n return false;\n }\n return true;\n}\n\nfunction numeric_keypress(evnt) {\n var charC = (evnt.which) ? evnt.which : evnt.keyCode;\n\n if (charC == 13) {\n check_numeric(this, evnt);\n } else {\n return isValid(this, charC);\n }\n}\n\n\n\n\n\nfunction make_numeric(qa, outerqDiv, qDiv, aDiv, id) {\n\n\n\n //console.log(answer);\n\n\n outerqDiv.className = \"NumericQn\";\n aDiv.style.display = 'block';\n\n var lab = document.createElement(\"label\");\n lab.className = \"InpLabel\";\n lab.textContent = \"Type numeric answer here:\";\n aDiv.append(lab);\n\n var inp = document.createElement(\"input\");\n inp.type = \"text\";\n //inp.id=\"input-\"+id;\n inp.id = id + \"-0\";\n inp.className = \"Input-text\";\n inp.setAttribute('data-answers', JSON.stringify(qa.answers));\n if (\"precision\" in qa) {\n inp.setAttribute('data-precision', qa.precision);\n }\n aDiv.append(inp);\n //console.log(inp);\n\n //inp.addEventListener(\"keypress\", check_numeric);\n //inp.addEventListener(\"keypress\", numeric_keypress);\n /*\n inp.addEventListener(\"keypress\", function(event) {\n return numeric_keypress(this, event);\n }\n );\n */\n //inp.onkeypress=\"return numeric_keypress(this, event)\";\n inp.onkeypress = numeric_keypress;\n inp.onpaste = event => false;\n\n inp.addEventListener(\"focus\", function (event) {\n this.value = \"\";\n return false;\n }\n );\n\n\n}\nfunction jaxify(string) {\n var mystring = string;\n\n var count = 0;\n var loc = mystring.search(/([^\\\\]|^)(\\$)/);\n\n var count2 = 0;\n var loc2 = mystring.search(/([^\\\\]|^)(\\$\\$)/);\n\n //console.log(loc);\n\n while ((loc >= 0) || (loc2 >= 0)) {\n\n /* Have to replace all the double $$ first with current implementation */\n if (loc2 >= 0) {\n if (count2 % 2 == 0) {\n mystring = mystring.replace(/([^\\\\]|^)(\\$\\$)/, \"$1\\\\[\");\n } else {\n mystring = mystring.replace(/([^\\\\]|^)(\\$\\$)/, \"$1\\\\]\");\n }\n count2++;\n } else {\n if (count % 2 == 0) {\n mystring = mystring.replace(/([^\\\\]|^)(\\$)/, \"$1\\\\(\");\n } else {\n mystring = mystring.replace(/([^\\\\]|^)(\\$)/, \"$1\\\\)\");\n }\n count++;\n }\n loc = mystring.search(/([^\\\\]|^)(\\$)/);\n loc2 = mystring.search(/([^\\\\]|^)(\\$\\$)/);\n //console.log(mystring,\", loc:\",loc,\", loc2:\",loc2);\n }\n\n //console.log(mystring);\n return mystring;\n}\n\n\nfunction show_questions(json, mydiv) {\n console.log('show_questions');\n //var mydiv=document.getElementById(myid);\n var shuffle_questions = mydiv.dataset.shufflequestions;\n var num_questions = mydiv.dataset.numquestions;\n var shuffle_answers = mydiv.dataset.shuffleanswers;\n var max_width = mydiv.dataset.maxwidth;\n\n if (num_questions > json.length) {\n num_questions = json.length;\n }\n\n var questions;\n if ((num_questions < json.length) || (shuffle_questions == \"True\")) {\n //console.log(num_questions+\",\"+json.length);\n questions = getRandomSubarray(json, num_questions);\n } else {\n questions = json;\n }\n\n //console.log(\"SQ: \"+shuffle_questions+\", NQ: \" + num_questions + \", SA: \", shuffle_answers);\n\n // Iterate over questions\n questions.forEach((qa, index, array) => {\n //console.log(qa.question); \n\n var id = makeid(8);\n //console.log(id);\n\n\n // Create Div to contain question and answers\n var iDiv = document.createElement('div');\n //iDiv.id = 'quizWrap' + id + index;\n iDiv.id = 'quizWrap' + id;\n iDiv.className = 'Quiz';\n iDiv.setAttribute('data-qnum', index);\n iDiv.style.maxWidth =max_width+\"px\";\n mydiv.appendChild(iDiv);\n // iDiv.innerHTML=qa.question;\n \n var outerqDiv = document.createElement('div');\n outerqDiv.id = \"OuterquizQn\" + id + index;\n // Create div to contain question part\n var qDiv = document.createElement('div');\n qDiv.id = \"quizQn\" + id + index;\n \n if (qa.question) {\n iDiv.append(outerqDiv);\n\n //qDiv.textContent=qa.question;\n qDiv.innerHTML = jaxify(qa.question);\n outerqDiv.append(qDiv);\n }\n\n // Create div for code inside question\n var codeDiv;\n if (\"code\" in qa) {\n codeDiv = document.createElement('div');\n codeDiv.id = \"code\" + id + index;\n codeDiv.className = \"QuizCode\";\n var codePre = document.createElement('pre');\n codeDiv.append(codePre);\n var codeCode = document.createElement('code');\n codePre.append(codeCode);\n codeCode.innerHTML = qa.code;\n outerqDiv.append(codeDiv);\n //console.log(codeDiv);\n }\n\n\n // Create div to contain answer part\n var aDiv = document.createElement('div');\n aDiv.id = \"quizAns\" + id + index;\n aDiv.className = 'Answer';\n iDiv.append(aDiv);\n\n //console.log(qa.type);\n\n var num_correct;\n if ((qa.type == \"multiple_choice\") || (qa.type == \"many_choice\") ) {\n num_correct = make_mc(qa, shuffle_answers, outerqDiv, qDiv, aDiv, id);\n if (\"answer_cols\" in qa) {\n //aDiv.style.gridTemplateColumns = 'auto '.repeat(qa.answer_cols);\n aDiv.style.gridTemplateColumns = 'repeat(' + qa.answer_cols + ', 1fr)';\n }\n } else if (qa.type == \"numeric\") {\n //console.log(\"numeric\");\n make_numeric(qa, outerqDiv, qDiv, aDiv, id);\n }\n\n\n //Make div for feedback\n var fb = document.createElement(\"div\");\n fb.id = \"fb\" + id;\n //fb.style=\"font-size: 20px;text-align:center;\";\n fb.className = \"Feedback\";\n fb.setAttribute(\"data-answeredcorrect\", 0);\n fb.setAttribute(\"data-numcorrect\", num_correct);\n iDiv.append(fb);\n\n\n });\n var preserveResponses = mydiv.dataset.preserveresponses;\n console.log(preserveResponses);\n console.log(preserveResponses == \"true\");\n if (preserveResponses == \"true\") {\n console.log(preserveResponses);\n // Create Div to contain record of answers\n var iDiv = document.createElement('div');\n iDiv.id = 'responses' + mydiv.id;\n iDiv.className = 'JCResponses';\n // Create a place to store responses as an empty array\n iDiv.setAttribute('data-responses', '[]');\n\n // Dummy Text\n iDiv.innerHTML=\"Select your answers and then follow the directions that will appear here.\"\n //iDiv.className = 'Quiz';\n mydiv.appendChild(iDiv);\n }\n//console.log(\"At end of show_questions\");\n if (typeof MathJax != 'undefined') {\n console.log(\"MathJax version\", MathJax.version);\n var version = MathJax.version;\n setTimeout(function(){\n var version = MathJax.version;\n console.log('After sleep, MathJax version', version);\n if (version[0] == \"2\") {\n MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n } else if (version[0] == \"3\") {\n MathJax.typeset([mydiv]);\n }\n }, 500);\nif (typeof version == 'undefined') {\n } else\n {\n if (version[0] == \"2\") {\n MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n } else if (version[0] == \"3\") {\n MathJax.typeset([mydiv]);\n } else {\n console.log(\"MathJax not found\");\n }\n }\n }\n return false;\n}\n/* This is to handle asynchrony issues in loading Jupyter notebooks\n where the quiz has been previously run. The Javascript was generally\n being run before the div was added to the DOM. I tried to do this\n more elegantly using Mutation Observer, but I didn't get it to work.\n\n Someone more knowledgeable could make this better ;-) */\n\n function try_show() {\n if(document.getElementById(\"abVDrvSsNeVg\")) {\n show_questions(questionsabVDrvSsNeVg, abVDrvSsNeVg); \n } else {\n setTimeout(try_show, 200);\n }\n };\n \n {\n // console.log(element);\n\n //console.log(\"abVDrvSsNeVg\");\n // console.log(document.getElementById(\"abVDrvSsNeVg\"));\n\n try_show();\n }\n ", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import json\n", + "from jupyterquiz import display_quiz\n", + "with open(\"jupyterquizexample.json\", \"r\") as file:\n", + " questions=json.load(file)\n", + "\n", + "display_quiz(questions, border_radius=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": true, + "hidePrompt": true + }, + "source": [ + "## H5p\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": true, + "hidePrompt": true + }, + "source": [ + "## Grasple\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Interactive widget without kernel\n", + "\n", + "The example below is taken from the FEM-module by Frans van der Meer.\n", + "When using Plotly graphs, make sure the notebook is executed in Jupyter Lab / Jupyter Notebook for the figures to be shown in the book. Running the code in VS code might break the result in the book, although the output is visible in VS code. Alternatively, add the following lines of code to your notebook to have a valid output in the book as well:\n", + "\n", + "```python\n", + "import plotly.io as pio\n", + "pio.renderers.default = 'notebook'\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%matplotlib widget\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import plotly.subplots as sp\n", + "import plotly.graph_objects as go\n", + "import plotly.io as pio\n", + "pio.renderers.default = 'notebook'" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6241088f78f046be86f2e814448e8790", + "version_major": 2, + "version_minor": 0 + }, + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAIyUlEQVR4nO3WMQEAIAzAMMC/5+ECjiYKenbPzCwAADLO7wAAAN4ygAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIuJnkHvKensmIAAAAASUVORK5CYII=", + "text/html": [ + "\n", + "
    \n", + "
    \n", + " Figure\n", + "
    \n", + " \n", + "
    \n", + " " + ], + "text/plain": [ + "Canvas(toolbar=None)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure()\n", + "plt.rcParams.update({'font.size': 12, 'lines.linewidth': 2})\n", + "\n", + "def shape_functions(total_length, num_nodes, node_x):\n", + " l = total_length / (num_nodes - 1)\n", + "\n", + " if node_x == 0:\n", + " x = np.array([node_x, node_x + l])\n", + " y = np.array([1.0, 0.0])\n", + " elif node_x == total_length:\n", + " x = np.array([node_x - l, node_x])\n", + " y = np.array([0.0, 1.0])\n", + " else:\n", + " x = np.array([node_x - l, node_x, node_x + l])\n", + " y = np.array([0.0, 1.0, 0.0])\n", + "\n", + " return x, y\n", + "\n", + "def shape_functions_for_plot(num_nodes, beam_length = 15):\n", + "\n", + " x_positions = np.linspace(0, beam_length, num_nodes)\n", + "\n", + " data = {}\n", + "\n", + " for node_num, x_coords in enumerate(x_positions):\n", + "\n", + " x, y = shape_functions(beam_length, num_nodes, x_coords)\n", + " data[node_num + 1] = {'x': x, 'y': y}\n", + "\n", + " return data" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "def linear_lagrange_shape_function(x, nodes_x_values, nodes_displacements):\n", + " \"\"\"\n", + " Compute the linear Lagrange shape function for a given x.\n", + " Args:\n", + " x (float): The x-coordinate.\n", + " x0 (float): Left node position.\n", + " x1 (float): Right node position.\n", + " Returns:\n", + " float: The shape function value.\n", + " \"\"\"\n", + "\n", + " ratio = (x - nodes_x_values[0]) / (nodes_x_values[1] - nodes_x_values[0])\n", + "\n", + " return (1 - ratio) * nodes_displacements[0] + ratio * nodes_displacements[1]\n", + "\n", + "def function(x):\n", + " \"\"\"\n", + " Define the function to be plotted.\n", + " Args:\n", + " x (numpy.ndarray): Input values.\n", + " Returns:\n", + " numpy.ndarray: Corresponding function values.\n", + " \"\"\"\n", + " return np.sin(x) * 0.14*x\n", + "\n", + "def shape_functions_applied(num_elements):\n", + " \"\"\"\n", + " Plot linear Lagrange shape functions on a beam.\n", + " Args:\n", + " num_elements (int): Number of elements (segments).\n", + " \"\"\"\n", + "\n", + " beam_length = 15\n", + "\n", + " num_nodes = num_elements + 1\n", + " x_at_nodes = np.linspace(0, beam_length, num_nodes)\n", + "\n", + " y_at_nodes = function(x_at_nodes)\n", + "\n", + " x_data = np.linspace(0, beam_length, 1000)\n", + "\n", + " # Use NumPy to compute y_vals efficiently\n", + " y_vals = np.zeros_like(x_data)\n", + "\n", + " for element_number in range(num_elements):\n", + " mask = (x_at_nodes[element_number] <= x_data) & (x_data <= x_at_nodes[element_number + 1])\n", + " y_vals[mask] = linear_lagrange_shape_function(x_data[mask], x_at_nodes[element_number:element_number + 2], y_at_nodes[element_number:element_number + 2])\n", + "\n", + " return x_data, function(x_data), y_vals\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "tags": [ + "remove-input" + ] + }, + "outputs": [ + { + "data": { + "text/html": [ + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Colors\n", + "grey = '#eeeeee' # light grey fill\n", + "\n", + "# Prepare all the graphs that are to be plotted\n", + "range_elements = np.concatenate((np.arange(1, 7, 1), np.arange(7, 35, 4), np.arange(35, 55, 8)))\n", + "\n", + "# Create subplots\n", + "fig = sp.make_subplots(rows=1, cols=2, subplot_titles=(\"Shape functions\", \"Exact and approximated solution\"))\n", + "\n", + "base_traces_left = 2 # Number of traces in the left plot that are always visible\n", + "base_traces_right = 0 # Number of traces in the right plot that are always visible\n", + "\n", + "# Add traces to the subplots\n", + "x_data, y_data, y_vals = shape_functions_applied(10)\n", + "\n", + "# Add left plot traces (Cumulative Influx and Cumulative Outflux)\n", + "fig.add_trace(\n", + " go.Scatter(\n", + " visible=True, # Show for the first value of a\n", + " x=x_data,\n", + " y=y_data,\n", + " # line=dict(color='#6a8ba4'),\n", + " mode='lines',\n", + " name='Exact solution'\n", + " ),\n", + " row=1,\n", + " col=2\n", + ")\n", + "\n", + "\n", + "# Add traces to the subplots\n", + "for a in range_elements:\n", + "\n", + " x_data, y_data, y_vals = shape_functions_applied(a)\n", + "\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " visible = True if a == range_elements[0] else False, # Show for the first value of a\n", + " x=x_data,\n", + " y=y_vals,\n", + " # line=dict(color='#FFA500'),\n", + " mode='lines',\n", + " name='Numerical solution'\n", + " ),\n", + " row=1,\n", + " col=2\n", + " )\n", + "\n", + "\n", + "# Add traces to the subplots\n", + "for a in range_elements:\n", + "\n", + " data = shape_functions_for_plot(a + 1)\n", + "\n", + " for i in range(1, a + 2):\n", + "\n", + " info = data[i]\n", + " x, y = info['x'], info['y']\n", + "\n", + " # Add right plot traces (Cumulative Outflux for each 'a' value)\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " visible = True if a == range_elements[0] else False,\n", + " x=x,\n", + " y=y,\n", + " mode='lines',\n", + " name=f'Node shape for node {i}'\n", + " ),\n", + " row=1,\n", + " col=1\n", + " )\n", + "\n", + "# Create and add slider\n", + "steps = []\n", + "for i in range(0, range_elements.shape[0]):\n", + "\n", + " a = range_elements[i]\n", + " visarray = [False] * len(fig.data)\n", + "\n", + " visarray[0] = True\n", + " visarray[i + 1] = True\n", + "\n", + " start_of_second_plot = 1 + len(range_elements)\n", + "\n", + " if i==0:\n", + " start_index=start_of_second_plot\n", + " else:\n", + " start_index = start_of_second_plot + np.sum(range_elements[0:i]) + i\n", + "\n", + " num_lines = range_elements[i] + 1\n", + " end_index = start_index + num_lines\n", + "\n", + " visarray[start_index:end_index] = [True] * (end_index - start_index)\n", + " \n", + " step = dict(\n", + " method=\"update\",\n", + " args=[{\"visible\": visarray}],\n", + " label=str(round(range_elements[i], 1))\n", + " )\n", + " steps.append(step)\n", + "\n", + "sliders = [dict(\n", + " active=0, # Show the first value of 'a' initially\n", + " currentvalue={\"prefix\": r\"Number of elements: \"},\n", + " steps=steps\n", + ")]\n", + "\n", + "fig.update_layout(\n", + " sliders=sliders,\n", + " title=\"Applying shape functions\",\n", + " legend_title=\"Legend\",\n", + " legend=dict(\n", + " x=1.05, # Adjust the legend position\n", + " y=0.5\n", + " )\n", + ")\n", + "\n", + "fig.update_xaxes(title_text='x', row=1, col=1, range=[x_data[0], x_data[-1]])\n", + "fig.update_xaxes(title_text='x', row=1, col=2, range=[x_data[0], x_data[-1]])\n", + "\n", + "# Update y-axis properties\n", + "fig.update_yaxes(title_text='Node shape value', row=1, col=1, range=[-0.1, 1.1])\n", + "fig.update_yaxes(title_text='y', row=1, col=2, range=[-2.1, 2.1])\n", + "\n", + "fig.show()" + ] + } + ], + "metadata": { + "celltoolbar": "Tags", + "hide_code_all_hidden": true, + "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.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/book/features/live_code.md b/book/features/live_code.md new file mode 100644 index 0000000..2676698 --- /dev/null +++ b/book/features/live_code.md @@ -0,0 +1,23 @@ +# Python-enabled interactivity + +Our book has been enable to run Python code live in the browser (thanks Max!). This page contains some installation instructions, and the other sections in this chapter provide some benchmarks which illustrate the functionality that has been added. + +## Custom cell tags +With the extended functionality of live code, additional cell tags have been developed to be added to cells in any .ipynb file, next to the [existing tags] of the jupyter book (https://jupyterbook.org/en/stable/content/metadata.html): +- `disable-execution-cell` disables the ability to execute the cell when thebe is activated. This might be useful if you notebook file includes interactivity for only a part of the coding cells +- `disable-execution-page` disable the ability to execute the entire page. This might be useful if you use python code for creating a page, but the code is not part of the actual content of the book. +- `auto-execute-page`, automatically starts the thebe live coding functionality whenever the page is opened. This is particularly useful if you're using widgets on a page. At this moment, this tags doesn't work in combination with `thebe-remove-input-init` +- `thebe-init`, directly starts running this cell as soon as the thebe live coding functionality starts. +- `thebe-remove-input-init`, as `thebe-init` the cell is started directly as soon as the thebe live coding functionality starts. However, the input is not shown to the students (in static and interactive mode). The original `remove-input` tag will not work as it deletes the entire cell, so it can never be executed. +- `keep-output` ...? Max? + +## Instructions: local build + +To test the fuctionality, you have to run a local server, otherwise the interactivity doens't work. So, you cannot just open the index.html file any more. This can be done with the following steps: +1. build the book by running the shell script `build-book.sh` (type `./build-book.sh true` in the terminal). If you are using Windows you should use the Git Bash terminal. Set this as your default in VS Code, then open it using "New Terminal." If you have not already done so, you will need to allow Git Bash to use your Python libraries by running the command `conda init bash`.Run the shell script from `book/` (not `book/book/`) using `./build-book.sh`. You may notice that `build-book.sh` creates a Python server (`python -m http.server....`) if you include `true` in the command.; this is critical for getting the interactive code pages to work, you cannot just open the index.html file any more. +2. Open the book by going to [http://127.0.0.1:8000/](http://127.0.0.1:8000/) in your browser. You may need to reload the page once the rebuild is complete. You can do this on Chrome by right-clicking somewhere on the page, select "Inspect", open the "Network" tab, then reload with `CTRL+R`. Or try `ctrl`+`F5` +3. Go to any page that is created from a Jupyter notebook +4. In the top right, click "Live Code" in the rocket ship menu item ({fa}`rocket`) +5. Once the message "Python interaction ready!" is displayed, you can run all the cells of the notebook! + +If you push to main, you can test the interactivity on the book-draft website as well. \ No newline at end of file diff --git a/book/features/theory_questions.md b/book/features/theory_questions.md new file mode 100644 index 0000000..0af474b --- /dev/null +++ b/book/features/theory_questions.md @@ -0,0 +1,62 @@ +# Combining theory with interactive questions + +This page shows you how to combine theory with questions, so there's no coding involved. + +```{index} Truss structures +``` +## Truss structures + +```{contents} +:local: +``` + +Truss structures are structures assembled of straight elements connected in nodes. These elements partly create triangles, which are of importance for its deformability. Of course, the structures you'll design shouldn't deform, so it's crucial to understand this topic. + +### Examples truss structures +Examples of these structures are commonly found in rail bridges like the one below: + +```{figure} theory_questions_images/pic0.jpg +:width: 400 +``` +Furthermore, these structures are commonly found in building as well, both invisible and visible: + +```{figure} theory_questions_images/pic1.jpg +:width: 400 +``` + +### Modelling truss structures +Truss structures are modelled as rigid bars (so elements which cannot deform) connected by hinges (so elements can rotate with respect to one each other). In our model, hinges are indicate with a circle, and bars with a line. For example, the structure you've seen in the second example (with two diagonal bars removed) is modelled as follows: + +```{figure} theory_questions_images/Truss1.svg +``` + +Now that you've been introduced to truss structures, answer the following question: + +:::{card} Test Yourself + +::: + +### Mechanisms +A mechanism is a structure which can deform as a whole, while the individual elements don't deform. This deformation doesn't have to be large. An example of a mechanism is a rectangular truss structure, it can deform as shown below: +```{figure} theory_questions_images/rectangle.svg +``` +A triangle cannot deform: +```{figure} theory_questions_images/triangle.svg +``` +For structures which consists of partly triangles, partly quadrilaterals, it is not directly clear whether the structure can deform. To identify whether the truss structure is a mechanism, you can try to move each node with respect to the nodes with which it is connected: + +```{figure} theory_questions_images/gifje.gif +:width: 300px +``` +An example of a truss structure of partly triangles, partly quadrilaterals, which is not a mechanism is the following: +```{figure} theory_questions_images/structure3.svg +``` +Now that you've been introduced to mechanisms, answer the following questions: + +:::{card} Test Yourself + +::: + +:::{card} Test Yourself + +::: \ No newline at end of file diff --git a/book/features/widgets.ipynb b/book/features/widgets.ipynb new file mode 100644 index 0000000..369c08b --- /dev/null +++ b/book/features/widgets.ipynb @@ -0,0 +1,192 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Widgets\n", + "\n", + "If you publish widgets in a jupyter book they might (depending on your implementation) require an active python kernel for the output to be interactive. Using the [thebe-integration](live_code.md), this is possible. Note: You can circumvent the use of a kernel by using packages which don't need such a kernel, for example the [non-kernel-based widgets using Plotly](interactive_element)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please note:\n", + "- You might want to hide the output before the thebe has been activated.\n", + "- You can hide the code by adding a custom-made tag: `thebe-remove-input-init`.\n", + "- You can automatically start the thebe functionality when you add `auto-execute-page` to a cell. \n", + "\n", + "## Example\n", + "The code below shows an example of the strain energy in a structure and work done by a force for different trial functions. Because of the actual python code, the plot 'refreshes' upon updating a widgets. This can be avoided by updating the current figure instead of plotting a new figure. Remember, first press {fa}`rocket` --> {guilabel}`Live Code` and then wait until all cells are executed:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init", + "auto-execute-page" + ] + }, + "outputs": [], + "source": [ + "import sympy as sp\n", + "F = 10\n", + "EI = 20e3\n", + "L = 3\n", + "\n", + "# Polynomial trial function\n", + "x, C1, C2, C3, C4, F, u0= sp.symbols('x, C1, C2, C3, C4, F, u0')\n", + "w = C1*x**3+C2*x**2+C3*x + C4\n", + "eq1 = sp.Eq(w.subs(x,0),0)\n", + "eq2 = sp.Eq(w.diff(x).subs(x,0),0)\n", + "eq3 = sp.Eq((-w.diff(x,2)*EI).subs(x,L),0)\n", + "eq4 = sp.Eq(w.subs(x,L),u0)\n", + "sol = sp.solve((eq1, eq2,eq3,eq4),(C1,C2,C3,C4))\n", + "w_sol = sp.nsimplify(w.subs(sol))\n", + "w_numpy_polynomial = sp.lambdify((u0,x),w_sol)\n", + "\n", + "M_sol_polynomial = sp.nsimplify(EI * w_sol.diff(x,2))\n", + "\n", + "Ev_polynomial = sp.integrate(sp.nsimplify(M_sol_polynomial**2/(EI*2)),(x,0,3))\n", + "\n", + "Ev_numpy_polynomial = sp.lambdify(u0,Ev_polynomial)\n", + "\n", + "V_polynomial = Ev_polynomial - 10 * u0\n", + "V_numpy_polynomial = sp.lambdify(u0,V_polynomial)\n", + "\n", + "#cosine trial function\n", + "w_sol = u0*sp.sin(x/12*2*sp.pi-sp.pi/2)+u0\n", + "w_numpy_sin = sp.lambdify((u0,x),w_sol)\n", + "\n", + "M_sol_sin = sp.nsimplify(EI * w_sol.diff(x,2))\n", + "\n", + "Ev_sin = sp.integrate(sp.nsimplify(M_sol_sin**2/(EI*2)),(x,0,3))\n", + "\n", + "Ev_numpy_sin = sp.lambdify(u0,Ev_sin)\n", + "\n", + "V_sin = Ev_sin - 10 * u0\n", + "V_numpy_sin = sp.lambdify(u0,V_sin)\n", + "\n", + "#sin trial function\n", + "w_sol = 2*u0*(sp.cosh(x/sp.pi))-2*u0\n", + "w_numpy_cosh = sp.lambdify((u0,x),w_sol)\n", + "\n", + "M_sol_cosh = sp.nsimplify(EI * w_sol.diff(x,2))\n", + "\n", + "Ev_cosh = sp.integrate(sp.nsimplify(M_sol_cosh**2/(EI*2)),(x,0,3))\n", + "\n", + "Ev_numpy_cosh = sp.lambdify(u0,Ev_cosh)\n", + "\n", + "V_cosh = Ev_cosh - 10 * u0\n", + "V_numpy_cosh = sp.lambdify(u0,V_cosh)\n", + "\n", + "import matplotlib.pylab as plt\n", + "import numpy as np\n", + "from ipywidgets import widgets, interact\n", + "\n", + "def func(u,trial_function):\n", + " fig, axs = plt.subplots(2, 2, figsize=(10, 6))\n", + "\n", + " x = np.linspace(0,3,100)\n", + " if trial_function == 'Polynomial':\n", + " w_numpy = w_numpy_polynomial\n", + " Ev_numpy = Ev_numpy_polynomial\n", + " V_numpy = V_numpy_polynomial\n", + " elif trial_function == 'cos':\n", + " w_numpy = w_numpy_sin\n", + " Ev_numpy = Ev_numpy_sin\n", + " V_numpy = V_numpy_sin\n", + " elif trial_function == 'cosh':\n", + " w_numpy = w_numpy_cosh\n", + " Ev_numpy = Ev_numpy_cosh\n", + " V_numpy = V_numpy_cosh\n", + " axs[0,0].plot(x,w_numpy(u,x))\n", + " axs[0,0].set_xlim([-0.2,3.2])\n", + " axs[0,0].set_ylim([-0.003,0.01])\n", + " axs[0,0].invert_yaxis()\n", + " axs[0,0].annotate(text='', xy=(3,w_numpy(u,3)), xytext=(3,0), arrowprops=dict(arrowstyle='fancy'))\n", + " axs[0,0].text(3.1,w_numpy(u,3),'$u_0$')\n", + " axs[0,0].axis('off')\n", + " axs[0,1].axis('off')\n", + " axs[0,1].set_xlim([-0.2,3.2])\n", + " axs[0,1].set_ylim([-0.003,0.01])\n", + " axs[0,1].annotate(text='', xy=(1.5,w_numpy(u,3)), xytext=(1.5,w_numpy(u,3)-0.003), arrowprops=dict(arrowstyle='simple'))\n", + " axs[0,1].invert_yaxis()\n", + " x_axis= ['$E_v$','$A_F$','$E_v - A_F$']\n", + " y_axis = [Ev_numpy(u),w_numpy(u,3)*10,Ev_numpy(u)-w_numpy(u,3)*10]\n", + " axs[1,0].bar(x_axis,y_axis,color=('blue','green','orange'))\n", + " axs[1,0].set_ylim([-0.03,0.1])\n", + " axs[1,0].set_yticklabels([])\n", + " axs[1,0].set_yticks([])\n", + " \n", + " u_range=np.linspace(-0.0015,0.01,100)\n", + " axs[1,1].plot(u_range,Ev_numpy(u_range),label='$E_v$',color='blue')\n", + " axs[1,1].plot(u_range,w_numpy(u_range,3)*10,label='$F_A$',color='green')\n", + " axs[1,1].plot(u_range,V_numpy(u_range),label='$E_v - A_F$',color='orange')\n", + " axs[1,1].legend()\n", + " axs[1,1].plot(u,Ev_numpy(u),'o',color='blue')\n", + " axs[1,1].plot(u,w_numpy(u,3)*10,'o',color='green')\n", + " axs[1,1].plot(u,Ev_numpy(u)-w_numpy(u,3)*10,'o',color='orange')\n", + " axs[1,1].set_ylim([-0.03,0.1])\n", + " axs[1,1].set_xlim([-0.0015,0.01])\n", + " axs[1,1].set_xlabel('$u_0$')\n", + " axs[1,1].set_xticks([])\n", + " axs[1,1].set_xticklabels([])\n", + " axs[1,1].set_yticks([])\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The graph will appear below in interactive mode:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "interact(func, u = widgets.FloatSlider(min=-0.001, max=0.01, value=0, step=0.0002, description=\"Displacement u_0\",readout_format='.4f',style= {'description_width': '180px'},layout = {'width': '400px'}),\n", + " trial_function = widgets.ToggleButtons(options=['Polynomial', 'cos', 'cosh']));" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "celltoolbar": "Tags", + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}