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": [
"# 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\n",
"\n",
"The _registry_ class implements a registry of `sympy` expressions. It is expected that this class\n",
"is mostly used to organize 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",
"* Easy registration process.\n",
"* Access to previously registered expressions by name\n",
"* Auto numbering of registered expressions:\n",
" $$\n",
" \\boxed{f(x) = b x^2 + c}\\tag{11}\n",
" $$\n",
" \n",
" or with optional display of equation name:\n",
" $$\n",
" \\boxed{f(x) = b x^2 + c}\\tag{11 - Energy}\n",
" $$\n",
"* Autocompletion of registered expressions in the notebook cell editor:\n",
" \n",
" ![Autocomplete](https://gist.github.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(show_names: bool = False)`\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",
" \\boxed{f(x) = b x^2 + c}\\tag{11 - name}\n",
" $$\n",
" \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) -> Math`**\n",
"\n",
"> _dunder_ method to make registry instances callable.\n",
">\n",
"> **Args**\n",
">\n",
"> **`name: str`**\n",
"> : The name of a registered `sympy` expression\n",
">\n",
"> **Returns**\n",
">\n",
"> An IPython `Math` object which is suitable for rendering as math in notebook output cells."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import sympy as sp\n",
"from IPython.display import Math"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"class EquationRegistry():\n",
"\n",
" def __init__(self, show_names: bool = False):\n",
" # bypass self.__setattr__\n",
" self.__dict__['_indexmap'] = {}\n",
" self.__dict__['_show_names'] = show_names\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) -> Math:\n",
" index = self._indexmap[name]\n",
" self._indexmap[name] = index # mark as published\n",
" label = str(index) + (' - ' + name) if self._show_names else ''\n",
" return Math(r'\\boxed{' \\\n",
" + sp.latex(self.__dict__[name]) \\\n",
" + r'} \\tag{' \\\n",
" + label \\\n",
" + '}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Example\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 *\n",
"import IPython.display as IPd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No 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."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"ER = EquationRegistry(show_names = 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/latex": [
"$\\displaystyle \\boxed{{\\epsilon}_{i} = x - {p}_{i}} \\tag{1 - error}$"
],
"text/plain": [
"<IPython.core.display.Math object>"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"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/latex": [
"$\\displaystyle \\boxed{E{\\left(x \\right)} = \\frac{\\sum_{i=0}^{n - 1} \\left(x - {p}_{i}\\right)^{2}}{n}} \\tag{2 - energy}$"
],
"text/plain": [
"<IPython.core.display.Math object>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"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/latex": [
"$\\displaystyle \\boxed{\\frac{d}{d x} E{\\left(x \\right)} = \\frac{\\sum_{i=0}^{n - 1} \\left(2 x - 2 {p}_{i}\\right)}{n}} \\tag{3 - dE_dx}$"
],
"text/plain": [
"<IPython.core.display.Math object>"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"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": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\boxed{\\frac{d}{d x} E{\\left(x \\right)} = \\frac{2 \\left(n x - \\sum_{i=0}^{n - 1} {p}_{i}\\right)}{n}} \\tag{4 - dE_dx_simple}$"
],
"text/plain": [
"<IPython.core.display.Math object>"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"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": {},
"source": [
"# Appendix"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"from jnbBuffs.manifest import notebook_manifest\n",
"import IPython.display as IPd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Packages used in this gist:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/markdown": [
"**Python Packages used in This Notebook**"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"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.2 | 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": [
"display(IPd.Markdown('**Python Packages used in This Notebook**'))\n",
"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