diff --git a/book/_toc.yml b/book/_toc.yml index ea13d526ff7792a23b4cd2d57c75960318c02ff7..cb5feb7d601e51411e0afe526e72f6343363a6f3 100644 --- a/book/_toc.yml +++ b/book/_toc.yml @@ -85,6 +85,8 @@ parts: - file: cookbook/benchmarks/benchmark_tags_dont_execute.ipynb - file: cookbook/benchmarks/benchmark_load_dont_execute.ipynb - file: cookbook/widgets_dont_execute.ipynb + - file: cookbook/exercise_checking_dont_execute.ipynb + - file: cookbook/example_gumbel_dont_execute.ipynb - caption: Old Material chapters: - file: old/blank diff --git a/book/cookbook/example_gumbel.py b/book/cookbook/example_gumbel.py new file mode 100644 index 0000000000000000000000000000000000000000..3ba40c0debcd146b8712025e74e9992bb07f9b8b --- /dev/null +++ b/book/cookbook/example_gumbel.py @@ -0,0 +1,49 @@ +from grading import check +from math import isclose + +tolerance = 0.001 + +# Using rel_tol is more reliable in the face of aliasing, but to fit the instructions give we use abs_total +check_float = lambda a, b: isclose(a, b, rel_tol=0, abs_tol=tolerance) + +# We have to pass in the globals object since we need to have access to changes as well +@check +def check_example(glob): + mu, beta = glob["mu"], glob["beta"] + if (check_float(mu, -3.21953) and check_float(beta, -8.65617)): + print(f"You got the parameters right (Part 1), well done! (checked with tolerance {tolerance})") + else: + print(f"Your parameters (Part 1) are incorrect, your inverse (Part 2) won't be graded until these are right. (checked with tolerance {tolerance})") + return + + function = glob["find_x_with_probability_p"] + + test_inputs = [(0.001, 13.50977500432694), (0.2, 0.8998147005530068), (0.9, -22.699089535951632)] + failed = [] + + for x, out in test_inputs: + result = function(x) + if not check_float(result, out): + failed.append((x, out, result)) + + if len(failed) == 0: + print(f"Well done, your inverse function is correct! (checked with tolerance {tolerance})") + else: + print(f"Your function (Part 2) failed some tests. Keep in mind the tolerance is {tolerance}") + print("Failed inputs:") + for case in failed: + print(f"{case[0]} gave {case[2]}, expected {case[1]}") +""" +Task 0: +x_1 = 4 +p_e_1 = 1-.9 +x_2 = 10 +p_e_2 = 1-.99 + +Task 1: +beta = -(x_2 - x_1) / log(log(p_e_2) / log(p_e_1)) +mu = x_1 + beta * log(-log(p_e_1)) + +Task 2: +x = mu - beta * log(-log(p)) +""" \ No newline at end of file diff --git a/book/cookbook/example_gumbel_dont_execute.ipynb b/book/cookbook/example_gumbel_dont_execute.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..a79ac7a70459dfdf5a37258f42280ffa0b843ff0 --- /dev/null +++ b/book/cookbook/example_gumbel_dont_execute.ipynb @@ -0,0 +1,225 @@ +{ + "cells": [ + { + "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, Task 2) 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": [ + "%pip install ipywidgets\n", + "\n", + "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": [ + "thebe-init" + ] + }, + "outputs": [], + "source": [ + "x_1 = 4\n", + "p_e_1 = _\n", + "x_2 = 10\n", + "p_e_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_e_1, x_2, p_e_2): \n", + " \"\"\"Compute Gumbel distribution parameters from two points of the CDF.\n", + " Arguments:\n", + " x_1 (float): point one\n", + " p_e_1 (float): probability of exceedance for point one\n", + " x_2 (float): point two\n", + " p_e_2 (float): probability of exceedance for point two\n", + " \"\"\"\n", + "\n", + " # YOUR CODE GOES HERE #\n", + " beta = _\n", + " mu = _\n", + " #######################\n", + "\n", + " return mu, beta" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below will print your parameter values and show a plot to help confirm you have the right implementation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mu, beta = gumbel_2_points(x_1, p_e_1, x_2, p_e_2)\n", + "\n", + "print(f\"Your mu: {mu:0.5f}\\nYour beta: {beta:0.5f}\")\n", + "\n", + "gumbel_distribution = lambda x: e**(-e**(-(x - mu)/beta))\n", + "\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, 15, 0.1)\n", + "plt.plot(x_axis, np.vectorize(gumbel_distribution)(x_axis), linewidth=2)\n", + "plt.plot(x_1, p_e_1, 'ro')\n", + "plt.annotate(\"Point 1\", (x_1 + 0.4, p_e_1 + 0.02))\n", + "plt.plot(x_2, p_e_2, 'ro')\n", + "plt.annotate(\"Point 2\", (x_2 + 0.4, p_e_2 + 0.001))\n", + "plt.yscale(\"log\") \n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Task 2:** write a function to solve for the desired 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": "code", + "execution_count": 30, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1bc0cf1f7d414d5c97de1e9c6b0b1130", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(description='Check answer', style=ButtonStyle())" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "389d505ac6754ca1bb57c112478735c6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "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.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/book/cookbook/exercise_checking_dont_execute.ipynb b/book/cookbook/exercise_checking_dont_execute.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..45448e3fb6a0789dd00c85b73bd63e866c443c04 --- /dev/null +++ b/book/cookbook/exercise_checking_dont_execute.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exercise checking with interactive elements\n", + "\n", + "Exercise checking can be intuitively incorporated into Python using tags. These examples show different approaches to achieving this goal. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "thebe-remove-input-init" + ] + }, + "outputs": [], + "source": [ + "%pip 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" + ] + }, + "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/cookbook/grading.py b/book/cookbook/grading.py new file mode 100644 index 0000000000000000000000000000000000000000..247b55e57b43874baf00c721ddae9ba8bc5cdfe9 --- /dev/null +++ b/book/cookbook/grading.py @@ -0,0 +1,16 @@ +import ipywidgets as widgets +from IPython.display import display + +def check(f): + def wrapper(*args, **kwargs): + output = widgets.Output() + button = widgets.Button(description="Check answer") + @output.capture(clear_output=True,wait=True) + def _inner_check(button): + try: + f(*args, **kwargs) + except: + print("Something went wrong, have you filled all the functions and run the cells?") + button.on_click(_inner_check) + display(button, output) + return wrapper