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