From 379005c009b0f17f7ca6248d931e0ce1ce981465 Mon Sep 17 00:00:00 2001 From: "C.A.P. Linssen" Date: Tue, 26 Jul 2022 16:01:03 -0700 Subject: [PATCH 1/3] add NESTML support to BMTK --- bmtk/simulator/pointnet/sonata_adaptors.py | 31 +++++ examples/point_120cells/README.md | 3 +- examples/point_120cells/build_network.py | 4 +- .../network/cortex_node_types.csv | 4 +- .../cell_models/iaf_psc_delta.nestml | 125 ++++++++++++++++++ 5 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 examples/point_components/cell_models/iaf_psc_delta.nestml diff --git a/bmtk/simulator/pointnet/sonata_adaptors.py b/bmtk/simulator/pointnet/sonata_adaptors.py index bb3296f95..11e171ad9 100644 --- a/bmtk/simulator/pointnet/sonata_adaptors.py +++ b/bmtk/simulator/pointnet/sonata_adaptors.py @@ -1,7 +1,9 @@ +import logging import numpy as np from collections import Counter import numbers import nest +import os import types import pandas as pd @@ -11,6 +13,8 @@ from bmtk.simulator.pointnet.glif_utils import convert_aibs2nest from bmtk.simulator.pointnet.nest_utils import nest_version +logger = logging.getLogger(__name__) + NEST_SYNAPSE_MODEL_PROP = 'model' if nest_version[0] == 2 else 'synapse_model' @@ -155,6 +159,7 @@ def get_batches(self, node_group): def preprocess_node_types(network, node_population): NodeAdaptor.preprocess_node_types(network, node_population) node_types_table = node_population.types_table + load_nestml_module = False if 'model_template' in node_types_table.columns and 'dynamics_params' in node_types_table.columns: node_type_ids = np.unique(node_population.type_ids) for nt_id in node_type_ids: @@ -166,6 +171,32 @@ def preprocess_node_types(network, node_population): node_type_attrs['model_template'] = model_template node_type_attrs['dynamics_params'] = dynamics_params + if mtemplate.startswith('nestml:'): + # NESTML model was requested + nestml_model_name_suffix = '_nestml' + + from pynestml.frontend.pynestml_frontend import generate_nest_target + import nest + + model_name = mtemplate.split(':')[1] + logger.info('Generating NESTML model \'' + model_name + '\'') + models_dir = network.get_component('point_neuron_models_dir') + model_fn = os.path.join(models_dir, model_name + '.nestml') + print('model_fn = ' + str(model_fn)) + generate_nest_target(input_path=model_fn, + logging_level='ERROR', + suffix=nestml_model_name_suffix) + load_nestml_module = True + + # replace model_template name with suffixed version + node_types_table[nt_id]['model_template'] = 'nest:' + model_name + nestml_model_name_suffix + node_types_table._df_cache = None # clear the table internal cache + + if load_nestml_module: + # a module by the same name can only be loaded once; do this at the very end of the function + nest.Install('nestmlmodule') + + @staticmethod def patch_adaptor(adaptor, node_group, network): node_adaptor = NodeAdaptor.patch_adaptor(adaptor, node_group, network) diff --git a/examples/point_120cells/README.md b/examples/point_120cells/README.md index cb3a286ec..716d54a46 100644 --- a/examples/point_120cells/README.md +++ b/examples/point_120cells/README.md @@ -1,6 +1,7 @@ # 120 point neuron network -A small network of 120 point-neurons. Uses PointNet and will require NEST to run. +A small network of 120 point-neurons. The neuron models are specified as NESTML models. Uses PointNet and will require +NEST and NESTML to run. ## Running: diff --git a/examples/point_120cells/build_network.py b/examples/point_120cells/build_network.py index 28265c8bf..82c98704c 100644 --- a/examples/point_120cells/build_network.py +++ b/examples/point_120cells/build_network.py @@ -10,7 +10,7 @@ 'ei': 'e', 'pop_name': 'LIF_exc', 'model_type': 'point_neuron', - 'model_template': 'nest:iaf_psc_delta', + 'model_template': 'nestml:iaf_psc_delta', 'dynamics_params': 'iaf_psc_delta_exc.json' }, 'LIF_inh': { @@ -18,7 +18,7 @@ 'ei': 'i', 'pop_name': 'LIF_inh', 'model_type': 'point_neuron', - 'model_template': 'nest:iaf_psc_delta', + 'model_template': 'nestml:iaf_psc_delta', 'dynamics_params': 'iaf_psc_delta_inh.json' } } diff --git a/examples/point_120cells/network/cortex_node_types.csv b/examples/point_120cells/network/cortex_node_types.csv index 1429b6eb4..1604abf3d 100644 --- a/examples/point_120cells/network/cortex_node_types.csv +++ b/examples/point_120cells/network/cortex_node_types.csv @@ -1,3 +1,3 @@ node_type_id ei dynamics_params model_type model_template pop_name -100 e iaf_psc_delta_exc.json point_neuron nest:iaf_psc_delta LIF_exc -101 i iaf_psc_delta_inh.json point_neuron nest:iaf_psc_delta LIF_inh +100 e iaf_psc_delta_exc.json point_neuron nestml:iaf_psc_delta LIF_exc +101 i iaf_psc_delta_inh.json point_neuron nestml:iaf_psc_delta LIF_inh diff --git a/examples/point_components/cell_models/iaf_psc_delta.nestml b/examples/point_components/cell_models/iaf_psc_delta.nestml new file mode 100644 index 000000000..58c60ac79 --- /dev/null +++ b/examples/point_components/cell_models/iaf_psc_delta.nestml @@ -0,0 +1,125 @@ +""" +iaf_psc_delta - Current-based leaky integrate-and-fire neuron model with delta-kernel post-synaptic currents +############################################################################################################ + +Description ++++++++++++ + +iaf_psc_delta is an implementation of a leaky integrate-and-fire model +where the potential jumps on each spike arrival. + +The threshold crossing is followed by an absolute refractory period +during which the membrane potential is clamped to the resting potential. + +Spikes arriving while the neuron is refractory, are discarded by +default. If the property ``with_refr_input`` is set to true, such +spikes are added to the membrane potential at the end of the +refractory period, dampened according to the interval between +arrival and end of refractoriness. + +The general framework for the consistent formulation of systems with +neuron like dynamics interacting by point events is described in +[1]_. A flow chart can be found in [2]_. + +Critical tests for the formulation of the neuron model are the +comparisons of simulation results for different computation step +sizes. sli/testsuite/nest contains a number of such tests. + +The iaf_psc_delta is the standard model used to check the consistency +of the nest simulation kernel because it is at the same time complex +enough to exhibit non-trivial dynamics and simple enough compute +relevant measures analytically. + + +References +++++++++++ + +.. [1] Rotter S, Diesmann M (1999). Exact simulation of + time-invariant linear systems with applications to neuronal + modeling. Biologial Cybernetics 81:381-402. + DOI: https://doi.org/10.1007/s004220050570 +.. [2] Diesmann M, Gewaltig M-O, Rotter S, & Aertsen A (2001). State + space analysis of synchronous spiking in cortical neural + networks. Neurocomputing 38-40:565-571. + DOI: https://doi.org/10.1016/S0925-2312(01)00409-X + + +See also +++++++++ + +iaf_psc_alpha, iaf_psc_exp +""" +neuron iaf_psc_delta: + + state: + refr_spikes_buffer mV = 0 mV + r integer = 0 # counts number of tick during the refractory period + V_abs mV = 0 mV + end + + equations: + kernel G = delta(t) + recordable inline V_m mV = V_abs + E_L # Membrane potential. + V_abs' = -V_abs / tau_m + (mV / pA / ms) * convolve(G, spikes) + (I_e + I_stim) / C_m + end + + parameters: + tau_m ms = 10 ms # Membrane time constant. + C_m pF = 250 pF # Capacity of the membrane + t_ref ms = 2 ms # Duration of refractory period. + tau_syn ms = 2 ms # Time constant of synaptic current. + E_L mV = -70 mV # Resting membrane potential. + V_reset mV = -70 mV - E_L # Reset potential of the membrane. + Theta mV = -55 mV - E_L # Spike threshold. + V_min mV = -inf * 1 mV # Absolute lower value for the membrane potential + with_refr_input boolean = false # If true, do not discard input during refractory period. Default: false. + + # constant external input current + I_e pA = 0 pA + end + + internals: + h ms = resolution() + RefractoryCounts integer = steps(t_ref) # refractory time in steps + end + + input: + spikes pA <- spike + I_stim pA <- continuous + end + + output: spike + + update: + if r == 0: # neuron not refractory + integrate_odes() + + # if we have accumulated spikes from refractory period, + # add and reset accumulator + if with_refr_input and refr_spikes_buffer != 0.0 mV: + V_abs += refr_spikes_buffer + refr_spikes_buffer = 0.0 mV + end + + # lower bound of membrane potential + V_abs = V_abs < V_min?V_min:V_abs + + else: # neuron is absolute refractory + # read spikes from buffer and accumulate them, discounting + # for decay until end of refractory period + # the buffer is clear automatically + if with_refr_input: + refr_spikes_buffer += spikes * exp(-r * h / tau_m) * mV/pA + end + r -= 1 + end + + if V_abs >= Theta: # threshold crossing + r = RefractoryCounts + V_abs = V_reset + emit_spike() + end + + end + +end From 19bd46163337c64ddc8bb4324f9cc1221ca8a4e8 Mon Sep 17 00:00:00 2001 From: Xiao-Ping Liu Date: Wed, 9 Nov 2022 14:20:27 -0800 Subject: [PATCH 2/3] nestml integration draft 2 --- bmtk/simulator/pointnet/pointnetwork.py | 33 ++++- bmtk/simulator/pointnet/pointsimulator.py | 8 +- bmtk/simulator/pointnet/sonata_adaptors.py | 48 ++++--- examples/point_120cells/README.md | 3 +- examples/point_120cells/build_network.py | 4 +- .../network/cortex_node_types.csv | 4 +- examples/point_120cells_nestml/README.md | 69 ++++++++++ .../point_120cells_nestml/build_network.py | 116 +++++++++++++++++ .../point_120cells_nestml/config.circuit.json | 35 ++++++ .../config.simulation.json | 50 ++++++++ .../config.simulation_perturbations.json | 69 ++++++++++ .../point_120cells_nestml/create_inputs.py | 25 ++++ .../inputs/thalamus_spikes.h5 | Bin 0 -> 45608 bytes examples/point_120cells_nestml/plot_output.py | 15 +++ .../point_120cells_nestml/run_pointnet.py | 18 +++ .../cell_models/iaf_psc_alpha.nestml | 119 ++++++++++++++++++ .../synaptic_models/static_synapse.nestml | 26 ++++ 17 files changed, 608 insertions(+), 34 deletions(-) create mode 100644 examples/point_120cells_nestml/README.md create mode 100644 examples/point_120cells_nestml/build_network.py create mode 100644 examples/point_120cells_nestml/config.circuit.json create mode 100644 examples/point_120cells_nestml/config.simulation.json create mode 100644 examples/point_120cells_nestml/config.simulation_perturbations.json create mode 100644 examples/point_120cells_nestml/create_inputs.py create mode 100644 examples/point_120cells_nestml/inputs/thalamus_spikes.h5 create mode 100644 examples/point_120cells_nestml/plot_output.py create mode 100644 examples/point_120cells_nestml/run_pointnet.py create mode 100644 examples/point_components/cell_models/iaf_psc_alpha.nestml create mode 100644 examples/point_components/synaptic_models/static_synapse.nestml diff --git a/bmtk/simulator/pointnet/pointnetwork.py b/bmtk/simulator/pointnet/pointnetwork.py index 89877b351..e3852caed 100644 --- a/bmtk/simulator/pointnet/pointnetwork.py +++ b/bmtk/simulator/pointnet/pointnetwork.py @@ -76,7 +76,6 @@ def __init__(self, **properties): self._params_cache = {} self._virtual_ids_map = {} - self._batch_nodes = True # self._nest_id_map = {} @@ -90,6 +89,8 @@ def __init__(self, **properties): self._gid_map = GidPool() self._virtual_gids = GidPool() + self._nestml_models = [] + @property def py_function_caches(self): return pyfunction_cache @@ -168,6 +169,9 @@ def build_recurrent_edges(self, force_resolution=False): for edge in edge_pop.get_edges(): nest_srcs = self.gid_map.get_nestids(edge_pop.source_nodes, edge.source_node_ids) nest_trgs = self.gid_map.get_nestids(edge_pop.target_nodes, edge.target_node_ids) + if np.isscalar(edge.nest_params['weight']): + edge.nest_params['weight'] = np.full(shape=len(nest_srcs), + fill_value=edge.nest_params['weight']) self._nest_connect(nest_srcs, nest_trgs, conn_spec='one_to_one', syn_spec=edge.nest_params) def find_edges(self, source_nodes=None, target_nodes=None): @@ -219,6 +223,9 @@ def add_spike_trains(self, spike_trains, node_set, sg_params={'precise_times': T for edge in edge_pop.get_edges(): nest_trgs = self.gid_map.get_nestids(edge_pop.target_nodes, edge.target_node_ids) nest_srcs = virt_gid_map.get_nestids(edge_pop.source_nodes, edge.source_node_ids) + if np.isscalar(edge.nest_params['weight']): + edge.nest_params['weight'] = np.full(shape=len(nest_srcs), + fill_value=edge.nest_params['weight']) self._nest_connect(nest_srcs, nest_trgs, conn_spec='one_to_one', syn_spec=edge.nest_params) def _nest_connect(self, nest_srcs, nest_trgs, conn_spec='one_to_one', syn_spec=None): @@ -230,7 +237,7 @@ def _nest_connect(self, nest_srcs, nest_trgs, conn_spec='one_to_one', syn_spec=N # An occuring issue is when dt > delay, add some extra messaging in log to help users fix problem. res_kernel = nest.GetKernelStatus().get('resolution', 'NaN') delay_edges = syn_spec.get('delay', 'NaN') - msg = 'synaptic "delay" value in edges ({}) is not compatable with simulator resolution/"dt" ({})'.format( + msg = 'synaptic "delay" value in edges ({}) is not compatible with simulator resolution/"dt" ({})'.format( delay_edges, res_kernel ) self.io.log_error('{}{}'.format(bde.errorname, bde.errormessage)) @@ -241,3 +248,25 @@ def _nest_connect(self, nest_srcs, nest_trgs, conn_spec='one_to_one', syn_spec=N # Record exception to log file. self.io.log_error(str(e)) raise + + def add_nestml_models(self, modelname): + if modelname not in self._nestml_models: + self._nestml_models.append(modelname) + + def initialize_nestml(self, rebuild_nestml=True): + from pynestml.frontend.pynestml_frontend import generate_nest_target + models_dir = os.path.commonpath([self.get_component('point_neuron_models_dir'), self.get_component('synaptic_models_dir')]) + nestml_model_name_suffix = '_nestml' + + if self._nestml_models: + if rebuild_nestml or not (os.path.exists(os.path.join(models_dir, 'nestml_target'))): + if not rebuild_nestml: + self.io.log_info('Cannot find nestml target, must rebuild') + generate_nest_target(input_path=self._nestml_models, + logging_level='ERROR', + suffix=nestml_model_name_suffix, + target_path=os.path.join(models_dir, 'nestml_target')) + self.io.log_info('Generating NESTML models: ', self._nestml_models) + # a module by the same name can only be loaded once; do this at the very end of the function + nest.Install('nestmlmodule') + diff --git a/bmtk/simulator/pointnet/pointsimulator.py b/bmtk/simulator/pointnet/pointsimulator.py index bd5f27ece..3c1454e9b 100644 --- a/bmtk/simulator/pointnet/pointsimulator.py +++ b/bmtk/simulator/pointnet/pointsimulator.py @@ -47,6 +47,7 @@ def __init__(self, graph, dt=0.001, overwrite=True, print_time=False): self._cells_built = False self._internal_connections_built = False + self._rebuild_nestml = True self._graph = graph self._external_cells = {} # dict-of-dict of external pointnet cells with keys [network_name][cell_id] @@ -228,6 +229,11 @@ def from_config(cls, configure, graph): if run_dict.get('allow_offgrid_spikes', False): network.set_spike_generator_params(allow_offgrid_spikes=True) + if 'rebuild_nestml' in config: + network._rebuild_nestml = config['rebuild_nestml'] + + graph.initialize_nestml(network._rebuild_nestml) + # Create the output-directory, or delete existing files if it already exists graph.io.log_info('Setting up output directory') if not os.path.exists(config['output']['output_dir']): @@ -276,7 +282,7 @@ def from_config(cls, configure, graph): mod = mods.SpikesMod(**report.params) elif isinstance(report, reports.MembraneReport): - # For convience and for compliance with SONATA format. "membrane_report" and "multimeter_report is the + # For convenience and for compliance with SONATA format. "membrane_report" and "multimeter_report is the # same in pointnet. mod = mods.MultimeterMod(**report.params) diff --git a/bmtk/simulator/pointnet/sonata_adaptors.py b/bmtk/simulator/pointnet/sonata_adaptors.py index 11e171ad9..342cb08e1 100644 --- a/bmtk/simulator/pointnet/sonata_adaptors.py +++ b/bmtk/simulator/pointnet/sonata_adaptors.py @@ -159,7 +159,7 @@ def get_batches(self, node_group): def preprocess_node_types(network, node_population): NodeAdaptor.preprocess_node_types(network, node_population) node_types_table = node_population.types_table - load_nestml_module = False + if 'model_template' in node_types_table.columns and 'dynamics_params' in node_types_table.columns: node_type_ids = np.unique(node_population.type_ids) for nt_id in node_type_ids: @@ -173,29 +173,17 @@ def preprocess_node_types(network, node_population): if mtemplate.startswith('nestml:'): # NESTML model was requested - nestml_model_name_suffix = '_nestml' - - from pynestml.frontend.pynestml_frontend import generate_nest_target - import nest model_name = mtemplate.split(':')[1] - logger.info('Generating NESTML model \'' + model_name + '\'') + models_dir = network.get_component('point_neuron_models_dir') model_fn = os.path.join(models_dir, model_name + '.nestml') - print('model_fn = ' + str(model_fn)) - generate_nest_target(input_path=model_fn, - logging_level='ERROR', - suffix=nestml_model_name_suffix) - load_nestml_module = True + network.add_nestml_models(model_fn) # replace model_template name with suffixed version - node_types_table[nt_id]['model_template'] = 'nest:' + model_name + nestml_model_name_suffix - node_types_table._df_cache = None # clear the table internal cache - - if load_nestml_module: - # a module by the same name can only be loaded once; do this at the very end of the function - nest.Install('nestmlmodule') + node_types_table[nt_id]['model_template'] = 'nest:' + model_name + '_nestml' + node_types_table._df_cache = None # clear the table internal cache @staticmethod def patch_adaptor(adaptor, node_group, network): @@ -287,14 +275,24 @@ def preprocess_edge_types(network, edge_population): # Fix for sonata/300_pointneurons EdgeAdaptor.preprocess_edge_types(network, edge_population) edge_types_table = edge_population.types_table - edge_type_ids = np.unique(edge_population.type_ids) - - for et_id in edge_type_ids: - edge_type = edge_types_table[et_id] - if 'model_template' in edge_types_table.columns: - model_template = edge_type['model_template'] - if model_template.startswith('nest'): - edge_type['model_template'] = model_template[5:] + + if 'model_template' in edge_types_table.columns and 'dynamics_params' in edge_types_table.columns: + edge_type_ids = np.unique(edge_population.type_ids) + for et_id in edge_type_ids: + edge_type_attrs = edge_types_table[et_id] + mtemplate = edge_type_attrs['model_template'] + if mtemplate.startswith('nest:'): + edge_type['model_template'] = mtemplate[5:] + + if mtemplate.startswith('nestml:'): + model_name = mtemplate.split(':')[1] + + models_dir = network.get_component('synaptic_models_dir') + model_fn = os.path.join(models_dir, model_name + '.nestml') + network.add_nestml_models(model_fn) + + edge_types_table[et_id]['model_template'] = model_name + '_nestml' + edge_types_table._df_cache = None # clear the table internal cache def get_batches(self, edge_group): src_ids = {} diff --git a/examples/point_120cells/README.md b/examples/point_120cells/README.md index 716d54a46..cb3a286ec 100644 --- a/examples/point_120cells/README.md +++ b/examples/point_120cells/README.md @@ -1,7 +1,6 @@ # 120 point neuron network -A small network of 120 point-neurons. The neuron models are specified as NESTML models. Uses PointNet and will require -NEST and NESTML to run. +A small network of 120 point-neurons. Uses PointNet and will require NEST to run. ## Running: diff --git a/examples/point_120cells/build_network.py b/examples/point_120cells/build_network.py index 82c98704c..28265c8bf 100644 --- a/examples/point_120cells/build_network.py +++ b/examples/point_120cells/build_network.py @@ -10,7 +10,7 @@ 'ei': 'e', 'pop_name': 'LIF_exc', 'model_type': 'point_neuron', - 'model_template': 'nestml:iaf_psc_delta', + 'model_template': 'nest:iaf_psc_delta', 'dynamics_params': 'iaf_psc_delta_exc.json' }, 'LIF_inh': { @@ -18,7 +18,7 @@ 'ei': 'i', 'pop_name': 'LIF_inh', 'model_type': 'point_neuron', - 'model_template': 'nestml:iaf_psc_delta', + 'model_template': 'nest:iaf_psc_delta', 'dynamics_params': 'iaf_psc_delta_inh.json' } } diff --git a/examples/point_120cells/network/cortex_node_types.csv b/examples/point_120cells/network/cortex_node_types.csv index 1604abf3d..1429b6eb4 100644 --- a/examples/point_120cells/network/cortex_node_types.csv +++ b/examples/point_120cells/network/cortex_node_types.csv @@ -1,3 +1,3 @@ node_type_id ei dynamics_params model_type model_template pop_name -100 e iaf_psc_delta_exc.json point_neuron nestml:iaf_psc_delta LIF_exc -101 i iaf_psc_delta_inh.json point_neuron nestml:iaf_psc_delta LIF_inh +100 e iaf_psc_delta_exc.json point_neuron nest:iaf_psc_delta LIF_exc +101 i iaf_psc_delta_inh.json point_neuron nest:iaf_psc_delta LIF_inh diff --git a/examples/point_120cells_nestml/README.md b/examples/point_120cells_nestml/README.md new file mode 100644 index 000000000..40de8f7f4 --- /dev/null +++ b/examples/point_120cells_nestml/README.md @@ -0,0 +1,69 @@ +# 120 point neuron network + +A small network of 120 point-neurons. Neuron and synapse models are specified as NESTML models to demonstrate how to incorporate NESTML models. Uses PointNet and will require NEST and NESTML to run. + + +## Running: +To run a simulation, install bmtk and run the following: +``` +$ python run_pointnet.py config.simulation.json +``` +If successful, will create a *output* directory containing log, spike trains and recorded cell variables. + +## The Network: +The network files have already been built and stored as SONATA files in the *network/* directory. The bmtk Builder +script used to create the network files is *build_network.py*. To adjust the parameters and/or topology of the network +change this file and run: +``` +$ python build_network.py +``` +This will overwrite the existing files in the network directory. Note that there is some randomness in how the network +is built, so expect (slightly) different simulation results everytime the network is rebuilt. + +## Simulation Parameters +Parameters to run the simulation, including run-time, inputs, recorded variables, and networks are stored in config.json +and can modified with a text editor. + +## Plotting results +``` +$ python plot_output.py +``` + +## Perturbation simulations +The file ```config_pertubration.json``` uses the same network (recurrent connections + feedforward inputs) as before. However +is designed to also simulate the effects of optogenetic or current clamp inhibition and excitation on a subset of the cells. +To run this example: +```bash +$ python run_pointnet.py config_perturbations.json +``` + +The only difference between this simulation and the original is in the **inputs** section in the config. To simulate the +perturbations we use a current clamp input to make cells 20 through 39 overly excitatory: +```json +"exc_perturbation": { + "input_type": "current_clamp", + "module": "IClamp", + "node_set": { + "population": "cortex", + "node_ids": [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39] + }, + "amp": 230.0, + "delay": 1.0, + "duration": 3000.0 +}, +``` + +For cells 40 through 49 we can use a negative current to depress the cell activity +```json +"inh_perturbation": { + "input_type": "current_clamp", + "module": "IClamp", + "node_set": { + "population": "cortex", + "node_ids": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49] + }, + "amp": -230.0, + "delay": 1.0, + "duration": 3000.0 +} +``` diff --git a/examples/point_120cells_nestml/build_network.py b/examples/point_120cells_nestml/build_network.py new file mode 100644 index 000000000..568d513bf --- /dev/null +++ b/examples/point_120cells_nestml/build_network.py @@ -0,0 +1,116 @@ +import numpy as np + +from bmtk.builder import NetworkBuilder +from bmtk.builder.auxi.node_params import positions_columinar + + +lif_models = { + 'LIF_exc': { + 'N': 80, + 'ei': 'e', + 'pop_name': 'LIF_exc', + 'model_type': 'point_neuron', + 'model_template': 'nestml:iaf_psc_alpha', + 'dynamics_params': 'iaf_psc_delta_exc.json', + }, + 'LIF_inh': { + 'N': 40, + 'ei': 'i', + 'pop_name': 'LIF_inh', + 'model_type': 'point_neuron', + 'model_template': 'nestml:iaf_psc_delta', + 'dynamics_params': 'iaf_psc_delta_inh.json', + } +} + +input_network_model = { + 'input_network': { + 'N': 100, + 'ei': 'e', + 'pop_name': 'input_network', + 'model_type': 'virtual' + } +} + + +def random_connections(source, target, p=0.1): + sid = source['node_id'] # Get source id + tid = target['node_id'] # Get target id + + # Avoid self-connections. + if sid == tid: + return None + + return np.random.binomial(1, p) # nsyns + + +def build_cortex_network(): + cortex = NetworkBuilder('cortex') + for model in lif_models: + params = lif_models[model].copy() + positions = positions_columinar(N=lif_models[model]['N'], center=[0, 10.0, 0], max_radius=50.0, height=200.0) + cortex.add_nodes( + x=positions[:, 0], + y=positions[:, 1], + z=positions[:, 2], + **params + ) + + cortex.add_edges( + source={'ei': 'e'}, + connection_rule=random_connections, + connection_params={'p': 0.1}, + syn_weight=2.0, # 2.0 + delay=1.5, + dynamics_params='ExcToInh.json', + model_template='static_synapse' + ) + + cortex.add_edges( + source={'ei': 'i'}, + connection_rule=random_connections, + connection_params={'p': 0.1}, + syn_weight=-1.5, + delay=1.5, + dynamics_params='InhToExc.json', + model_template='static_synapse' + ) + + cortex.build() + cortex.save(output_dir='network') + + return cortex + + +def build_thalamus_network(cortex): + thalamus = NetworkBuilder('thalamus') + thalamus.add_nodes(**input_network_model['input_network']) + + thalamus.add_edges( + target=cortex.nodes(ei='e'), + connection_rule=random_connections, + connection_params={'p': 0.1}, + syn_weight=220.0, + delay=1.5, + dynamics_params='ExcToExc.json', + model_template='nestml:static_synapse' + ) + + thalamus.add_edges( + target=cortex.nodes(ei='i'), + connection_rule=random_connections, + connection_params={'p': 0.1}, + syn_weight=5.0, + delay=1.5, + dynamics_params='ExcToExc.json', + model_template='nestml:static_synapse' + ) + thalamus.build() + thalamus.save(output_dir='network') + + return thalamus + + +if __name__ == '__main__': + cortex_net = build_cortex_network() + thalamus_net = build_thalamus_network(cortex_net) diff --git a/examples/point_120cells_nestml/config.circuit.json b/examples/point_120cells_nestml/config.circuit.json new file mode 100644 index 000000000..41e47d509 --- /dev/null +++ b/examples/point_120cells_nestml/config.circuit.json @@ -0,0 +1,35 @@ +{ + "manifest": { + "$BASE_DIR": ".", + "$NETWORK_DIR": "$BASE_DIR/network", + "$MODELS_DIR": "$BASE_DIR/../point_components" + }, + + "components": { + "point_neuron_models_dir": "$MODELS_DIR/cell_models", + "synaptic_models_dir": "$MODELS_DIR/synaptic_models" + }, + + "networks": { + "nodes": [ + { + "nodes_file": "$NETWORK_DIR/cortex_nodes.h5", + "node_types_file": "$NETWORK_DIR/cortex_node_types.csv" + }, + { + "nodes_file": "$NETWORK_DIR/thalamus_nodes.h5", + "node_types_file": "$NETWORK_DIR/thalamus_node_types.csv" + } + ], + "edges": [ + { + "edges_file": "$NETWORK_DIR/cortex_cortex_edges.h5", + "edge_types_file": "$NETWORK_DIR/cortex_cortex_edge_types.csv" + }, + { + "edges_file": "$NETWORK_DIR/thalamus_cortex_edges.h5", + "edge_types_file": "$NETWORK_DIR/thalamus_cortex_edge_types.csv" + } + ] + } +} diff --git a/examples/point_120cells_nestml/config.simulation.json b/examples/point_120cells_nestml/config.simulation.json new file mode 100644 index 000000000..9fb2c3b8b --- /dev/null +++ b/examples/point_120cells_nestml/config.simulation.json @@ -0,0 +1,50 @@ +{ + "manifest": { + "$BASE_DIR": "${configdir}", + "$NETWORK_DIR": "$BASE_DIR/network", + "$MODELS_DIR": "$BASE_DIR/../point_components", + "$OUTPUT_DIR": "$BASE_DIR/output", + "$INPUT_DIR": "$BASE_DIR/inputs" + }, + + "run": { + "tstop": 3000.0, + "dt": 0.001, + "block_run": false, + "block_size": 1000.0 + }, + + "inputs": { + "thalamus_spikes": { + "input_type": "spikes", + "module": "sonata", + "input_file": "$INPUT_DIR/thalamus_spikes.h5", + "node_set": "thalamus" + } + }, + + "reports": { + "membrane_potential": { + "cells": { + "population": "cortex", + "node_id": [0, 20, 60, 80, 100] + }, + "variable_name": "V_m", + "module": "multimeter_report", + "sections": "soma" + } + }, + + "output": { + "log_file": "log.txt", + "spikes_file": "spikes.h5", + "spikes_file_csv": "spikes.csv", + "output_dir": "$OUTPUT_DIR", + "overwrite_output_dir": true, + "quiet_simulator": true + }, + + "target_simulator":"NEST", + "rebuild_nestml": true, + "network": "config.circuit.json" +} diff --git a/examples/point_120cells_nestml/config.simulation_perturbations.json b/examples/point_120cells_nestml/config.simulation_perturbations.json new file mode 100644 index 000000000..9fc38daa9 --- /dev/null +++ b/examples/point_120cells_nestml/config.simulation_perturbations.json @@ -0,0 +1,69 @@ +{ + "manifest": { + "$BASE_DIR": ".", + "$OUTPUT_DIR": "$BASE_DIR/output", + "$INPUT_DIR": "$BASE_DIR/inputs" + }, + + "target_simulator":"NEST", + "rebuild_nestml":true, + "run": { + "tstop": 3000.0, + "dt": 0.001 + }, + + "inputs": { + "LGN_spikes": { + "input_type": "spikes", + "module": "csv", + "input_file": "thalamus_spikes.csv", + "node_set": "thalamus" + }, + + "exc_perturbation": { + "input_type": "current_clamp", + "module": "IClamp", + "node_set": { + "population": "cortex", + "node_ids": [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39] + }, + "amp": 230.0, + "delay": 1.0, + "duration": 3000.0 + }, + + "inh_perturbation": { + "input_type": "current_clamp", + "module": "IClamp", + "node_set": { + "population": "cortex", + "node_ids": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49] + }, + "amp": -230.0, + "delay": 1.0, + "duration": 3000.0 + } + + }, + + "reports": { + "membrane_potential": { + "cells": {"population": "cortex", "node_id": [0, 20, 60, 80, 100]}, + "variable_name": "V_m", + "module": "multimeter_report", + "sections": "soma", + "enabled": true + } + }, + + "output": { + "log_file": "log.txt", + "spikes_file": "spikes.h5", + "spikes_file_csv": "spikes.csv", + "output_dir": "$OUTPUT_DIR", + "overwrite_output_dir": true, + "quiet_simulator": true + }, + + "network": "config.circuit.json" +} diff --git a/examples/point_120cells_nestml/create_inputs.py b/examples/point_120cells_nestml/create_inputs.py new file mode 100644 index 000000000..058433f81 --- /dev/null +++ b/examples/point_120cells_nestml/create_inputs.py @@ -0,0 +1,25 @@ +import numpy as np + +from bmtk.utils.reports.spike_trains import SpikeTrains, PoissonSpikeGenerator + + +def create_inputs_const(pop, firing_rate_hz=10.0, times=(0.0, 3.0)): + psg = PoissonSpikeGenerator() # Uses 'seed' to ensure same results every time + psg.add(node_ids='network/{}_nodes.h5'.format(pop), firing_rate=firing_rate_hz, times=times, population=pop) + psg.to_sonata('inputs/{}_spikes.h5'.format(pop)) + # psg.to_csv('inputs/{}_spikes.csv'.format(pop)) + + +def create_inputs_sigmoid(pop, min_rate=1.0, max_rate=10.0, onset=0.5, ts_begin=0.0, ts_end=3.0): + times = np.linspace(ts_begin, ts_end, 1000) + rates = (max_rate-min_rate)/(1.0 + np.exp(-(times-onset)*20)) + min_rate + + psg = PoissonSpikeGenerator() + psg.add(node_ids='network/{}_nodes.h5'.format(pop), firing_rate=rates, times=times, population=pop) + psg.to_sonata('inputs/{}_spikes.h5'.format(pop)) + # psg.to_csv('inputs/{}_spikes.csv'.format(pop) + + +if __name__ == '__main__': + create_inputs_const(pop='thalamus', firing_rate_hz=10.0) + # create_inputs_sigmoid() diff --git a/examples/point_120cells_nestml/inputs/thalamus_spikes.h5 b/examples/point_120cells_nestml/inputs/thalamus_spikes.h5 new file mode 100644 index 0000000000000000000000000000000000000000..48cc4d498243d1f8662e5136a9bb4b1858f7ad78 GIT binary patch literal 45608 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 V_th: # threshold crossing + # A supra-threshold membrane potential should never be observable. + # The reset at the time of threshold crossing enables accurate + # integration independent of the computation step size, see [2,3] for + # details. + r = RefractoryCounts + V_abs = V_reset + emit_spike() + end + + end + +end diff --git a/examples/point_components/synaptic_models/static_synapse.nestml b/examples/point_components/synaptic_models/static_synapse.nestml new file mode 100644 index 000000000..8a30b14aa --- /dev/null +++ b/examples/point_components/synaptic_models/static_synapse.nestml @@ -0,0 +1,26 @@ +""" +Static synapse +############## + +Description ++++++++++++ +A synapse where the synaptic strength (weight) does not evolve with simulated time, but is defined as a (constant) parameter. +""" +synapse static: + + parameters: + w real = 900 @nest::weight @homogeneous + d ms = .9 ms @nest::delay @heterogeneous + a real = 3.141592653589793 @nest::a @homogeneous + b real = 100. @nest::b @heterogeneous + end + + input: + pre_spikes mV <- spike + end + + onReceive(pre_spikes): + deliver_spike(3.18E-3 * a * b * w, d) + end + +end From 0eae1d3a5bf619a354b16d6c4419612459bcb7de Mon Sep 17 00:00:00 2001 From: "C.A.P. Linssen" Date: Thu, 2 Mar 2023 10:59:02 -0800 Subject: [PATCH 3/3] fix network node types --- examples/point_120cells_nestml/config.circuit.json | 5 +++-- examples/point_120cells_nestml/network/cortex_node_types.csv | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 examples/point_120cells_nestml/network/cortex_node_types.csv diff --git a/examples/point_120cells_nestml/config.circuit.json b/examples/point_120cells_nestml/config.circuit.json index 41e47d509..fa6f70e08 100644 --- a/examples/point_120cells_nestml/config.circuit.json +++ b/examples/point_120cells_nestml/config.circuit.json @@ -1,7 +1,8 @@ { "manifest": { "$BASE_DIR": ".", - "$NETWORK_DIR": "$BASE_DIR/network", + "$NETWORK_DIR": "$BASE_DIR/../point_120cells/network", + "$LOCAL_NETWORK_DIR": "$BASE_DIR/network", "$MODELS_DIR": "$BASE_DIR/../point_components" }, @@ -14,7 +15,7 @@ "nodes": [ { "nodes_file": "$NETWORK_DIR/cortex_nodes.h5", - "node_types_file": "$NETWORK_DIR/cortex_node_types.csv" + "node_types_file": "$LOCAL_NETWORK_DIR/cortex_node_types.csv" }, { "nodes_file": "$NETWORK_DIR/thalamus_nodes.h5", diff --git a/examples/point_120cells_nestml/network/cortex_node_types.csv b/examples/point_120cells_nestml/network/cortex_node_types.csv new file mode 100644 index 000000000..1604abf3d --- /dev/null +++ b/examples/point_120cells_nestml/network/cortex_node_types.csv @@ -0,0 +1,3 @@ +node_type_id ei dynamics_params model_type model_template pop_name +100 e iaf_psc_delta_exc.json point_neuron nestml:iaf_psc_delta LIF_exc +101 i iaf_psc_delta_inh.json point_neuron nestml:iaf_psc_delta LIF_inh