diff --git a/.github/workflows/docs-test.yml b/.github/workflows/docs-test.yml new file mode 100644 index 00000000..b3b2de60 --- /dev/null +++ b/.github/workflows/docs-test.yml @@ -0,0 +1,33 @@ +name: Doc test + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + doc-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.14" + cache: "pip" + + - name: Install Python dependencies + run: | + sudo apt-get install python3-pip graphviz + pip install -r requirements.txt + pip install ghp-import + PATH="${PATH}:${HOME}/.local/bin" + + - name: Build book HTML + run: | + ./build_and_process.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cc35d354..720016c8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,11 +11,13 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.14" cache: "pip" + - name: Install Python dependencies run: | sudo apt-get install python3-pip graphviz @@ -25,7 +27,7 @@ jobs: - name: Build book HTML run: | - KERAS_BACKEND="torch" jupyter-book build ./content + ./build_and_process.sh - name: Push _build/html to gh-pages run: | diff --git a/build_and_process.sh b/build_and_process.sh new file mode 100755 index 00000000..96915168 --- /dev/null +++ b/build_and_process.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +KERAS_BACKEND="torch" jupyter-book build content + +cd content/_build/html +for i in $(grep -lR "# alt-text" | grep html) +do + echo $i + ../../../parse_alt.py $i +done diff --git a/content/02-numpy/numpy-basics.ipynb b/content/02-numpy/numpy-basics.ipynb index 0658305f..8432b1b1 100644 --- a/content/02-numpy/numpy-basics.ipynb +++ b/content/02-numpy/numpy-basics.ipynb @@ -576,7 +576,7 @@ "\n", "Multidimensional arrays are stored in a contiguous space in memory -- this means that the columns / rows need to be unraveled (flattened) so that it can be thought of as a single one-dimensional array. Different programming languages do this via different conventions:\n", "\n", - "![](row_column_major.png)\n", + "![multidimensional array formatting, row-major vs. column-major](row_column_major.png)\n", "\n", "Storage order:\n", "\n", @@ -780,7 +780,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.2" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/content/04-matplotlib/matplotlib-basics.ipynb b/content/04-matplotlib/matplotlib-basics.ipynb index 20fae828..4ec8b46a 100644 --- a/content/04-matplotlib/matplotlib-basics.ipynb +++ b/content/04-matplotlib/matplotlib-basics.ipynb @@ -102,7 +102,7 @@ "### Anatomy of a figure\n", "\n", "Figures are the highest level object and can include multiple axes\n", - "![](anatomy1.png)\n", + "![anatomy of a matplotlib figure showing the different components](anatomy1.png)\n", "\n", "(figure from: http://matplotlib.org/faq/usage_faq.html#parts-of-a-figure )\n" ] @@ -192,7 +192,8 @@ "ax.plot(x, y)\n", "ax.set_xlabel(r\"$x$\")\n", "ax.set_ylabel(r\"$\\cos(x)$\")\n", - "ax.set_xlim(0, 2*np.pi)" + "ax.set_xlim(0, 2*np.pi)\n", + "# alt-text: a plot of a cosine function" ] }, { @@ -272,7 +273,8 @@ "ax.plot(x, np.sin(x), marker=\"o\", label=\"sine\")\n", "ax.plot(x, np.cos(x), marker=\"x\", label=\"cosine\")\n", "ax.set_xlim(0.0, 2.0*np.pi)\n", - "ax.legend()" + "ax.legend()\n", + "# alt-text: a plot of sine and cosine functions" ] }, { @@ -337,7 +339,8 @@ "source": [ "fig, ax = plt.subplots()\n", "ax.plot(x, np.sin(x), linestyle=\"--\", linewidth=3.0)\n", - "ax.plot(x, np.cos(x), linestyle=\"-\")" + "ax.plot(x, np.cos(x), linestyle=\"-\")\n", + "# alt-text: sine and cosine functions now with the sine using a dotted line" ] }, { @@ -443,7 +446,8 @@ "ax = fig.add_subplot(111)\n", "ax.plot(x, np.sin(x), linestyle=\"--\", linewidth=3.0)\n", "ax.plot(x, np.cos(x), linestyle=\"-\")\n", - "ax.set_xlim(0.0, 2.0*np.pi)" + "ax.set_xlim(0.0, 2.0*np.pi)\n", + "# alt-text: a different theme -- now the figure background is gray and the font and colors are different" ] }, { @@ -522,7 +526,8 @@ "ax2.set_yscale(\"log\")\n", "\n", "# tight_layout() makes sure things don't overlap\n", - "fig.tight_layout()" + "fig.tight_layout()\n", + "# alt-text: two axes stacked vertically, with a cubic function drawn on the top and a Gaussian (log-scale) on the bottom" ] }, { @@ -608,7 +613,8 @@ "fig, ax = plt.subplots()\n", "\n", "im = ax.imshow(g(xv, yv), origin=\"lower\")\n", - "fig.colorbar(im, ax=ax)" + "fig.colorbar(im, ax=ax)\n", + "# alt-text: a heat-map of a 2D Gaussian" ] }, { @@ -663,7 +669,8 @@ "fig, ax = plt.subplots()\n", "\n", "contours = ax.contour(g(xv, yv))\n", - "ax.axis(\"equal\") # this adjusts the size of image to make x and y lengths equal" + "ax.axis(\"equal\") # this adjusts the size of image to make x and y lengths equal\n", + "# alt-text: contour plot of a 2D Gaussian" ] }, { @@ -764,7 +771,8 @@ ], "source": [ "fig, ax = plt.subplots()\n", - "ax.errorbar(x, y, yerr=sigma, fmt=\"o\")" + "ax.errorbar(x, y, yerr=sigma, fmt=\"o\")\n", + "# alt-text: a plot showing data points with vertical error bars" ] }, { @@ -834,7 +842,8 @@ "source": [ "fig, ax = plt.subplots()\n", "ax.plot(xx, np.sin(xx))\n", - "ax.text(np.pi/2, np.sin(np.pi/2), r\"maximum\")" + "ax.text(np.pi/2, np.sin(np.pi/2), r\"maximum\")\n", + "# alt-text: a sine wave with the peak labeled \"maximum\"" ] }, { @@ -866,7 +875,8 @@ "ax.spines['top'].set_visible(False)\n", "ax.xaxis.set_ticks_position('bottom') \n", "ax.yaxis.set_ticks_position('left') \n", - "fig" + "fig\n", + "# alt-text: a sine wave with the peak labeled \"maximum\" and only the left and lower axes drawn" ] }, { @@ -935,7 +945,8 @@ " arrowprops=dict(facecolor='black', shrink=0.05),\n", " horizontalalignment='left',\n", " verticalalignment='bottom',\n", - " )\n" + " )\n", + "# alt-text: a polar plot of a spiral with a point labeled " ] }, { @@ -1007,7 +1018,8 @@ "x = r*np.sin(theta)\n", "y = r*np.cos(theta)\n", "\n", - "ax.plot(x,y,z)" + "ax.plot(x,y,z)\n", + "# alt-text: a 3D plot of a curve that spirals around the vertical axis" ] }, { @@ -1055,7 +1067,8 @@ "\n", "# and the view (note: most interactive backends will allow you to rotate this freely)\n", "ax.azim = 90\n", - "ax.elev = 40" + "ax.elev = 40\n", + "# alt-text: a surface plot of a function colored by z value" ] }, { @@ -1110,76 +1123,9 @@ "x = np.linspace(-5,5,200)\n", "sigma = 1.0\n", "ax.plot(x, np.exp(-x**2/(2*sigma**2)) / (sigma*np.sqrt(2.0*np.pi)),\n", - " c=\"r\", lw=2)\n", - "ax.set_xlabel(\"x\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Plotting data from a file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`numpy.loadtxt()` provides an easy way to read columns of data from an ASCII file" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(128, 8)\n" - ] - } - ], - "source": [ - "data = np.loadtxt(\"test1.exact.128.out\")\n", - "print(data.shape)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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w4bVe82wYcPqnA/w3XffnfR8IYQQQuiL1900U6ZM4aabbmLQoEEMGTKEV199lbKyMiZOdO4jMmHCBNq1a8eMGTMAuPfeeznrrLN4+eWXGTt2LAsXLmTNmjW89dZbgX81QgghhAg5Xicj48eP59ChQ0ybNo2CggL69+/P0qVLPYNUc3NzMRhqCi7Dhw9nwYIFPProozz88MOcdtppfP755/Tp0yewr8QLFouF6dOnn9AVJAJP3uumI+9105H3uunIe910tHyvvV5nRAghhBAikGRvGiGEEEJoSpIRIYQQQmhKkhEhhBBCaEqSESGEEEJoKmyTkZkzZ5KamkpkZCRDhw4lIyPjlOd/9NFH9OjRg8jISPr27cuSJUuaLNZQ5817PXv2bEaOHEnz5s1p3rw5o0eP/tP/NqKGt+3abeHChSiKwqWXXtroMYYLb9/r48ePc/fdd9O2bVssFgvdunWT3yMN5O17/eqrr9K9e3eioqJISUnh/vvvp7KyssniDVU///wz48aNIzk5GUVRTrlHnNvy5csZOHAgFouFrl27Mm/evMYJTg1DCxcuVM1mszpnzhx106ZN6qRJk9RmzZqphYWF9Z7/66+/qkajUX3hhRfUzZs3q48++qgaERGhZmdnN3nsocbb9/q6665TZ86cqa5fv17NyclRb775ZjUhIUHdt29fk8cearx9r912796ttmvXTh05cqT6l7/8pcniDWXevtdVVVXqoEGD1IsuukhdsWKFunv3bnX58uVqZmZmk8cearx9r+fPn69aLBZ1/vz56u7du9VvvvlGbdu2rXr//fc3eeyhZsmSJeojjzyifvrppyqgfvbZZ6c8f9euXWp0dLQ6ZcoUdfPmzerrr7+uGo1GdenSpQGPLSyTkSFDhqh333235992u11NTk5WZ8yYUe/5V199tTp27Ng6jw0dOlS94447Gj3WUOfte/1HNptNjYuLU999991GjDI8+PJe22w2dfjw4erbb7+t3nTTTZKMNJC37/Ubb7yhdu7cWbVarU0YZXjw9r2+++671XPOOafOY1OmTFFHjBjR6LGGk4YkI//4xz/U3r1713ls/Pjx6pgxYwIeT9h101itVtauXcvo0aM9jxkMBkaPHs3KlSvrvWblypV1zgcYM2bMSc8XTr68139UXl5OdXU1LVq0aMRIQ5+v7/WTTz5J69atufXWW5so0tDny3v9xRdfMGzYMO6++26SkpLo06cPzz77LHa7vQkjDz2+vNfDhw9n7dq1nq6cXbt2sWTJEi666KImi1svmvKzMSh37fXH4cOHsdvtnhVh3ZKSktiyZUu91xQUFNR7fkFBQaPGGup8ea//6MEHHyQ5OfmEBi/q8uW9XrFiBe+88w6ZmZlNFGV48OW93rVrFz/88APXX389S5YsYceOHdx1111UV1czffr0Joo89PjyXl933XUcPnyYM844A1VVsdls/PWvf+Xhhx9uoqj142SfjcXFxVRUVBAVFRWw5wq7yogIHc899xwLFy7ks88+IzIyUutwwkpJSQk33ngjs2fPJjExUetwwp7D4aB169a89dZbpKenM378eB555BFmzZqldWhhZ/ny5Tz77LP85z//Yd26dXz66acsXryYp556SuvQhB/CrjKSmJiI0WiksLCwzuOFhYW0adOm3mvatGnj1fnCyZf32u2ll17iueee4/vvv6dfv36NHGno8/a93rlzJ3v27GHcuHGexxwOBwAmk4mtW7fSpUuXJog89PjSrtu2bUtERARGo9HzWM+ePSkoKMBqtWI2mxs97lDky3v92GOPceONN3LbbbcB0LdvX8rKyrj99tt55JFH6uyNJvxzss/G+Pj4gFZFCMfKiNlsJj09nWXLlnkeczgcLFu2jGHDhtV7zbBhw+qcD/Ddd9+d9Hzh5Mt7DfDCCy/w1FNPsXTpUgYNGtRE0YY2b9/rHj16kJ2dTWZmpufrkksuYdSoUWRmZpKSktLEryB0+NKuR4wYwY4dOzwJH8C2bdto27atJCKn4Mt7XV5efkLC4U4CZau1wGrSz8aAD4kNAgsXLlQtFos6b948dfPmzertt9+uNmvWTC0oKFBVVVVvvPFG9aGHHvKc/+uvv6omk0l96aWX1JycHHX69OkytbeBvH2vn3vuOdVsNqsff/yxeuDAAc9XSUmJhq8iNHj7Xv+RzKZpOG/f69zcXDUuLk6dPHmyunXrVvWrr75SW7durT799NMavorQ4O17PX36dDUuLk794IMP1F27dqnffvut2qVLF/Xqq6/W8FWEhpKSEnX9+vXq+vXrVUB95ZVX1PXr16t79+5VVVVVH3roIfXGG2/0nO+e2vt///d/ak5Ojjpz5kyZ2uut119/Xe3QoYNqNpvVIUOGqL///rvnZ2eddZZ600031Tl/0aJFardu3VSz2az27t1bXbx4sQZRhyZv3uuOHTuqwAlf06dP1yj60OJtu65NkhHvePte//bbb+rQoUNVi8Widu7cWX3mmWdUm82mQeShx5v3urq6Wn388cfVLl26qJGRkWpKSop61113qceOHdMo+tDx448/1vv71/3+3nTTTepZZ511wjX9+/dXzWaz2rlzZ3Xu3LmNEpuiSl1LCCGEEBoKuzEjQgghhAgtkowIIYQQQlOSjAghhBBCU5KMCCGEEEJTkowIIYQQQlOSjAghhBBCU5KMCCGEEEJTkowIIYQQOvXzzz8zbtw4kpOTURSFzz//3Ot7qKrKSy+9RLdu3bBYLLRr145nnnnGq3uE3UZ5QgghhGiYsrIy0tLSuOWWW7j88st9use9997Lt99+y0svvUTfvn05evQoR48e9eoesgKrEEIIIVAUhc8++4xLL73U81hVVRWPPPIIH3zwAcePH6dPnz48//zznH322QDk5OTQr18/Nm7cSPfu3X1+bummEUIIIUS9Jk+ezMqVK1m4cCFZWVlcddVVXHDBBWzfvh2AL7/8ks6dO/PVV1/RqVMnUlNTue2227yujEgyIoQQQogT5ObmMnfuXD766CNGjhxJly5deOCBBzjjjDOYO3cuALt27WLv3r189NFHvPfee8ybN4+1a9dy5ZVXevVcMmZECCGEECfIzs7GbrfTrVu3Oo9XVVXRsmVLABwOB1VVVbz33nue89555x3S09PZunVrg7tuJBkRQgghxAlKS0sxGo2sXbsWo9FY52exsbEAtG3bFpPJVCdh6dmzJ7gqK5KMCCGEEMJnAwYMwG63c/DgQUaOHFnvOSNGjMBms7Fz5066dOkCwLZt2wDo2LFjg59LZtMIIYQQOlVaWsqOHTvAlXy88sorjBo1ihYtWtChQwduuOEGfv31V15++WUGDBjAoUOHWLZsGf369WPs2LE4HA4GDx5MbGwsr776Kg6Hg7vvvpv4+Hi+/fbbBschyYgQQgihU8uXL2fUqFEnPH7TTTcxb948qqurefrpp3nvvffIz88nMTGR008/nSeeeIK+ffsCsH//fu655x6+/fZbYmJiuPDCC3n55Zdp0aJFg+OQZEQIIYQQmpKpvUIIIYTQlCQjQgghhNCUJCNCCCGE0JQkI0IIIYTQlCQjQgghhNCUJCNCCCGE0JQkI0IIIYTQlCQjQgghhNCUJCNCCCGE0JQkI0IIIYTQlCQjQgghhNCUJCNCCCGE0NT/A+poD4C0MFXJAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = plt.subplots()\n", - "ax.plot(data[:,1], data[:,2]/np.max(data[:,2]), label=r\"$\\rho$\")\n", - "ax.plot(data[:,1], data[:,3]/np.max(data[:,3]), label=r\"$u$\")\n", - "ax.plot(data[:,1], data[:,4]/np.max(data[:,4]), label=r\"$p$\")\n", - "ax.plot(data[:,1], data[:,5]/np.max(data[:,5]), label=r\"$T$\")\n", - "ax.set_ylim(0,1.1)\n", - "ax.legend(frameon=False, fontsize=12)" + " c=\"C1\", lw=2)\n", + "ax.set_xlabel(\"x\")\n", + "# alt-text: a histogram of Gaussian random numbers, with a Gaussian function plotted matching it well" ] }, { @@ -1234,7 +1180,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.2" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/content/04-matplotlib/matplotlib-exercises.ipynb b/content/04-matplotlib/matplotlib-exercises.ipynb index 126e4c0a..d073b570 100644 --- a/content/04-matplotlib/matplotlib-exercises.ipynb +++ b/content/04-matplotlib/matplotlib-exercises.ipynb @@ -286,7 +286,8 @@ ], "source": [ "fig, ax = plt.subplots()\n", - "ax.imshow(m)" + "ax.imshow(m)\n", + "# alt-text: a plot of the Mandelbrot set" ] }, { @@ -313,7 +314,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/content/05-scipy/scipy-basics-2.ipynb b/content/05-scipy/scipy-basics-2.ipynb index c40d60e5..7438a8aa 100644 --- a/content/05-scipy/scipy-basics-2.ipynb +++ b/content/05-scipy/scipy-basics-2.ipynb @@ -125,7 +125,8 @@ "source": [ "fig, ax = plt.subplots()\n", "ax.scatter(x,y)\n", - "ax.errorbar(x, y, yerr=sigma, fmt=\"o\")" + "ax.errorbar(x, y, yerr=sigma, fmt=\"o\")\n", + "# alt-text: a plot showing data points with vertical error bars" ] }, { @@ -214,7 +215,8 @@ ], "source": [ "ax.plot(x, afit[0] + afit[1]*x + afit[2]*x*x )\n", - "fig" + "fig\n", + "# alt-text: a plot showing data points with error bars and a quadratic fit to them" ] }, { @@ -320,7 +322,8 @@ "source": [ "fig, ax = plt.subplots()\n", "ax.scatter(x,y)\n", - "ax.errorbar(x, y, yerr=sigma, fmt=\"o\", label=\"_nolegend_\")" + "ax.errorbar(x, y, yerr=sigma, fmt=\"o\", label=\"_nolegend_\")\n", + "# alt-text: a plot showing a noisy exponential dataset with error bars" ] }, { @@ -402,7 +405,8 @@ " label=r\"$a_0 = $ %f; $a_1 = $ %f\" % (afit[0], afit[1]))\n", "ax.plot(x, a0_orig*np.exp(a1_orig*x), \":\", label=\"original function\")\n", "ax.legend(numpoints=1, frameon=False)\n", - "fig" + "fig\n", + "# alt-text: a plot showing noisy exponential data points, a fit, and the original function" ] }, { @@ -589,7 +593,8 @@ "source": [ "npts = 128\n", "xx, f = single_freq_sine(npts)\n", - "plot_FFT(xx, f)" + "plot_FFT(xx, f)\n", + "# alt-text: a plot with 4 vertical panels showing (1) a sine (2) the Fourier transform of the sine (3) the power in Fourier space (4) the data transformed back to real space" ] }, { @@ -645,7 +650,8 @@ ], "source": [ "xx, f = single_freq_cosine(npts)\n", - "plot_FFT(xx, f)" + "plot_FFT(xx, f)\n", + "# alt-text: a plot with 4 vertical panes showing a single-mode cosine transformed to Fourier space and back" ] }, { @@ -701,7 +707,8 @@ ], "source": [ "xx, f = single_freq_sine_plus_shift(npts)\n", - "plot_FFT(xx, f)" + "plot_FFT(xx, f)\n", + "# alt-text: a plot with 4 vertical panes showing a single-mode sine with a phase shift transformed to Fourier space and back" ] }, { @@ -782,7 +789,8 @@ "xx, f = two_freq_sine(npts)\n", "\n", "fig, ax = plt.subplots()\n", - "ax.plot(xx, f)" + "ax.plot(xx, f)\n", + "# alt-text: a plot showing a two-mode sine wave" ] }, { @@ -847,7 +855,8 @@ "fig, ax = plt.subplots()\n", "ax.plot(kfreq, fk.real, label=\"real\")\n", "ax.plot(kfreq, fk.imag, \":\", label=\"imaginary\")\n", - "ax.legend(frameon=False)" + "ax.legend(frameon=False)\n", + "# alt-text: the FFT of our two-mode sine-wave showing two spikes" ] }, { @@ -915,7 +924,8 @@ ], "source": [ "fig, ax = plt.subplots()\n", - "ax.plot(xx, fkinv.real)" + "ax.plot(xx, fkinv.real)\n", + "# alt-text: a plot of a single-mode sine wave" ] }, { @@ -1278,7 +1288,8 @@ ], "source": [ "fig, ax = plt.subplots()\n", - "ax.plot(x[1:N-1], sol)" + "ax.plot(x[1:N-1], sol)\n", + "# alt-text: a plot showing a function that looks approximately like -sin(x)" ] }, { @@ -1305,7 +1316,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.2" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/content/05-scipy/scipy-basics.ipynb b/content/05-scipy/scipy-basics.ipynb index 0bd91e9f..7ae24ee6 100644 --- a/content/05-scipy/scipy-basics.ipynb +++ b/content/05-scipy/scipy-basics.ipynb @@ -538,7 +538,8 @@ "x_fine = np.linspace(0, 20, 10*N)\n", "\n", "ax.scatter(x, y)\n", - "ax.plot(x_fine, f_exact(x_fine), ls=\":\", label=\"original function\")" + "ax.plot(x_fine, f_exact(x_fine), ls=\":\", label=\"original function\")\n", + "# alt-text: a figure showing data points and an interpolant passing through them" ] }, { @@ -578,7 +579,8 @@ "ax.plot(x_fine, f_interp(x_fine), label=\"interpolant\")\n", "\n", "ax.legend(frameon=False, loc=\"best\")\n", - "fig" + "fig\n", + "# alt-text: a figure showing data points, an interpolant to them, and the original function we sampled" ] }, { @@ -666,7 +668,8 @@ "fig, ax = plt.subplots()\n", "data = func(x, y)\n", "im = ax.imshow(data.T, extent=(0, 1, 0, 1), origin=\"lower\")\n", - "fig.colorbar(im, ax=ax)" + "fig.colorbar(im, ax=ax)\n", + "# alt-text: a heat-map figure showing a function with small-amplitude ripples" ] }, { @@ -714,7 +717,8 @@ "source": [ "fig, ax = plt.subplots()\n", "im = ax.imshow(coarse.T, extent=(0, 1, 0, 1), origin=\"lower\")\n", - "fig.colorbar(im, ax=ax)" + "fig.colorbar(im, ax=ax)\n", + "# alt-text: a heat-map showing coarsened representation of our function" ] }, { @@ -820,7 +824,8 @@ "source": [ "fig, ax = plt.subplots()\n", "im = ax.imshow(new_data.T, extent=(0, 1, 0, 1), origin=\"lower\")\n", - "fig.colorbar(im, ax=ax)" + "fig.colorbar(im, ax=ax)\n", + "# alt-text: a heat-map showing the reconstructed function via interpolation" ] }, { @@ -860,7 +865,8 @@ "diff = new_data - data\n", "fig, ax = plt.subplots()\n", "im = ax.imshow(diff.T, origin=\"lower\", extent=(0, 1, 0, 1))\n", - "fig.colorbar(im, ax=ax)" + "fig.colorbar(im, ax=ax)\n", + "# alt-text: a heat-map showing the error in our interpolation. It is better than 10%" ] }, { @@ -964,7 +970,8 @@ "fig, ax = plt.subplots()\n", "ax.plot(x, f(x))\n", "ax.scatter(np.array([root]), np.array([f(root)]))\n", - "ax.grid()" + "ax.grid()\n", + "# alt-text: a plot of our function with the root represented as a point" ] }, { @@ -1104,7 +1111,8 @@ "fig = plt.figure()\n", "ax = plt.axes(projection='3d')\n", "ax.plot(X[0,:], X[1,:], X[2,:])\n", - "fig.set_size_inches(8.0,6.0)" + "fig.set_size_inches(8.0,6.0)\n", + "# alt-text: a 3D line plot of the solution -- it is dominated by two lobe-like structures" ] }, { @@ -1192,13 +1200,14 @@ "\n", "ax.plot(X[0,:], X[1,:], X[2,:])\n", "\n", - "ax.scatter(sol1.x[0], sol1.x[1], sol1.x[2], marker=\"x\", color=\"r\")\n", - "ax.scatter(sol2.x[0], sol2.x[1], sol2.x[2], marker=\"x\", color=\"r\")\n", - "ax.scatter(sol3.x[0], sol3.x[1], sol3.x[2], marker=\"x\", color=\"r\")\n", + "ax.scatter(sol1.x[0], sol1.x[1], sol1.x[2], marker=\"x\", color=\"C1\")\n", + "ax.scatter(sol2.x[0], sol2.x[1], sol2.x[2], marker=\"x\", color=\"C1\")\n", + "ax.scatter(sol3.x[0], sol3.x[1], sol3.x[2], marker=\"x\", color=\"C1\")\n", "\n", "ax.set_xlabel(\"x\")\n", "ax.set_ylabel(\"y\")\n", - "ax.set_zlabel(\"z\")" + "ax.set_zlabel(\"z\")\n", + "# alt-text: the 3D solution again represented as a line / trajectory, now with the stable-points marked" ] }, { @@ -1342,7 +1351,8 @@ "ax.loglog(ts, Ys[2,:], label=r\"$y_3$\")\n", "\n", "ax.legend(loc=\"best\", frameon=False)\n", - "ax.set_xlabel(\"time\")" + "ax.set_xlabel(\"time\")\n", + "# alt-text: the time-evolution of the species on a log scale" ] }, { @@ -1373,7 +1383,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.3" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/content/05-scipy/scipy-exercises-2.ipynb b/content/05-scipy/scipy-exercises-2.ipynb index 75750590..ca77ced1 100644 --- a/content/05-scipy/scipy-exercises-2.ipynb +++ b/content/05-scipy/scipy-exercises-2.ipynb @@ -1,20 +1,20 @@ { "cells": [ { - "cell_type": "code", - "execution_count": 1, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt" + "# More SciPy Exercises" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 1, "metadata": {}, + "outputs": [], "source": [ - "# More SciPy Exercises" + "import numpy as np\n", + "import matplotlib.pyplot as plt" ] }, { @@ -176,7 +176,8 @@ ], "source": [ "plt.plot(x, noisy)\n", - "plt.plot(x, orig)" + "plt.plot(x, orig)\n", + "# alt-text: a plot showing noisy sinusoidal and the original, un-noised data" ] }, { @@ -299,7 +300,8 @@ "source": [ "plt.plot(t, restrict_theta(y[0,:]))\n", "plt.xlabel(\"t\")\n", - "plt.ylabel(r\"$\\theta$\")" + "plt.ylabel(r\"$\\theta$\")\n", + "# alt-text: a plot showing many periods of a sinusoidal function" ] }, { @@ -367,7 +369,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.2" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/content/11-machine-learning/gradient-descent.ipynb b/content/11-machine-learning/gradient-descent.ipynb index 489d03cb..32ecf178 100644 --- a/content/11-machine-learning/gradient-descent.ipynb +++ b/content/11-machine-learning/gradient-descent.ipynb @@ -186,7 +186,8 @@ "im = ax.imshow(np.log10(np.transpose(rosenbrock(x2d, y2d, a, b))),\n", " origin=\"lower\", extent=[xmin, xmax, ymin, ymax])\n", "\n", - "fig.colorbar(im, ax=ax)" + "fig.colorbar(im, ax=ax)\n", + "# alt-text: a heat-map plot of the banana function" ] }, { @@ -305,7 +306,8 @@ } ], "source": [ - "fig" + "fig\n", + "# alt-text: a heat-map of the banana function with the gradient descent trajectory plotted" ] }, { @@ -347,7 +349,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.2" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/content/11-machine-learning/keras-mnist.ipynb b/content/11-machine-learning/keras-mnist.ipynb index 049d965c..66cc7555 100644 --- a/content/11-machine-learning/keras-mnist.ipynb +++ b/content/11-machine-learning/keras-mnist.ipynb @@ -197,7 +197,8 @@ ], "source": [ "plt.imshow(X_train[0], cmap=\"gray_r\")\n", - "print(y_train[0])" + "print(y_train[0])\n", + "# alt-text: the number 5 represented as a small grayscale image" ] }, { @@ -1121,7 +1122,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.3" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/content/11-machine-learning/neural-net-basics.md b/content/11-machine-learning/neural-net-basics.md index a8c8c490..6074e672 100644 --- a/content/11-machine-learning/neural-net-basics.md +++ b/content/11-machine-learning/neural-net-basics.md @@ -97,9 +97,9 @@ We want to choose a function $g(\xi)$ that is differentiable. A common choice i $$g(\xi) = \frac{1}{1 + e^{-\xi}}$$ ```{figure} sigmoid.png ---- -align: center ---- +:align: center +:alt: a plot of the sigmoid function + The sigmoid function ``` diff --git a/parse_alt.py b/parse_alt.py new file mode 100755 index 00000000..60a849cc --- /dev/null +++ b/parse_alt.py @@ -0,0 +1,56 @@ +#!/bin/env python + +import re +import shutil +import sys + + +def doit(filename): + + if not filename.endswith(".html"): + # nothing to do here + sys.exit(f"skipping {filename}\n") + + alt_define = re.compile(r'.*#\s*alt-text:\s*([^<]+)') + img_define = re.compile(r'(]*\balt=")[^"]*(")') + empty_span = re.compile(r'<\s*span\b[^>]*>\s*') + + # back up the original file + backup = f"{filename}.backup" + try: + shutil.copy(filename, backup) + except OSError: + sys.exit(f"unable to work on {filename}\n") + + # now overwrite the original with the new version with the alt + # text substituted + with open(backup, encoding='utf-8') as fin, open(filename, "w", encoding='utf-8') as fout: + current_alt = None + for line in fin: + # check if we define an alt? + if g := alt_define.match(line): + current_alt = g.group(1) + print(f"{current_alt=}") + + # now remove the comment + line = line.replace(f"# alt-text: {current_alt}", "") + + # it the line is now just an empty then skip + if empty_span.match(line.strip()): + continue + + # check if we are defining an image + if current_alt: + line, n = img_define.subn(rf'\g<1>{current_alt}\g<2>', line) + if n > 0: + current_alt = None + + fout.write(line) + + if current_alt: + # something back happened + sys.exit("Error: alt text without associated img tag\n") + +if __name__ == "__main__": + filename = sys.argv[1] + doit(filename)