{ "cells": [ { "cell_type": "markdown", "id": "386a5424-30e2-4e40-ac4e-547846da1294", "metadata": {}, "source": [ "# Extending PointNet Networks and Simulation with External and Customized Models.\n", "\n", "In this tutorial, we explain through a series of examples how to use and simulate built-in NEST cell and synaptic models, and create and import custom models using NESTML, as well as ways to dynamically change cells parametters before each simulation.\n", "\n", "**Note** - scripts and files for running this tutorial can be found in the directory [02_opt_nest_custom_models/](https://github.com/AllenInstitute/bmtk/tree/develop/docs/tutorial/02_opt_nest_custom_models)" ] }, { "cell_type": "markdown", "id": "b24846e8-de07-4d2e-bd41-ea2b62c64ad3", "metadata": {}, "source": [ "## 1. Example: Using Built-In NEST Models <a class=\"anchor\" id=\"example-nest-models\">" ] }, { "cell_type": "markdown", "id": "e9d5cd7c-b786-424e-9cfe-f51cf0e203c1", "metadata": {}, "source": [ "PointNet utilizes the NEST simulator for running point-neuron simulations, which includes many different varieties and types of neuronal models the majority of which can be readily used during simulation in BMTK. A very useful practice is to take an existing network model built with a certain model of a cell and replace it with a different cell model that more closely fits the needs of our research. With BMTK and SONATA this can be easily done, often without any programming, and even done on the fly before any given simulation.\n", "\n", "A list of available models built into the NEST simulator can be found [here](https://nest-simulator.readthedocs.io/en/stable/models/index.html).\n", "\n", "Most of the spiking models will work out-of-the-box in BMTK, readily swapped in and out of existing simulations, and you can use multiple models in the same network. (*We will ignore rates-based models which can only tentatively work with BMTK. And for devices like genators and recorders PointNet uses a separate way of inserting them into the simulation*). To define what model will be used with a specific subset of cells and their properties, SONATA has two defined reserved attributes `model_template` and `dynamics_params` which we will take advantage of in this example." ] }, { "cell_type": "markdown", "id": "9de5ab45-f067-4b56-ab29-cd01569ab5c1", "metadata": {}, "source": [ "### Setting the model type in our network." ] }, { "cell_type": "markdown", "id": "13ad2a5b-6d0d-450b-b78e-e75806477644", "metadata": {}, "source": [ "The default way to set the model type for subset of neurons (which may be just a single neuron) that all use the same built-in NEST model type is to set the **model_type** attributes to a value `nest:<nest-model-name>` where `<nest-model-name>` is the name of one of the spiking [NEST cell models](https://nest-simulator.readthedocs.io/en/stable/models/index.html). However, different models will have different parameters. There are multiple ways to set model parameters in BMTK, but the one that is most flexible and will make it easier to switch out different models while testing and optimizing our network is to set the **dynamics_params** to an easy to edit json file.\n", "\n", "In the below example (*build_network.built_in.py*) we will build a simple 100 cell network with synaptic stimuli. Initially we will use [izhikevich](https://nest-simulator.readthedocs.io/en/stable/models/izhikevich.html) models using default params (**hint**: if the dynamics_params .json file is just an empty json file, then the cells will be loaded with the default params. This is a useful starting point). But you can change the **nest_model** (and optionally the **dynamics_params**) variable below to build the same network using different model types" ] }, { "cell_type": "code", "execution_count": 1, "id": "8dfcdc1f-8e52-459c-82ba-819e4304e7fc", "metadata": {}, "outputs": [], "source": [ "nest_model = 'nest:izhikevich'\n", "# nest_model = 'nest:iaf_psc_delta'\n", "# nest_model = 'nest:aeif_cond_alpha'\n", "# nest_model = 'nest:glif_psc'\n", "\n", "dynamics_params = 'custom_model_params.default.json'\n", "# dynamics_params = 'custom_model_params.izhikevich.json'\n", "# dynamics_params = 'custom_model_params.aeif_cond_alpha.json'\n", "# dynamics_params = 'custom_model_params.glif_psc.json'" ] }, { "cell_type": "code", "execution_count": null, "id": "9a59d040-aad2-4853-9091-d4f6e3092eda", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from bmtk.builder import NetworkBuilder\n", "\n", "\n", "net = NetworkBuilder('net')\n", "net.add_nodes(\n", " N=100,\n", " model_type='point_neuron',\n", " model_template=nest_model,\n", " dynamics_params=dynamics_params\n", ")\n", "\n", "net.add_edges(\n", " source=net.nodes(), target=net.nodes(),\n", " connection_rule=1,\n", " syn_weight=2.0,\n", " delay=1.5,\n", " dynamics_params='ExcToInh.json',\n", " model_template='static_synapse'\n", ")\n", "\n", "net.build()\n", "net.save(output_dir='network_built_in')\n", "\n", "\n", "virt_exc = NetworkBuilder('virt_exc')\n", "virt_exc.add_nodes(\n", " N=10,\n", " model_type='virtual'\n", ")\n", "\n", "virt_exc.add_edges(\n", " target=net.nodes(),\n", " connection_rule=lambda *_: np.random.randint(0, 10),\n", " syn_weight=2.0,\n", " delay=1.0,\n", " dynamics_params='ExcToInh.json',\n", " model_template='static_synapse'\n", ")\n", "\n", "virt_exc.build()\n", "virt_exc.save(output_dir='network_built_in')" ] }, { "cell_type": "markdown", "id": "2ced04e1-511f-4e17-8c26-d8da389a7236", "metadata": {}, "source": [ "If you have access to an existing model but not the build-script, or if rebuilding the network is too time consuming and/or expensive, then another option to swap out different models is to open the *node_types.csv* in an text editor (especially ones that support csv forats like VSCode or Atom), update the **model_template** and **dynamics_params** attributes, then rerun the simulation." ] }, { "cell_type": "code", "execution_count": 3, "id": "4167ebf8-4802-4c2c-8ac7-fea1cabd5505", "metadata": {}, "outputs": [ { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>node_type_id</th>\n", " <th>dynamics_params</th>\n", " <th>model_type</th>\n", " <th>model_template</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>0</th>\n", " <td>100</td>\n", " <td>custom_model_params.default.json</td>\n", " <td>point_neuron</td>\n", " <td>nest:izhikevich</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "</div>" ], "text/plain": [ " node_type_id dynamics_params model_type \\\n", "0 100 custom_model_params.default.json point_neuron \n", "\n", " model_template \n", "0 nest:izhikevich " ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "\n", "pd.read_csv('network_built_in/net_node_types.csv', sep=' ')" ] }, { "cell_type": "markdown", "id": "6e0700fb-674e-40d9-bbec-b49697e4e9e9", "metadata": {}, "source": [ "We encourage you to try running the network yourself by either running in the command line or un\n", "```\n", "$ python run_pointnet.built_in.py config.built_in.json\n", "```\n", "or with the below cell, trying it with different cell models. " ] }, { "cell_type": "code", "execution_count": 4, "id": "3aa1cd9b-0b93-4d33-b0d8-89ea13cff66c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " -- N E S T --\n", " Copyright (C) 2004 The NEST Initiative\n", "\n", " Version: 3.7.0\n", " Built: May 24 2024 10:13:36\n", "\n", " This program is provided AS IS and comes with\n", " NO WARRANTY. See the file LICENSE for details.\n", "\n", " Problems or suggestions?\n", " Visit https://www.nest-simulator.org\n", "\n", " Type 'nest.help()' to find out more about NEST.\n", "\n", "2024-10-21 15:11:35,805 [INFO] Created log file\n", "2024-10-21 15:11:35,813 [INFO] Batch processing nodes for net/0.\n", "2024-10-21 15:11:35,816 [INFO] Batch processing nodes for virt_exc/0.\n", "2024-10-21 15:11:35,827 [INFO] Setting up output directory\n", "2024-10-21 15:11:35,827 [INFO] Building cells.\n", "2024-10-21 15:11:35,830 [INFO] Building recurrent connections\n", "2024-10-21 15:11:35,834 [INFO] Network created.\n", "2024-10-21 15:11:35,834 [INFO] Build virtual cell stimulations for thalamus_spikes\n", "2024-10-21 15:11:35,844 [INFO] Starting Simulation\n", "2024-10-21 15:11:39,062 [INFO] Simulation finished, finalizing results.\n", "2024-10-21 15:11:39,351 [INFO] Done.\n" ] } ], "source": [ "from bmtk.simulator import pointnet\n", "\n", "configure = pointnet.Config.from_json('config.built_in.json')\n", "configure.build_env()\n", "\n", "network = pointnet.PointNetwork.from_config(configure)\n", "sim = pointnet.PointSimulator.from_config(configure, network)\n", "sim.run()" ] }, { "cell_type": "markdown", "id": "48bae8e9-4b80-4c81-b67a-2bc3759e75ad", "metadata": {}, "source": [ "One thing to note is that when you replace one cell-model with another you may often get widely varying results. A network that is stable or silent in using one cell model may \"explode\" into runaway activity when a model gets replaced. Fixing this may require the or both of the following:\n", "* Adjusting the **dynamics_params** of the new cell model.\n", "* Adjust the synaptic models and parameters (especially **syn_weight**)." ] }, { "cell_type": "markdown", "id": "2ad7f269-b7a8-474a-826d-54b18a8a9466", "metadata": {}, "source": [ "## 2. Example: Importing Custom NESTML cell models <a class=\"anchor\" id=\"example-nestml\">" ] }, { "cell_type": "markdown", "id": "4aed8676-b485-4da0-8ee5-b162de8933ca", "metadata": {}, "source": [ "If you aren't able to find a cell model that will work for your network already built into NEST, then one option is to create it yourself (or import from an existing model) using [NESTML](https://nestml.readthedocs.io/en/latest/). NESTML is a library to build, compile, and insert customized NEST cell and synapse models into a simulation. NESTML provides a very simple language for building many kinds of cell models. And these models can be readily inserted into BMTK with only a few minor changes to the SONATA configuration files.\n", "\n", "**Note**\n", "\n", "To build cell models using NESTML it requires [installing the python package](https://nestml.readthedocs.io/en/latest/installation.html). While NESTML does have a PyPI (eg `pip`) option for installing the package, due to its rapid changes we recommend that for the time being install directly from github. As of at-least version 7.0.2 the instructions below and in the official tutorials has issues with the PyPI install." ] }, { "cell_type": "markdown", "id": "30752987-7dd3-4864-9177-5f69fac6f367", "metadata": {}, "source": [ "### Building the models" ] }, { "cell_type": "markdown", "id": "f3399aa0-86f6-4f7f-a1a4-5d7c17a5400d", "metadata": {}, "source": [ "First step is to create and compile the NESTML cell model(s). This usually requires creating a `.nestml` file in a regular text editor and adding information about parameters, equations, inputs and outputs in a tab indented file. The documentation contains very [well written language documentation](https://nestml.readthedocs.io/en/latest/nestml_language/nestml_language_concepts.html) and their [github repo contains many example cell and synaptic models](https://github.com/nest/nestml/tree/master/models) which you can expand upon for your purposes. For simplicity we'll go ahead and reimplement the `Izhikevich` model from the tutorial. The model is implemented in the following:" ] }, { "cell_type": "code", "execution_count": 5, "id": "1ad33736-deaa-4e8b-aaea-396fa646688a", "metadata": {}, "outputs": [], "source": [ "nestml_izh_model = \"\"\"\n", "model custom_izh_neuron:\n", "\n", " state:\n", " v mV = -65 mV # Membrane potential in mV\n", " u real = 0 # Membrane potential recovery variable\n", "\n", " equations:\n", " v' = (.04 * v * v / mV + 5 * v + (140 - u) * mV + (I_e * GOhm)) / ms\n", " u' = a * (b * v - u * mV) / (mV * ms)\n", "\n", " parameters:\n", " a real = .02 # describes time scale of recovery variable\n", " b real = .2 # sensitivity of recovery variable\n", " c mV = -65 mV # after-spike reset value of v\n", " d real = 8. # after-spike reset value of u\n", "\n", " input:\n", " spikes <- spike\n", " I_e pA <- continuous\n", "\n", " output:\n", " spike\n", "\n", " update:\n", " integrate_odes()\n", "\n", " onReceive(spikes):\n", " # add synaptic current\n", " v += spikes * mV * s\n", "\n", " onCondition(v >= 30mV):\n", " # threshold crossing\n", " v = c\n", " u += d\n", " emit_spike()\n", "\"\"\"" ] }, { "cell_type": "markdown", "id": "0813c963-9b42-4594-9a6c-e6bb12ef3d5c", "metadata": {}, "source": [ "Then we use the following to build the model" ] }, { "cell_type": "code", "execution_count": 6, "id": "7c023a32-8e16-4f3f-a0a5-c06f41069df5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " -- N E S T --\n", " Copyright (C) 2004 The NEST Initiative\n", "\n", " Version: 3.7.0\n", " Built: May 24 2024 10:13:36\n", "\n", " This program is provided AS IS and comes with\n", " NO WARRANTY. See the file LICENSE for details.\n", "\n", " Problems or suggestions?\n", " Visit https://www.nest-simulator.org\n", "\n", " Type 'nest.help()' to find out more about NEST.\n", "\n", "[13,custom_izh_neuron_nestml, WARNING, [13:8;13:17]]: Variable 'a' has the same name as a physical unit!\n", "[14,custom_izh_neuron_nestml, WARNING, [16:8;16:17]]: Variable 'd' has the same name as a physical unit!\n", "[23,custom_izh_neuron_nestml, WARNING, [13:8;13:17]]: Variable 'a' has the same name as a physical unit!\n", "[24,custom_izh_neuron_nestml, WARNING, [16:8;16:17]]: Variable 'd' has the same name as a physical unit!\n", "\u001b[33mCMake Warning (dev) at CMakeLists.txt:93 (project):\n", " cmake_minimum_required() should be called prior to this top-level project()\n", " call. Please see the cmake-commands(7) manual for usage documentation of\n", " both commands.\n", "This warning is for project developers. Use -Wno-dev to suppress it.\n", "\u001b[0m\n", "-- The CXX compiler identification is Clang 17.0.6\n", "-- Detecting CXX compiler ABI info\n", "-- Detecting CXX compiler ABI info - done\n", "-- Check for working CXX compiler: /opt/anaconda3/envs/bmtk/bin/arm64-apple-darwin20.0.0-clang++ - skipped\n", "-- Detecting CXX compile features\n", "-- Detecting CXX compile features - done\n", "\u001b[0m\u001b[0m\n", "\u001b[0m-------------------------------------------------------\u001b[0m\n", "\u001b[0mnestml_izh_module Configuration Summary\u001b[0m\n", "\u001b[0m-------------------------------------------------------\u001b[0m\n", "\u001b[0m\u001b[0m\n", "\u001b[0mC++ compiler : /opt/anaconda3/envs/bmtk/bin/arm64-apple-darwin20.0.0-clang++\u001b[0m\n", "\u001b[0mBuild static libs : OFF\u001b[0m\n", "\u001b[0mC++ compiler flags : -ftree-vectorize -fPIC -fstack-protector-strong -O2 -pipe -stdlib=libc++ -fvisibility-inlines-hidden -fmessage-length=0 -isystem /opt/anaconda3/envs/bmtk/include\u001b[0m\n", "\u001b[0mNEST compiler flags : -ftree-vectorize -fPIC -fstack-protector-strong -O2 -pipe -stdlib=libc++ -fvisibility-inlines-hidden -fmessage-length=0 -isystem /opt/anaconda3/envs/bmtk/include -fdebug-prefix-map=/Users/runner/miniforge3/conda-bld/nest-simulator_1716545272563/work=/usr/local/src/conda/nest-simulator-3.7 -fdebug-prefix-map=/opt/anaconda3/envs/bmtk=/usr/local/src/conda-prefix -std=c++17 -Wall -Xclang -fopenmp -O2\u001b[0m\n", "\u001b[0mNEST include dirs : -I/opt/anaconda3/envs/bmtk/include/nest -I/opt/anaconda3/envs/bmtk/include -I/opt/anaconda3/envs/bmtk/include -I/opt/anaconda3/envs/bmtk/include -I/opt/anaconda3/envs/bmtk/include\u001b[0m\n", "\u001b[0mNEST libraries flags : -L/opt/anaconda3/envs/bmtk/lib/nest -lnest -lsli /opt/anaconda3/envs/bmtk/lib/libltdl.dylib /opt/anaconda3/envs/bmtk/lib/libreadline.dylib /opt/anaconda3/envs/bmtk/lib/libncurses.dylib /opt/anaconda3/envs/bmtk/lib/libgsl.dylib /opt/anaconda3/envs/bmtk/lib/libgslcblas.dylib /opt/anaconda3/envs/bmtk/lib/libomp.dylib\u001b[0m\n", "\u001b[0m\u001b[0m\n", "\u001b[0m-------------------------------------------------------\u001b[0m\n", "\u001b[0m\u001b[0m\n", "\u001b[0mYou can now build and install 'nestml_izh_module' using\u001b[0m\n", "\u001b[0m make\u001b[0m\n", "\u001b[0m make install\u001b[0m\n", "\u001b[0m\u001b[0m\n", "\u001b[0mThe library file libnestml_izh_module.so will be installed to\u001b[0m\n", "\u001b[0m /var/folders/pr/_10c7kr546b1t7cf9z259z_40000gp/T/nestml_target_4yafvsuk\u001b[0m\n", "\u001b[0mThe module can be loaded into NEST using\u001b[0m\n", "\u001b[0m (nestml_izh_module) Install (in SLI)\u001b[0m\n", "\u001b[0m nest.Install(nestml_izh_module) (in PyNEST)\u001b[0m\n", "\u001b[0m\u001b[0m\n", "\u001b[33mCMake Warning (dev) in CMakeLists.txt:\n", " No cmake_minimum_required command is present. A line of code such as\n", "\n", " cmake_minimum_required(VERSION 3.29)\n", "\n", " should be added at the top of the file. The version specified may be lower\n", " if you wish to support older CMake versions for this project. For more\n", " information run \"cmake --help-policy CMP0000\".\n", "This warning is for project developers. Use -Wno-dev to suppress it.\n", "\u001b[0m\n", "-- Configuring done (1.3s)\n", "-- Generating done (0.0s)\n", "-- Build files have been written to: /Users/beatriz.herrera/Documents/GitHub/bmtk/docs/tutorial/02_opt_nest_custom_models/components/nestml\n", "[ 66%] \u001b[32mBuilding CXX object CMakeFiles/nestml_izh_module_module.dir/nestml_izh_module.o\u001b[0m\n", "[ 66%] \u001b[32mBuilding CXX object CMakeFiles/nestml_izh_module_module.dir/custom_izh_neuron_nestml.o\u001b[0m\n", "/Users/beatriz.herrera/Documents/GitHub/bmtk/docs/tutorial/02_opt_nest_custom_models/components/nestml/custom_izh_neuron_nestml.cpp:186:16: warning: unused variable '__timestep' [-Wunused-variable]\n", " 186 | const double __timestep = nest::Time::get_resolution().get_ms(); // do not remove, this is necessary for the timestep() function\n", " | ^~~~~~~~~~\n", "/Users/beatriz.herrera/Documents/GitHub/bmtk/docs/tutorial/02_opt_nest_custom_models/components/nestml/custom_izh_neuron_nestml.cpp:262:16: warning: unused variable '__timestep' [-Wunused-variable]\n", " 262 | const double __timestep = nest::Time::get_resolution().get_ms(); // do not remove, this is necessary for the timestep() function\n", " | ^~~~~~~~~~\n", "/Users/beatriz.herrera/Documents/GitHub/bmtk/docs/tutorial/02_opt_nest_custom_models/components/nestml/custom_izh_neuron_nestml.cpp:315:10: warning: unused variable 'get_t' [-Wunused-variable]\n", " 315 | auto get_t = [origin, lag](){ return nest::Time( nest::Time::step( origin.get_steps() + lag + 1) ).get_ms(); };\n", " | ^~~~~\n", "/Users/beatriz.herrera/Documents/GitHub/bmtk/docs/tutorial/02_opt_nest_custom_models/components/nestml/custom_izh_neuron_nestml.cpp:309:16: warning: unused variable '__timestep' [-Wunused-variable]\n", " 309 | const double __timestep = nest::Time::get_resolution().get_ms(); // do not remove, this is necessary for the timestep() function\n", " | ^~~~~~~~~~\n", "/Users/beatriz.herrera/Documents/GitHub/bmtk/docs/tutorial/02_opt_nest_custom_models/components/nestml/custom_izh_neuron_nestml.cpp:478:16: warning: unused variable '__timestep' [-Wunused-variable]\n", " 478 | const double __timestep = nest::Time::get_resolution().get_ms(); // do not remove, this is necessary for the timestep() function \n", " | ^~~~~~~~~~\n", "5 warnings generated.\n", "[100%] \u001b[32m\u001b[1mLinking CXX shared module nestml_izh_module.so\u001b[0m\n", "[100%] Built target nestml_izh_module_module\n", "[100%] Built target nestml_izh_module_module\n", "\u001b[36mInstall the project...\u001b[0m\n", "-- Install configuration: \"\"\n", "-- Installing: /var/folders/pr/_10c7kr546b1t7cf9z259z_40000gp/T/nestml_target_4yafvsuk/nestml_izh_module.so\n", "nestml_izh_module custom_izh_neuron_nestml\n" ] } ], "source": [ "from pynestml.codegeneration.nest_code_generator_utils import NESTCodeGeneratorUtils\n", "\n", "\n", "module_name, neuron_model_name = NESTCodeGeneratorUtils.generate_code_for(\n", " nestml_izh_model,\n", " module_name='nestml_izh_module',\n", " target_path='components/nestml'\n", ")\n", "\n", "print(module_name, neuron_model_name)" ] }, { "cell_type": "markdown", "id": "8079de05-1e0f-4849-99df-3c69b68762cf", "metadata": {}, "source": [ "If successful, you should see it call `cmake` and `make` commands to compile the model in nest and create a library *.so (along with source code) in the **components/nestml/** directory.\n", "\n", "**Note** that although in this case we passed in the model as a string into the `generate_code_for` function, you also have an option of saving the model to a *.nestml textfile and passing it in. In said case it would just be the following\n", "\n", "```python\n", "module_name, neuron_model_name = NESTCodeGeneratorUtils.generate_code_for(\n", " 'nestml_izh_model.nestml',\n", " module_name='nestml_izh_module',\n", " target_path='components/nestml'\n", ")\n", "```\n" ] }, { "cell_type": "markdown", "id": "5f40741c-5b99-4b33-83c3-d20ab79e35a2", "metadata": {}, "source": [ "### 3. Using the NESTML model in PointNet" ] }, { "cell_type": "markdown", "id": "29ba5b9b-c083-4cf2-9d24-e185372cd419", "metadata": {}, "source": [ "Once we have compiled our module (eg .so or .a file) in the `target_path` folder we can go ahead and use the models like we would with a built-in nest models. In this case we have a network pre-built (*build_network.nestml.py*) in the network_nestml/ folder. First thing we must do is make sure that our **model_template** attributes point to the correct name of the models and have corresponding parameter files.\n", "\n", "Note that by default NESTML append the suffix \"_nestml\" to the name of our model. " ] }, { "cell_type": "code", "execution_count": 7, "id": "ae33f42b-d1a3-4f63-8c93-540e5fc1f568", "metadata": {}, "outputs": [ { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>node_type_id</th>\n", " <th>ei</th>\n", " <th>model_type</th>\n", " <th>dynamics_params</th>\n", " <th>pop_name</th>\n", " <th>model_template</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>0</th>\n", " <td>100</td>\n", " <td>e</td>\n", " <td>point_neuron</td>\n", " <td>custom_izh_neuron_exc.json</td>\n", " <td>LIF_exc</td>\n", " <td>nestml:custom_izh_neuron_nestml</td>\n", " </tr>\n", " <tr>\n", " <th>1</th>\n", " <td>101</td>\n", " <td>i</td>\n", " <td>point_neuron</td>\n", " <td>custom_izh_neuron_inh.json</td>\n", " <td>LIF_inh</td>\n", " <td>nestml:custom_izh_neuron_nestml</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "</div>" ], "text/plain": [ " node_type_id ei model_type dynamics_params pop_name \\\n", "0 100 e point_neuron custom_izh_neuron_exc.json LIF_exc \n", "1 101 i point_neuron custom_izh_neuron_inh.json LIF_inh \n", "\n", " model_template \n", "0 nestml:custom_izh_neuron_nestml \n", "1 nestml:custom_izh_neuron_nestml " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "\n", "pd.read_csv('network_nestml/izh_network_node_types.csv', sep=' ')" ] }, { "cell_type": "markdown", "id": "17fede69-e4f3-4cd6-8732-c88a71758116", "metadata": {}, "source": [ "We must also let PointNet know where the corresponding *.so file is containing the compiled model. In our SONATA configuration file we add the a \"nest_modules\" entry to the **\"components\"**\n", "\n", "```json\n", " \"components\": {\n", " \"point_neuron_models_dir\": \"$MODELS_DIR/cell_models\",\n", "\t \"synaptic_models_dir\": \"$MODELS_DIR/synaptic_models\",\n", " \"nest_modules\": \"$MODELS_DIR/nestml/nestml_izh_module.so\"\n", " }\n", "```\n", "\n", "If you have multiple NEST and/or NESTML modules you can either specify \"nest_modules\" as a directory\n", "\n", "```json\n", "\n", "\n", " \"nest_modules\": \"/path/to/modules/dir/\"\n", "```\n", "\n", "and BMTK will try to find and load all library files in the folder, or alternatively you can pass in a list of library binaries:\n", "\n", "```json\n", " \"nest_modules\": [\"/first/path/to/module1.so\", \"/second/path/to/module2.so\", \"...\"]\n", "```\n", "\n", "and run the simulation as normal" ] }, { "cell_type": "code", "execution_count": 8, "id": "2479269e-ec10-4b39-8e0b-f14ed214d5ba", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,097 [INFO] Created log file\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Created log file\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,129 [INFO] Batch processing nodes for izh_network/0.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Batch processing nodes for izh_network/0.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,143 [INFO] Batch processing nodes for virt_exc/0.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Batch processing nodes for virt_exc/0.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,319 [INFO] Setting up output directory\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Setting up output directory\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,320 [INFO] Building cells.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Building cells.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,322 [INFO] Building recurrent connections\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Building recurrent connections\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,325 [INFO] Network created.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Network created.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,326 [INFO] Build virtual cell stimulations for thalamus_spikes\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Build virtual cell stimulations for thalamus_spikes\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,335 [INFO] Starting Simulation\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Starting Simulation\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,875 [INFO] Simulation finished, finalizing results.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Simulation finished, finalizing results.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,893 [INFO] Done.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Done.\n" ] } ], "source": [ "from bmtk.simulator import pointnet\n", "\n", "configure = pointnet.Config.from_json('config.nestml.json')\n", "configure.build_env()\n", "\n", "network = pointnet.PointNetwork.from_config(configure)\n", "sim = pointnet.PointSimulator.from_config(configure, network)\n", "sim.run()" ] }, { "cell_type": "markdown", "id": "5232f217-7b0c-4ebd-b2d4-4d93e320f85a", "metadata": {}, "source": [ "## 3. Example: Overriding the creation of cell models <a class=\"anchor\" id=\"example-cell-creation\">" ] }, { "cell_type": "markdown", "id": "27bfbb77-ec4e-4f9d-bb59-fd5b6889df5c", "metadata": {}, "source": [ "Before BMTK starts a simulation, it will go ahead and call on NEST to create neurons for all cells using attributes like **model_type**, **model_template**, and **dynamics_params**. BMTK will initialize the creation of these cells for you and for the vast majority of simulations the user does not have to do anything special other than specify the models and parameters in the SONATA file. \n", "\n", "However, on rare occasions you may find yourself requiring more grainular control of how PointNet initializes and creates the neurons before the simulation begins. For example, you may want to find or alter cell parameters at the start of a simulation. Some models may require a non-standard way of initializing them. Or you may need to attach Parrot or Relay neurons to a subset of your cells. \n", "\n", "When BMTK initializes cells for simualation it will normally call the function `loadNESTModel` function in [`bmtk.simulation.pointnet.default_setters.cell_models`](). This is a simple function that calls that NEST `Create` function to initialize a batch of neurons that share the same model-name and properties.\n", "\n", "```python\n", "def loadNESTModel(cell, template_name, dynamics_params):\n", " return nest.Create(template_name, cell.n_nodes, dynamics_params)\n", "```\n", "\n", "If you need to override this function, BMTK provides a special [python decorator](https://peps.python.org/pep-0318/) called `@cell_model`. To use the decorator you can add the following to your *run_pointnet.py* (or in a separate python file imported before you run the simulation) as such\n", "\n", "```python\n", "from bmtk.simulator.pointnet import cell_model\n", "\n", "@cell_model(directive='nest', model_type='point_neuron')\n", "def loadNESTModel(cell, template_name, dynamics_params):\n", " nest_nodes = ... # create and initialize cells\n", " return nest_nodes\n", "```\n", "To explain what is happening in this function\n", "* The `@cell_model` decorator is placed right before our user defined function in order to tell bmtk that this is a special function that will be used in the creation of \"nest\" cells\n", " * The parameters tell BMTK that this function will be called for every group of cells with **model_template** of format `nest:...` and have **model_type** of point_neuron\n", "* There are three parameters the BMTK will pass into this function\n", " * `cell` is copy of cell object and can be used to access all of an cell(s) properities (can be accessed like a dictionary)\n", " * `template` will be the name of the model (eg. izhikevich, glif_psc, hh_cond, etc)\n", " * `dynamics_params` will be a dictionary containing the contents of the json or hdf5 **dynamics_params**.\n", "* Inside the function we should create our cell(s), usually using the NEST [Create](https://nest-simulator.readthedocs.io/en/v3.3/ref_material/pynest_apis.html#module-nest.lib.hl_api_nodes) function.\n", "* You must return the results of the Create function, usually a list of ids, back to BMTK.\n", "\n", "During the initialization process BMTK will call this custom function. It tries to do so in batches, often initializing cells with the same models and signitures in batches.\n" ] }, { "cell_type": "markdown", "id": "f1e646fd-c529-407c-a8c6-bc8e533641a7", "metadata": {}, "source": [ "<div class=\"alert alert-block alert-info\">\n", "⚠️ WARNING: Overriding loadNESTModel can be dangerous\n", "</div>\n", "\n", "If your network has many different models then overriding `loadNESTModel` to deal with only a subset of the cells can have unintended side-effects. However BMTK lets you create specific load methods for different models\n" ] }, { "cell_type": "markdown", "id": "d12fcb57-8c57-4fb8-86f1-6c8a431e44c9", "metadata": {}, "source": [ "### Overriding Izhikeich model\n", "\n", "For example, let's say we want to modify and adjust parameters on-the-fly (eg. we don't want to have to rebuild the network every time) but only for the `izhikevich` models. Specially we want to add jitter to the \"d\" parameter which won't exist for other model types. To do so we add a function `loadIzhikevich` and in the `@cell_model` parameters make sure that it only applies to `nest:Izhikevich` models, so that all other model types gets created in the default manner.\n", "\n", "We add the following function" ] }, { "cell_type": "code", "execution_count": 9, "id": "605848aa-1c59-4929-a68f-73641e1dfec9", "metadata": {}, "outputs": [], "source": [ "from bmtk.simulator.pointnet import cell_model\n", "from bmtk.simulator.pointnet.io_tools import io\n", "import nest\n", "\n", "@cell_model(directive='nest:izhikevich', model_type='point_neuron')\n", "def loadIzhikevich(cell, template_name, dynamics_params):\n", " nodes = nest.Create(template_name, cell.n_nodes, dynamics_params)\n", " \n", " d_orig = nest.GetStatus(nodes, 'd')\n", " jitter = cell['jitter']\n", " d_noisy = d_orig*jitter\n", " nest.SetStatus(nodes, {'d': d_noisy})\n", " io.log_info(f'Modifying the parameters of {cell.n_nodes} {template_name} neurons.')\n", " return nodes" ] }, { "cell_type": "markdown", "id": "2238dbbe-25fa-4143-808c-2b29c838f5a4", "metadata": {}, "source": [ "* First we create the cells as normal using the `Create` method. Then we use the `nest.GetStatus` and `nest.SetStatus` to update the \"d\" parameters for each cell. It is sometimes possible to alter the way `Create()` method is called, but in general it is best to use [GetStatus](https://nest-simulator.readthedocs.io/en/v3.3/ref_material/pynest_apis.html#module-nest.lib.hl_api_info) to alter parameters\n", "* The network has a jitter parameter which we can multiply by the default \"d\" value (it is stored in the hdf5 file). We call `cell[jitter]` to get this value as an array, and can use `cell` to fetch other cell attributes.\n", "* As an optional measure we add some logging. This is a good sanity check to make sure our funnction is being called as it should.\n", "\n", "After we added our custom function we can load PointNet in the normal manner:" ] }, { "cell_type": "code", "execution_count": 10, "id": "0acdb9d5-cd8a-441b-a4aa-5da5f879cdb0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,907 [INFO] Created log file\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Created log file\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,930 [INFO] Batch processing nodes for net/0.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Batch processing nodes for net/0.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,938 [INFO] Batch processing nodes for virt_exc/0.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Batch processing nodes for virt_exc/0.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,958 [INFO] Setting up output directory\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Setting up output directory\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,959 [INFO] Building cells.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Building cells.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,960 [INFO] Modifying the parameters of 10 izhikevich neurons.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Modifying the parameters of 10 izhikevich neurons.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,961 [INFO] Building recurrent connections\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Building recurrent connections\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,963 [INFO] Network created.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Network created.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,963 [INFO] Build virtual cell stimulations for thalamus_spikes\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Build virtual cell stimulations for thalamus_spikes\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:51,971 [INFO] Starting Simulation\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Starting Simulation\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:52,297 [INFO] Simulation finished, finalizing results.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Simulation finished, finalizing results.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2024-10-21 15:11:52,316 [INFO] Done.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:Done.\n" ] } ], "source": [ "from bmtk.simulator import pointnet\n", "\n", "configure = pointnet.Config.from_json('config.iz_updated.json')\n", "configure.build_env()\n", "\n", "network = pointnet.PointNetwork.from_config(configure)\n", "sim = pointnet.PointSimulator.from_config(configure, network)\n", "sim.run()" ] }, { "cell_type": "markdown", "id": "d1fb1efd-fe46-4b56-890b-c691db36aeab", "metadata": {}, "source": [ "## 4. Example: Switching between synaptic (and electrical) models <a class=\"anchor\" id=\"example-switching-models\">" ] }, { "cell_type": "markdown", "id": "09f7ecab-5329-4d4a-b43e-282529e70206", "metadata": {}, "source": [ "Similar to cell models, PointNet also allows modelers to switch different types of synapses between simulations. Like cells, SONATA groups synaptic and electrical (gap junction) connections (this is, edges) by type and parameters, and you can have a network where different connections have different models. " ] }, { "cell_type": "markdown", "id": "e660528c-e393-4d02-b22f-a3d24bf1a54a", "metadata": {}, "source": [ "And like with the cells, the type of edge models in a network are controlled by the SONATA **model_template** and **dynamics_params** attributes. In the previous examples we have been using the default `static_synapse`, but the NEST simulator has a variety of different [built-in synaptic model](https://nest-simulator.readthedocs.io/en/v3.3/models/index.html#synapse) that can be readily taken advantage of in BMTK. If we wanted to replace it with one of the synapses that support [short-term plasticity](https://nest-simulator.readthedocs.io/en/v3.3/models/tsodyks2_synapse.html) we would modify the build network `add_edges` call to:\n", "```python\n", "\n", "net.add_edges(\n", " ...\n", " model_template='tsodysk2_synapse',\n", " dynamics_params='tsodysk2.syn_exc.json'\n", " ...\n", ")\n", "```\n", "* unlike with cell, the SONATA format does not require a `nest:` schema prefix for the **model_template**\n", "* *tsodysk2.syn_exc.json* is a .json file that is saved in the \"synaptic_models_dir\", and each synapse model will have different parameter that will need to be adjusted by the modeler." ] }, { "cell_type": "markdown", "id": "3f7f919c-9d15-4927-9751-5fa0b50198f2", "metadata": {}, "source": [ "Or if the modeler is unable to rebuild the network from scratch, another option for switching between model is to open the *network/\\*edge_types.csv* file in a text editor and switch the **model_template** and **dynamics_params** values for the appropiate rows:" ] }, { "cell_type": "code", "execution_count": 11, "id": "3862d4e2-e51c-4a0e-95e8-a7cf3377c754", "metadata": {}, "outputs": [ { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>node_type_id</th>\n", " <th>model_template</th>\n", " <th>model_type</th>\n", " <th>dynamics_params</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>0</th>\n", " <td>100</td>\n", " <td>nest:izhikevich</td>\n", " <td>point_neuron</td>\n", " <td>custom_model_params.izhikevich.json</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "</div>" ], "text/plain": [ " node_type_id model_template model_type \\\n", "0 100 nest:izhikevich point_neuron \n", "\n", " dynamics_params \n", "0 custom_model_params.izhikevich.json " ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "\n", "pd.read_csv('network_stp_syns/net_node_types.csv', sep=' ')" ] }, { "cell_type": "code", "execution_count": null, "id": "52deba3e-fe57-41bc-9840-b37c53076182", "metadata": {}, "outputs": [], "source": [] } ], "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.10.16" } }, "nbformat": 4, "nbformat_minor": 5 }