Skip to content

Instantly share code, notes, and snippets.

@WetHat
Last active July 27, 2022 20:25
Show Gist options
  • Save WetHat/6a8e38fb92ab831d8cdb5d66f2d2e524 to your computer and use it in GitHub Desktop.
Save WetHat/6a8e38fb92ab831d8cdb5d66f2d2e524 to your computer and use it in GitHub Desktop.
A simple registry to manage and auto-number sympy math expressions
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Note**: Click [here](https://nbviewer.jupyter.org/gist/WetHat/6a8e38fb92ab831d8cdb5d66f2d2e524/PY-EquationRegistry.ipynb)\n",
"to view the full fidelity version of this notebook with\n",
"[nbviewer](https://nbviewer.jupyter.org/)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# A Simple Registry for Managing and Auto-numbering _SymPy_ Equations\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The combination of [sympy](https://www.sympy.org) and [Jupyter Notebooks](https://jupyter.org/) is\n",
"an efficient way to post experiments or articles involving [computer algebra](https://en.wikipedia.org/wiki/Computer_algebra). However, as the notebook gets bigger following challenges may arise:\n",
"* Manual numbering of equations (so that they can be referred to in downstream equations or text) becomes labor intensive.\n",
" Particularly if the order of notebook cells need to be changed.\n",
"* The Python namespace becomes cluttered with variables holding intermediary results.\n",
" Local variables defined else where make the notebook harder to understand and also sometimes\n",
" cause unforeseen side effects.\n",
"\n",
"In this notebook we show a simple utility class to organize [sympy](https://www.sympy.org) expressions. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# The Notebook Equation Registry Class\n",
"\n",
"The _registry_ class implements a dictionary for `sympy` expressions. It is expected that this class\n",
"is mostly used for equations, so it is named `EquationRegistry`, though it can manage any type of `sympy`\n",
"expressions.\n",
"\n",
"The implementation in this notebook offers following features:\n",
"\n",
"* Github compatible rendering mode or Jupyter mode\n",
"* Easy registration process.\n",
"* Access to previously registered expressions by name\n",
"* Expression names can be used to create Markdown links to navigate to the\n",
" notebook cells where the expression is displayed:\n",
" \n",
" ~~~ markdown\n",
" Take me to equation registered under the name [foo](#foo)\n",
" ~~~\n",
"* Auto numbering of registered expressions (GitHub rendering):\n",
" $$\n",
" \\begin{matrix}\\boxed{f(x) = b x^2 + c} & (11) \\end{matrix}\n",
" $$\n",
" \n",
" or with optional display of the equation name (GitHub rendering):\n",
" $$\n",
" \\begin{matrix}\\boxed{f(x) = b x^2 + c} & (11) \\ \\text{Energy}\\end{matrix}\n",
" $$\n",
"* Autocompletion of registered expressions in the notebook cell editor:\n",
" \n",
" ![Autocomplete](https://gist.githubusercontent.com/WetHat/6a8e38fb92ab831d8cdb5d66f2d2e524/raw/ed180f06d6bfd29bd4557dbb1e66504c1e643273/Xautocomplete.png)\n",
"* Automatic renumbering of registered expressions if cell order has changed (Requires re-running of all notebook cells)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## class `EquationRegistry`\n",
"\n",
"**`EquationRegistry(show_names = False, github = True)`**\n",
"\n",
"Create a notebook registry to manage `sympy` expressions.\n",
"\n",
"`sympy` expressions can be registered with the registry simply by assigning\n",
"it to an (non-)existing property like so:\n",
"~~~ python\n",
"ER.SomeName = Eq(a,b) # ER is a registry object\n",
"~~~\n",
"Once registered an equation can be retrieved from the registry by using\n",
"its property name.\n",
"\n",
"~~~ python\n",
"ER.SomeName # ER is a registry object\n",
"~~~\n",
"\n",
"> **Args**\n",
"\n",
"> **`show_names: bool = False`**\n",
"> : Optional flag to configure the registry to display equation names\n",
"> along with their sequence number.\n",
"> \n",
"> if `true` the equation name is added to the sequence number:\n",
"> $$\n",
"> \\begin{matrix}\\boxed{f(x) = b x^2 + c} & (11) \\ \\text{Energy}\\end{matrix}\n",
"> $$\n",
"> \n",
"> **`github: bool = True`**\n",
"> : Optional flag to configure LaTeX rendering mode. `True` to generate\n",
"> a less aligned, GitHub compatible LaTex notation. `False` to generate\n",
"> a LaTex notation which is optimized for notebook rendering.\n",
"- - -\n",
"\n",
"**Methods**\n",
"\n",
"**`__setattr__(name: str, value)`**\n",
"\n",
"> _Dunder_ method to allow the on-the-fly creation of new registration properties.\n",
">\n",
"> **Args**\n",
">\n",
"> **`name: str`**\n",
"> : The registration name of a `sympy` expression.\n",
">\n",
"> **`value`**\n",
"> : A `sympy` expression. Most likely an equation object (`Eq`).\n",
"\n",
"**`__call__(name: str)`**\n",
"\n",
"> _Dunder_ method to render the `sympy` expression with the given name\n",
"> in an output cell by _calling_ the registry object.\n",
">\n",
"> ~~~ python\n",
"> ER('foo') # display math for equation 'foo' in the output cell\n",
"> ~~~\n",
">\n",
"> When the equation is displyed an HTML anchor (bookmark) is established\n",
"> so that Markdown links navigating to the output cell can be created like so:\n",
">\n",
"> ~~~ markdown\n",
"> Take me to the equation I registered under the name [foo](#foo)\n",
"> ~~~\n",
">\n",
"> **Args**\n",
">\n",
"> **`name: str`**\n",
"> : The name of a registered `sympy` expression"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"tags": [
"CodeExport"
]
},
"outputs": [],
"source": [
"import sympy as sp\n",
"from IPython.display import Math, Markdown"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"tags": [
"CodeExport"
]
},
"outputs": [],
"source": [
"class EquationRegistry():\n",
"\n",
" def __init__(self, show_names: bool = False, github: bool = True):\n",
" # bypass self.__setattr__\n",
" self.__dict__['_indexmap'] = {}\n",
" self.__dict__['_show_names'] = show_names\n",
" self.__dict__['_github'] = github\n",
"\n",
" def __setattr__(self, name: str, value):\n",
" index = self._indexmap.get(name, 0)\n",
" if index == 0: # a new field is requested\n",
" self._indexmap[name] = len(self._indexmap) + 1\n",
" self.__dict__[name] = value\n",
"\n",
" def __call__(self, name: str):\n",
" index = self._indexmap[name]\n",
" self._indexmap[name] = index # mark as published\n",
" display(Markdown(f'\\n<a name=\"{name}\"/>')) # create an HTML anchor\n",
" if self._github:\n",
" label = '(' + str(index) + ')' + ((r'\\ \\text{' + name + r'}') if self._show_names else '')\n",
" math = Math(r'\\begin{matrix}\\boxed{\\displaystyle ' \\\n",
" + sp.latex(self.__dict__[name]) \\\n",
" + r'} & ' \\\n",
" + label\\\n",
" + r'\\end{matrix}')\n",
" else:\n",
" label = str(index) + ((' - ' + name) if self._show_names else '')\n",
" math = Math(r'\\boxed{' \\\n",
" + sp.latex(self.__dict__[name]) \\\n",
" + r'} \\tag{' \\\n",
" + label \\\n",
" + '}')\n",
" display(math) # render the expression"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Showcase\n",
"\n",
"In this section we demonstrate a typical use case for the equation registry.\n",
"To get started we get the required Python imports out of the way."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from sympy import Sum, Eq, IndexedBase, diff, Function, factor_terms, expand\n",
"from sympy.abc import *"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we create an instance of the registry. We use one registry for the entire\n",
"notebook. However, it is conceivable that in some cases more than one registry may be created.\n",
"\n",
"We configure the registry to also display equation names after the number. Since we want to publish this notebook as a GitHub Gist we choose `github = True`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"ER = EquationRegistry(show_names = True, github=True) # create the equation registry for this notebook"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next we create a simple function which could represent the error $\\epsilon_i$\n",
"for a value $p_i$ from a data set containing $n$ values. The exact details are not important\n",
"here. We just need something to do some math with.\n",
"\n",
"We register the corresponding `sympy` equation under the name `error` and also display it:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"\n",
"<a name=\"error\"/>"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/latex": [
"$\\displaystyle \\begin{matrix}\\boxed{\\displaystyle {\\epsilon}_{i} = x - {p}_{i}} & (1)\\ \\text{error}\\end{matrix}$"
],
"text/plain": [
"<IPython.core.display.Math object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"epsilon = IndexedBase('\\epsilon')\n",
"p = IndexedBase('p')\n",
"\n",
"ER.error = Eq(epsilon[i], x - p[i]) # define and register equation\n",
"ER('error') # Display registered equation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We get the previously defined `error` function from the registry with `ER.error`\n",
"and derive a so called energy function $E(x)$.\n",
"We register the corresponding `sympy` equation under the name `energy` and display it.\n",
"Note how `ER.error.lhs` and `ER.error.rhs` is used to access the left hand and right hand side of the registered\n",
"equation."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"\n",
"<a name=\"energy\"/>"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/latex": [
"$\\displaystyle \\begin{matrix}\\boxed{\\displaystyle E{\\left(x \\right)} = \\frac{\\sum_{i=0}^{n - 1} \\left(x - {p}_{i}\\right)^{2}}{n}} & (2)\\ \\text{energy}\\end{matrix}$"
],
"text/plain": [
"<IPython.core.display.Math object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"E = Function('E')\n",
"\n",
"ER.energy = Eq(E(x), Sum(ER.error.rhs**2, (i, 0, n - 1)) / n)\n",
"ER('energy')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now get the `energy` equation from the registry (`ER.energy`) to compute a derivative which we then register under the name `dE_dx`:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"\n",
"<a name=\"dE_dx\"/>"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/latex": [
"$\\displaystyle \\begin{matrix}\\boxed{\\displaystyle \\frac{d}{d x} E{\\left(x \\right)} = \\frac{\\sum_{i=0}^{n - 1} \\left(2 x - 2 {p}_{i}\\right)}{n}} & (3)\\ \\text{dE_dx}\\end{matrix}$"
],
"text/plain": [
"<IPython.core.display.Math object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ER.dE_dx = Eq(diff(ER.energy.lhs, x), ER.energy.rhs.diff(x))\n",
"ER('dE_dx')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally we perform some simplification:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/markdown": [
"\n",
"<a name=\"dE_dx_simple\"/>"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/latex": [
"$\\displaystyle \\begin{matrix}\\boxed{\\displaystyle \\frac{d}{d x} E{\\left(x \\right)} = \\frac{2 \\left(n x - \\sum_{i=0}^{n - 1} {p}_{i}\\right)}{n}} & (4)\\ \\text{dE_dx_simple}\\end{matrix}$"
],
"text/plain": [
"<IPython.core.display.Math object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ER.dE_dx_simple = Eq(ER.dE_dx.lhs, factor_terms(expand(ER.dE_dx.rhs)).doit())\n",
"ER('dE_dx_simple')"
]
},
{
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"Finally take me back to the equation I registered under the name [error](#error). We can\n",
"also generate latex style links like so: [$E(x)$](#energy)\n",
"\n",
"**Note** Hyperlinks do not work in GitHub Gist rendering to view this notebook in full fidelity use\n",
"[this link](https://nbviewer.jupyter.org/gist/WetHat/6a8e38fb92ab831d8cdb5d66f2d2e524/PY-EquationRegistry.ipynb) to open the notebook in the\n",
"[nbviewer](https://nbviewer.jupyter.org/) web service."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Appendix"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"from jnbBuffs.manifest import notebook_manifest"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Packages Used In The Making Of This Notebook"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/markdown": [
"| Component | Version | Description |\n",
"| --------------------------------- | -------------------------- | -------------------- |\n",
"| [Python](https://www.python.org/) | 3.8.6 | Programming Language |\n",
"| [jnbBuffs](https://github.com/WetHat/jupyter-notebooks) | 0.1.8 | Utilities for authoring JupyterLab Python notebooks. |\n",
"| [jupyterlab](http://jupyter.org) | 3.0.12 | The JupyterLab server extension. |\n",
"| [sympy](https://sympy.org) | 1.7.1 | Computer algebra system (CAS) in Python |"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"notebook_manifest('jupyterlab', 'sympy', 'jnbBuffs')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (JupyterLab)",
"language": "python",
"name": "jupyterlab"
},
"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.8.6"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment