{ "cells": [ { "cell_type": "markdown", "id": "393ce06d", "metadata": {}, "source": [ "# Using the backend management system\n", "Rockpool supports multiple optional computational backends, while keeping the number of core dependencies low. To do so we use a particular sub-package structure to isolate the backends, and provide some utility functions to assist with backend detection and management." ] }, { "cell_type": "markdown", "id": "aa988373", "metadata": {}, "source": [ "## Code architecture of backend-specific `Module` packages" ] }, { "cell_type": "raw", "id": "a52c0ce3", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Rockpool keeps backend-specific :py:class:`.Module` subclasses in separate packages under :py:mod:`.nn.modules`. This allows us to \"quarantine\" the backend to that particular sub-package.\n", "\n", "Here we use the example of the :py:mod:`.nn.modules.torch` sub-package.\n", "\n", "Each new :py:class:`.Module` is implemented in its own Python file, which is written cleanly by assuming that all required dependencies are available. i.e., each file simply writes ``import torch`` without needing to perform any dependency checks.\n", "\n", "This simplifies the code, but implies that if we attempt to ``import`` the symbols when a dependency is missing, we will raise an ``ImportError``.\n", "\n", "In ``__init__.py``, we ``import`` all the :py:class:`.Module` classes up to the level of ``__init__.py`` to make the logical hierarchy of :py:mod:`.nn.modules` simplier for the end-user. But this implies that we need to perform dependency checks in ``__init__.py``, and handle andy missing dependencies accordingly.\n", "\n", "We use the :py:func:`.backend_available` function provided by :py:mod:`.utilities.backend_management` to detect whether a given backend is importable. If it is missing, we create a fake class which raises an error if used, indiciating that the backend is not available and the class is not able to be used." ] }, { "cell_type": "markdown", "id": "bed4b75f", "metadata": {}, "source": [ "```python\n", "try:\n", " from .rate_torch import *\n", " ...\n", "\n", "except:\n", " from rockpool.utilities.backend_management import (\n", " backend_available,\n", " missing_backend_shim,\n", " )\n", " if not backend_available('torch'):\n", " RateTorch = missing_backend_shim('RateTorch', 'torch')\n", " ...\n", "\n", " else:\n", " raise\n", "```" ] }, { "cell_type": "raw", "id": "6295e65e", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "This block attemps to import all exported symbols from :py:mod:`.rate_torch`. If an error is raised, then :py:func:`.backend_available` is used to check the availability of ``torch``. If ``torch`` is available, this returns ``True``.\n", "\n", "If ``torch`` is missing, then a fake \"shim\" class ``RateTorch`` is created. The arguments to :py:func:`.missing_backend_shim` specify the name of the shim class and the backend dependency or dependencies which are missing." ] }, { "cell_type": "code", "execution_count": 1, "id": "a5bef046", "metadata": { "tags": [ "raises-exception" ] }, "outputs": [ { "ename": "ModuleNotFoundError", "evalue": "Missing the `torch` backend. `FakeClass` objects, and others relying on `torch` are not available.", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mFakeClass\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmissing_backend_shim\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'FakeClass'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'torch'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m \u001b[0mFakeClass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m~/SynSense Dropbox/Dylan Muir/LiveSync/Development/rockpool_GIT/rockpool/utilities/backend_management.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 137\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 138\u001b[0m raise ModuleNotFoundError(\n\u001b[0;32m--> 139\u001b[0;31m \u001b[0;34mf\"Missing the `{backend_name}` backend. `{class_name}` objects, and others relying on `{backend_name}` are not available.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 140\u001b[0m )\n\u001b[1;32m 141\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mModuleNotFoundError\u001b[0m: Missing the `torch` backend. `FakeClass` objects, and others relying on `torch` are not available." ] } ], "source": [ "from rockpool.utilities.backend_management import missing_backend_shim\n", "\n", "FakeClass = missing_backend_shim('FakeClass', 'torch')\n", "\n", "FakeClass()" ] }, { "cell_type": "markdown", "id": "b85eea66", "metadata": {}, "source": [ "## Facilities provided by `backend_management`\n", "### Checking standard back-ends" ] }, { "cell_type": "raw", "id": "0a761382", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\"Standard\" back-ends can be checked conveniently with :py:func:`.backend_available`, which accepts the name of a back-end to check.\n", "\"Standard\" back-ends are ``{\"numpy\", \"numba\", \"nest\", \"jax\", \"torch\", \"sinabs\", \"sinabs-exodus, \"brian\"}``." ] }, { "cell_type": "markdown", "id": "4bb443af", "metadata": {}, "source": [ "```python\n", "def backend_available(*backends) -> bool:\n", " ...\n", "```" ] }, { "cell_type": "markdown", "id": "1dfb2470", "metadata": {}, "source": [ "### Checking non-standard back-ends" ] }, { "cell_type": "raw", "id": "7620c994", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Other back-ends not included in the standard list can also easily be added and checked, without hacking the backend management package, by using the :py:func:`.check_backend` function." ] }, { "cell_type": "markdown", "id": "06d59a26", "metadata": {}, "source": [ "```python\n", "def check_backend(\n", " backend_name: str,\n", " required_modules: Optional[Union[Tuple[str], List[str]]] = None,\n", " check_flag: bool = True,\n", ") -> bool:\n", " ...\n", "```" ] }, { "cell_type": "raw", "id": "977bc400", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Here ``backend_name`` is an arbitrary user-facing string specifying the back-end in a \"nice\" way.\n", "``required_modules`` is a list of strings that will be each attempted to be imported. These specify the required python modules which comprise the back-end.\n", "If any of these modules cannot be imported for any reason, then :py:func:`.check_backend` will return ``False``. Otherwise it will return ``True``.\n", "\n", "``check_flag`` provides a facility to perform a developer-defined arbitrary check for the backend, which is then incorporated in the back-end checking process. If ``check_flag = False`` then :py:func:`.check_backend` will always return ``False``." ] } ], "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.8.13 (default, Mar 28 2022, 06:16:26) \n[Clang 12.0.0 ]" }, "vscode": { "interpreter": { "hash": "723f4042abb8f9d529afab4de0dbe358333c5793643e221f096cd6700cd11abe" } } }, "nbformat": 4, "nbformat_minor": 5 }