Skip to content

Commit

Permalink
Merge branch 'jelic/pyproject' into jelic/cibw
Browse files Browse the repository at this point in the history
  • Loading branch information
JCGoran committed Mar 1, 2024
2 parents 66b342a + ce03dd8 commit 62fd8a7
Show file tree
Hide file tree
Showing 43 changed files with 1,507 additions and 147 deletions.
29 changes: 10 additions & 19 deletions .github/workflows/nmodl-doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,28 @@ jobs:
with:
fetch-depth: 0

- name: Install Python3 dependencies
working-directory: ${{runner.workspace}}/nmodl
run: |
pip3 install -U pip setuptools 'pip-tools>=7.4.0'
pip3 install --user -r <(pip-compile --all-build-deps --all-extras --no-strip-extras 2>&1)
# This step will set up an SSH connection on tmate.io for live debugging.
# To trigger it, simply add 'live-debug-docs' to your last pushed commit message.
- name: live debug session on failure
if: failure() && contains(github.event.head_commit.message, 'live-debug-docs')
uses: mxschmitt/action-tmate@v3

- name: Documentation
id: documentation
working-directory: ${{runner.workspace}}/nmodl
run: |
echo "------- Build Documentation -------";
# build wheel and install it
pip wheel . --wheel-dir wheelhouse/ -C build-dir=_build --no-deps
pip install wheelhouse/*.whl
bash docs/generate_docs.sh
touch public/.nojekyll
echo "<meta http-equiv=\"refresh\" content=\"0; url=./html/index.html\" />" > public/index.html;
bash docs/generate_docs.sh public $(command -v python${PYTHON_VERSION})
touch public/docs/.nojekyll
echo "<meta http-equiv=\"refresh\" content=\"0; url=./html/index.html\" />" > public/docs/index.html
echo "status=done" >> $GITHUB_OUTPUT
env:
CCACHE_DIR: ${{runner.workspace}}/ccache

# This step will set up an SSH connection on tmate.io for live debugging.
# To trigger it, simply add 'live-debug-docs' to your last pushed commit message.
- name: live debug session on failure
if: failure() && contains(github.event.head_commit.message, 'live-debug-docs')
uses: mxschmitt/action-tmate@v3

- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4
if: steps.documentation.outputs.status == 'done' && startsWith(github.ref, 'refs/heads/master')
with:
branch: gh-pages # The branch the action should deploy to.
folder: ${{runner.workspace}}/nmodl/public
folder: ${{runner.workspace}}/nmodl/public/docs
clean: false # Automatically remove deleted files from the deploy branch
5 changes: 2 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ option(NMODL_BUILD_WHEEL "Flag to signal we are building a wheel" OFF)
if(NMODL_BUILD_WHEEL)
set(LINK_AGAINST_PYTHON OFF)
set(NMODL_ENABLE_TESTS OFF)
set(FMT_INSTALL OFF)
endif()

# =============================================================================
Expand Down Expand Up @@ -112,7 +111,7 @@ endif()
cpp_cc_git_submodule(eigen)
# We could have fmt incoming from NEURON
if(NOT TARGET fmt)
cpp_cc_git_submodule(fmt BUILD PACKAGE fmt REQUIRED)
cpp_cc_git_submodule(fmt BUILD EXCLUDE_FROM_ALL PACKAGE fmt REQUIRED)
endif()
# If we're building from the submodule, make sure we pass -fPIC so that we can link the code into a
# shared library later.
Expand Down Expand Up @@ -140,7 +139,7 @@ endif()
cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED)

if(NMODL_ENABLE_BACKWARD)
cpp_cc_git_submodule(backward BUILD PACKAGE backward REQUIRED)
cpp_cc_git_submodule(backward BUILD EXCLUDE_FROM_ALL PACKAGE backward REQUIRED)
endif()

# =============================================================================
Expand Down
38 changes: 35 additions & 3 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ pull the changes from the main (upstream) repository:
Development Conventions
------------------------

New Lines
~~~~~~~~~

When generating/printing code it's important to use ``add_newline`` to
start a new line of code. When printing a string containing multiple lines,
i.e. one that contains a ``"\n"`` one must use ``add_multi_line``.

It's important that NMODL knows the line number it's currently on.

Formatting
~~~~~~~~~~

Expand Down Expand Up @@ -201,9 +210,8 @@ the Python API:
1. setup a sandbox environment with either *virtualenv*, *pyenv*, or
*pipenv*. For instance with *virtualenv*:
``python -m venv .venv && source .venv/bin/activate``
2. build the Python package with the command: ``python setup.py build``
3. install *pytest* Python package: ``pip install pytest``
4. execute the unit-tests: ``pytest``
2. build the Python wheel with the command: ``python -m pip wheel . --no-deps``
3. execute the unit-tests for the wheel: ``bash packaging/test_wheel.bash $(command -v python) WHEEL``, where ``WHEEL`` is the path to the wheel generated in the previous step.

Memory Leaks and clang-tidy
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -226,3 +234,27 @@ have ``CMake >= 3.15`` and use following cmake option:
::

cmake .. -DENABLE_CLANG_TIDY=ON

Blaming NMODL
~~~~~~~~~~~~~

While developing NMODL one may want to know which line of code in NMODL
produced a particular line of code in the generated file, e.g. when faced with
a compiler error such as

.. code-block::
hodhux.cpp:105:26: error: ‘coreneuron’ has not been declared
105 | double* celsius{&coreneuron::celsius};
| ^~~~~~~~~~
One can find the line by doing:

.. code-block::
$ nmodl hodhux.mod ... blame --line 105
which will print a backtrace every time NMODL writes to line 105. While this is
useful for finding the line responsible for printing, i.e. convert AST to C++,
that line it doesn't immediately explain why the AST ended up that way.
Currently, we don't have a tool for the latter.
51 changes: 41 additions & 10 deletions INSTALL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ of all dependencies we recommend using `homebrew <https://brew.sh/>`__:
brew install flex bison cmake python3
The necessary Python packages can then easily be added using the pip3
command.
All of the Python dependencies (build, run, and development) can be installed
using:

.. code:: sh
pip3 install --user -r requirements.txt
pip3 install --user pip-tools
pip3 install --user -r <(pip-compile --all-build-deps --all-extras --no-strip-extras 2>&1)
Make sure to have latest flex/bison in $PATH :

Expand All @@ -74,11 +75,13 @@ installed along with the system toolchain:
apt-get install flex bison gcc python3 python3-pip
The Python dependencies are installed using:
All of the Python dependencies (build, run, and development) can be installed
using:

.. code:: sh
pip3 install --user -r requirements.txt
pip3 install --user pip-tools
pip3 install --user -r <(pip-compile --all-build-deps --all-extras --no-strip-extras 2>&1)
Build Project
-------------
Expand Down Expand Up @@ -124,8 +127,8 @@ to cmake as:
-DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \
-DCMAKE_INSTALL_PREFIX=$HOME/nmodl
Using Python setuptools
~~~~~~~~~~~~~~~~~~~~~~~
Using the Python build system
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you are mainly interested in the NMODL Framework parsing and analysis
tools and wish to use them from Python, we recommend building and
Expand All @@ -139,6 +142,19 @@ This should build the NMODL framework and install it into your pip user
``site-packages`` folder such that it becomes available as a Python
module.

Building a wheel
~~~~~~~~~~~~~~~~

You can also build a wheel you can test and install in another environment using:

.. code:: sh
pip3 wheel . --no-deps [-C OPTION1=VALUE1 -C OPTION2=VALUE2...] [--wheel-dir DIRECTORY]
where the various ``OPTION`` values describe the build options (for a list of
all available options, please consult the `reference <https://scikit-build-core.readthedocs.io/en/latest/configuration.html>`_).
Notably, due to a bug in CMake, on MacOS one should pass ``-C build-dir=DIRECTORY`` to the above.

When building without linking against libpython
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -210,6 +226,16 @@ example in your Python 3 interpeter as follows:
SUFFIX hh
}
You can also run all of the Python tests for a given wheel using:

.. code:: sh
bash packaging/test_wheel.bash PYTHON_EXECUTABLE WHEEL
where ``PYTHON_EXECUTABLE`` should be replaced by the path to the Python
executable, and ``WHEEL`` should be replaced by the path to the wheel you wish
to test.

NMODL is now setup correctly!

Generating Documentation
Expand All @@ -219,9 +245,14 @@ In order to build the documentation you must have additionally
``pandoc`` installed. Use your system’s package manager to do this
(e.g. ``sudo apt-get install pandoc``).

You can build the entire documentation simply by using sphinx from
``setup.py``:
You can build the entire documentation simply by using the ``generate_docs.sh``
script:

.. code:: sh
python3 setup.py build_ext --inplace docs
bash docs/generate_docs.sh DIRECTORY [PYTHON_EXECUTABLE]
where ``DIRECTORY`` is where you want to put the output files. The HTML
documentation will then be available in ``DIRECTORY/docs``, and the temporary
build will be stored in ``DIRECTORY/build``. You can also specify the path to
the Python executable if it is not picked up automatically.
97 changes: 97 additions & 0 deletions docs/contents/cable_equations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
Cable Equation
==============

.. note::

NEURON has a sophisticated system for allowing users to describe the
geometry of neurons. Here we'll try to derive the equations in a manner that
hides those details whenever they're not relevant to NMODL. Please consult
its geometry related documentation or one of its publications, e.g. `The
NEURON Simulation Environment`_.

.. _The NEURON Simulation Environment: https://doi.org/10.1162/neco.1997.9.6.1179

In order to derive the `cable equations` we model a neuron as an electrical
circuit. We first pick points along the neuron at which we model the voltage.
We'll call them nodes and connect the nodes to form a graph. At every branch
point we place a node, see Figure 1.

.. figure:: ../images/cable-eqn_nodes.svg

Figure 1: Illustration of the placement of node along a neurite.

Two adjacent nodes are connected by a resistor. The interesting behaviour comes
from a difference in ion concentrations across the membrane. This difference is
upheld by three processes: a) the membrane which is largely impermeable to
ions, effectively creating a barrier for ions; b) (voltage-gated) ion channels
that conditionally allow ions to quickly cross the membrane; and c) ion pumps
which continuously pump ions across the membrane to restore a resting state.

The fact that the membrane is (mostly) impermeable to ions means that it
behaves like a dielectric material and can therefore be modeled by a capacitor.
The ion pumps and channels we simply model by a current :math:`I`.

This model gives rise to the circuit shown in Figure 2.

.. figure:: ../images/cable-eqn_circuit.svg

Figure 2: Illustration of the circuit near one node. The total trans-membrane
current is :math:`I_M`, the current due to the dielectric property of the
membrane is :math:`I_C`, and all mechanism specific currents are represented
by :math:`I`.

We can start writing down equations. Let's recall the formula for a capacitor
and Ohm's Law:

.. math::
I = C \frac{dV}{dt}, \qquad
\Delta V = R I
Using Kirchoff's Law we can write down two equations for the trans-membrane
current:

.. math::
I_M &= I_C + I(V_1) \\
I_{0,1} &= I_{1, 2} + I_M
which leads to

.. math::
I_C + I = I_{0,1} - I_{1, 2} \\
I_C + I_{1,2} - I_{0, 1} = -I
which can be rewritten in terms of the voltage as follows:

.. math::
C \frac{dV_1}{dt} + R_{1,2}^{-1} (V_{2} - V{1}) - R_{0,1}^{-1} (V_{1} - V_{0}) = - I(V_1)
This can be discretized by implicit Euler:

.. math::
C \frac{V_1^{n+1} - V_1^{n}}{\Delta t} + R_{1,2}^{-1} \left(V_{2}^{n+1} - V_{1}^{n+1}\right) - R_{0,1}^{-1} \left(V_{1}^{n+1} - V_{0}^{n+1}\right) = - I(V_1^{n+1})
We collect terms as follows:

.. math::
R_{0,1}^{-1} V_{0}^{n+1}
+ \left(\frac{C}{\Delta t} + R_{0,1}^{-1} - R_{1,2}^{-1}\right) V_1^{n+1}
+ R_{1,2}^{-1} V_{2}^{n+1}
= \frac{C}{\Delta t} V_1^{n} - I(V_1^{n+1})
The unpleasant term is :math:`I(V_1^{n+1})` since it makes the system non-linear.
Therefore, it's linearized as follows:

.. math::
I(V_1^{n+1})
&\approx I_1^{n} + \left(V^{n+1} - V^{n}\right) \frac{dI_1}{dV_1} \\
&=: I_1^{n} + \left(V^{n+1} - V^{n}\right) g_i^{n}
where :math:`g_i^{n}` is the mechanism dependent (differential) conductance.

39 changes: 34 additions & 5 deletions docs/generate_docs.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
#!/usr/bin/env bash

# script for generating documentation for NMODL
# note that the NMODL Python wheel must be installed
# for the script to work properly

set -xe
set -eu

if [ $# -lt 1 ]
then
echo "Usage: $(basename "$0") output_dir [python_exe]"
exit 1
fi

# the dir where we put the temporary build and the docs
output_dir="$1"
# path to the Python executable
python_exe="${2:-"$(command -v python3)"}"

if ! [ -d "${output_dir}" ]
then
mkdir -p "${output_dir}"
fi

build_dir="build"
docs_dir="docs"

echo "== Building documentation files in: ${output_dir}/${docs_dir} =="
echo "== Temporary project build directory is: ${output_dir}/${build_dir} =="

venv_name="${output_dir}/env"
${python_exe} -m venv "${venv_name}"
. "${venv_name}/bin/activate"
python_exe="$(command -v python)"
${python_exe} -m pip install -U pip
${python_exe} -m pip install ".[docs]" -C build-dir="${output_dir}/${build_dir}"

# the abs dir where this script is located (so we can call it from wherever)
script_dir="$(realpath "$(dirname "$0")")"
script_dir="$(cd "$(dirname "$0")"; pwd -P)"

cd "${script_dir}"
doxygen Doxyfile
sphinx-build . "${script_dir}/../public"
cd -
sphinx-build docs/ "${output_dir}/${docs_dir}"

deactivate
Loading

0 comments on commit 62fd8a7

Please sign in to comment.