From 16b71484ca5a2970950291d5d945c0189cf3bc7d Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 15 Sep 2023 14:28:59 +0200 Subject: [PATCH 01/93] Attempt to fix the merge of astrocyte_lr_1994 back to hj (to be finished) --- nestkernel/conn_builder.cpp | 306 +++++++++++++++ nestkernel/conn_builder.h | 26 ++ nestkernel/nest_names.cpp | 10 + nestkernel/nest_names.h | 10 + nestkernel/nestmodule.cpp | 1 + pynest/examples/astrocyte_brunel.py | 431 +++++++++++++++++++++ pynest/examples/astrocyte_small_network.py | 289 ++++++++++++++ 7 files changed, 1073 insertions(+) create mode 100644 pynest/examples/astrocyte_brunel.py create mode 100644 pynest/examples/astrocyte_small_network.py diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index a50c4f996e..57bdc74d62 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1585,6 +1585,312 @@ nest::BernoulliBuilder::inner_connect_( const int tid, RngPtr rng, Node* target, } +void +nest::BernoulliAstroBuilder::connect_() +{ +#pragma omp parallel + { + const size_t tid = kernel().vp_manager.get_thread_id(); + + // use RNG generating same number sequence on all threads + RngPtr synced_rng = get_vp_synced_rng( tid ); + RngPtr rng = get_vp_specific_rng( tid ); + + try + { + // for source neuron + size_t snode_id; + std::set< size_t > connected_snode_ids; + // for target neuron + Node* target; + size_t target_thread; + unsigned long indegree; + // for astrocyte + Node* astrocyte; + size_t astrocyte_thread; + size_t anode_id; + std::set< size_t > connected_anode_ids; + // for astrocyte pool selection + std::vector< size_t > astro_pool_this_target; + size_t astrocytes_size = astrocytes_->size(); + size_t targets_size = targets_->size(); + int default_n_astro_per_target_ = astrocytes_size / targets_size > 1 ? astrocytes_size / targets_size : 1; + int default_n_target_per_astro = targets_size / astrocytes_size > 1 ? targets_size / astrocytes_size : 1; + int target_index = 0; + int astro_index = 0; + int n_astro_overlap_per_target = 0; + // for binomial distribution + binomial_distribution bino_dist; + binomial_distribution::param_type param( sources_->size(), p_ ); + + // user warnings for astro_pool_by_index + if ( astro_pool_by_index_ == true ) + { + // when number of target neurons are larger than but cannot be exactly divided by number of astrocytes + if ( targets_size > astrocytes_size ) + { + if ( targets_size % astrocytes_size != 0 ) + { + LOG( M_WARNING, + "BernoulliAstroBuilder::connect", + "Number of target neurons cannot be exactly divided by number of astrocytes. " + "Some neurons will be excluded from neuron-astrocyte pairings. " ); + } + } + // when number of astrocytes are larger than but cannot be exactly divided by number of target neurons + else + { + if ( astrocytes_size % targets_size != 0 ) + { + LOG( M_WARNING, + "BernoulliAstroBuilder::connect", + "Number of astrocytes cannot be exactly divided by number of target neurons. " + "Some astrocytes will be excluded from neuron-astrocyte pairings. " ); + } + } + // // uneven pairings in a special case + // if ( max_astro_per_target_ > 0 and max_astro_per_target_ % 2 == 0 and default_n_target_per_astro % 2 == 1) + // { + // LOG( M_WARNING, + // "BernoulliAstroBuilder::connect", + // "Uneven pairings could exist with the assigned neuron and astrocyte numbers. "); + // } + } + + // when max_astro_per_target_ is not given, take defaults + if ( max_astro_per_target_ == 0 ) + { + if ( astro_pool_by_index_ == true ) + { + max_astro_per_target_ = default_n_astro_per_target_; + } + else + { + max_astro_per_target_ = astrocytes_size; + } + } + + // iterate through targets + for ( NodeCollection::const_iterator target_it = targets_->begin(); target_it != targets_->end(); ++target_it ) + { + // get target node + const size_t tnode_id = ( *target_it ).node_id; + target = kernel().node_manager.get_node_or_proxy( tnode_id, tid ); + target_index = targets_->get_lid( tnode_id ); + + // check if the target is on the current thread + if ( target->is_proxy() ) + { + target_thread = invalid_thread; + } + else + { + target_thread = tid; + } + + // draw indegree for this target + indegree = bino_dist( synced_rng, param ); + // when targets overlap with sources and p=1, but autapses are not allowed; indegree must be decreased by 1 + if ( not allow_autapses_ and indegree == sources_->size() and sources_->get_lid( tnode_id ) >= 0 ) + { + LOG( M_WARNING, + "BernoulliAstroBuilder::connect", + "The indegree equals source size and targets overlap with sources, but autapses are not allowed. " + "The indegree has to be decreased by 1 (source size - 1). " ); + indegree -= 1; + } + + // reset lists of connected sources and astrocytes for this target + connected_snode_ids.clear(); + connected_anode_ids.clear(); + + // reset and select astrocyte pool for this target (astrocytes that can but not necessarily be paired with it) + astro_pool_this_target.clear(); + // deterministic + if ( astro_pool_by_index_ == true ) + { + if ( max_astro_per_target_ > 0 ) + { + // shifting, for even pairings + n_astro_overlap_per_target = max_astro_per_target_ - default_n_astro_per_target_; + int shift = std::ceil( n_astro_overlap_per_target / 2.0 ); + if ( targets_size > astrocytes_size ) + { + if ( max_astro_per_target_ % 2 == 0 + and target_index % default_n_target_per_astro >= default_n_target_per_astro / 2.0 ) + { + shift -= 1; // rule with even max_astro_per_target_ => reduce shifting in the latter half + } + // start from this astrocyte + astro_index = ( target_index / default_n_target_per_astro ) - shift; + // exclude remainder of neurons + if ( target_index >= int( astrocytes_size * default_n_target_per_astro ) ) + { + break; + } + } + else + { + astro_index = target_index * default_n_astro_per_target_ - shift; + } + // the starting index cannot be smaller than 0 or larger than ( astrocytes_size - max_astro_per_target_ ) + if ( astro_index < 0 ) + { + astro_index = 0; + } + else if ( astro_index > int( astrocytes_size - max_astro_per_target_ ) ) + { + astro_index = astrocytes_size - max_astro_per_target_; + } + // once the starting index is determined, iterate and add astrocytes to the pool until desired size + for ( size_t i = 0; i < max_astro_per_target_; i++ ) + { + anode_id = ( *astrocytes_ )[ astro_index ]; + astro_pool_this_target.push_back( anode_id ); + astro_index++; + } + } + } + // probabilistic + else + { + if ( max_astro_per_target_ > 0 ) + { + for ( size_t i = 0; i < max_astro_per_target_; i++ ) + { + // draw without repetition + do + { + anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; + } while ( std::find( astro_pool_this_target.begin(), astro_pool_this_target.end(), anode_id ) + != astro_pool_this_target.end() ); + astro_pool_this_target.push_back( anode_id ); + } + } + } + + // iterate through indegree to make connections for this target + size_t i = 0; + while ( i < indegree ) + { + // choose source randomly + snode_id = ( *sources_ )[ synced_rng->ulrand( sources_->size() ) ]; + + // block multapses and handle autapses + if ( connected_snode_ids.find( snode_id ) != connected_snode_ids.end() ) + { + continue; + } + if ( not allow_autapses_ and snode_id == tnode_id ) + { + continue; + } + + // determine the source + connected_snode_ids.insert( snode_id ); + + // increase i which counts the number of incoming connections + ++i; + + // if target is local, connect source=>target + if ( target_thread == tid ) + { + assert( target != NULL ); + for ( size_t synapse_indx = 0; synapse_indx < synapse_model_id_.size(); ++synapse_indx ) + { + update_param_dict_( snode_id, *target, target_thread, synced_rng, synapse_indx ); + double weight_n2n = weights_n2n_[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); + double delay_n2n = delays_n2n_[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); + // not compatible with connections that block individual weights + kernel().connection_manager.connect( snode_id, + target, + target_thread, + synapse_model_id_[ synapse_indx ], + param_dicts_[ synapse_indx ][ target_thread ], + delay_n2n, + weight_n2n ); + } + // single_connect_( snode_id, *target, target_thread, synced_rng ); + } + + // Bernoulli trial to determine whether to pair this neuron=>neuron connection with astrocyte + if ( synced_rng->drand() >= p_syn_astro_ ) + { + continue; + } + // if a max number of astrocyte is demanded, select an astrocyte from the astrocyte pool + if ( max_astro_per_target_ > 0 ) + { + anode_id = astro_pool_this_target.at( synced_rng->ulrand( astro_pool_this_target.size() ) ); + } + // otherwise, any of the astrocytes can be connected + else + { + anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; + } + + // if astrocyte is local, connect source=>astrocyte + astrocyte = kernel().node_manager.get_node_or_proxy( anode_id, tid ); + if ( !astrocyte->is_proxy() ) + { + astrocyte_thread = tid; + assert( astrocyte != NULL ); + for ( size_t synapse_indx = 0; synapse_indx < synapse_model_id_.size(); ++synapse_indx ) + { + update_param_dict_( snode_id, *astrocyte, astrocyte_thread, synced_rng, synapse_indx ); + double weight_n2a = + weights_n2a_[ synapse_indx ]->value_double( astrocyte_thread, rng, snode_id, astrocyte ); + double delay_n2a = + delays_n2a_[ synapse_indx ]->value_double( astrocyte_thread, rng, snode_id, astrocyte ); + // not compatible with connections that block individual weights + kernel().connection_manager.connect( snode_id, + astrocyte, + astrocyte_thread, + synapse_model_id_[ synapse_indx ], + param_dicts_[ synapse_indx ][ astrocyte_thread ], + delay_n2a, + weight_n2a ); + } + // single_connect_( snode_id, *astrocyte, astrocyte_thread, synced_rng ); + } + + // if target is local, connect astrocyte=>target + if ( target_thread == tid ) + { + // avoid connecting the same astrocyte to the target more than once + // if ( connected_anode_ids.find( anode_id ) != connected_anode_ids.end() ) + // { + // continue; + // } + assert( target != NULL ); + for ( size_t synapse_indx = 0; synapse_indx < synapse_model_id_.size(); ++synapse_indx ) + { + update_param_dict_( anode_id, *target, target_thread, synced_rng, synapse_indx ); + double weight_a2n = weights_a2n_[ synapse_indx ]->value_double( target_thread, rng, anode_id, target ); + double delay_a2n = delays_a2n_[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); + kernel().connection_manager.connect( anode_id, + target, + target_thread, + synapse_model_id_a2n_[ synapse_indx ], + param_dicts_[ synapse_indx ][ target_thread ], + delay_a2n, + weight_a2n ); + connected_anode_ids.insert( anode_id ); + } + } + } + } + } + catch ( std::exception& err ) + { + // We must create a new exception here, err's lifetime ends at + // the end of the catch block. + exceptions_raised_.at( tid ) = std::shared_ptr< WrappedThreadException >( new WrappedThreadException( err ) ); + } + } +} + + nest::SymmetricBernoulliBuilder::SymmetricBernoulliBuilder( NodeCollectionPTR sources, NodeCollectionPTR targets, const DictionaryDatum& conn_spec, diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index 53e0f7f04b..660b39a532 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -447,6 +447,32 @@ class BernoulliBuilder : public ConnBuilder ParameterDatum p_; //!< connection probability }; +class BernoulliAstroBuilder : public ConnBuilder +{ +public: + BernoulliAstroBuilder( NodeCollectionPTR, + NodeCollectionPTR, + const DictionaryDatum&, + const std::vector< DictionaryDatum >& ); + +protected: + void connect_() override; + +private: + NodeCollectionPTR astrocytes_; + double p_; //!< connection probability for neuron=>neuron connections + double p_syn_astro_; //!< probability of astrocyte pairing + bool astro_pool_by_index_; //!< if true, select astrocyte pool per target by index + size_t max_astro_per_target_; //!< max number of astrocytes per tartget neuron + std::vector< ConnParameter* > weights_n2n_; //!< synaptic weights neuron=>neuron + std::vector< ConnParameter* > weights_n2a_; //!< synaptic weights neuron=>astrocyte + std::vector< ConnParameter* > delays_n2n_; //!< synaptic delays neuron=>neuron and neuron=>astrocyte + std::vector< ConnParameter* > delays_n2a_; //!< synaptic delays neuron=>neuron and neuron=>astrocyte + std::vector< size_t > synapse_model_id_a2n_; //!< synapse models astrocyte=>neuron + std::vector< ConnParameter* > weights_a2n_; //!< synaptic weights astrocyte=>neuron + std::vector< ConnParameter* > delays_a2n_; //!< synaptic weights astrocyte=>neuron +}; + class SymmetricBernoulliBuilder : public ConnBuilder { public: diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp index d24e1f1968..81c95a031b 100644 --- a/nestkernel/nest_names.cpp +++ b/nestkernel/nest_names.cpp @@ -623,6 +623,16 @@ const Name y_1( "y_1" ); const Name z( "z" ); const Name z_connected( "z_connected" ); + +const Name p_syn_astro( "p_syn_astro" ); +const Name astro_pool_by_index( "astro_pool_by_index" ); +const Name max_astro_per_target( "max_astro_per_target" ); +const Name weights_n2n( "weights_n2n" ); +const Name weights_n2a( "weights_n2a" ); +const Name delays_n2n( "delays_n2n" ); +const Name delays_n2a( "delays_n2a" ); +const Name weights_a2n( "weights_a2n" ); +const Name delays_a2n( "delays_a2n" ); } // namespace names } // namespace nest diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h index c2469d8f62..167ae9462f 100644 --- a/nestkernel/nest_names.h +++ b/nestkernel/nest_names.h @@ -649,6 +649,16 @@ extern const Name y_1; extern const Name z; extern const Name z_connected; + +extern const Name p_syn_astro_; +extern const Name astro_pool_by_index_; +extern const Name max_astro_per_target_; +extern const Name weights_n2n_; +extern const Name weights_n2a_; +extern const Name delays_n2n_; +extern const Name delays_n2a_; +extern const Name weights_a2n_; +extern const Name delays_a2n_; } // namespace names } // namespace nest diff --git a/nestkernel/nestmodule.cpp b/nestkernel/nestmodule.cpp index 079f178430..79674cbef2 100644 --- a/nestkernel/nestmodule.cpp +++ b/nestkernel/nestmodule.cpp @@ -2164,6 +2164,7 @@ NestModule::init( SLIInterpreter* i ) kernel().connection_manager.register_conn_builder< FixedInDegreeBuilder >( "fixed_indegree" ); kernel().connection_manager.register_conn_builder< FixedOutDegreeBuilder >( "fixed_outdegree" ); kernel().connection_manager.register_conn_builder< BernoulliBuilder >( "pairwise_bernoulli" ); + kernel().connection_manager.register_conn_builder< BernoulliAstroBuilder >( "pairwise_bernoulli_astro" ); kernel().connection_manager.register_conn_builder< SymmetricBernoulliBuilder >( "symmetric_pairwise_bernoulli" ); kernel().connection_manager.register_conn_builder< FixedTotalNumberBuilder >( "fixed_total_number" ); #ifdef HAVE_LIBNEUROSIM diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py new file mode 100644 index 0000000000..4c94da4f11 --- /dev/null +++ b/pynest/examples/astrocyte_brunel.py @@ -0,0 +1,431 @@ +# -*- coding: utf-8 -*- +# +# astrocyte_brunel.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +""" +Random balanced network with astrocytes +------------------------------------------------------------ + +This script simulates a random balanced network with excitatory and inhibitory +neurons and astrocytes. The ``astrocyte_lr_1994`` model is according to the +articles [1]_, [2]_, and [3]_. The ``aeif_cond_alpha_astro`` model is an +adaptive exponential integrate and fire neuron supporting neuron-astrocyte +interactions. + +The network is created using the ``pairwise_bernoulli_astro`` rule, with the +``tsodyks_synapse`` as the connections from neurons to neruons and astrocytes, +and the ``sic_connection`` as the connections from astrocytes to neurons. + +This network is an example of an astrocytic effect on neuronal excitabitlity. +With the slow inward current (SIC) delivered from the astrocytes through the +``sic_connection``, the neurons show higher firing rates and more synchronized +bursting actitivity. The degrees of local and global synchrony are quantitized +by pairwise spike count correlations and a measure of global synchrony in [4]_ +respectively, as shown in a plot made in this script (neuron_synchrony.png). +Plots of astrocytic dynamics and SIC in neurons are also made in this script. + +References +~~~~~~~~~~ + +.. [1] De Young, G. W., & Keizer, J. (1992). A single-pool inositol + 1,4,5-trisphosphate-receptor-based model for agonist-stimulated + oscillations in Ca2+ concentration. Proceedings of the National Academy + of Sciences, 89(20), 9895-9899. DOI: + https://doi.org/10.1073/pnas.89.20.9895 + +.. [2] Li, Y. X., & Rinzel, J. (1994). Equations for InsP3 receptor-mediated + [Ca2+]i oscillations derived from a detailed kinetic model: a + Hodgkin-Huxley like formalism. Journal of theoretical Biology, 166(4), + 461-473. DOI: https://doi.org/10.1006/jtbi.1994.1041 + +.. [3] Nadkarni S, and Jung P. Spontaneous oscillations of dressed neurons: A + new mechanism for epilepsy? Physical Review Letters, 91:26. DOI: + https://doi.org/10.1103/PhysRevLett.91.268101 + +.. [4] Golomb, D. (2007). Neuronal synchrony measures. Scholarpedia, 2(1), 1347. + DOI: http://dx.doi.org/10.4249/scholarpedia.1347 + +See Also +~~~~~~~~ + +:doc:`astrocyte_small_network` + +""" + +############################################################################### +# Import all necessary modules for simulation, analysis and plotting. Scipy +# should be imported before nest. + +import os +import sys +import time +import numpy as np +import random +import hashlib +import json + +import nest +import nest.raster_plot +import matplotlib.pyplot as plt + +plt.rcParams.update({"font.size": 13}) + +############################################################################### +# Simulation parameters. + +sim_params = { + "dt": 0.1, # simulation resolution in ms + "pre_sim_time": 100.0, # pre-simulation time in ms + "sim_time": 1000.0, # simulation time in ms + "N_rec_spk": 100, # number of samples (neuron) for spike detector + "N_rec_mm": 50, # number of samples (neuron, astrocyte) for multimeter +} + +############################################################################### +# Network parameters. + +network_params = { + "N_ex": 8000, # number of excitatory neurons + "N_in": 2000, # number of inhibitory neurons + "N_astro": 10000, # number of astrocytes + "p": 0.1, # neuron-neuron connection probability. + "p_syn_astro": 0.5, # synapse-astrocyte pairing probability + "max_astro_per_target": 10, # max number of astrocytes per target neuron + "astro_pool_by_index": False, # Astrocyte pool selection by index + "poisson_rate": 2000, # rate of Poisson input +} + +syn_params = { + "synapse_model": "tsodyks_synapse", # model of neuron-to-neuron and neuron-to-astrocyte connections + "astro2post": "sic_connection", # model of astrocyte-to-neuron connection + "w_a2n": 0.01, # weight of astrocyte-to-neuron connection + "w_e": 1.0, # weight of excitatory connection in nS + "w_i": -4.0, # weight of inhibitory connection in nS + "d_e": 2.0, # delay of excitatory connection in ms + "d_i": 1.0, # delay of inhibitory connection in ms +} + +############################################################################### +# Astrocyte parameters. + +astrocyte_model = "astrocyte_lr_1994" +astrocyte_params = { + "IP3": 0.4, # IP3 initial value in uM + "delta_IP3": 0.5, # Step increase in IP3 concentration with each unit synaptic weight received by the astrocyte in uM + "tau_IP3": 2.0, # Time constant of astrocytic IP3 degradation in ms +} + +############################################################################### +# Neuron parameters. + +neuron_model = "aeif_cond_alpha_astro" +tau_syn_ex = 2.0 +tau_syn_in = 4.0 + +neuron_params_ex = { + "tau_syn_ex": tau_syn_ex, # excitatory synaptic time constant in ms + "tau_syn_in": tau_syn_in, # inhibitory synaptic time constant in ms +} + +neuron_params_in = { + "tau_syn_ex": tau_syn_ex, # excitatory synaptic time constant in ms + "tau_syn_in": tau_syn_in, # inhibitory synaptic time constant in ms +} + +############################################################################### +# Function for network building. + + +def create_astro_network(scale=1.0): + """Create nodes for a neuron-astrocyte network.""" + print("Creating nodes ...") + nodes_ex = nest.Create(neuron_model, int(network_params["N_ex"] * scale), params=neuron_params_ex) + nodes_in = nest.Create(neuron_model, int(network_params["N_in"] * scale), params=neuron_params_in) + nodes_astro = nest.Create(astrocyte_model, int(network_params["N_astro"] * scale), params=astrocyte_params) + nodes_noise = nest.Create("poisson_generator", params={"rate": network_params["poisson_rate"]}) + return nodes_ex, nodes_in, nodes_astro, nodes_noise + + +def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1.0): + """Connect the nodes in a neuron-astrocyte network. + The astrocytes are paired with excitatory connections only. + """ + print("Connecting Poisson generator ...") + assert scale >= 1.0, "scale must be >= 1.0" + syn_params_noise = {"synapse_model": "static_synapse", "weight": syn_params["w_e"]} + nest.Connect(nodes_noise, nodes_ex + nodes_in, syn_spec=syn_params_noise) + print("Connecting neurons and astrocytes ...") + conn_params_e = { + "rule": "pairwise_bernoulli_astro", + "astrocyte": nodes_astro, + "p": network_params["p"] / scale, + "p_syn_astro": network_params["p_syn_astro"], + "max_astro_per_target": network_params["max_astro_per_target"], + "astro_pool_by_index": network_params["astro_pool_by_index"], + } + syn_params_e = { + "synapse_model": syn_params["synapse_model"], + "weight_pre2post": syn_params["w_e"], + "tau_psc": tau_syn_ex, + "astro2post": syn_params["astro2post"], + "weight_astro2post": syn_params["w_a2n"], + "delay_pre2post": syn_params["d_e"], + "delay_pre2astro": syn_params["d_e"], + } + conn_params_i = {"rule": "pairwise_bernoulli", "p": network_params["p"] / scale} + syn_params_i = { + "synapse_model": syn_params["synapse_model"], + "weight": syn_params["w_i"], + "tau_psc": tau_syn_in, + "delay": syn_params["d_i"], + } + nest.Connect(nodes_ex, nodes_ex + nodes_in, conn_params_e, syn_params_e) + nest.Connect(nodes_in, nodes_ex + nodes_in, conn_params_i, syn_params_i) + + return nodes_ex, nodes_in, nodes_astro, nodes_noise + + +############################################################################### +# Function for calculating correlation. + + +def get_corr(hlist): + """Calculate pairwise correlation coefficients for a list of histograms.""" + coef_list = [] + n_pass = 0 + n_fail = 0 + for i, hist1 in enumerate(hlist): + idxs = list(range(i + 1, len(hlist))) + for j in idxs: + hist2 = hlist[j] + if np.sum(hist1) != 0 and np.sum(hist2) != 0: + coef = np.corrcoef(hist1, hist2)[0, 1] + coef_list.append(coef) + n_pass += 1 + else: + n_fail += 1 + + return coef_list, n_pass, n_fail + + +############################################################################### +# Function for calculating and plotting neuronal synchrony. + + +def plot_synchrony(neuron_spikes, data_path, start, end, N=100, bw=10): + # get data + senders = neuron_spikes["senders"][neuron_spikes["times"] > start] + times = neuron_spikes["times"][neuron_spikes["times"] > start] + rate = len(senders) / (end - start) * 1000.0 / len(set(senders)) + print(f"Mean neuronal firing rate = {rate} (n = {len(set(senders))})") + # sample neurons + n_sample = min(len(set(senders)), N) + print(f"Sampling {n_sample} neurons for synchrony analysis ...") + sampled = random.sample(list(set(senders)), n_sample) + times = times[np.isin(senders, sampled)] + senders = senders[np.isin(senders, sampled)] + # make histogram + bins = np.arange(start, end + 0.1, bw) # time bins + hists = [ + np.histogram(times[senders == x], bins)[0].tolist() for x in set(senders) + ] # spiking histograms of individual neurons + hist_global = ( + np.histogram(times, bins)[0] / len(set(senders)) + ).tolist() # spiking histogram of all neurons sampled + # calculate local and global synchrony of neurons sampled + print("Calculating neuronal local and global synchrony ...") + coefs, n_pass_, n_fail_ = get_corr(hists) # local (spike count correlation) + lsync_mu, lsync_sd = np.mean(coefs), np.std(coefs) + gsync = np.var(hist_global) / np.mean(np.var(hists, axis=1)) # global (variance of all/variance of individual) + # make plot + plt.hist(coefs) + title = ( + f"Firing rate={rate:.2f} spikes/s (n={len(set(senders))}) \n" + + f"Local sync.={lsync_mu:.3f}$\pm${np.std(coefs):.3f}, Global sync.={gsync:.3f}\n" + + f"(n={n_sample}, total n of pairs={n_pass_})\n" + ) + plt.title(title) + plt.xlabel("Pairwise spike count correlation (Pearson's r)") + plt.ylabel("n of pairs") + plt.tight_layout() + plt.savefig(os.path.join(data_path, "neuron_synchrony.png")) + plt.close() + print(f"Local synchrony = {lsync_mu:.3f}+-{lsync_sd:.3f}") + print(f"Global synchrony = {gsync:.3f}") + print(f"n of neurons sampled for synchrony analysis = {len(hists)}") + print(f"n of pairs included/excluded = {n_pass_}/{n_fail_}\n") + + +############################################################################### +# Function for plotting dynamics. + + +def plot_dynamics(astro_data, neuron_data, data_path, start): + # plot dynamics + print("Plotting dynamics ...") + # astrocyte data + a = astro_data + a_mask = a["times"] > start + a_ip3 = a["IP3"][a_mask] + a_cal = a["Ca"][a_mask] + a_t = a["times"][a_mask] + t_astro = list(set(a_t)) + m_ip3 = np.array([np.mean(a_ip3[a_t == t]) for t in t_astro]) + s_ip3 = np.array([np.std(a_ip3[a_t == t]) for t in t_astro]) + m_cal = np.array([np.mean(a_cal[a_t == t]) for t in t_astro]) + s_cal = np.array([np.std(a_cal[a_t == t]) for t in t_astro]) + # neuron data + b = neuron_data + b_mask = b["times"] > start + b_sic = b["SIC"][b["times"] > start] + b_t = b["times"][b["times"] > start] + t_neuro = list(set(b_t)) + m_sic = np.array([np.mean(b_sic[b_t == t]) for t in t_neuro]) + s_sic = np.array([np.std(b_sic[b_t == t]) for t in t_neuro]) + # plots + str_ip3 = r"IP$_{3}$" + str_cal = r"Ca$^{2+}$" + color_ip3 = "tab:blue" + color_cal = "tab:green" + color_sic = "tab:purple" + fig, axes = plt.subplots(2, 1, sharex=True) + # astrocyte plot + axes[0].set_title(f"{str_ip3} and {str_cal} in astrocytes (n={len(set(a['senders']))})") + axes[0].set_ylabel(r"IP$_{3}$ ($\mu$M)") + axes[0].tick_params(axis="y", labelcolor=color_ip3) + axes[0].fill_between(t_astro, m_ip3 + s_ip3, m_ip3 - s_ip3, alpha=0.3, linewidth=0.0, color=color_ip3) + axes[0].plot(t_astro, m_ip3, linewidth=2, color=color_ip3) + ax = axes[0].twinx() + ax.set_ylabel(r"Ca$^{2+}$ ($\mu$M)") + ax.tick_params(axis="y", labelcolor=color_cal) + ax.fill_between(t_astro, m_cal + s_cal, m_cal - s_cal, alpha=0.3, linewidth=0.0, color=color_cal) + ax.plot(t_astro, m_cal, linewidth=2, color=color_cal) + # neuron plot + axes[1].set_title(f"SIC in neurons (n={len(set(a['senders']))})") + axes[1].set_ylabel("SIC (pA)") + axes[1].set_xlabel("Time (ms)") + axes[1].fill_between(t_neuro, m_sic + s_sic, m_sic - s_sic, alpha=0.3, linewidth=0.0, color=color_sic) + axes[1].plot(t_neuro, m_sic, linewidth=2, color=color_sic) + # save + plt.tight_layout() + plt.savefig(os.path.join(data_path, "dynamics.png")) + plt.close() + + +############################################################################### +# Main function for simulation. + + +def run_simulation(data_path): + # NEST configuration + nest.ResetKernel() + nest.resolution = sim_params["dt"] + nest.print_time = True + nest.overwrite_files = True + try: + nest.local_num_threads = int(sys.argv[1]) + except: + nest.local_num_threads = 4 + + # Simulation settings + pre_sim_time = sim_params["pre_sim_time"] + sim_time = sim_params["sim_time"] + # Time before building + startbuild = time.time() + + # Create and connect nodes + exc, inh, astro, noise = create_astro_network() + connect_astro_network(exc, inh, astro, noise) + + # Create and connect recorders (multimeter default resolution = 1 ms) + sr_neuron = nest.Create("spike_recorder") + mm_neuron = nest.Create("multimeter", params={"record_from": ["SIC"]}) + mm_astro = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) + + # Time after building + endbuild = time.time() + + # Run pre-simulation and simulation + print("Running pre-simulation ...") + nest.Simulate(pre_sim_time) + print("Connecting recorders ...") + n_spk_neuro = min(len(exc + inh), sim_params["N_rec_spk"]) + n_mm_neuro = min(len(exc + inh), sim_params["N_rec_mm"]) + n_mm_astro = min(len(astro), sim_params["N_rec_mm"]) + nest.Connect((exc + inh)[:n_spk_neuro], sr_neuron) + nest.Connect(mm_neuron, (exc + inh)[:n_mm_neuro]) + nest.Connect(mm_astro, astro[:n_mm_astro]) + print("Running simulation ...") + nest.Simulate(sim_time) + + # Time after simulation + endsimulate = time.time() + + # Read out recordings + neuron_spikes = sr_neuron.events + neuron_data = mm_neuron.events + astro_data = mm_astro.events + + # Report firing rates and building/running time + build_time = endbuild - startbuild + run_time = endsimulate - endbuild + print("Brunel network with astrocytes:") + print(f"Building time = {build_time:.2f} s") + print(f"Simulation time = {run_time:.2f} s\n") + + # Data saving + os.system(f"mkdir -p {data_path}") + os.system(f"cp astrocyte_brunel.py {data_path}") + params = { + "sim_params": sim_params, + "network_params": network_params, + "syn_params": syn_params, + "neuron_model": neuron_model, + "neuron_params_ex": neuron_params_ex, + "neuron_params_in": neuron_params_in, + "astrocyte_model": astrocyte_model, + "astrocyte_params": astrocyte_params, + } + with open(os.path.join(data_path, "params.json"), "w") as f: + json.dump(params, f, indent=4) + + # Make raster plot and histogram + nest.raster_plot.from_device(sr_neuron, hist=True) + plt.title("") + plt.savefig(os.path.join(data_path, "neuron_raster.png"), bbox_inches="tight") + plt.close() + + # Calculate and plot neuronal spiking synchrony + plot_synchrony(neuron_spikes, data_path, pre_sim_time, pre_sim_time + sim_time) + + # Plot dynamics in astrocytes and neurons + plot_dynamics(astro_data, neuron_data, data_path, pre_sim_time) + + # Copy files + os.system(f"cp run.jdf out.* err*. {data_path}") + + print("Done!") + + +############################################################################### +# Run simulation. + +hash = hashlib.md5(os.urandom(16)).hexdigest() +run_simulation(os.path.join("astrocyte_brunel", hash)) diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py new file mode 100644 index 0000000000..f0fd980d70 --- /dev/null +++ b/pynest/examples/astrocyte_small_network.py @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- +# +# astrocyte_small_network.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +""" +A small neuron-astrocyte network +------------------------------------------------------------ + +This script demonstrates how to use the NEST connection builder and the +"pairwise_bernoulli_astro" rule to create a small neuron-astrocyte network with +20 neurons and 10 astrocytes. + +See Also +~~~~~~~~ + +:doc:`astrocyte_brunel` + +""" + +############################################################################### +# Import all necessary modules. + +import os +import hashlib as hl + +from mpi4py import MPI +import nest +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt + +plt.rcParams.update({"font.size": 13}) +pd.set_option("display.max_rows", None) + +############################################################################### +# Initialize kernel. + +nest.ResetKernel() +comm = MPI.COMM_WORLD +rank = comm.Get_rank() + +############################################################################### +# Create save path. + +spath = os.path.join("astrocyte_small_network", hl.md5(os.urandom(16)).hexdigest()) +os.system(f"mkdir -p {spath}") + +############################################################################### +# Astrocyte parameters. + +astrocyte_model = "astrocyte_lr_1994" +astrocyte_params = { + "IP3": 0.4, # IP3 initial value in uM + "delta_IP3": 0.5, # Step increase in IP3 concentration with each unit synaptic weight received by the astrocyte in uM + "tau_IP3": 10.0, # Time constant of astrocytic IP3 degradation in ms +} + +############################################################################### +# Neuron parameters. + +neuron_model = "aeif_cond_alpha_astro" +neuron_params = { + "tau_syn_ex": 2.0, # excitatory synaptic time constant in ms + "I_e": 1000.0, # external current input in pA +} + +############################################################################### +# Create and connect populations and devices. + +pre_neurons = nest.Create(neuron_model, 10, params=neuron_params) +post_neurons = nest.Create(neuron_model, 10, params=neuron_params) +astrocytes = nest.Create(astrocyte_model, 10, params=astrocyte_params) +nest.Connect( + pre_neurons, + post_neurons, + conn_spec={ + "rule": "pairwise_bernoulli_astro", + "astrocyte": astrocytes, + "p": 1.0, + "p_syn_astro": 1.0, + "max_astro_per_target": 3, + "astro_pool_by_index": True, + }, + syn_spec={ + "weight_pre2post": 1.0, + "weight_pre2astro": 1.0, + "weight_astro2post": 1.0, + "delay_pre2post": 1.0, + "delay_pre2astro": 1.0, + "delay_astro2post": 1.0, + }, +) + +mm_pre_neurons = nest.Create("multimeter", params={"record_from": ["V_m"]}) +mm_post_neurons = nest.Create("multimeter", params={"record_from": ["V_m", "SIC"]}) +mm_astrocytes = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) +nest.Connect(mm_pre_neurons, pre_neurons) +nest.Connect(mm_post_neurons, post_neurons) +nest.Connect(mm_astrocytes, astrocytes) + +############################################################################### +# Print and save population and connection data. + +pre_loc = np.array(nest.GetLocalNodeCollection(pre_neurons)) +print(f"pre_neurons on rank {rank}:\n{pre_loc}") +post_loc = np.array(nest.GetLocalNodeCollection(post_neurons)) +print(f"post_neurons on rank {rank}:\n{post_loc}") +astrocytes_loc = np.array(nest.GetLocalNodeCollection(astrocytes)) +print(f"astrocytes on rank {rank}:\n{astrocytes_loc}") + +conns_a2n = nest.GetConnections(astrocytes, post_neurons) +conns_n2n = nest.GetConnections(pre_neurons, post_neurons) +conns_n2a = nest.GetConnections(pre_neurons, astrocytes) +print_list = ["source", "target", "weight", "delay", "synapse_model"] +pd.DataFrame(conns_n2n.get())[print_list].to_csv(os.path.join(spath, f"connections_n2n_rank={rank}.csv"), index=False) +pd.DataFrame(conns_n2a.get())[print_list].to_csv(os.path.join(spath, f"connections_n2a_rank={rank}.csv"), index=False) +pd.DataFrame(conns_a2n.get())[print_list].to_csv(os.path.join(spath, f"connections_a2n_rank={rank}.csv"), index=False) + +############################################################################### +# Functions for plotting. + + +def plot_connections(conn_n2n, conn_n2a, conn_a2n, rank=0): # Doesn't work with MPI yet + print("Plotting connections ...") + # Get data + dict_n2n = conns_n2n.get() + dict_n2a = conns_n2a.get() + dict_a2n = conns_a2n.get() + # Set of cells + sset_n2n = np.unique(dict_n2n["source"]) + tset_n2n = np.unique(dict_n2n["target"]) + sset_n2a = np.unique(dict_n2a["source"]) + aset_n2a = np.unique(dict_n2a["target"]) + aset_a2n = np.unique(dict_a2n["source"]) + tset_a2n = np.unique(dict_a2n["target"]) + # List of cells in connections + slist_n2n = dict_n2n["source"] - sset_n2n.mean() + tlist_n2n = dict_n2n["target"] - tset_n2n.mean() + slist_n2a = dict_n2a["source"] - sset_n2a.mean() + alist_n2a = dict_n2a["target"] - aset_n2a.mean() + alist_a2n = dict_a2n["source"] - aset_a2n.mean() + tlist_a2n = dict_a2n["target"] - tset_a2n.mean() + # Shift sets + sset_n2a = sset_n2a - sset_n2a.mean() + aset_n2a = aset_n2a - aset_n2a.mean() + aset_a2n = aset_a2n - aset_a2n.mean() + tset_a2n = tset_a2n - tset_a2n.mean() + + def set_frame_invisible(ax): + ax.get_xaxis().set_visible(False) + ax.get_yaxis().set_visible(False) + ax.spines["top"].set_visible(False) + ax.spines["bottom"].set_visible(False) + ax.spines["left"].set_visible(False) + ax.spines["right"].set_visible(False) + + fig, axs = plt.subplots(1, 1, figsize=(10, 8)) + axs.scatter(sset_n2a, [2] * len(sset_n2a), s=400, color="gray", marker="^", label="pre_neurons", zorder=3) + axs.scatter(aset_a2n, [1] * len(aset_a2n), s=400, color="g", marker="o", label="astrocyte", zorder=3) + axs.scatter(tset_a2n, [0] * len(tset_a2n), s=400, color="k", marker="^", label="post_neurons", zorder=3) + for sx, tx in zip(slist_n2n, tlist_n2n): + axs.plot([sx, tx], [2, 0], linestyle=":", color="b", alpha=0.5, linewidth=1) + for sx, tx in zip(slist_n2a, alist_n2a): + axs.plot([sx, tx], [2, 1], linestyle="-", color="orange", alpha=0.5, linewidth=2) + for sx, tx in zip(alist_a2n, tlist_a2n): + axs.plot([sx, tx], [1, 0], linestyle="-", color="g", alpha=0.8, linewidth=4) + axs.legend(bbox_to_anchor=(0.5, 1.1), loc="upper center", ncol=3) + set_frame_invisible(axs) + plt.tight_layout() + plt.savefig(os.path.join(spath, f"connections_rank={rank}.png")) + + +def plot_vm(pre_data, post_data, data_path, start, rank=0): + # plot dynamics + print("Plotting V_m ...") + # presynaptic data + a = pre_data + a_mask = a["times"] > start + a_vm = a["V_m"][a_mask] + a_t = a["times"][a_mask] + t_a = list(set(a_t)) + m_pre_vm = np.array([np.mean(a_vm[a_t == t]) for t in t_a]) + s_pre_vm = np.array([np.std(a_vm[a_t == t]) for t in t_a]) + # postsynaptic data + b = post_data + b_mask = b["times"] > start + b_vm = b["V_m"][b_mask] + b_t = b["times"][b_mask] + t_b = list(set(b_t)) + m_post_vm = np.array([np.mean(b_vm[b_t == t]) for t in t_b]) + s_post_vm = np.array([np.std(b_vm[b_t == t]) for t in t_b]) + # plots + color_pre = "tab:blue" + color_post = "tab:blue" + fig, axes = plt.subplots(2, 1, sharex=True) + # presynaptic + axes[0].set_title(f"presynaptic neurons (n={len(set(a['senders']))})") + axes[0].set_ylabel(r"$V_{m}$ (mV)") + axes[0].fill_between(t_a, m_pre_vm + s_pre_vm, m_pre_vm - s_pre_vm, alpha=0.3, linewidth=0.0, color=color_pre) + axes[0].plot(t_a, m_pre_vm, linewidth=2, color=color_pre) + # postsynaptic + axes[1].set_title(f"postsynaptic neurons (n={len(set(b['senders']))})") + axes[1].set_ylabel(r"$V_{m}$ (mV)") + axes[1].set_xlabel("Time (ms)") + axes[1].fill_between(t_b, m_post_vm + s_post_vm, m_post_vm - s_post_vm, alpha=0.3, linewidth=0.0, color=color_post) + axes[1].plot(t_b, m_post_vm, linewidth=2, color=color_post) + # save + plt.tight_layout() + plt.savefig(os.path.join(data_path, f"Vm_rank={rank}.png")) + plt.close() + + +def plot_dynamics(astro_data, neuron_data, data_path, start, rank=0): + # plot dynamics + print("Plotting dynamics ...") + # astrocyte data + a = astro_data + a_mask = a["times"] > start + a_ip3 = a["IP3"][a_mask] + a_cal = a["Ca"][a_mask] + a_t = a["times"][a_mask] + t_astro = list(set(a_t)) + m_ip3 = np.array([np.mean(a_ip3[a_t == t]) for t in t_astro]) + s_ip3 = np.array([np.std(a_ip3[a_t == t]) for t in t_astro]) + m_cal = np.array([np.mean(a_cal[a_t == t]) for t in t_astro]) + s_cal = np.array([np.std(a_cal[a_t == t]) for t in t_astro]) + # neuron data + b = neuron_data + b_mask = b["times"] > start + b_sic = b["SIC"][b_mask] + b_t = b["times"][b_mask] + t_neuro = list(set(b_t)) + m_sic = np.array([np.mean(b_sic[b_t == t]) for t in t_neuro]) + s_sic = np.array([np.std(b_sic[b_t == t]) for t in t_neuro]) + # plots + str_ip3 = r"IP$_{3}$" + str_cal = r"Ca$^{2+}$" + color_ip3 = "tab:blue" + color_cal = "tab:green" + color_sic = "tab:purple" + fig, axes = plt.subplots(2, 1, sharex=True) + # astrocyte plot + axes[0].set_title(f"{str_ip3} and {str_cal} in astrocytes (n={len(set(a['senders']))})") + axes[0].set_ylabel(r"IP$_{3}$ ($\mu$M)") + axes[0].tick_params(axis="y", labelcolor=color_ip3) + axes[0].fill_between(t_astro, m_ip3 + s_ip3, m_ip3 - s_ip3, alpha=0.3, linewidth=0.0, color=color_ip3) + axes[0].plot(t_astro, m_ip3, linewidth=2, color=color_ip3) + ax = axes[0].twinx() + ax.set_ylabel(r"Ca$^{2+}$ ($\mu$M)") + ax.tick_params(axis="y", labelcolor=color_cal) + ax.fill_between(t_astro, m_cal + s_cal, m_cal - s_cal, alpha=0.3, linewidth=0.0, color=color_cal) + ax.plot(t_astro, m_cal, linewidth=2, color=color_cal) + # neuron plot + axes[1].set_title(f"SIC in postsynaptic neurons (n={len(set(a['senders']))})") + axes[1].set_ylabel("SIC (pA)") + axes[1].set_xlabel("Time (ms)") + axes[1].fill_between(t_neuro, m_sic + s_sic, m_sic - s_sic, alpha=0.3, linewidth=0.0, color=color_sic) + axes[1].plot(t_neuro, m_sic, linewidth=2, color=color_sic) + # save + plt.tight_layout() + plt.savefig(os.path.join(data_path, f"dynamics_rank={rank}.png")) + plt.close() + + +############################################################################### +# Run simulation and save results. + +nest.Simulate(1000.0) +os.system(f"cp astrocyte_small_network.py {spath}") +plot_connections(conns_n2n, conns_n2a, conns_a2n, rank) +plot_vm(mm_pre_neurons.events, mm_post_neurons.events, spath, 0.0, rank) +plot_dynamics(mm_astrocytes.events, mm_post_neurons.events, spath, 0.0, rank) From 2965e4411ab91643c408d9c55fa2b89e6c69ce9e Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sat, 16 Sep 2023 22:11:32 +0200 Subject: [PATCH 02/93] Fix the code after merging astrocyte_lr_1994 back --- nestkernel/conn_builder.cpp | 118 +++++++++++++++++++++ nestkernel/nest_names.cpp | 14 +-- nestkernel/nest_names.h | 20 ++-- pynest/examples/astrocyte_brunel.py | 4 +- pynest/examples/astrocyte_small_network.py | 4 +- 5 files changed, 141 insertions(+), 19 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 57bdc74d62..6868642f84 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1585,6 +1585,124 @@ nest::BernoulliBuilder::inner_connect_( const int tid, RngPtr rng, Node* target, } +nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, + NodeCollectionPTR targets, + const DictionaryDatum& conn_spec, + const std::vector< DictionaryDatum >& syn_specs ) + : ConnBuilder( sources, targets, conn_spec, syn_specs ) +{ + // Initialize parameters for astrocyte connectivity + astrocytes_ = getValue< NodeCollectionDatum >( conn_spec, names::astrocyte ); + weights_n2n_.resize( syn_specs.size() ); + weights_n2a_.resize( syn_specs.size() ); + delays_n2n_.resize( syn_specs.size() ); + delays_n2a_.resize( syn_specs.size() ); + synapse_model_id_a2n_.resize( syn_specs.size() ); + weights_a2n_.resize( syn_specs.size() ); + delays_a2n_.resize( syn_specs.size() ); + + // probability of neuron=>neuron connection + p_ = ( *conn_spec )[ names::p ]; + if ( p_ < 0 or 1 < p_ ) + { + throw BadProperty( "Connection probability 0 <= p <= 1 required." ); + } + + // probability of astrocyte pairing per neuron=>neuron connection + if ( conn_spec->known( names::p_syn_astro ) ) + { + p_syn_astro_ = ( *conn_spec )[ names::p_syn_astro ]; + if ( p_syn_astro_ < 0 or 1 < p_syn_astro_ ) + { + throw BadProperty( "Connection probability 0 <= p_syn_astro <= 1 required." ); + } + } + else + { + p_syn_astro_ = 1.0; + } + + // deterministic (by index) or probabilistic selection of astrocyte pool per target neuron + if ( conn_spec->known( names::astro_pool_by_index ) ) + { + astro_pool_by_index_ = ( *conn_spec )[ names::astro_pool_by_index ]; + } + else + { + astro_pool_by_index_ = false; + } + + // maximum number of astrocytes per target neuron + if ( conn_spec->known( names::max_astro_per_target ) ) + { + max_astro_per_target_ = ( *conn_spec )[ names::max_astro_per_target ]; + if ( max_astro_per_target_ < 1 or max_astro_per_target_ > astrocytes_->size() ) + { + throw BadProperty( + "Maximum number of astrocytes per target neuron can not be smaller than 1 or larger than the number of " + "astrocytes." ); + } + } + else + { + // when not given, put 0 and determine later according to numbers of astrocytes and target neurons + max_astro_per_target_ = 0; + } + + // weights and delays of connections + for ( size_t synapse_indx = 0; synapse_indx < syn_specs.size(); ++synapse_indx ) + { + // neuron=>neuron and neuron=>astrocyte + if ( syn_specs[ synapse_indx ]->known( names::synapse_model ) ) + { + std::string syn_name = ( *syn_specs[ synapse_indx ] )[ names::synapse_model ]; + synapse_model_id_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( syn_name ); + } + else + { + synapse_model_id_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( "tsodyks_synapse" ); + } + DictionaryDatum syn_defaults = kernel().model_manager.get_connector_defaults( synapse_model_id_[ synapse_indx ] ); + weights_n2n_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::weight_pre2post ) + ? ConnParameter::create( + ( *syn_specs[ synapse_indx ] )[ names::weight_pre2post ], kernel().vp_manager.get_num_threads() ) + : ConnParameter::create( ( *syn_defaults )[ names::weight ], kernel().vp_manager.get_num_threads() ); + weights_n2a_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::weight_pre2astro ) + ? ConnParameter::create( + ( *syn_specs[ synapse_indx ] )[ names::weight_pre2astro ], kernel().vp_manager.get_num_threads() ) + : ConnParameter::create( ( *syn_defaults )[ names::weight ], kernel().vp_manager.get_num_threads() ); + delays_n2n_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::delay_pre2post ) + ? ConnParameter::create( + ( *syn_specs[ synapse_indx ] )[ names::delay_pre2post ], kernel().vp_manager.get_num_threads() ) + : ConnParameter::create( ( *syn_defaults )[ names::delay ], kernel().vp_manager.get_num_threads() ); + delays_n2a_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::delay_pre2astro ) + ? ConnParameter::create( + ( *syn_specs[ synapse_indx ] )[ names::delay_pre2astro ], kernel().vp_manager.get_num_threads() ) + : ConnParameter::create( ( *syn_defaults )[ names::delay ], kernel().vp_manager.get_num_threads() ); + + // astrocyte=>neuron + if ( syn_specs[ synapse_indx ]->known( names::astro2post ) ) + { + std::string syn_name = ( *syn_specs[ synapse_indx ] )[ names::astro2post ]; + synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( syn_name ); + } + else + { + synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( "sic_connection" ); + } + DictionaryDatum syn_defaults_a2n = + kernel().model_manager.get_connector_defaults( synapse_model_id_a2n_[ synapse_indx ] ); + weights_a2n_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::weight_astro2post ) + ? ConnParameter::create( + ( *syn_specs[ synapse_indx ] )[ names::weight_astro2post ], kernel().vp_manager.get_num_threads() ) + : ConnParameter::create( ( *syn_defaults_a2n )[ names::weight ], kernel().vp_manager.get_num_threads() ); + delays_a2n_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::delay_astro2post ) + ? ConnParameter::create( + ( *syn_specs[ synapse_indx ] )[ names::delay_astro2post ], kernel().vp_manager.get_num_threads() ) + : ConnParameter::create( ( *syn_defaults_a2n )[ names::delay ], kernel().vp_manager.get_num_threads() ); + } +} + void nest::BernoulliAstroBuilder::connect_() { diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp index 81c95a031b..a5c3c8bc3a 100644 --- a/nestkernel/nest_names.cpp +++ b/nestkernel/nest_names.cpp @@ -624,15 +624,17 @@ const Name y_1( "y_1" ); const Name z( "z" ); const Name z_connected( "z_connected" ); +const Name astrocyte( "astrocyte" ); +const Name astro2post( "astro2post" ); const Name p_syn_astro( "p_syn_astro" ); const Name astro_pool_by_index( "astro_pool_by_index" ); const Name max_astro_per_target( "max_astro_per_target" ); -const Name weights_n2n( "weights_n2n" ); -const Name weights_n2a( "weights_n2a" ); -const Name delays_n2n( "delays_n2n" ); -const Name delays_n2a( "delays_n2a" ); -const Name weights_a2n( "weights_a2n" ); -const Name delays_a2n( "delays_a2n" ); +extern const Name weight_pre2post( "weight_pre2post" ); +extern const Name weight_pre2astro( "weight_pre2astro" ); +extern const Name delay_pre2post( "delay_pre2post" ); +extern const Name delay_pre2astro( "delay_pre2astro" ); +extern const Name weight_astro2post( "weight_astro2post"); +extern const Name delay_astro2post( "delay_astro2post"); } // namespace names } // namespace nest diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h index 167ae9462f..2957badfd8 100644 --- a/nestkernel/nest_names.h +++ b/nestkernel/nest_names.h @@ -650,15 +650,17 @@ extern const Name y_1; extern const Name z; extern const Name z_connected; -extern const Name p_syn_astro_; -extern const Name astro_pool_by_index_; -extern const Name max_astro_per_target_; -extern const Name weights_n2n_; -extern const Name weights_n2a_; -extern const Name delays_n2n_; -extern const Name delays_n2a_; -extern const Name weights_a2n_; -extern const Name delays_a2n_; +extern const Name astrocyte; +extern const Name astro2post; +extern const Name p_syn_astro; +extern const Name astro_pool_by_index; +extern const Name max_astro_per_target; +extern const Name weight_pre2post; +extern const Name weight_pre2astro; +extern const Name delay_pre2post; +extern const Name delay_pre2astro; +extern const Name weight_astro2post; +extern const Name delay_astro2post; } // namespace names } // namespace nest diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index 4c94da4f11..4d6696e74b 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -294,7 +294,7 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): # neuron data b = neuron_data b_mask = b["times"] > start - b_sic = b["SIC"][b["times"] > start] + b_sic = b["I_SIC"][b["times"] > start] b_t = b["times"][b["times"] > start] t_neuro = list(set(b_t)) m_sic = np.array([np.mean(b_sic[b_t == t]) for t in t_neuro]) @@ -356,7 +356,7 @@ def run_simulation(data_path): # Create and connect recorders (multimeter default resolution = 1 ms) sr_neuron = nest.Create("spike_recorder") - mm_neuron = nest.Create("multimeter", params={"record_from": ["SIC"]}) + mm_neuron = nest.Create("multimeter", params={"record_from": ["I_SIC"]}) mm_astro = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) # Time after building diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index f0fd980d70..8729316155 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -109,7 +109,7 @@ ) mm_pre_neurons = nest.Create("multimeter", params={"record_from": ["V_m"]}) -mm_post_neurons = nest.Create("multimeter", params={"record_from": ["V_m", "SIC"]}) +mm_post_neurons = nest.Create("multimeter", params={"record_from": ["V_m", "I_SIC"]}) mm_astrocytes = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) nest.Connect(mm_pre_neurons, pre_neurons) nest.Connect(mm_post_neurons, post_neurons) @@ -244,7 +244,7 @@ def plot_dynamics(astro_data, neuron_data, data_path, start, rank=0): # neuron data b = neuron_data b_mask = b["times"] > start - b_sic = b["SIC"][b_mask] + b_sic = b["I_SIC"][b_mask] b_t = b["times"][b_mask] t_neuro = list(set(b_t)) m_sic = np.array([np.mean(b_sic[b_t == t]) for t in t_neuro]) From 607b1031f39c28649f0b5eb918bc5109927740b9 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sun, 17 Sep 2023 12:22:29 +0200 Subject: [PATCH 03/93] Detele unused code and add comments for BernoulliAstroBuilder --- nestkernel/conn_builder.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 6868642f84..dee3ead4e5 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1614,15 +1614,19 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, p_syn_astro_ = ( *conn_spec )[ names::p_syn_astro ]; if ( p_syn_astro_ < 0 or 1 < p_syn_astro_ ) { - throw BadProperty( "Connection probability 0 <= p_syn_astro <= 1 required." ); + throw BadProperty( "Probability of astrocyte pairing for each synapse 0 <= p_syn_astro <= 1 required." ); } } else { + LOG( M_WARNING, + "BernoulliAstroBuilder::connect", + "Probability of astrocyte pairing for each synapse not given. " + "A default value of 1.0 is used. " ); p_syn_astro_ = 1.0; } - // deterministic (by index) or probabilistic selection of astrocyte pool per target neuron + // deterministic (astro_pool_by_index = true) or probabilistic selection of astrocyte pool per target neuron if ( conn_spec->known( names::astro_pool_by_index ) ) { astro_pool_by_index_ = ( *conn_spec )[ names::astro_pool_by_index ]; @@ -1645,7 +1649,9 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, } else { - // when not given, put 0 and determine later according to numbers of astrocytes and target neurons + // when not given, put 0 and determine later by: + // numbers of astrocytes and target neurons, if astro_pool_by_index = true + // number of astrocytes, if astro_pool_by_index = false max_astro_per_target_ = 0; } @@ -1653,15 +1659,6 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, for ( size_t synapse_indx = 0; synapse_indx < syn_specs.size(); ++synapse_indx ) { // neuron=>neuron and neuron=>astrocyte - if ( syn_specs[ synapse_indx ]->known( names::synapse_model ) ) - { - std::string syn_name = ( *syn_specs[ synapse_indx ] )[ names::synapse_model ]; - synapse_model_id_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( syn_name ); - } - else - { - synapse_model_id_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( "tsodyks_synapse" ); - } DictionaryDatum syn_defaults = kernel().model_manager.get_connector_defaults( synapse_model_id_[ synapse_indx ] ); weights_n2n_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::weight_pre2post ) ? ConnParameter::create( @@ -1681,6 +1678,7 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, : ConnParameter::create( ( *syn_defaults )[ names::delay ], kernel().vp_manager.get_num_threads() ); // astrocyte=>neuron + // currently, astro2post is useless; must be sic_connection anyway if ( syn_specs[ synapse_indx ]->known( names::astro2post ) ) { std::string syn_name = ( *syn_specs[ synapse_indx ] )[ names::astro2post ]; From ac6001ef4b5576476fa6829d434b76e14d3bba92 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sun, 17 Sep 2023 22:14:39 +0200 Subject: [PATCH 04/93] Reorganize the constructor of BernoulliAstroBuilder --- nestkernel/conn_builder.cpp | 60 +++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index dee3ead4e5..cf61924725 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1656,33 +1656,43 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, } // weights and delays of connections + size_t num_threads = kernel().vp_manager.get_num_threads(); for ( size_t synapse_indx = 0; synapse_indx < syn_specs.size(); ++synapse_indx ) { // neuron=>neuron and neuron=>astrocyte + DictionaryDatum syn_params = syn_specs[ synapse_indx ]; DictionaryDatum syn_defaults = kernel().model_manager.get_connector_defaults( synapse_model_id_[ synapse_indx ] ); - weights_n2n_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::weight_pre2post ) - ? ConnParameter::create( - ( *syn_specs[ synapse_indx ] )[ names::weight_pre2post ], kernel().vp_manager.get_num_threads() ) - : ConnParameter::create( ( *syn_defaults )[ names::weight ], kernel().vp_manager.get_num_threads() ); - weights_n2a_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::weight_pre2astro ) - ? ConnParameter::create( - ( *syn_specs[ synapse_indx ] )[ names::weight_pre2astro ], kernel().vp_manager.get_num_threads() ) - : ConnParameter::create( ( *syn_defaults )[ names::weight ], kernel().vp_manager.get_num_threads() ); - delays_n2n_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::delay_pre2post ) - ? ConnParameter::create( - ( *syn_specs[ synapse_indx ] )[ names::delay_pre2post ], kernel().vp_manager.get_num_threads() ) - : ConnParameter::create( ( *syn_defaults )[ names::delay ], kernel().vp_manager.get_num_threads() ); - delays_n2a_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::delay_pre2astro ) - ? ConnParameter::create( - ( *syn_specs[ synapse_indx ] )[ names::delay_pre2astro ], kernel().vp_manager.get_num_threads() ) - : ConnParameter::create( ( *syn_defaults )[ names::delay ], kernel().vp_manager.get_num_threads() ); + weights_n2n_[ synapse_indx ] = syn_params->known( names::weight_pre2post ) + ? ConnParameter::create( ( *syn_params )[ names::weight_pre2post ], num_threads ) + : ConnParameter::create( ( *syn_defaults )[ names::weight ], num_threads ); + weights_n2a_[ synapse_indx ] = syn_params->known( names::weight_pre2astro ) + ? ConnParameter::create( ( *syn_params )[ names::weight_pre2astro ], num_threads ) + : ConnParameter::create( ( *syn_defaults )[ names::weight ], num_threads ); + delays_n2n_[ synapse_indx ] = syn_params->known( names::delay_pre2post ) + ? ConnParameter::create( ( *syn_params )[ names::delay_pre2post ], num_threads ) + : ConnParameter::create( ( *syn_defaults )[ names::delay ], num_threads ); + delays_n2a_[ synapse_indx ] = syn_params->known( names::delay_pre2astro ) + ? ConnParameter::create( ( *syn_params )[ names::delay_pre2astro ], num_threads ) + : ConnParameter::create( ( *syn_defaults )[ names::delay ], num_threads ); // astrocyte=>neuron - // currently, astro2post is useless; must be sic_connection anyway if ( syn_specs[ synapse_indx ]->known( names::astro2post ) ) { std::string syn_name = ( *syn_specs[ synapse_indx ] )[ names::astro2post ]; - synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( syn_name ); + // currently, it has to be sic_connection + // possibly in the future, examine if it is in a set of connection type + if ( syn_name == "sic_connection" ) + { + synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( syn_name ); + } + else + { + LOG( M_WARNING, + "BernoulliAstroBuilder::connect", + "An incompatible model is requested for connections from astrocytes to neurons. " + "The compatible model is: sic_connection. Using sic_connection. " ); + synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( "sic_connection" ); + } } else { @@ -1690,14 +1700,12 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, } DictionaryDatum syn_defaults_a2n = kernel().model_manager.get_connector_defaults( synapse_model_id_a2n_[ synapse_indx ] ); - weights_a2n_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::weight_astro2post ) - ? ConnParameter::create( - ( *syn_specs[ synapse_indx ] )[ names::weight_astro2post ], kernel().vp_manager.get_num_threads() ) - : ConnParameter::create( ( *syn_defaults_a2n )[ names::weight ], kernel().vp_manager.get_num_threads() ); - delays_a2n_[ synapse_indx ] = syn_specs[ synapse_indx ]->known( names::delay_astro2post ) - ? ConnParameter::create( - ( *syn_specs[ synapse_indx ] )[ names::delay_astro2post ], kernel().vp_manager.get_num_threads() ) - : ConnParameter::create( ( *syn_defaults_a2n )[ names::delay ], kernel().vp_manager.get_num_threads() ); + weights_a2n_[ synapse_indx ] = syn_params->known( names::weight_astro2post ) + ? ConnParameter::create(( *syn_params )[ names::weight_astro2post ], num_threads ) + : ConnParameter::create( ( *syn_defaults_a2n )[ names::weight ], num_threads ); + delays_a2n_[ synapse_indx ] = syn_params->known( names::delay_astro2post ) + ? ConnParameter::create(( *syn_params )[ names::delay_astro2post ], num_threads ) + : ConnParameter::create( ( *syn_defaults_a2n )[ names::delay ], num_threads ); } } From e506e1815413fdd9d7e85a312dd28bcf3c19d8fd Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 18 Sep 2023 13:06:09 +0200 Subject: [PATCH 05/93] Reorganize connect_() in BernoulliAstroBuilder - move the code that determines the starting astrocyte in the deterministic case to a separate function - update some comments --- nestkernel/conn_builder.cpp | 138 ++++++++++++++++++------------------ nestkernel/conn_builder.h | 1 + 2 files changed, 71 insertions(+), 68 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index cf61924725..0684402b88 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1709,6 +1709,42 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, } } +int +nest::BernoulliAstroBuilder::get_start_astro_index( const int max_astro_per_target, + const int default_n_astro_per_target, const int targets_size, const int astrocytes_size, const int target_index ) +{ + // this function determines the starting astrocyte for the astrocyte pool + // of a target neuron, in the deterministic (astro_pool_by_index = true) case + int start_astro_index = 0; // index of the starting astrocyte + // number of possible overlapping (with other neurons) astrocytes because of requested max_astro_per_target + int n_astro_overlap = max_astro_per_target - default_n_astro_per_target; + // shifting, for even pairings + int shift = std::ceil( n_astro_overlap / 2.0 ); + if ( targets_size > astrocytes_size ) + { + if ( max_astro_per_target % 2 == 0 + and target_index % default_n_astro_per_target >= default_n_astro_per_target / 2.0 ) + { + shift -= 1; // rule with even max_astro_per_target => reduce shifting in the latter half + } + start_astro_index = ( target_index / default_n_astro_per_target ) - shift; + } + else + { + start_astro_index = target_index * default_n_astro_per_target - shift; + } + // the starting index cannot be smaller than 0 or larger than ( astrocytes_size - max_astro_per_target ) + if ( start_astro_index < 0 ) + { + start_astro_index = 0; + } + else if ( start_astro_index > int( astrocytes_size - max_astro_per_target ) ) + { + start_astro_index = astrocytes_size - max_astro_per_target; + } + return start_astro_index; +} + void nest::BernoulliAstroBuilder::connect_() { @@ -1722,27 +1758,28 @@ nest::BernoulliAstroBuilder::connect_() try { - // for source neuron + // variables for source neurons size_t snode_id; std::set< size_t > connected_snode_ids; - // for target neuron + // variables for target neurons Node* target; size_t target_thread; unsigned long indegree; - // for astrocyte + // variables for astrocytes Node* astrocyte; size_t astrocyte_thread; size_t anode_id; std::set< size_t > connected_anode_ids; - // for astrocyte pool selection + // variables for astrocyte pool selection + // astro_pool_this_target is the astrocyte pool of a target neuron + // i.e. astrocytes that can (but not necessarily) be connected with this target neuron std::vector< size_t > astro_pool_this_target; size_t astrocytes_size = astrocytes_->size(); size_t targets_size = targets_->size(); - int default_n_astro_per_target_ = astrocytes_size / targets_size > 1 ? astrocytes_size / targets_size : 1; + int default_n_astro_per_target = astrocytes_size / targets_size > 1 ? astrocytes_size / targets_size : 1; int default_n_target_per_astro = targets_size / astrocytes_size > 1 ? targets_size / astrocytes_size : 1; int target_index = 0; int astro_index = 0; - int n_astro_overlap_per_target = 0; // for binomial distribution binomial_distribution bino_dist; binomial_distribution::param_type param( sources_->size(), p_ ); @@ -1781,18 +1818,13 @@ nest::BernoulliAstroBuilder::connect_() // } } - // when max_astro_per_target_ is not given, take defaults + // defaults when max_astro_per_target_ is not given if ( max_astro_per_target_ == 0 ) { - if ( astro_pool_by_index_ == true ) - { - max_astro_per_target_ = default_n_astro_per_target_; - } - else - { - max_astro_per_target_ = astrocytes_size; - } + max_astro_per_target_ = astro_pool_by_index_ == true ? default_n_astro_per_target : astrocytes_size; } + // check if max_astro_per_target_ is a positive number + assert ( max_astro_per_target_ > 0 ) // iterate through targets for ( NodeCollection::const_iterator target_it = targets_->begin(); target_it != targets_->end(); ++target_it ) @@ -1828,68 +1860,38 @@ nest::BernoulliAstroBuilder::connect_() connected_snode_ids.clear(); connected_anode_ids.clear(); - // reset and select astrocyte pool for this target (astrocytes that can but not necessarily be paired with it) + // select astrocyte pool for this target astro_pool_this_target.clear(); - // deterministic + // deterministic case if ( astro_pool_by_index_ == true ) { - if ( max_astro_per_target_ > 0 ) + // exclude neurons that are remainder + if ( astro_pool_by_index_ == true and target_index >= int( astrocytes_size * default_n_target_per_astro ) ) { - // shifting, for even pairings - n_astro_overlap_per_target = max_astro_per_target_ - default_n_astro_per_target_; - int shift = std::ceil( n_astro_overlap_per_target / 2.0 ); - if ( targets_size > astrocytes_size ) - { - if ( max_astro_per_target_ % 2 == 0 - and target_index % default_n_target_per_astro >= default_n_target_per_astro / 2.0 ) - { - shift -= 1; // rule with even max_astro_per_target_ => reduce shifting in the latter half - } - // start from this astrocyte - astro_index = ( target_index / default_n_target_per_astro ) - shift; - // exclude remainder of neurons - if ( target_index >= int( astrocytes_size * default_n_target_per_astro ) ) - { - break; - } - } - else - { - astro_index = target_index * default_n_astro_per_target_ - shift; - } - // the starting index cannot be smaller than 0 or larger than ( astrocytes_size - max_astro_per_target_ ) - if ( astro_index < 0 ) - { - astro_index = 0; - } - else if ( astro_index > int( astrocytes_size - max_astro_per_target_ ) ) - { - astro_index = astrocytes_size - max_astro_per_target_; - } - // once the starting index is determined, iterate and add astrocytes to the pool until desired size - for ( size_t i = 0; i < max_astro_per_target_; i++ ) - { - anode_id = ( *astrocytes_ )[ astro_index ]; - astro_pool_this_target.push_back( anode_id ); - astro_index++; - } + break; + } + // determine the starting astrocyte + astro_index = get_start_astro_index( max_astro_per_target_, default_n_astro_per_target, targets_size, astrocytes_size, target_index ); + // iterate from starting astrocyte and add astrocytes until desired pool size reached + for ( size_t i = 0; i < max_astro_per_target_; i++ ) + { + anode_id = ( *astrocytes_ )[ astro_index ]; + astro_pool_this_target.push_back( anode_id ); + astro_index++; } } - // probabilistic + // probabilistic case else { - if ( max_astro_per_target_ > 0 ) + for ( size_t i = 0; i < max_astro_per_target_; i++ ) { - for ( size_t i = 0; i < max_astro_per_target_; i++ ) + // draw astrocytes without repetition until desired pool size reached + do { - // draw without repetition - do - { - anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; - } while ( std::find( astro_pool_this_target.begin(), astro_pool_this_target.end(), anode_id ) - != astro_pool_this_target.end() ); - astro_pool_this_target.push_back( anode_id ); - } + anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; + } while ( std::find( astro_pool_this_target.begin(), astro_pool_this_target.end(), anode_id ) + != astro_pool_this_target.end() ); + astro_pool_this_target.push_back( anode_id ); } } @@ -1910,7 +1912,7 @@ nest::BernoulliAstroBuilder::connect_() continue; } - // determine the source + // add source to list connected_snode_ids.insert( snode_id ); // increase i which counts the number of incoming connections diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index 660b39a532..9418244c6c 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -456,6 +456,7 @@ class BernoulliAstroBuilder : public ConnBuilder const std::vector< DictionaryDatum >& ); protected: + int get_start_astro_index( const int, const int, const int, const int, const int ); void connect_() override; private: From 975bbf1e9399c208213f216d5073c6dda2d3b89e Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 18 Sep 2023 13:54:59 +0200 Subject: [PATCH 06/93] Add missing ";" for the last commit --- nestkernel/conn_builder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 0684402b88..ae7add002d 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1824,7 +1824,7 @@ nest::BernoulliAstroBuilder::connect_() max_astro_per_target_ = astro_pool_by_index_ == true ? default_n_astro_per_target : astrocytes_size; } // check if max_astro_per_target_ is a positive number - assert ( max_astro_per_target_ > 0 ) + assert ( max_astro_per_target_ > 0 ); // iterate through targets for ( NodeCollection::const_iterator target_it = targets_->begin(); target_it != targets_->end(); ++target_it ) From fc8eb697615708a6059b408bec824bffe552e024 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 18 Sep 2023 17:52:28 +0200 Subject: [PATCH 07/93] Update comments and delete unnecessary code in BernoulliAstroBuilder --- nestkernel/conn_builder.cpp | 64 ++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index ae7add002d..d2b1ddb542 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1713,7 +1713,7 @@ int nest::BernoulliAstroBuilder::get_start_astro_index( const int max_astro_per_target, const int default_n_astro_per_target, const int targets_size, const int astrocytes_size, const int target_index ) { - // this function determines the starting astrocyte for the astrocyte pool + // helper function to determine the starting astrocyte for the astrocyte pool // of a target neuron, in the deterministic (astro_pool_by_index = true) case int start_astro_index = 0; // index of the starting astrocyte // number of possible overlapping (with other neurons) astrocytes because of requested max_astro_per_target @@ -1769,7 +1769,6 @@ nest::BernoulliAstroBuilder::connect_() Node* astrocyte; size_t astrocyte_thread; size_t anode_id; - std::set< size_t > connected_anode_ids; // variables for astrocyte pool selection // astro_pool_this_target is the astrocyte pool of a target neuron // i.e. astrocytes that can (but not necessarily) be connected with this target neuron @@ -1823,10 +1822,12 @@ nest::BernoulliAstroBuilder::connect_() { max_astro_per_target_ = astro_pool_by_index_ == true ? default_n_astro_per_target : astrocytes_size; } - // check if max_astro_per_target_ is a positive number + // check if max_astro_per_target_ is > 0 and < astrocytes_size assert ( max_astro_per_target_ > 0 ); + assert ( max_astro_per_target_ <= astrocytes_size ); - // iterate through targets + // Iterate through target neurons. For each, three steps are done: + // 1. draw indegree 2. select astrocyte pool 3. make connections for ( NodeCollection::const_iterator target_it = targets_->begin(); target_it != targets_->end(); ++target_it ) { // get target node @@ -1844,9 +1845,9 @@ nest::BernoulliAstroBuilder::connect_() target_thread = tid; } - // draw indegree for this target + // step 1, draw indegree for this target indegree = bino_dist( synced_rng, param ); - // when targets overlap with sources and p=1, but autapses are not allowed; indegree must be decreased by 1 + // when targets overlap with sources and p=1, but autapses are not allowed, indegree should be reduced by 1 if ( not allow_autapses_ and indegree == sources_->size() and sources_->get_lid( tnode_id ) >= 0 ) { LOG( M_WARNING, @@ -1856,11 +1857,8 @@ nest::BernoulliAstroBuilder::connect_() indegree -= 1; } - // reset lists of connected sources and astrocytes for this target - connected_snode_ids.clear(); - connected_anode_ids.clear(); - - // select astrocyte pool for this target + // step 2, select astrocyte pool for this target + // reset the list of astrocyte pool astro_pool_this_target.clear(); // deterministic case if ( astro_pool_by_index_ == true ) @@ -1872,7 +1870,7 @@ nest::BernoulliAstroBuilder::connect_() } // determine the starting astrocyte astro_index = get_start_astro_index( max_astro_per_target_, default_n_astro_per_target, targets_size, astrocytes_size, target_index ); - // iterate from starting astrocyte and add astrocytes until desired pool size reached + // iterate from the starting astrocyte and add astrocytes until desired pool size reached for ( size_t i = 0; i < max_astro_per_target_; i++ ) { anode_id = ( *astrocytes_ )[ astro_index ]; @@ -1885,7 +1883,7 @@ nest::BernoulliAstroBuilder::connect_() { for ( size_t i = 0; i < max_astro_per_target_; i++ ) { - // draw astrocytes without repetition until desired pool size reached + // randomly draw astrocytes without repetition until desired pool size reached do { anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; @@ -1895,14 +1893,16 @@ nest::BernoulliAstroBuilder::connect_() } } - // iterate through indegree to make connections for this target + // step 3, iterate through indegree to make connections for this target + // reset the lists of connected sources + connected_snode_ids.clear(); size_t i = 0; while ( i < indegree ) { - // choose source randomly + // choose source node randomly snode_id = ( *sources_ )[ synced_rng->ulrand( sources_->size() ) ]; - // block multapses and handle autapses + // block multapses and autapses (if requested) if ( connected_snode_ids.find( snode_id ) != connected_snode_ids.end() ) { continue; @@ -1912,13 +1912,13 @@ nest::BernoulliAstroBuilder::connect_() continue; } - // add source to list + // add source node to list connected_snode_ids.insert( snode_id ); // increase i which counts the number of incoming connections ++i; - // if target is local, connect source=>target + // if the target is local, connect source=>target if ( target_thread == tid ) { assert( target != NULL ); @@ -1927,7 +1927,6 @@ nest::BernoulliAstroBuilder::connect_() update_param_dict_( snode_id, *target, target_thread, synced_rng, synapse_indx ); double weight_n2n = weights_n2n_[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); double delay_n2n = delays_n2n_[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); - // not compatible with connections that block individual weights kernel().connection_manager.connect( snode_id, target, target_thread, @@ -1936,26 +1935,27 @@ nest::BernoulliAstroBuilder::connect_() delay_n2n, weight_n2n ); } - // single_connect_( snode_id, *target, target_thread, synced_rng ); } - // Bernoulli trial to determine whether to pair this neuron=>neuron connection with astrocyte + // Bernoulli trial to determine if the considered neuron=>neuron connection should be paired with an astrocyte if ( synced_rng->drand() >= p_syn_astro_ ) { continue; } - // if a max number of astrocyte is demanded, select an astrocyte from the astrocyte pool - if ( max_astro_per_target_ > 0 ) + + // if a pairing should be done, select an astrocyte + // select from all the astrocytes, if not limited + if ( max_astro_per_target_ == astrocytes_size ) { - anode_id = astro_pool_this_target.at( synced_rng->ulrand( astro_pool_this_target.size() ) ); + anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; } - // otherwise, any of the astrocytes can be connected + // otherwise, select from the astrocyte pool else { - anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; + anode_id = astro_pool_this_target.at( synced_rng->ulrand( astro_pool_this_target.size() ) ); } - // if astrocyte is local, connect source=>astrocyte + // if the astrocyte is local, connect source=>astrocyte astrocyte = kernel().node_manager.get_node_or_proxy( anode_id, tid ); if ( !astrocyte->is_proxy() ) { @@ -1968,7 +1968,6 @@ nest::BernoulliAstroBuilder::connect_() weights_n2a_[ synapse_indx ]->value_double( astrocyte_thread, rng, snode_id, astrocyte ); double delay_n2a = delays_n2a_[ synapse_indx ]->value_double( astrocyte_thread, rng, snode_id, astrocyte ); - // not compatible with connections that block individual weights kernel().connection_manager.connect( snode_id, astrocyte, astrocyte_thread, @@ -1977,17 +1976,11 @@ nest::BernoulliAstroBuilder::connect_() delay_n2a, weight_n2a ); } - // single_connect_( snode_id, *astrocyte, astrocyte_thread, synced_rng ); } - // if target is local, connect astrocyte=>target + // if the target is local, connect astrocyte=>target if ( target_thread == tid ) { - // avoid connecting the same astrocyte to the target more than once - // if ( connected_anode_ids.find( anode_id ) != connected_anode_ids.end() ) - // { - // continue; - // } assert( target != NULL ); for ( size_t synapse_indx = 0; synapse_indx < synapse_model_id_.size(); ++synapse_indx ) { @@ -2001,7 +1994,6 @@ nest::BernoulliAstroBuilder::connect_() param_dicts_[ synapse_indx ][ target_thread ], delay_a2n, weight_a2n ); - connected_anode_ids.insert( anode_id ); } } } From 9e51bf800ee2c4246c8fa2af3fc8a1d78414eeaa Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 18 Sep 2023 23:57:24 +0200 Subject: [PATCH 08/93] Add single_connect_astro_() to BernoulliAstroBuilder --- nestkernel/conn_builder.cpp | 131 +++++++++++++++++------------------- nestkernel/conn_builder.h | 3 + 2 files changed, 63 insertions(+), 71 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index d2b1ddb542..28f57c55f7 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1745,6 +1745,29 @@ nest::BernoulliAstroBuilder::get_start_astro_index( const int max_astro_per_targ return start_astro_index; } +void +nest::BernoulliAstroBuilder::single_connect_astro_( const size_t snode_id, + Node* target, const size_t target_thread, RngPtr synced_rng, RngPtr rng, + const std::vector< ConnParameter* >& weights, const std::vector< ConnParameter* >& delays, + const std::vector< size_t >& synapse_model_id ) +{ + // function to make single connections + assert( target != NULL ); + for ( size_t synapse_indx = 0; synapse_indx < synapse_model_id.size(); ++synapse_indx ) + { + update_param_dict_( snode_id, *target, target_thread, synced_rng, synapse_indx ); + double weight = weights[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); + double delay = delays[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); + kernel().connection_manager.connect( snode_id, + target, + target_thread, + synapse_model_id[ synapse_indx ], + param_dicts_[ synapse_indx ][ target_thread ], + delay, + weight ); + } +} + void nest::BernoulliAstroBuilder::connect_() { @@ -1858,38 +1881,42 @@ nest::BernoulliAstroBuilder::connect_() } // step 2, select astrocyte pool for this target - // reset the list of astrocyte pool - astro_pool_this_target.clear(); - // deterministic case - if ( astro_pool_by_index_ == true ) + // not required if max_astro_per_target_ = astrocytes_size + if ( max_astro_per_target_ < astrocytes_size ) { - // exclude neurons that are remainder - if ( astro_pool_by_index_ == true and target_index >= int( astrocytes_size * default_n_target_per_astro ) ) + // reset the list of astrocyte pool + astro_pool_this_target.clear(); + // deterministic case + if ( astro_pool_by_index_ == true ) { - break; - } - // determine the starting astrocyte - astro_index = get_start_astro_index( max_astro_per_target_, default_n_astro_per_target, targets_size, astrocytes_size, target_index ); - // iterate from the starting astrocyte and add astrocytes until desired pool size reached - for ( size_t i = 0; i < max_astro_per_target_; i++ ) - { - anode_id = ( *astrocytes_ )[ astro_index ]; - astro_pool_this_target.push_back( anode_id ); - astro_index++; + // exclude neurons that are remainder + if ( astro_pool_by_index_ == true and target_index >= int( astrocytes_size * default_n_target_per_astro ) ) + { + break; + } + // determine the starting astrocyte + astro_index = get_start_astro_index( max_astro_per_target_, default_n_astro_per_target, targets_size, astrocytes_size, target_index ); + // iterate from the starting astrocyte and add astrocytes until desired pool size reached + for ( size_t i = 0; i < max_astro_per_target_; i++ ) + { + anode_id = ( *astrocytes_ )[ astro_index ]; + astro_pool_this_target.push_back( anode_id ); + astro_index++; + } } - } - // probabilistic case - else - { - for ( size_t i = 0; i < max_astro_per_target_; i++ ) + // probabilistic case + else { - // randomly draw astrocytes without repetition until desired pool size reached - do + for ( size_t i = 0; i < max_astro_per_target_; i++ ) { - anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; - } while ( std::find( astro_pool_this_target.begin(), astro_pool_this_target.end(), anode_id ) - != astro_pool_this_target.end() ); - astro_pool_this_target.push_back( anode_id ); + // randomly draw astrocytes without repetition until desired pool size reached + do + { + anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; + } while ( std::find( astro_pool_this_target.begin(), astro_pool_this_target.end(), anode_id ) + != astro_pool_this_target.end() ); + astro_pool_this_target.push_back( anode_id ); + } } } @@ -1921,20 +1948,8 @@ nest::BernoulliAstroBuilder::connect_() // if the target is local, connect source=>target if ( target_thread == tid ) { - assert( target != NULL ); - for ( size_t synapse_indx = 0; synapse_indx < synapse_model_id_.size(); ++synapse_indx ) - { - update_param_dict_( snode_id, *target, target_thread, synced_rng, synapse_indx ); - double weight_n2n = weights_n2n_[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); - double delay_n2n = delays_n2n_[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); - kernel().connection_manager.connect( snode_id, - target, - target_thread, - synapse_model_id_[ synapse_indx ], - param_dicts_[ synapse_indx ][ target_thread ], - delay_n2n, - weight_n2n ); - } + single_connect_astro_( snode_id, + target, target_thread, synced_rng, rng, weights_n2n_, delays_n2n_, synapse_model_id_ ); } // Bernoulli trial to determine if the considered neuron=>neuron connection should be paired with an astrocyte @@ -1960,41 +1975,15 @@ nest::BernoulliAstroBuilder::connect_() if ( !astrocyte->is_proxy() ) { astrocyte_thread = tid; - assert( astrocyte != NULL ); - for ( size_t synapse_indx = 0; synapse_indx < synapse_model_id_.size(); ++synapse_indx ) - { - update_param_dict_( snode_id, *astrocyte, astrocyte_thread, synced_rng, synapse_indx ); - double weight_n2a = - weights_n2a_[ synapse_indx ]->value_double( astrocyte_thread, rng, snode_id, astrocyte ); - double delay_n2a = - delays_n2a_[ synapse_indx ]->value_double( astrocyte_thread, rng, snode_id, astrocyte ); - kernel().connection_manager.connect( snode_id, - astrocyte, - astrocyte_thread, - synapse_model_id_[ synapse_indx ], - param_dicts_[ synapse_indx ][ astrocyte_thread ], - delay_n2a, - weight_n2a ); - } + single_connect_astro_( snode_id, + astrocyte, astrocyte_thread, synced_rng, rng, weights_n2a_, delays_n2a_, synapse_model_id_ ); } // if the target is local, connect astrocyte=>target if ( target_thread == tid ) { - assert( target != NULL ); - for ( size_t synapse_indx = 0; synapse_indx < synapse_model_id_.size(); ++synapse_indx ) - { - update_param_dict_( anode_id, *target, target_thread, synced_rng, synapse_indx ); - double weight_a2n = weights_a2n_[ synapse_indx ]->value_double( target_thread, rng, anode_id, target ); - double delay_a2n = delays_a2n_[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); - kernel().connection_manager.connect( anode_id, - target, - target_thread, - synapse_model_id_a2n_[ synapse_indx ], - param_dicts_[ synapse_indx ][ target_thread ], - delay_a2n, - weight_a2n ); - } + single_connect_astro_( anode_id, + target, target_thread, synced_rng, rng, weights_a2n_, delays_a2n_, synapse_model_id_a2n_ ); } } } diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index 9418244c6c..64c19901a8 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -457,6 +457,9 @@ class BernoulliAstroBuilder : public ConnBuilder protected: int get_start_astro_index( const int, const int, const int, const int, const int ); + void single_connect_astro_( const size_t, + Node*, const size_t, RngPtr, RngPtr, + const std::vector< ConnParameter* >&, const std::vector< ConnParameter* >&, const std::vector< size_t >& ); void connect_() override; private: From 0b6fdabec7acaa1057c337cf7d0e8f14300d91c7 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Tue, 19 Sep 2023 11:01:22 +0200 Subject: [PATCH 09/93] Update comments in BernoulliAstroBuilder --- nestkernel/conn_builder.cpp | 57 ++++++++++++++++++------------------- nestkernel/conn_builder.h | 20 ++++++------- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 28f57c55f7..3c563e8ccb 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1601,27 +1601,25 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, weights_a2n_.resize( syn_specs.size() ); delays_a2n_.resize( syn_specs.size() ); - // probability of neuron=>neuron connection p_ = ( *conn_spec )[ names::p ]; if ( p_ < 0 or 1 < p_ ) { - throw BadProperty( "Connection probability 0 <= p <= 1 required." ); + throw BadProperty( "Probability of neuron-neuron connections 0 <= p <= 1 required." ); } - // probability of astrocyte pairing per neuron=>neuron connection if ( conn_spec->known( names::p_syn_astro ) ) { p_syn_astro_ = ( *conn_spec )[ names::p_syn_astro ]; if ( p_syn_astro_ < 0 or 1 < p_syn_astro_ ) { - throw BadProperty( "Probability of astrocyte pairing for each synapse 0 <= p_syn_astro <= 1 required." ); + throw BadProperty( "Probability of astrocyte pairing per neuron-neuron connection 0 <= p_syn_astro <= 1 required." ); } } else { LOG( M_WARNING, "BernoulliAstroBuilder::connect", - "Probability of astrocyte pairing for each synapse not given. " + "Probability of astrocyte pairing per neuron-neuron connection not given. " "A default value of 1.0 is used. " ); p_syn_astro_ = 1.0; } @@ -1636,14 +1634,14 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, astro_pool_by_index_ = false; } - // maximum number of astrocytes per target neuron + // maximal number of astrocytes per target neuron if ( conn_spec->known( names::max_astro_per_target ) ) { max_astro_per_target_ = ( *conn_spec )[ names::max_astro_per_target ]; if ( max_astro_per_target_ < 1 or max_astro_per_target_ > astrocytes_->size() ) { throw BadProperty( - "Maximum number of astrocytes per target neuron can not be smaller than 1 or larger than the number of " + "Maximal number of astrocytes per target neuron cannot be smaller than 1 or larger than the number of " "astrocytes." ); } } @@ -1655,11 +1653,12 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, max_astro_per_target_ = 0; } - // weights and delays of connections + // determine parameters for individual connections size_t num_threads = kernel().vp_manager.get_num_threads(); for ( size_t synapse_indx = 0; synapse_indx < syn_specs.size(); ++synapse_indx ) { - // neuron=>neuron and neuron=>astrocyte + // neuron to neuron, neuron to astrocyte + // weights and delays DictionaryDatum syn_params = syn_specs[ synapse_indx ]; DictionaryDatum syn_defaults = kernel().model_manager.get_connector_defaults( synapse_model_id_[ synapse_indx ] ); weights_n2n_[ synapse_indx ] = syn_params->known( names::weight_pre2post ) @@ -1675,12 +1674,13 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, ? ConnParameter::create( ( *syn_params )[ names::delay_pre2astro ], num_threads ) : ConnParameter::create( ( *syn_defaults )[ names::delay ], num_threads ); - // astrocyte=>neuron + // astrocyte to neuron + // connection model(s) if ( syn_specs[ synapse_indx ]->known( names::astro2post ) ) { std::string syn_name = ( *syn_specs[ synapse_indx ] )[ names::astro2post ]; // currently, it has to be sic_connection - // possibly in the future, examine if it is in a set of connection type + // in the future, examine if it belongs to a set of connection type if ( syn_name == "sic_connection" ) { synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( syn_name ); @@ -1698,6 +1698,7 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, { synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( "sic_connection" ); } + // weights and delays DictionaryDatum syn_defaults_a2n = kernel().model_manager.get_connector_defaults( synapse_model_id_a2n_[ synapse_indx ] ); weights_a2n_[ synapse_indx ] = syn_params->known( names::weight_astro2post ) @@ -1725,7 +1726,7 @@ nest::BernoulliAstroBuilder::get_start_astro_index( const int max_astro_per_targ if ( max_astro_per_target % 2 == 0 and target_index % default_n_astro_per_target >= default_n_astro_per_target / 2.0 ) { - shift -= 1; // rule with even max_astro_per_target => reduce shifting in the latter half + shift -= 1; // rule with even max_astro_per_target (reduce shifting in the latter half) } start_astro_index = ( target_index / default_n_astro_per_target ) - shift; } @@ -1806,7 +1807,7 @@ nest::BernoulliAstroBuilder::connect_() binomial_distribution bino_dist; binomial_distribution::param_type param( sources_->size(), p_ ); - // user warnings for astro_pool_by_index + // user warnings in the case of astro_pool_by_index = true if ( astro_pool_by_index_ == true ) { // when number of target neurons are larger than but cannot be exactly divided by number of astrocytes @@ -1840,12 +1841,12 @@ nest::BernoulliAstroBuilder::connect_() // } } - // defaults when max_astro_per_target_ is not given + // defaults for max_astro_per_target_ when not given if ( max_astro_per_target_ == 0 ) { max_astro_per_target_ = astro_pool_by_index_ == true ? default_n_astro_per_target : astrocytes_size; } - // check if max_astro_per_target_ is > 0 and < astrocytes_size + // assert max_astro_per_target_ > 0 and <= astrocytes_size assert ( max_astro_per_target_ > 0 ); assert ( max_astro_per_target_ <= astrocytes_size ); @@ -1870,7 +1871,8 @@ nest::BernoulliAstroBuilder::connect_() // step 1, draw indegree for this target indegree = bino_dist( synced_rng, param ); - // when targets overlap with sources and p=1, but autapses are not allowed, indegree should be reduced by 1 + // if targets overlap with sources and p=1 and autapses are not allowed, indegree cannot be reached + // in this case, indegree should be forced to reduced by 1 if ( not allow_autapses_ and indegree == sources_->size() and sources_->get_lid( tnode_id ) >= 0 ) { LOG( M_WARNING, @@ -1929,7 +1931,7 @@ nest::BernoulliAstroBuilder::connect_() // choose source node randomly snode_id = ( *sources_ )[ synced_rng->ulrand( sources_->size() ) ]; - // block multapses and autapses (if requested) + // block multapses and autapses (if autapses not allowed) if ( connected_snode_ids.find( snode_id ) != connected_snode_ids.end() ) { continue; @@ -1945,32 +1947,27 @@ nest::BernoulliAstroBuilder::connect_() // increase i which counts the number of incoming connections ++i; - // if the target is local, connect source=>target + // if the target is local, connect the source to the target if ( target_thread == tid ) { single_connect_astro_( snode_id, target, target_thread, synced_rng, rng, weights_n2n_, delays_n2n_, synapse_model_id_ ); } - // Bernoulli trial to determine if the considered neuron=>neuron connection should be paired with an astrocyte + // Bernoulli trial to determine if the considered neuron-neuron connection should be paired with an astrocyte if ( synced_rng->drand() >= p_syn_astro_ ) { continue; } // if a pairing should be done, select an astrocyte - // select from all the astrocytes, if not limited - if ( max_astro_per_target_ == astrocytes_size ) - { - anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; - } + // select from all the astrocytes, if max_astro_per_target_ = astrocytes_size // otherwise, select from the astrocyte pool - else - { - anode_id = astro_pool_this_target.at( synced_rng->ulrand( astro_pool_this_target.size() ) ); - } + anode_id = max_astro_per_target_ == astrocytes_size + ? ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ] + : astro_pool_this_target.at( synced_rng->ulrand( astro_pool_this_target.size() ) ); - // if the astrocyte is local, connect source=>astrocyte + // if the astrocyte is local, connect the source to the astrocyte astrocyte = kernel().node_manager.get_node_or_proxy( anode_id, tid ); if ( !astrocyte->is_proxy() ) { @@ -1979,7 +1976,7 @@ nest::BernoulliAstroBuilder::connect_() astrocyte, astrocyte_thread, synced_rng, rng, weights_n2a_, delays_n2a_, synapse_model_id_ ); } - // if the target is local, connect astrocyte=>target + // if the target is local, connect the astrocyte to the target if ( target_thread == tid ) { single_connect_astro_( anode_id, diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index 64c19901a8..b919f38772 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -464,17 +464,17 @@ class BernoulliAstroBuilder : public ConnBuilder private: NodeCollectionPTR astrocytes_; - double p_; //!< connection probability for neuron=>neuron connections - double p_syn_astro_; //!< probability of astrocyte pairing + double p_; //!< connection probability for neuron-neuron connections + double p_syn_astro_; //!< probability of astrocyte pairing per neuron-neuron connection bool astro_pool_by_index_; //!< if true, select astrocyte pool per target by index - size_t max_astro_per_target_; //!< max number of astrocytes per tartget neuron - std::vector< ConnParameter* > weights_n2n_; //!< synaptic weights neuron=>neuron - std::vector< ConnParameter* > weights_n2a_; //!< synaptic weights neuron=>astrocyte - std::vector< ConnParameter* > delays_n2n_; //!< synaptic delays neuron=>neuron and neuron=>astrocyte - std::vector< ConnParameter* > delays_n2a_; //!< synaptic delays neuron=>neuron and neuron=>astrocyte - std::vector< size_t > synapse_model_id_a2n_; //!< synapse models astrocyte=>neuron - std::vector< ConnParameter* > weights_a2n_; //!< synaptic weights astrocyte=>neuron - std::vector< ConnParameter* > delays_a2n_; //!< synaptic weights astrocyte=>neuron + size_t max_astro_per_target_; //!< maximal number of astrocytes per tartget neuron + std::vector< ConnParameter* > weights_n2n_; //!< synaptic weights for neuron-neuron connections + std::vector< ConnParameter* > weights_n2a_; //!< synaptic weights for neuron-astrocyte connections + std::vector< ConnParameter* > delays_n2n_; //!< synaptic delays for neuron-neuron and neuron-astrocyte connections + std::vector< ConnParameter* > delays_n2a_; //!< synaptic delays for neuron-neuron and neuron-astrocyte connections + std::vector< size_t > synapse_model_id_a2n_; //!< synapse models for astrocyte-neuron connections + std::vector< ConnParameter* > weights_a2n_; //!< synaptic weights for astrocyte-neuron connections + std::vector< ConnParameter* > delays_a2n_; //!< synaptic weights for astrocyte-neuron connections }; class SymmetricBernoulliBuilder : public ConnBuilder From 7eded3c4ffcd893c0235fb3d6fdfe84ebba32cac Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Tue, 19 Sep 2023 11:02:34 +0200 Subject: [PATCH 10/93] Add test_connect_pairwise_bernoulli_astro.py back --- .../test_connect_pairwise_bernoulli_astro.py | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 testsuite/pytests/test_connect_pairwise_bernoulli_astro.py diff --git a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py new file mode 100644 index 0000000000..679a3fcd9a --- /dev/null +++ b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +# +# test_connect_pairwise_bernoulli_astro.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + + +import numpy as np +import unittest +import scipy.stats +import connect_test_base +import nest + + +class TestPairwiseBernoulliAstro(connect_test_base.ConnectTestBase): + # specify connection pattern and specific params + rule = "pairwise_bernoulli_astro" + astrocyte_model = "astrocyte_lr_1994" + p = 0.5 + p_syn_astro = 1.0 + max_astro_per_target = 1 + astro_pool_by_index = True + conn_dict = { + "rule": rule, + "p": p, + "p_syn_astro": p_syn_astro, + "max_astro_per_target": max_astro_per_target, + "astro_pool_by_index": astro_pool_by_index, + } + # sizes of source-, target-population and connection probability for + # statistical test + N_s = 50 + N_t = 50 + # Critical values and number of iterations of two level test + stat_dict = {"alpha2": 0.05, "n_runs": 20} + + def setUpNetwork(self, conn_dict=None, syn_dict=None, N1=None, N2=None, neuron_model="aeif_cond_alpha_astro"): + if N1 is None: + N1 = self.N_s + if N2 is None: + N2 = self.N_t + self.pop1 = nest.Create(neuron_model, N1) + self.pop2 = nest.Create(neuron_model, N2) + self.pop_astro = nest.Create(self.astrocyte_model, N2) + conn_dict["astrocyte"] = self.pop_astro + nest.set_verbosity("M_FATAL") + nest.Connect(self.pop1, self.pop2, conn_dict, syn_dict) + + def testWeightSetting(self): + # test if weights are set correctly + + # 'weight_pre2post' is used in this connectivity rule to stand for the + # weight for neuron-to-neuron connections, to differentiate from + # the 'weight_pre2astro' for the neuron-to-astrocyte connections + w0 = 0.351 + syn_params = {"weight_pre2post": w0} + connect_test_base.check_synapse(["weight"], [w0], syn_params, self) + + def testStatistics(self): + for fan in ["in", "out"]: + expected = connect_test_base.get_expected_degrees_bernoulli(self.p, fan, self.N_s, self.N_t) + + pvalues = [] + for i in range(self.stat_dict["n_runs"]): + connect_test_base.reset_seed(i + 1, self.nr_threads) + self.setUpNetwork(conn_dict=self.conn_dict) + degrees = connect_test_base.get_degrees(fan, self.pop1, self.pop2) + degrees = connect_test_base.gather_data(degrees) + if degrees is not None: + chi, p = connect_test_base.chi_squared_check(degrees, expected, "pairwise_bernoulli") + pvalues.append(p) + connect_test_base.mpi_barrier() + if degrees is not None: + ks, p = scipy.stats.kstest(pvalues, "uniform") + self.assertTrue(p > self.stat_dict["alpha2"]) + + def testAutapsesTrue(self): + conn_params = self.conn_dict.copy() + N = 10 + conn_params["allow_multapses"] = False + + # test that autapses exist + conn_params["p"] = 1.0 + conn_params["allow_autapses"] = True + pop = nest.Create("aeif_cond_alpha_astro", N) + astro_pop = nest.Create(self.astrocyte_model, N) + conn_params["astrocyte"] = astro_pop + nest.Connect(pop, pop, conn_params) + # make sure all connections do exist + M = connect_test_base.get_connectivity_matrix(pop, pop) + connect_test_base.mpi_assert(np.diag(M), np.ones(N), self) + + def testAutapsesFalse(self): + conn_params = self.conn_dict.copy() + N = 10 + + # test that autapses were excluded + conn_params["p"] = 1.0 + conn_params["allow_autapses"] = False + pop = nest.Create("aeif_cond_alpha_astro", N) + astro_pop = nest.Create(self.astrocyte_model, N) + conn_params["astrocyte"] = astro_pop + nest.Connect(pop, pop, conn_params) + # make sure all connections do exist + M = connect_test_base.get_connectivity_matrix(pop, pop) + connect_test_base.mpi_assert(np.diag(M), np.zeros(N), self) + + def testRPortSetting(self): + neuron_model = "iaf_psc_exp_multisynapse" + neuron_dict = {"tau_syn": [0.5, 0.7]} + rtype = 2 + syn_params = {"synapse_model": "static_synapse", "receptor_type": rtype} + self.pop1 = nest.Create(neuron_model, self.N_s, neuron_dict) + self.pop2 = nest.Create(neuron_model, self.N_t, neuron_dict) + self.pop_astro = nest.Create(self.astrocyte_model, self.N_t) + self.conn_dict["astrocyte"] = self.pop_astro + conn_spec = self.conn_dict.copy() + # astrocyte excluded because not compatible + conn_spec["p_syn_astro"] = 0.0 + nest.Connect(self.pop1, self.pop2, conn_spec, syn_params) + conns = nest.GetConnections(self.pop1, self.pop2) + ports = conns.get("receptor") + self.assertTrue(connect_test_base.all_equal(ports)) + self.assertTrue(ports[0] == rtype) + + def testRPortAllSynapses(self): + # static_synapse_hom_w excluded because not compatible with astrocyte + syns = ["cont_delay_synapse", "ht_synapse", "quantal_stp_synapse", "tsodyks2_synapse", "tsodyks_synapse"] + syn_params = {"receptor_type": 1} + + for i, syn in enumerate(syns): + if syn == "stdp_dopamine_synapse": + vol = nest.Create("volume_transmitter") + nest.SetDefaults("stdp_dopamine_synapse", {"vt": vol.get("global_id")}) + syn_params["synapse_model"] = syn + self.pop1 = nest.Create("iaf_psc_exp_multisynapse", self.N_s, {"tau_syn": [0.2, 0.5]}) + self.pop2 = nest.Create("iaf_psc_exp_multisynapse", self.N_t, {"tau_syn": [0.2, 0.5]}) + self.pop_astro = nest.Create(self.astrocyte_model, self.N_t) + self.conn_dict["astrocyte"] = self.pop_astro + conn_spec = self.conn_dict.copy() + # astrocyte excluded because not compatible + conn_spec["p_syn_astro"] = 0.0 + nest.Connect(self.pop1, self.pop2, conn_spec, syn_params) + conns = nest.GetConnections(self.pop1, self.pop2) + conn_params = conns.get("receptor") + self.assertTrue(connect_test_base.all_equal(conn_params)) + self.assertTrue(conn_params[0] == syn_params["receptor_type"]) + self.setUp() + + def testWeightAllSynapses(self): + # test all synapses apart from static_synapse_hom_w where weight is not + # settable + syns = ["cont_delay_synapse", "ht_synapse", "quantal_stp_synapse", "tsodyks2_synapse", "tsodyks_synapse"] + syn_params = {"weight_pre2post": 0.372} + + for syn in syns: + if syn == "stdp_dopamine_synapse": + vol = nest.Create("volume_transmitter") + nest.SetDefaults("stdp_dopamine_synapse", {"vt": vol.get("global_id")}) + syn_params["synapse_model"] = syn + connect_test_base.check_synapse(["weight"], [syn_params["weight_pre2post"]], syn_params, self) + self.setUp() + + def testDelayAllSynapses(self): + # static_synapse_hom_w excluded because not compatible + syns = ["cont_delay_synapse", "ht_synapse", "quantal_stp_synapse", "tsodyks2_synapse", "tsodyks_synapse"] + syn_params = {"delay_pre2post": 0.4} + + for syn in syns: + if syn == "stdp_dopamine_synapse": + vol = nest.Create("volume_transmitter") + nest.SetDefaults("stdp_dopamine_synapse", {"vt": vol.get("global_id")}) + syn_params["synapse_model"] = syn + connect_test_base.check_synapse(["delay"], [syn_params["delay_pre2post"]], syn_params, self) + self.setUp() + + def testDelaySetting(self): + # test if delays are set correctly + + # one delay for all connections + d0 = 0.275 + syn_params = {"delay_pre2post": d0} + self.setUpNetwork(self.conn_dict, syn_params) + connections = nest.GetConnections(self.pop1, self.pop2) + nest_delays = connections.get("delay") + # all delays need to be equal + self.assertTrue(connect_test_base.all_equal(nest_delays)) + # delay (rounded) needs to equal the delay that was put in + self.assertTrue(abs(d0 - nest_delays[0]) < self.dt) + + # skip all STDP synapses + def testStdpFacetshwSynapseHom(self): + pass + + def testStdpPlSynapseHom(self): + pass + + def testStdpSynapse(self): + pass + + def testStdpSynapseHom(self): + pass + + def testStdpDopamineSynapse(self): + pass + + +def suite(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestPairwiseBernoulliAstro) + return suite + + +def run(): + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite()) + + +if __name__ == "__main__": + run() From 063a5fc9bff9c300ba5586c837fb0244cdca17ca Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Tue, 19 Sep 2023 22:39:27 +0200 Subject: [PATCH 11/93] Add test_connect_astro.py to test the pairwise_bernoulli_astro rule in a better way --- testsuite/pytests/test_connect_astro.py | 225 ++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 testsuite/pytests/test_connect_astro.py diff --git a/testsuite/pytests/test_connect_astro.py b/testsuite/pytests/test_connect_astro.py new file mode 100644 index 0000000000..d54b928712 --- /dev/null +++ b/testsuite/pytests/test_connect_astro.py @@ -0,0 +1,225 @@ +import numpy as np +import scipy.stats +import pytest + +import nest + +# copied from connect_test_base.py +try: + from mpi4py import MPI + haveMPI4Py = True +except ImportError: + haveMPI4Py = False + +# adapted from connect_test_base.py +def setup_network(conn_dict, syn_dict, N1, N2, neuron_model="aeif_cond_alpha_astro", astrocyte_model="astrocyte_lr_1994"): + pop1 = nest.Create(neuron_model, N1) + pop2 = nest.Create(neuron_model, N2) + pop_astro = nest.Create(astrocyte_model, N2) + conn_dict["astrocyte"] = pop_astro + nest.set_verbosity("M_FATAL") + nest.Connect(pop1, pop2, conn_dict, syn_dict) + return pop1, pop2, pop_astro + +# copied from connect_test_base.py +def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): + """ + Calculate expected degree distribution. + + Degrees with expected number of observations below e_min are combined + into larger bins. + + Return values + ------------- + 2D array. The four columns contain degree, + expected number of observation, actual number observations, and + the number of bins combined. + """ + + n = len_source_pop if fan == "in" else len_target_pop + n_p = len_target_pop if fan == "in" else len_source_pop + mid = int(round(n * p)) + e_min = 5 + + # Combine from front. + data_front = [] + cumexp = 0.0 + bins_combined = 0 + for degree in range(mid): + cumexp += scipy.stats.binom.pmf(degree, n, p) * n_p + bins_combined += 1 + if cumexp < e_min: + if degree == mid - 1: + if len(data_front) == 0: + raise RuntimeWarning("Not enough data") + deg, exp, obs, num = data_front[-1] + data_front[-1] = (deg, exp + cumexp, obs, num + bins_combined) + else: + continue + else: + data_front.append((degree - bins_combined + 1, cumexp, 0, bins_combined)) + cumexp = 0.0 + bins_combined = 0 + + # Combine from back. + data_back = [] + cumexp = 0.0 + bins_combined = 0 + for degree in reversed(range(mid, n + 1)): + cumexp += scipy.stats.binom.pmf(degree, n, p) * n_p + bins_combined += 1 + if cumexp < e_min: + if degree == mid: + if len(data_back) == 0: + raise RuntimeWarning("Not enough data") + deg, exp, obs, num = data_back[-1] + data_back[-1] = (degree, exp + cumexp, obs, num + bins_combined) + else: + continue + else: + data_back.append((degree, cumexp, 0, bins_combined)) + cumexp = 0.0 + bins_combined = 0 + data_back.reverse() + + expected = np.array(data_front + data_back) + if fan == "out": + assert sum(expected[:, 3]) == len_target_pop + 1 + else: # , 'Something is wrong' + assert sum(expected[:, 3]) == len_source_pop + 1 + + # np.hstack((np.asarray(data_front)[0], np.asarray(data_back)[0])) + return expected + +# copied from connect_test_base.py +def get_degrees(fan, pop1, pop2): + M = get_connectivity_matrix(pop1, pop2) + if fan == "in": + degrees = np.sum(M, axis=1) + elif fan == "out": + degrees = np.sum(M, axis=0) + return degrees + +# copied from connect_test_base.py +def gather_data(data_array): + """ + Gathers data from all mpi processes by collecting all element in a list if + data is a list and summing all elements to one numpy-array if data is one + numpy-array. Returns gathered data if rank of current mpi node is zero and + None otherwise. + + """ + if haveMPI4Py: + data_array_list = MPI.COMM_WORLD.gather(data_array, root=0) + if MPI.COMM_WORLD.Get_rank() == 0: + if isinstance(data_array, list): + gathered_data = [item for sublist in data_array_list for item in sublist] + else: + gathered_data = sum(data_array_list) + return gathered_data + else: + return None + else: + return data_array + +# copied from connect_test_base.py +def chi_squared_check(degrees, expected, distribution=None): + """ + Create a single network and compare the resulting degree distribution + with the expected distribution using Pearson's chi-squared GOF test. + + Parameters + ---------- + seed : PRNG seed value. + control: Boolean value. If True, _generate_multinomial_degrees will + be used instead of _get_degrees. + + Return values + ------------- + chi-squared statistic. + p-value from chi-squared test. + """ + + if distribution in ("pairwise_bernoulli", "symmetric_pairwise_bernoulli"): + observed = {} + for degree in degrees: + if degree not in observed: + observed[degree] = 1 + else: + observed[degree] += 1 + # Add observations to data structure, combining multiple observations + # where necessary. + expected[:, 2] = 0.0 + for row in expected: + for i in range(int(row[3])): + deg = int(row[0]) + i + if deg in observed: + row[2] += observed[deg] + + # ddof: adjustment to the degrees of freedom. df = k-1-ddof + return scipy.stats.chisquare(np.array(expected[:, 2]), np.array(expected[:, 1])) + else: + # ddof: adjustment to the degrees of freedom. df = k-1-ddof + return scipy.stats.chisquare(np.array(degrees), np.array(expected)) + +# copied from connect_test_base.py +def mpi_barrier(): + if haveMPI4Py: + MPI.COMM_WORLD.Barrier() + +# copied from connect_test_base.py +def get_connectivity_matrix(pop1, pop2): + """ + Returns a connectivity matrix describing all connections from pop1 to pop2 + such that M_ij describes the connection between the jth neuron in pop1 to + the ith neuron in pop2. + """ + + M = np.zeros((len(pop2), len(pop1))) + connections = nest.GetConnections(pop1, pop2) + index_dic = {} + for count, node in enumerate(pop1): + index_dic[node.get("global_id")] = count + for count, node in enumerate(pop2): + index_dic[node.get("global_id")] = count + for source, target in zip(connections.sources(), connections.targets()): + M[index_dic[target]][index_dic[source]] += 1 + return M + +# adapted from test_connect_pairwise_bernoulli.py +@pytest.mark.parametrize("p_n2n", [0.1, 0.2]) +@pytest.mark.parametrize("N1", [50, 60]) +@pytest.mark.parametrize("N2", [50, 60]) +def testStatistics(p_n2n, N1, N2): + # set connection parameters + conn_dict = { + "rule": "pairwise_bernoulli_astro", + "p": p_n2n, + "p_syn_astro": 1.0, + "max_astro_per_target": 1, + "astro_pool_by_index": True, + } + # set test parameters + stat_dict = {"alpha2": 0.05, "n_runs": 20} + nr_threads = 2 + # test indegree and outdegree separately + for fan in ["in", "out"]: + expected = get_expected_degrees_bernoulli(conn_dict["p"], fan, N1, N2) + pvalues = [] + for i in range(stat_dict["n_runs"]): + nest.ResetKernel() + nest.local_num_threads = nr_threads + nest.rng_seed = i + 1 + pop1, pop2, pop_astro = setup_network(conn_dict, None, N1, N2) + degrees = get_degrees(fan, pop1, pop2) + degrees = gather_data(degrees) + if degrees is not None: + chi, p = chi_squared_check(degrees, expected, "pairwise_bernoulli") + pvalues.append(p) + mpi_barrier() + if degrees is not None: + ks, p = scipy.stats.kstest(pvalues, "uniform") + assert p > stat_dict["alpha2"] + # print(f"{fan}degree test succeeded!") + +# testStatistics() From 77dbd8d39db8aeb47ea4b4e4d1300947db3696d9 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Wed, 20 Sep 2023 09:36:39 +0200 Subject: [PATCH 12/93] Add test_autapses_true and test_autapses_false to test_connect_astro.py --- testsuite/pytests/test_connect_astro.py | 83 ++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/testsuite/pytests/test_connect_astro.py b/testsuite/pytests/test_connect_astro.py index d54b928712..0b88b31226 100644 --- a/testsuite/pytests/test_connect_astro.py +++ b/testsuite/pytests/test_connect_astro.py @@ -1,3 +1,4 @@ +# To-do: test max_astro_per_target import numpy as np import scipy.stats import pytest @@ -17,7 +18,6 @@ def setup_network(conn_dict, syn_dict, N1, N2, neuron_model="aeif_cond_alpha_ast pop2 = nest.Create(neuron_model, N2) pop_astro = nest.Create(astrocyte_model, N2) conn_dict["astrocyte"] = pop_astro - nest.set_verbosity("M_FATAL") nest.Connect(pop1, pop2, conn_dict, syn_dict) return pop1, pop2, pop_astro @@ -186,40 +186,103 @@ def get_connectivity_matrix(pop1, pop2): M[index_dic[target]][index_dic[source]] += 1 return M +# adapted from connect_test_base.py +def mpi_assert(data_original, data_test): + """ + Compares data_original and data_test. + """ + + data_original = gather_data(data_original) + # only test if on rank 0 + if data_original is not None: + if isinstance(data_original, (np.ndarray, np.generic)) and isinstance(data_test, (np.ndarray, np.generic)): + assert data_original == pytest.approx(data_test) + else: + TestCase.assertTrue(data_original == data_test) + # adapted from test_connect_pairwise_bernoulli.py -@pytest.mark.parametrize("p_n2n", [0.1, 0.2]) -@pytest.mark.parametrize("N1", [50, 60]) -@pytest.mark.parametrize("N2", [50, 60]) -def testStatistics(p_n2n, N1, N2): +# test three levels of neuron-neuron connection probabilities +@pytest.mark.parametrize("p_n2n", [0.1, 0.5, 0.9]) +def testStatistics(p_n2n): # set connection parameters + N1 = 50 + N2 = 50 conn_dict = { "rule": "pairwise_bernoulli_astro", "p": p_n2n, - "p_syn_astro": 1.0, - "max_astro_per_target": 1, - "astro_pool_by_index": True, } + # set test parameters stat_dict = {"alpha2": 0.05, "n_runs": 20} nr_threads = 2 + + # set NEST verbosity + nest.set_verbosity("M_FATAL") + # test indegree and outdegree separately for fan in ["in", "out"]: expected = get_expected_degrees_bernoulli(conn_dict["p"], fan, N1, N2) pvalues = [] for i in range(stat_dict["n_runs"]): + # setup network and connect nest.ResetKernel() nest.local_num_threads = nr_threads nest.rng_seed = i + 1 pop1, pop2, pop_astro = setup_network(conn_dict, None, N1, N2) + # get indegree or outdegree degrees = get_degrees(fan, pop1, pop2) + # gather data from MPI processes degrees = gather_data(degrees) + # do chi-square test for indegree or outdegree if degrees is not None: chi, p = chi_squared_check(degrees, expected, "pairwise_bernoulli") pvalues.append(p) mpi_barrier() + # test if the p-values are uniformly distributed if degrees is not None: ks, p = scipy.stats.kstest(pvalues, "uniform") assert p > stat_dict["alpha2"] - # print(f"{fan}degree test succeeded!") -# testStatistics() +# adapted from test_connect_pairwise_bernoulli +def test_autapses_true(): + # set connection parameters + N = 50 + conn_dict = { + "rule": "pairwise_bernoulli_astro", + "p": 1.0, + "allow_autapses": True, + } + + # set NEST verbosity + nest.set_verbosity("M_FATAL") + + # test that autapses exist + pop = nest.Create("aeif_cond_alpha_astro", N) + pop_astro = nest.Create("astrocyte_lr_1994", N) + conn_dict["astrocyte"] = pop_astro + nest.Connect(pop, pop, conn_dict) + # make sure all connections do exist + M = get_connectivity_matrix(pop, pop) + mpi_assert(np.diag(M), np.ones(N)) + +# adapted from test_connect_pairwise_bernoulli +def test_autapses_false(): + # set connection parameters + N = 50 + conn_dict = { + "rule": "pairwise_bernoulli_astro", + "p": 1.0, + "allow_autapses": False, + } + + # set NEST verbosity + nest.set_verbosity("M_FATAL") + + # test that autapses were excluded + pop = nest.Create("aeif_cond_alpha_astro", N) + pop_astro = nest.Create("astrocyte_lr_1994", N) + conn_dict["astrocyte"] = pop_astro + nest.Connect(pop, pop, conn_dict) + # make sure all connections do exist + M = get_connectivity_matrix(pop, pop) + mpi_assert(np.diag(M), np.zeros(N)) From d1755120449b744b81e318ec4fc32e221a5941ea Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Wed, 20 Sep 2023 11:06:58 +0200 Subject: [PATCH 13/93] Move the tests for "pairwise_bernoulli_astro" back to test_connect_pairwise_bernoulli_astro.py - add a test for max_astro_per_target - improve comments --- testsuite/pytests/test_connect_astro.py | 288 ---------- .../test_connect_pairwise_bernoulli_astro.py | 498 +++++++++++------- 2 files changed, 294 insertions(+), 492 deletions(-) delete mode 100644 testsuite/pytests/test_connect_astro.py diff --git a/testsuite/pytests/test_connect_astro.py b/testsuite/pytests/test_connect_astro.py deleted file mode 100644 index 0b88b31226..0000000000 --- a/testsuite/pytests/test_connect_astro.py +++ /dev/null @@ -1,288 +0,0 @@ -# To-do: test max_astro_per_target -import numpy as np -import scipy.stats -import pytest - -import nest - -# copied from connect_test_base.py -try: - from mpi4py import MPI - haveMPI4Py = True -except ImportError: - haveMPI4Py = False - -# adapted from connect_test_base.py -def setup_network(conn_dict, syn_dict, N1, N2, neuron_model="aeif_cond_alpha_astro", astrocyte_model="astrocyte_lr_1994"): - pop1 = nest.Create(neuron_model, N1) - pop2 = nest.Create(neuron_model, N2) - pop_astro = nest.Create(astrocyte_model, N2) - conn_dict["astrocyte"] = pop_astro - nest.Connect(pop1, pop2, conn_dict, syn_dict) - return pop1, pop2, pop_astro - -# copied from connect_test_base.py -def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): - """ - Calculate expected degree distribution. - - Degrees with expected number of observations below e_min are combined - into larger bins. - - Return values - ------------- - 2D array. The four columns contain degree, - expected number of observation, actual number observations, and - the number of bins combined. - """ - - n = len_source_pop if fan == "in" else len_target_pop - n_p = len_target_pop if fan == "in" else len_source_pop - mid = int(round(n * p)) - e_min = 5 - - # Combine from front. - data_front = [] - cumexp = 0.0 - bins_combined = 0 - for degree in range(mid): - cumexp += scipy.stats.binom.pmf(degree, n, p) * n_p - bins_combined += 1 - if cumexp < e_min: - if degree == mid - 1: - if len(data_front) == 0: - raise RuntimeWarning("Not enough data") - deg, exp, obs, num = data_front[-1] - data_front[-1] = (deg, exp + cumexp, obs, num + bins_combined) - else: - continue - else: - data_front.append((degree - bins_combined + 1, cumexp, 0, bins_combined)) - cumexp = 0.0 - bins_combined = 0 - - # Combine from back. - data_back = [] - cumexp = 0.0 - bins_combined = 0 - for degree in reversed(range(mid, n + 1)): - cumexp += scipy.stats.binom.pmf(degree, n, p) * n_p - bins_combined += 1 - if cumexp < e_min: - if degree == mid: - if len(data_back) == 0: - raise RuntimeWarning("Not enough data") - deg, exp, obs, num = data_back[-1] - data_back[-1] = (degree, exp + cumexp, obs, num + bins_combined) - else: - continue - else: - data_back.append((degree, cumexp, 0, bins_combined)) - cumexp = 0.0 - bins_combined = 0 - data_back.reverse() - - expected = np.array(data_front + data_back) - if fan == "out": - assert sum(expected[:, 3]) == len_target_pop + 1 - else: # , 'Something is wrong' - assert sum(expected[:, 3]) == len_source_pop + 1 - - # np.hstack((np.asarray(data_front)[0], np.asarray(data_back)[0])) - return expected - -# copied from connect_test_base.py -def get_degrees(fan, pop1, pop2): - M = get_connectivity_matrix(pop1, pop2) - if fan == "in": - degrees = np.sum(M, axis=1) - elif fan == "out": - degrees = np.sum(M, axis=0) - return degrees - -# copied from connect_test_base.py -def gather_data(data_array): - """ - Gathers data from all mpi processes by collecting all element in a list if - data is a list and summing all elements to one numpy-array if data is one - numpy-array. Returns gathered data if rank of current mpi node is zero and - None otherwise. - - """ - if haveMPI4Py: - data_array_list = MPI.COMM_WORLD.gather(data_array, root=0) - if MPI.COMM_WORLD.Get_rank() == 0: - if isinstance(data_array, list): - gathered_data = [item for sublist in data_array_list for item in sublist] - else: - gathered_data = sum(data_array_list) - return gathered_data - else: - return None - else: - return data_array - -# copied from connect_test_base.py -def chi_squared_check(degrees, expected, distribution=None): - """ - Create a single network and compare the resulting degree distribution - with the expected distribution using Pearson's chi-squared GOF test. - - Parameters - ---------- - seed : PRNG seed value. - control: Boolean value. If True, _generate_multinomial_degrees will - be used instead of _get_degrees. - - Return values - ------------- - chi-squared statistic. - p-value from chi-squared test. - """ - - if distribution in ("pairwise_bernoulli", "symmetric_pairwise_bernoulli"): - observed = {} - for degree in degrees: - if degree not in observed: - observed[degree] = 1 - else: - observed[degree] += 1 - # Add observations to data structure, combining multiple observations - # where necessary. - expected[:, 2] = 0.0 - for row in expected: - for i in range(int(row[3])): - deg = int(row[0]) + i - if deg in observed: - row[2] += observed[deg] - - # ddof: adjustment to the degrees of freedom. df = k-1-ddof - return scipy.stats.chisquare(np.array(expected[:, 2]), np.array(expected[:, 1])) - else: - # ddof: adjustment to the degrees of freedom. df = k-1-ddof - return scipy.stats.chisquare(np.array(degrees), np.array(expected)) - -# copied from connect_test_base.py -def mpi_barrier(): - if haveMPI4Py: - MPI.COMM_WORLD.Barrier() - -# copied from connect_test_base.py -def get_connectivity_matrix(pop1, pop2): - """ - Returns a connectivity matrix describing all connections from pop1 to pop2 - such that M_ij describes the connection between the jth neuron in pop1 to - the ith neuron in pop2. - """ - - M = np.zeros((len(pop2), len(pop1))) - connections = nest.GetConnections(pop1, pop2) - index_dic = {} - for count, node in enumerate(pop1): - index_dic[node.get("global_id")] = count - for count, node in enumerate(pop2): - index_dic[node.get("global_id")] = count - for source, target in zip(connections.sources(), connections.targets()): - M[index_dic[target]][index_dic[source]] += 1 - return M - -# adapted from connect_test_base.py -def mpi_assert(data_original, data_test): - """ - Compares data_original and data_test. - """ - - data_original = gather_data(data_original) - # only test if on rank 0 - if data_original is not None: - if isinstance(data_original, (np.ndarray, np.generic)) and isinstance(data_test, (np.ndarray, np.generic)): - assert data_original == pytest.approx(data_test) - else: - TestCase.assertTrue(data_original == data_test) - -# adapted from test_connect_pairwise_bernoulli.py -# test three levels of neuron-neuron connection probabilities -@pytest.mark.parametrize("p_n2n", [0.1, 0.5, 0.9]) -def testStatistics(p_n2n): - # set connection parameters - N1 = 50 - N2 = 50 - conn_dict = { - "rule": "pairwise_bernoulli_astro", - "p": p_n2n, - } - - # set test parameters - stat_dict = {"alpha2": 0.05, "n_runs": 20} - nr_threads = 2 - - # set NEST verbosity - nest.set_verbosity("M_FATAL") - - # test indegree and outdegree separately - for fan in ["in", "out"]: - expected = get_expected_degrees_bernoulli(conn_dict["p"], fan, N1, N2) - pvalues = [] - for i in range(stat_dict["n_runs"]): - # setup network and connect - nest.ResetKernel() - nest.local_num_threads = nr_threads - nest.rng_seed = i + 1 - pop1, pop2, pop_astro = setup_network(conn_dict, None, N1, N2) - # get indegree or outdegree - degrees = get_degrees(fan, pop1, pop2) - # gather data from MPI processes - degrees = gather_data(degrees) - # do chi-square test for indegree or outdegree - if degrees is not None: - chi, p = chi_squared_check(degrees, expected, "pairwise_bernoulli") - pvalues.append(p) - mpi_barrier() - # test if the p-values are uniformly distributed - if degrees is not None: - ks, p = scipy.stats.kstest(pvalues, "uniform") - assert p > stat_dict["alpha2"] - -# adapted from test_connect_pairwise_bernoulli -def test_autapses_true(): - # set connection parameters - N = 50 - conn_dict = { - "rule": "pairwise_bernoulli_astro", - "p": 1.0, - "allow_autapses": True, - } - - # set NEST verbosity - nest.set_verbosity("M_FATAL") - - # test that autapses exist - pop = nest.Create("aeif_cond_alpha_astro", N) - pop_astro = nest.Create("astrocyte_lr_1994", N) - conn_dict["astrocyte"] = pop_astro - nest.Connect(pop, pop, conn_dict) - # make sure all connections do exist - M = get_connectivity_matrix(pop, pop) - mpi_assert(np.diag(M), np.ones(N)) - -# adapted from test_connect_pairwise_bernoulli -def test_autapses_false(): - # set connection parameters - N = 50 - conn_dict = { - "rule": "pairwise_bernoulli_astro", - "p": 1.0, - "allow_autapses": False, - } - - # set NEST verbosity - nest.set_verbosity("M_FATAL") - - # test that autapses were excluded - pop = nest.Create("aeif_cond_alpha_astro", N) - pop_astro = nest.Create("astrocyte_lr_1994", N) - conn_dict["astrocyte"] = pop_astro - nest.Connect(pop, pop, conn_dict) - # make sure all connections do exist - M = get_connectivity_matrix(pop, pop) - mpi_assert(np.diag(M), np.zeros(N)) diff --git a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py index 679a3fcd9a..460323b072 100644 --- a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py +++ b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py @@ -21,214 +21,304 @@ import numpy as np -import unittest import scipy.stats -import connect_test_base -import nest +import pytest +import nest -class TestPairwiseBernoulliAstro(connect_test_base.ConnectTestBase): - # specify connection pattern and specific params - rule = "pairwise_bernoulli_astro" - astrocyte_model = "astrocyte_lr_1994" - p = 0.5 - p_syn_astro = 1.0 - max_astro_per_target = 1 - astro_pool_by_index = True +# copied from connect_test_base.py +try: + from mpi4py import MPI + haveMPI4Py = True +except ImportError: + haveMPI4Py = False + +# adapted from connect_test_base.py +def setup_network(conn_dict, syn_dict, N1, N2, neuron_model="aeif_cond_alpha_astro", astrocyte_model="astrocyte_lr_1994"): + pop1 = nest.Create(neuron_model, N1) + pop2 = nest.Create(neuron_model, N2) + pop_astro = nest.Create(astrocyte_model, N2) + conn_dict["astrocyte"] = pop_astro + nest.Connect(pop1, pop2, conn_dict, syn_dict) + return pop1, pop2, pop_astro + +# copied from connect_test_base.py +def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): + """ + Calculate expected degree distribution. + + Degrees with expected number of observations below e_min are combined + into larger bins. + + Return values + ------------- + 2D array. The four columns contain degree, + expected number of observation, actual number observations, and + the number of bins combined. + """ + + n = len_source_pop if fan == "in" else len_target_pop + n_p = len_target_pop if fan == "in" else len_source_pop + mid = int(round(n * p)) + e_min = 5 + + # Combine from front. + data_front = [] + cumexp = 0.0 + bins_combined = 0 + for degree in range(mid): + cumexp += scipy.stats.binom.pmf(degree, n, p) * n_p + bins_combined += 1 + if cumexp < e_min: + if degree == mid - 1: + if len(data_front) == 0: + raise RuntimeWarning("Not enough data") + deg, exp, obs, num = data_front[-1] + data_front[-1] = (deg, exp + cumexp, obs, num + bins_combined) + else: + continue + else: + data_front.append((degree - bins_combined + 1, cumexp, 0, bins_combined)) + cumexp = 0.0 + bins_combined = 0 + + # Combine from back. + data_back = [] + cumexp = 0.0 + bins_combined = 0 + for degree in reversed(range(mid, n + 1)): + cumexp += scipy.stats.binom.pmf(degree, n, p) * n_p + bins_combined += 1 + if cumexp < e_min: + if degree == mid: + if len(data_back) == 0: + raise RuntimeWarning("Not enough data") + deg, exp, obs, num = data_back[-1] + data_back[-1] = (degree, exp + cumexp, obs, num + bins_combined) + else: + continue + else: + data_back.append((degree, cumexp, 0, bins_combined)) + cumexp = 0.0 + bins_combined = 0 + data_back.reverse() + + expected = np.array(data_front + data_back) + if fan == "out": + assert sum(expected[:, 3]) == len_target_pop + 1 + else: # , 'Something is wrong' + assert sum(expected[:, 3]) == len_source_pop + 1 + + # np.hstack((np.asarray(data_front)[0], np.asarray(data_back)[0])) + return expected + +# copied from connect_test_base.py +def get_degrees(fan, pop1, pop2): + M = get_connectivity_matrix(pop1, pop2) + if fan == "in": + degrees = np.sum(M, axis=1) + elif fan == "out": + degrees = np.sum(M, axis=0) + return degrees + +# copied from connect_test_base.py +def gather_data(data_array): + """ + Gathers data from all mpi processes by collecting all element in a list if + data is a list and summing all elements to one numpy-array if data is one + numpy-array. Returns gathered data if rank of current mpi node is zero and + None otherwise. + + """ + if haveMPI4Py: + data_array_list = MPI.COMM_WORLD.gather(data_array, root=0) + if MPI.COMM_WORLD.Get_rank() == 0: + if isinstance(data_array, list): + gathered_data = [item for sublist in data_array_list for item in sublist] + else: + gathered_data = sum(data_array_list) + return gathered_data + else: + return None + else: + return data_array + +# copied from connect_test_base.py +def chi_squared_check(degrees, expected, distribution=None): + """ + Create a single network and compare the resulting degree distribution + with the expected distribution using Pearson's chi-squared GOF test. + + Parameters + ---------- + seed : PRNG seed value. + control: Boolean value. If True, _generate_multinomial_degrees will + be used instead of _get_degrees. + + Return values + ------------- + chi-squared statistic. + p-value from chi-squared test. + """ + + if distribution in ("pairwise_bernoulli", "symmetric_pairwise_bernoulli"): + observed = {} + for degree in degrees: + if degree not in observed: + observed[degree] = 1 + else: + observed[degree] += 1 + # Add observations to data structure, combining multiple observations + # where necessary. + expected[:, 2] = 0.0 + for row in expected: + for i in range(int(row[3])): + deg = int(row[0]) + i + if deg in observed: + row[2] += observed[deg] + + # ddof: adjustment to the degrees of freedom. df = k-1-ddof + return scipy.stats.chisquare(np.array(expected[:, 2]), np.array(expected[:, 1])) + else: + # ddof: adjustment to the degrees of freedom. df = k-1-ddof + return scipy.stats.chisquare(np.array(degrees), np.array(expected)) + +# copied from connect_test_base.py +def mpi_barrier(): + if haveMPI4Py: + MPI.COMM_WORLD.Barrier() + +# copied from connect_test_base.py +def get_connectivity_matrix(pop1, pop2): + """ + Returns a connectivity matrix describing all connections from pop1 to pop2 + such that M_ij describes the connection between the jth neuron in pop1 to + the ith neuron in pop2. + """ + + M = np.zeros((len(pop2), len(pop1))) + connections = nest.GetConnections(pop1, pop2) + index_dic = {} + for count, node in enumerate(pop1): + index_dic[node.get("global_id")] = count + for count, node in enumerate(pop2): + index_dic[node.get("global_id")] = count + for source, target in zip(connections.sources(), connections.targets()): + M[index_dic[target]][index_dic[source]] += 1 + return M + +# adapted from connect_test_base.py +def mpi_assert(data_original, data_test): + """ + Compares data_original and data_test. + """ + + data_original = gather_data(data_original) + # only test if on rank 0 + if data_original is not None: + if isinstance(data_original, (np.ndarray, np.generic)) and isinstance(data_test, (np.ndarray, np.generic)): + assert data_original == pytest.approx(data_test) + else: + TestCase.assertTrue(data_original == data_test) + +# adapted from test_connect_pairwise_bernoulli.py +# a test for parameters "p" and "max_astro_per_target" +# run for three levels of neuron-neuron connection probabilities +@pytest.mark.parametrize("p_n2n", [0.1, 0.3, 0.5]) +def test_statistics(p_n2n): + # set connection parameters + N1 = 50 + N2 = 50 + max_astro_per_target = 5 conn_dict = { - "rule": rule, - "p": p, - "p_syn_astro": p_syn_astro, - "max_astro_per_target": max_astro_per_target, - "astro_pool_by_index": astro_pool_by_index, + "rule": "pairwise_bernoulli_astro", + "p": p_n2n, + "max_astro_per_target": max_astro_per_target } - # sizes of source-, target-population and connection probability for - # statistical test - N_s = 50 - N_t = 50 - # Critical values and number of iterations of two level test - stat_dict = {"alpha2": 0.05, "n_runs": 20} - def setUpNetwork(self, conn_dict=None, syn_dict=None, N1=None, N2=None, neuron_model="aeif_cond_alpha_astro"): - if N1 is None: - N1 = self.N_s - if N2 is None: - N2 = self.N_t - self.pop1 = nest.Create(neuron_model, N1) - self.pop2 = nest.Create(neuron_model, N2) - self.pop_astro = nest.Create(self.astrocyte_model, N2) - conn_dict["astrocyte"] = self.pop_astro - nest.set_verbosity("M_FATAL") - nest.Connect(self.pop1, self.pop2, conn_dict, syn_dict) - - def testWeightSetting(self): - # test if weights are set correctly - - # 'weight_pre2post' is used in this connectivity rule to stand for the - # weight for neuron-to-neuron connections, to differentiate from - # the 'weight_pre2astro' for the neuron-to-astrocyte connections - w0 = 0.351 - syn_params = {"weight_pre2post": w0} - connect_test_base.check_synapse(["weight"], [w0], syn_params, self) - - def testStatistics(self): - for fan in ["in", "out"]: - expected = connect_test_base.get_expected_degrees_bernoulli(self.p, fan, self.N_s, self.N_t) - - pvalues = [] - for i in range(self.stat_dict["n_runs"]): - connect_test_base.reset_seed(i + 1, self.nr_threads) - self.setUpNetwork(conn_dict=self.conn_dict) - degrees = connect_test_base.get_degrees(fan, self.pop1, self.pop2) - degrees = connect_test_base.gather_data(degrees) - if degrees is not None: - chi, p = connect_test_base.chi_squared_check(degrees, expected, "pairwise_bernoulli") - pvalues.append(p) - connect_test_base.mpi_barrier() + # set test parameters + stat_dict = {"alpha2": 0.05, "n_runs": 20} + nr_threads = 2 + + # set NEST verbosity + nest.set_verbosity("M_FATAL") + + # here we test + # 1. p yields the correct indegree and outdegree + # 2. max_astro_per_target limits the number of astrocytes connected to each target neuron + for fan in ["in", "out"]: + expected = get_expected_degrees_bernoulli(conn_dict["p"], fan, N1, N2) + pvalues = [] + n_astrocytes = [] + for i in range(stat_dict["n_runs"]): + # setup network and connect + nest.ResetKernel() + nest.local_num_threads = nr_threads + nest.rng_seed = i + 1 + pop1, pop2, pop_astro = setup_network(conn_dict, None, N1, N2) + # get indegree or outdegree + degrees = get_degrees(fan, pop1, pop2) + # gather data from MPI processes + degrees = gather_data(degrees) + # do chi-square test for indegree or outdegree if degrees is not None: - ks, p = scipy.stats.kstest(pvalues, "uniform") - self.assertTrue(p > self.stat_dict["alpha2"]) - - def testAutapsesTrue(self): - conn_params = self.conn_dict.copy() - N = 10 - conn_params["allow_multapses"] = False - - # test that autapses exist - conn_params["p"] = 1.0 - conn_params["allow_autapses"] = True - pop = nest.Create("aeif_cond_alpha_astro", N) - astro_pop = nest.Create(self.astrocyte_model, N) - conn_params["astrocyte"] = astro_pop - nest.Connect(pop, pop, conn_params) - # make sure all connections do exist - M = connect_test_base.get_connectivity_matrix(pop, pop) - connect_test_base.mpi_assert(np.diag(M), np.ones(N), self) - - def testAutapsesFalse(self): - conn_params = self.conn_dict.copy() - N = 10 - - # test that autapses were excluded - conn_params["p"] = 1.0 - conn_params["allow_autapses"] = False - pop = nest.Create("aeif_cond_alpha_astro", N) - astro_pop = nest.Create(self.astrocyte_model, N) - conn_params["astrocyte"] = astro_pop - nest.Connect(pop, pop, conn_params) - # make sure all connections do exist - M = connect_test_base.get_connectivity_matrix(pop, pop) - connect_test_base.mpi_assert(np.diag(M), np.zeros(N), self) - - def testRPortSetting(self): - neuron_model = "iaf_psc_exp_multisynapse" - neuron_dict = {"tau_syn": [0.5, 0.7]} - rtype = 2 - syn_params = {"synapse_model": "static_synapse", "receptor_type": rtype} - self.pop1 = nest.Create(neuron_model, self.N_s, neuron_dict) - self.pop2 = nest.Create(neuron_model, self.N_t, neuron_dict) - self.pop_astro = nest.Create(self.astrocyte_model, self.N_t) - self.conn_dict["astrocyte"] = self.pop_astro - conn_spec = self.conn_dict.copy() - # astrocyte excluded because not compatible - conn_spec["p_syn_astro"] = 0.0 - nest.Connect(self.pop1, self.pop2, conn_spec, syn_params) - conns = nest.GetConnections(self.pop1, self.pop2) - ports = conns.get("receptor") - self.assertTrue(connect_test_base.all_equal(ports)) - self.assertTrue(ports[0] == rtype) - - def testRPortAllSynapses(self): - # static_synapse_hom_w excluded because not compatible with astrocyte - syns = ["cont_delay_synapse", "ht_synapse", "quantal_stp_synapse", "tsodyks2_synapse", "tsodyks_synapse"] - syn_params = {"receptor_type": 1} - - for i, syn in enumerate(syns): - if syn == "stdp_dopamine_synapse": - vol = nest.Create("volume_transmitter") - nest.SetDefaults("stdp_dopamine_synapse", {"vt": vol.get("global_id")}) - syn_params["synapse_model"] = syn - self.pop1 = nest.Create("iaf_psc_exp_multisynapse", self.N_s, {"tau_syn": [0.2, 0.5]}) - self.pop2 = nest.Create("iaf_psc_exp_multisynapse", self.N_t, {"tau_syn": [0.2, 0.5]}) - self.pop_astro = nest.Create(self.astrocyte_model, self.N_t) - self.conn_dict["astrocyte"] = self.pop_astro - conn_spec = self.conn_dict.copy() - # astrocyte excluded because not compatible - conn_spec["p_syn_astro"] = 0.0 - nest.Connect(self.pop1, self.pop2, conn_spec, syn_params) - conns = nest.GetConnections(self.pop1, self.pop2) - conn_params = conns.get("receptor") - self.assertTrue(connect_test_base.all_equal(conn_params)) - self.assertTrue(conn_params[0] == syn_params["receptor_type"]) - self.setUp() - - def testWeightAllSynapses(self): - # test all synapses apart from static_synapse_hom_w where weight is not - # settable - syns = ["cont_delay_synapse", "ht_synapse", "quantal_stp_synapse", "tsodyks2_synapse", "tsodyks_synapse"] - syn_params = {"weight_pre2post": 0.372} - - for syn in syns: - if syn == "stdp_dopamine_synapse": - vol = nest.Create("volume_transmitter") - nest.SetDefaults("stdp_dopamine_synapse", {"vt": vol.get("global_id")}) - syn_params["synapse_model"] = syn - connect_test_base.check_synapse(["weight"], [syn_params["weight_pre2post"]], syn_params, self) - self.setUp() - - def testDelayAllSynapses(self): - # static_synapse_hom_w excluded because not compatible - syns = ["cont_delay_synapse", "ht_synapse", "quantal_stp_synapse", "tsodyks2_synapse", "tsodyks_synapse"] - syn_params = {"delay_pre2post": 0.4} - - for syn in syns: - if syn == "stdp_dopamine_synapse": - vol = nest.Create("volume_transmitter") - nest.SetDefaults("stdp_dopamine_synapse", {"vt": vol.get("global_id")}) - syn_params["synapse_model"] = syn - connect_test_base.check_synapse(["delay"], [syn_params["delay_pre2post"]], syn_params, self) - self.setUp() - - def testDelaySetting(self): - # test if delays are set correctly - - # one delay for all connections - d0 = 0.275 - syn_params = {"delay_pre2post": d0} - self.setUpNetwork(self.conn_dict, syn_params) - connections = nest.GetConnections(self.pop1, self.pop2) - nest_delays = connections.get("delay") - # all delays need to be equal - self.assertTrue(connect_test_base.all_equal(nest_delays)) - # delay (rounded) needs to equal the delay that was put in - self.assertTrue(abs(d0 - nest_delays[0]) < self.dt) - - # skip all STDP synapses - def testStdpFacetshwSynapseHom(self): - pass - - def testStdpPlSynapseHom(self): - pass - - def testStdpSynapse(self): - pass - - def testStdpSynapseHom(self): - pass - - def testStdpDopamineSynapse(self): - pass - - -def suite(): - suite = unittest.TestLoader().loadTestsFromTestCase(TestPairwiseBernoulliAstro) - return suite - - -def run(): - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite()) - - -if __name__ == "__main__": - run() + chi, p_degrees = chi_squared_check(degrees, expected, "pairwise_bernoulli") + pvalues.append(p_degrees) + mpi_barrier() + # get number of astrocytes connected to each target neuron + conns_n2n = nest.GetConnections(pop1, pop2).get() + conns_a2n = nest.GetConnections(pop_astro, pop2).get() + for id in list(set(conns_n2n["target"])): + astrocytes = np.array(conns_a2n["source"]) + targets = np.array(conns_a2n["target"]) + n_astrocytes.append(len(set(astrocytes[targets == id]))) + # assert that the p-values are uniformly distributed + if degrees is not None: + ks, p_uniform = scipy.stats.kstest(pvalues, "uniform") + assert p_uniform > stat_dict["alpha2"] + # assert that for each target neuron, number of astrocytes is smaller than max_astro_per_target + assert all(n <= max_astro_per_target for n in n_astrocytes) + +# adapted from test_connect_pairwise_bernoulli +def test_autapses_true(): + # set connection parameters + N = 50 + conn_dict = { + "rule": "pairwise_bernoulli_astro", + "p": 1.0, + "allow_autapses": True, + } + + # set NEST verbosity + nest.set_verbosity("M_FATAL") + + # test that autapses exist + pop = nest.Create("aeif_cond_alpha_astro", N) + pop_astro = nest.Create("astrocyte_lr_1994", N) + conn_dict["astrocyte"] = pop_astro + nest.Connect(pop, pop, conn_dict) + # make sure all connections do exist + M = get_connectivity_matrix(pop, pop) + mpi_assert(np.diag(M), np.ones(N)) + +# adapted from test_connect_pairwise_bernoulli +def test_autapses_false(): + # set connection parameters + N = 50 + conn_dict = { + "rule": "pairwise_bernoulli_astro", + "p": 1.0, + "allow_autapses": False, + } + + # set NEST verbosity + nest.set_verbosity("M_FATAL") + + # test that autapses were excluded + pop = nest.Create("aeif_cond_alpha_astro", N) + pop_astro = nest.Create("astrocyte_lr_1994", N) + conn_dict["astrocyte"] = pop_astro + nest.Connect(pop, pop, conn_dict) + # make sure all connections do exist + M = get_connectivity_matrix(pop, pop) + mpi_assert(np.diag(M), np.zeros(N)) From 317d944d6cab69338a716277af3c2816282126c7 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 22 Sep 2023 09:08:19 +0200 Subject: [PATCH 14/93] Add astrocyte documentation - add astrocyte_small_network.py and astrocyte_brunel.py to example list - add a subsection for astrocyte in the documentation "Connection Rules" --- doc/htmldoc/examples/index.rst | 4 ++++ .../synapses/connection_management.rst | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/doc/htmldoc/examples/index.rst b/doc/htmldoc/examples/index.rst index fd70e54ac4..3f05e3ea57 100644 --- a/doc/htmldoc/examples/index.rst +++ b/doc/htmldoc/examples/index.rst @@ -59,6 +59,8 @@ PyNEST examples * :doc:`../auto_examples/astrocyte_single` * :doc:`../auto_examples/astrocyte_tripartite` + * :doc:`../auto_examples/astrocyte_small_network` + * :doc:`../auto_examples/astrocyte_brunel` .. grid:: 1 1 2 3 @@ -327,6 +329,8 @@ PyNEST examples ../auto_examples/hpc_benchmark ../auto_examples/astrocyte_single ../auto_examples/astrocyte_tripartite + ../auto_examples/astrocyte_small_network + ../auto_examples/astrocyte_brunel .. toctree:: :hidden: diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 88263bb3e4..317c002775 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -238,6 +238,25 @@ created with probability ``p``. conn_spec_dict = {'rule': 'pairwise_bernoulli', 'p': p} nest.Connect(A, B, conn_spec_dict) +pairwise bernoulli astro +~~~~~~~~~~~~~~~~~~ + +A pairwise Bernoulli rule for neuron-astrocyte networks. For each possible pair +of nodes from ``A`` and ``B``, a connection is created with probability ``p``. +For each connection created between neurons, an astrocyte is paired with it with +probability ``p_syn_astro``. A connection from the presynaptic neuron and a +connection to the postsynaptic neuron are made for the paired astrocyte. + +.. code-block:: python + + n, m, x, p, p_syn_astro = 10, 12, 5, 0.2, 0.5 + A = nest.Create('aeif_cond_alpha_astro', n) + B = nest.Create('aeif_cond_alpha_astro', m) + C = nest.Create('astrocyte_lr_1994', x) + conn_spec_dict = {'rule': 'pairwise_bernoulli_astro', 'astrocyte': C, + 'p': p, 'p_syn_astro': p_syn_astro} + nest.Connect(A, B, conn_spec_dict) + symmetric pairwise bernoulli ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From c299345eb19ebbe40b330442f0a85a1f494046e0 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 22 Sep 2023 14:48:10 +0200 Subject: [PATCH 15/93] Fix C++ formatting with clang-format --- nestkernel/conn_builder.cpp | 39 +++++++++++++++++++++++-------------- nestkernel/conn_builder.h | 9 +++++++-- nestkernel/nest_names.cpp | 22 ++++++++++----------- nestkernel/nest_names.h | 23 +++++++++++----------- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 3c563e8ccb..5c82aab6d6 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1612,7 +1612,8 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, p_syn_astro_ = ( *conn_spec )[ names::p_syn_astro ]; if ( p_syn_astro_ < 0 or 1 < p_syn_astro_ ) { - throw BadProperty( "Probability of astrocyte pairing per neuron-neuron connection 0 <= p_syn_astro <= 1 required." ); + throw BadProperty( + "Probability of astrocyte pairing per neuron-neuron connection 0 <= p_syn_astro <= 1 required." ); } } else @@ -1702,17 +1703,20 @@ nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, DictionaryDatum syn_defaults_a2n = kernel().model_manager.get_connector_defaults( synapse_model_id_a2n_[ synapse_indx ] ); weights_a2n_[ synapse_indx ] = syn_params->known( names::weight_astro2post ) - ? ConnParameter::create(( *syn_params )[ names::weight_astro2post ], num_threads ) + ? ConnParameter::create( ( *syn_params )[ names::weight_astro2post ], num_threads ) : ConnParameter::create( ( *syn_defaults_a2n )[ names::weight ], num_threads ); delays_a2n_[ synapse_indx ] = syn_params->known( names::delay_astro2post ) - ? ConnParameter::create(( *syn_params )[ names::delay_astro2post ], num_threads ) + ? ConnParameter::create( ( *syn_params )[ names::delay_astro2post ], num_threads ) : ConnParameter::create( ( *syn_defaults_a2n )[ names::delay ], num_threads ); } } int nest::BernoulliAstroBuilder::get_start_astro_index( const int max_astro_per_target, - const int default_n_astro_per_target, const int targets_size, const int astrocytes_size, const int target_index ) + const int default_n_astro_per_target, + const int targets_size, + const int astrocytes_size, + const int target_index ) { // helper function to determine the starting astrocyte for the astrocyte pool // of a target neuron, in the deterministic (astro_pool_by_index = true) case @@ -1748,8 +1752,12 @@ nest::BernoulliAstroBuilder::get_start_astro_index( const int max_astro_per_targ void nest::BernoulliAstroBuilder::single_connect_astro_( const size_t snode_id, - Node* target, const size_t target_thread, RngPtr synced_rng, RngPtr rng, - const std::vector< ConnParameter* >& weights, const std::vector< ConnParameter* >& delays, + Node* target, + const size_t target_thread, + RngPtr synced_rng, + RngPtr rng, + const std::vector< ConnParameter* >& weights, + const std::vector< ConnParameter* >& delays, const std::vector< size_t >& synapse_model_id ) { // function to make single connections @@ -1847,8 +1855,8 @@ nest::BernoulliAstroBuilder::connect_() max_astro_per_target_ = astro_pool_by_index_ == true ? default_n_astro_per_target : astrocytes_size; } // assert max_astro_per_target_ > 0 and <= astrocytes_size - assert ( max_astro_per_target_ > 0 ); - assert ( max_astro_per_target_ <= astrocytes_size ); + assert( max_astro_per_target_ > 0 ); + assert( max_astro_per_target_ <= astrocytes_size ); // Iterate through target neurons. For each, three steps are done: // 1. draw indegree 2. select astrocyte pool 3. make connections @@ -1897,7 +1905,8 @@ nest::BernoulliAstroBuilder::connect_() break; } // determine the starting astrocyte - astro_index = get_start_astro_index( max_astro_per_target_, default_n_astro_per_target, targets_size, astrocytes_size, target_index ); + astro_index = get_start_astro_index( + max_astro_per_target_, default_n_astro_per_target, targets_size, astrocytes_size, target_index ); // iterate from the starting astrocyte and add astrocytes until desired pool size reached for ( size_t i = 0; i < max_astro_per_target_; i++ ) { @@ -1950,8 +1959,8 @@ nest::BernoulliAstroBuilder::connect_() // if the target is local, connect the source to the target if ( target_thread == tid ) { - single_connect_astro_( snode_id, - target, target_thread, synced_rng, rng, weights_n2n_, delays_n2n_, synapse_model_id_ ); + single_connect_astro_( + snode_id, target, target_thread, synced_rng, rng, weights_n2n_, delays_n2n_, synapse_model_id_ ); } // Bernoulli trial to determine if the considered neuron-neuron connection should be paired with an astrocyte @@ -1972,15 +1981,15 @@ nest::BernoulliAstroBuilder::connect_() if ( !astrocyte->is_proxy() ) { astrocyte_thread = tid; - single_connect_astro_( snode_id, - astrocyte, astrocyte_thread, synced_rng, rng, weights_n2a_, delays_n2a_, synapse_model_id_ ); + single_connect_astro_( + snode_id, astrocyte, astrocyte_thread, synced_rng, rng, weights_n2a_, delays_n2a_, synapse_model_id_ ); } // if the target is local, connect the astrocyte to the target if ( target_thread == tid ) { - single_connect_astro_( anode_id, - target, target_thread, synced_rng, rng, weights_a2n_, delays_a2n_, synapse_model_id_a2n_ ); + single_connect_astro_( + anode_id, target, target_thread, synced_rng, rng, weights_a2n_, delays_a2n_, synapse_model_id_a2n_ ); } } } diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index b919f38772..3b514e83c2 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -458,8 +458,13 @@ class BernoulliAstroBuilder : public ConnBuilder protected: int get_start_astro_index( const int, const int, const int, const int, const int ); void single_connect_astro_( const size_t, - Node*, const size_t, RngPtr, RngPtr, - const std::vector< ConnParameter* >&, const std::vector< ConnParameter* >&, const std::vector< size_t >& ); + Node*, + const size_t, + RngPtr, + RngPtr, + const std::vector< ConnParameter* >&, + const std::vector< ConnParameter* >&, + const std::vector< size_t >& ); void connect_() override; private: diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp index a5c3c8bc3a..5e87e3d63c 100644 --- a/nestkernel/nest_names.cpp +++ b/nestkernel/nest_names.cpp @@ -70,6 +70,9 @@ const Name asc_amps( "asc_amps" ); const Name asc_decay( "asc_decay" ); const Name asc_init( "asc_init" ); const Name asc_r( "asc_r" ); +const Name astro_pool_by_index( "astro_pool_by_index" ); +const Name astro2post( "astro2post" ); +const Name astrocyte( "astrocyte" ); const Name available( "available" ); const Name azimuth_angle( "azimuth_angle" ); @@ -117,6 +120,9 @@ const Name dead_time( "dead_time" ); const Name dead_time_random( "dead_time_random" ); const Name dead_time_shape( "dead_time_shape" ); const Name delay( "delay" ); +const Name delay_astro2post( "delay_astro2post" ); +const Name delay_pre2astro( "delay_pre2astro" ); +const Name delay_pre2post( "delay_pre2post" ); const Name delay_u_bars( "delay_u_bars" ); const Name deliver_interval( "deliver_interval" ); const Name delta( "delta" ); @@ -285,6 +291,7 @@ const Name major_axis( "major_axis" ); const Name make_symmetric( "make_symmetric" ); const Name mask( "mask" ); const Name max( "max" ); +const Name max_astro_per_target( "max_astro_per_target" ); const Name max_buffer_size_target_data( "max_buffer_size_target_data" ); const Name max_delay( "max_delay" ); const Name max_num_syn_models( "max_num_syn_models" ); @@ -344,6 +351,7 @@ const Name overwrite_files( "overwrite_files" ); const Name P( "P" ); const Name p( "p" ); const Name p_copy( "p_copy" ); +const Name p_syn_astro( "p_syn_astro" ); const Name p_transmit( "p_transmit" ); const Name pairwise_bernoulli_on_source( "pairwise_bernoulli_on_source" ); const Name pairwise_bernoulli_on_target( "pairwise_bernoulli_on_target" ); @@ -605,7 +613,10 @@ const Name Wmax( "Wmax" ); const Name Wmin( "Wmin" ); const Name w( "w" ); const Name weight( "weight" ); +const Name weight_astro2post( "weight_astro2post" ); const Name weight_per_lut_entry( "weight_per_lut_entry" ); +const Name weight_pre2astro( "weight_pre2astro" ); +const Name weight_pre2post( "weight_pre2post" ); const Name weight_recorder( "weight_recorder" ); const Name weights( "weights" ); const Name wfr_comm_interval( "wfr_comm_interval" ); @@ -624,17 +635,6 @@ const Name y_1( "y_1" ); const Name z( "z" ); const Name z_connected( "z_connected" ); -const Name astrocyte( "astrocyte" ); -const Name astro2post( "astro2post" ); -const Name p_syn_astro( "p_syn_astro" ); -const Name astro_pool_by_index( "astro_pool_by_index" ); -const Name max_astro_per_target( "max_astro_per_target" ); -extern const Name weight_pre2post( "weight_pre2post" ); -extern const Name weight_pre2astro( "weight_pre2astro" ); -extern const Name delay_pre2post( "delay_pre2post" ); -extern const Name delay_pre2astro( "delay_pre2astro" ); -extern const Name weight_astro2post( "weight_astro2post"); -extern const Name delay_astro2post( "delay_astro2post"); } // namespace names } // namespace nest diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h index 2957badfd8..bda9b7f1f3 100644 --- a/nestkernel/nest_names.h +++ b/nestkernel/nest_names.h @@ -96,6 +96,9 @@ extern const Name asc_amps; extern const Name asc_decay; extern const Name asc_init; extern const Name asc_r; +extern const Name astro_pool_by_index; +extern const Name astro2post; +extern const Name astrocyte; extern const Name available; extern const Name azimuth_angle; @@ -143,6 +146,9 @@ extern const Name dead_time; extern const Name dead_time_random; extern const Name dead_time_shape; extern const Name delay; +extern const Name delay_astro2post; +extern const Name delay_pre2astro; +extern const Name delay_pre2post; extern const Name delay_u_bars; extern const Name deliver_interval; extern const Name delta; @@ -311,6 +317,7 @@ extern const Name major_axis; extern const Name make_symmetric; extern const Name mask; extern const Name max; +extern const Name max_astro_per_target; extern const Name max_buffer_size_target_data; extern const Name max_delay; extern const Name max_num_syn_models; @@ -370,6 +377,7 @@ extern const Name overwrite_files; extern const Name P; extern const Name p; extern const Name p_copy; +extern const Name p_syn_astro; extern const Name p_transmit; extern const Name pairwise_bernoulli_on_source; extern const Name pairwise_bernoulli_on_target; @@ -631,7 +639,10 @@ extern const Name Wmax; extern const Name Wmin; extern const Name w; extern const Name weight; +extern const Name weight_astro2post; extern const Name weight_per_lut_entry; +extern const Name weight_pre2astro; +extern const Name weight_pre2post; extern const Name weight_recorder; extern const Name weights; extern const Name wfr_comm_interval; @@ -649,18 +660,6 @@ extern const Name y_1; extern const Name z; extern const Name z_connected; - -extern const Name astrocyte; -extern const Name astro2post; -extern const Name p_syn_astro; -extern const Name astro_pool_by_index; -extern const Name max_astro_per_target; -extern const Name weight_pre2post; -extern const Name weight_pre2astro; -extern const Name delay_pre2post; -extern const Name delay_pre2astro; -extern const Name weight_astro2post; -extern const Name delay_astro2post; } // namespace names } // namespace nest From 38a558b8147c8af83168f1d99e0457c9814d64bb Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 22 Sep 2023 15:32:49 +0200 Subject: [PATCH 16/93] Update astrocyte_brunel.py --- pynest/examples/astrocyte_brunel.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index 4d6696e74b..897c18ad57 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -24,10 +24,9 @@ ------------------------------------------------------------ This script simulates a random balanced network with excitatory and inhibitory -neurons and astrocytes. The ``astrocyte_lr_1994`` model is according to the -articles [1]_, [2]_, and [3]_. The ``aeif_cond_alpha_astro`` model is an -adaptive exponential integrate and fire neuron supporting neuron-astrocyte -interactions. +neurons and astrocytes. The ``astrocyte_lr_1994`` model is according to [1]_, +[2]_, and [3]_. The ``aeif_cond_alpha_astro`` model is an adaptive exponential +integrate and fire neuron supporting neuron-astrocyte interactions. The network is created using the ``pairwise_bernoulli_astro`` rule, with the ``tsodyks_synapse`` as the connections from neurons to neruons and astrocytes, @@ -44,20 +43,20 @@ References ~~~~~~~~~~ -.. [1] De Young, G. W., & Keizer, J. (1992). A single-pool inositol +.. [1] Li, Y. X., & Rinzel, J. (1994). Equations for InsP3 receptor-mediated + [Ca2+]i oscillations derived from a detailed kinetic model: a + Hodgkin-Huxley like formalism. Journal of theoretical Biology, 166(4), + 461-473. DOI: https://doi.org/10.1006/jtbi.1994.1041 + +.. [2] De Young, G. W., & Keizer, J. (1992). A single-pool inositol 1,4,5-trisphosphate-receptor-based model for agonist-stimulated oscillations in Ca2+ concentration. Proceedings of the National Academy of Sciences, 89(20), 9895-9899. DOI: https://doi.org/10.1073/pnas.89.20.9895 -.. [2] Li, Y. X., & Rinzel, J. (1994). Equations for InsP3 receptor-mediated - [Ca2+]i oscillations derived from a detailed kinetic model: a - Hodgkin-Huxley like formalism. Journal of theoretical Biology, 166(4), - 461-473. DOI: https://doi.org/10.1006/jtbi.1994.1041 - -.. [3] Nadkarni S, and Jung P. Spontaneous oscillations of dressed neurons: A - new mechanism for epilepsy? Physical Review Letters, 91:26. DOI: - https://doi.org/10.1103/PhysRevLett.91.268101 +.. [3] Nadkarni, S., & Jung, P. (2003). Spontaneous oscillations of dressed + neurons: a new mechanism for epilepsy?. Physical review letters, 91(26), + 268101. DOI: https://doi.org/10.1103/PhysRevLett.91.268101 .. [4] Golomb, D. (2007). Neuronal synchrony measures. Scholarpedia, 2(1), 1347. DOI: http://dx.doi.org/10.4249/scholarpedia.1347 From fd7bc3aef8adcb8cfdaf71d73c061c3009d04843 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 22 Sep 2023 15:35:44 +0200 Subject: [PATCH 17/93] Fix python formatting with black --- .../test_connect_pairwise_bernoulli_astro.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py index 460323b072..9fa74183b0 100644 --- a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py +++ b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py @@ -29,12 +29,16 @@ # copied from connect_test_base.py try: from mpi4py import MPI + haveMPI4Py = True except ImportError: haveMPI4Py = False + # adapted from connect_test_base.py -def setup_network(conn_dict, syn_dict, N1, N2, neuron_model="aeif_cond_alpha_astro", astrocyte_model="astrocyte_lr_1994"): +def setup_network( + conn_dict, syn_dict, N1, N2, neuron_model="aeif_cond_alpha_astro", astrocyte_model="astrocyte_lr_1994" +): pop1 = nest.Create(neuron_model, N1) pop2 = nest.Create(neuron_model, N2) pop_astro = nest.Create(astrocyte_model, N2) @@ -42,6 +46,7 @@ def setup_network(conn_dict, syn_dict, N1, N2, neuron_model="aeif_cond_alpha_ast nest.Connect(pop1, pop2, conn_dict, syn_dict) return pop1, pop2, pop_astro + # copied from connect_test_base.py def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): """ @@ -112,6 +117,7 @@ def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): # np.hstack((np.asarray(data_front)[0], np.asarray(data_back)[0])) return expected + # copied from connect_test_base.py def get_degrees(fan, pop1, pop2): M = get_connectivity_matrix(pop1, pop2) @@ -121,6 +127,7 @@ def get_degrees(fan, pop1, pop2): degrees = np.sum(M, axis=0) return degrees + # copied from connect_test_base.py def gather_data(data_array): """ @@ -143,6 +150,7 @@ def gather_data(data_array): else: return data_array + # copied from connect_test_base.py def chi_squared_check(degrees, expected, distribution=None): """ @@ -183,11 +191,13 @@ def chi_squared_check(degrees, expected, distribution=None): # ddof: adjustment to the degrees of freedom. df = k-1-ddof return scipy.stats.chisquare(np.array(degrees), np.array(expected)) + # copied from connect_test_base.py def mpi_barrier(): if haveMPI4Py: MPI.COMM_WORLD.Barrier() + # copied from connect_test_base.py def get_connectivity_matrix(pop1, pop2): """ @@ -207,6 +217,7 @@ def get_connectivity_matrix(pop1, pop2): M[index_dic[target]][index_dic[source]] += 1 return M + # adapted from connect_test_base.py def mpi_assert(data_original, data_test): """ @@ -221,6 +232,7 @@ def mpi_assert(data_original, data_test): else: TestCase.assertTrue(data_original == data_test) + # adapted from test_connect_pairwise_bernoulli.py # a test for parameters "p" and "max_astro_per_target" # run for three levels of neuron-neuron connection probabilities @@ -230,11 +242,7 @@ def test_statistics(p_n2n): N1 = 50 N2 = 50 max_astro_per_target = 5 - conn_dict = { - "rule": "pairwise_bernoulli_astro", - "p": p_n2n, - "max_astro_per_target": max_astro_per_target - } + conn_dict = {"rule": "pairwise_bernoulli_astro", "p": p_n2n, "max_astro_per_target": max_astro_per_target} # set test parameters stat_dict = {"alpha2": 0.05, "n_runs": 20} @@ -279,6 +287,7 @@ def test_statistics(p_n2n): # assert that for each target neuron, number of astrocytes is smaller than max_astro_per_target assert all(n <= max_astro_per_target for n in n_astrocytes) + # adapted from test_connect_pairwise_bernoulli def test_autapses_true(): # set connection parameters @@ -301,6 +310,7 @@ def test_autapses_true(): M = get_connectivity_matrix(pop, pop) mpi_assert(np.diag(M), np.ones(N)) + # adapted from test_connect_pairwise_bernoulli def test_autapses_false(): # set connection parameters From 5d59b3f558b27ba5f22104d31e179cbc2df32fa3 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 22 Sep 2023 16:15:54 +0200 Subject: [PATCH 18/93] Improve documentation in astrocyte_small_network.py and astrocyte_brunel.py Co-Authored-By: Jugoslava Acimovic Co-Authored-By: Tiina Manninen Co-Authored-By: Jonas Stapmanns Co-Authored-By: Sacha van Albada --- pynest/examples/astrocyte_brunel.py | 6 +++--- pynest/examples/astrocyte_small_network.py | 24 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index 897c18ad57..9f6a17e295 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -24,9 +24,9 @@ ------------------------------------------------------------ This script simulates a random balanced network with excitatory and inhibitory -neurons and astrocytes. The ``astrocyte_lr_1994`` model is according to [1]_, -[2]_, and [3]_. The ``aeif_cond_alpha_astro`` model is an adaptive exponential -integrate and fire neuron supporting neuron-astrocyte interactions. +neurons and astrocytes. The ``astrocyte_lr_1994`` model is implemented according +to [1]_, [2]_, and [3]_. The ``aeif_cond_alpha_astro`` model is an adaptive +exponential integrate and fire neuron supporting neuron-astrocyte interactions. The network is created using the ``pairwise_bernoulli_astro`` rule, with the ``tsodyks_synapse`` as the connections from neurons to neruons and astrocytes, diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 8729316155..11d4c21ca3 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -25,7 +25,29 @@ This script demonstrates how to use the NEST connection builder and the "pairwise_bernoulli_astro" rule to create a small neuron-astrocyte network with -20 neurons and 10 astrocytes. +20 neurons and 10 astrocytes. This connection rule creates the tripartite +connectivity between neurons and astrocytes. The ``astrocyte_lr_1994`` model is +implemented according to [1]_, [2]_, and [3]_. The ``aeif_cond_alpha_astro`` +model is an adaptive exponential integrate and fire neuron supporting +neuron-astrocyte interactions. + +References +~~~~~~~~~~ + +.. [1] Li, Y. X., & Rinzel, J. (1994). Equations for InsP3 receptor-mediated + [Ca2+]i oscillations derived from a detailed kinetic model: a + Hodgkin-Huxley like formalism. Journal of theoretical Biology, 166(4), + 461-473. DOI: https://doi.org/10.1006/jtbi.1994.1041 + +.. [2] De Young, G. W., & Keizer, J. (1992). A single-pool inositol + 1,4,5-trisphosphate-receptor-based model for agonist-stimulated + oscillations in Ca2+ concentration. Proceedings of the National Academy + of Sciences, 89(20), 9895-9899. DOI: + https://doi.org/10.1073/pnas.89.20.9895 + +.. [3] Nadkarni, S., & Jung, P. (2003). Spontaneous oscillations of dressed + neurons: a new mechanism for epilepsy?. Physical review letters, 91(26), + 268101. DOI: https://doi.org/10.1103/PhysRevLett.91.268101 See Also ~~~~~~~~ From fc9b8e52b1c400154171a3f81196370475061350 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Fri, 22 Sep 2023 23:56:11 +0200 Subject: [PATCH 19/93] No longer derive astrocyte model from ArchivingNode, as it does not spike --- models/astrocyte_lr_1994.cpp | 16 ++-------------- models/astrocyte_lr_1994.h | 9 +++------ 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/models/astrocyte_lr_1994.cpp b/models/astrocyte_lr_1994.cpp index 03405e7de8..ba194d6ce1 100644 --- a/models/astrocyte_lr_1994.cpp +++ b/models/astrocyte_lr_1994.cpp @@ -321,7 +321,7 @@ nest::astrocyte_lr_1994::Buffers_::Buffers_( const Buffers_&, astrocyte_lr_1994& * ---------------------------------------------------------------- */ nest::astrocyte_lr_1994::astrocyte_lr_1994() - : ArchivingNode() + : StructuralPlasticityNode() , P_() , S_( P_ ) , B_( *this ) @@ -330,7 +330,7 @@ nest::astrocyte_lr_1994::astrocyte_lr_1994() } nest::astrocyte_lr_1994::astrocyte_lr_1994( const astrocyte_lr_1994& n ) - : ArchivingNode( n ) + : StructuralPlasticityNode( n ) , P_( n.P_ ) , S_( n.S_ ) , B_( n.B_, *this ) @@ -366,8 +366,6 @@ nest::astrocyte_lr_1994::init_buffers_() B_.sic_values.resize( kernel().connection_manager.get_min_delay(), 0.0 ); // set size of SIC buffer according to min_delay - ArchivingNode::clear_history(); - B_.logger_.reset(); B_.step_ = Time::get_resolution().get_ms(); @@ -485,16 +483,6 @@ nest::astrocyte_lr_1994::update( Time const& origin, const long from, const long kernel().event_delivery_manager.send_secondary( *this, sic ); } -/** - * Default implementation of register_stdp_connection() just - * throws IllegalConnection - */ -void -nest::astrocyte_lr_1994::register_stdp_connection( double, double ) -{ - throw IllegalConnection( "The target node does not support STDP synapses." ); -} - void nest::astrocyte_lr_1994::handle( SpikeEvent& e ) { diff --git a/models/astrocyte_lr_1994.h b/models/astrocyte_lr_1994.h index 4dd2995395..8efb1ca91e 100644 --- a/models/astrocyte_lr_1994.h +++ b/models/astrocyte_lr_1994.h @@ -241,7 +241,7 @@ aeif_cond_alpha_astro, sic_connection EndUserDocs */ -class astrocyte_lr_1994 : public ArchivingNode +class astrocyte_lr_1994 : public StructuralPlasticityNode { public: @@ -272,9 +272,6 @@ class astrocyte_lr_1994 : public ArchivingNode { } - // disable the use of STDP connections in this model - void register_stdp_connection( double t_first_read, double delay ) override; - void get_status( DictionaryDatum& ) const override; void set_status( const DictionaryDatum& ) override; @@ -467,7 +464,7 @@ astrocyte_lr_1994::get_status( DictionaryDatum& d ) const { P_.get( d ); S_.get( d ); - ArchivingNode::get_status( d ); + StructuralPlasticityNode::get_status( d ); ( *d )[ names::recordables ] = recordablesMap_.get_list(); } @@ -484,7 +481,7 @@ astrocyte_lr_1994::set_status( const DictionaryDatum& d ) // write them back to (P_, S_) before we are also sure that // the properties to be set in the parent class are internally // consistent. - ArchivingNode::set_status( d ); + StructuralPlasticityNode::set_status( d ); // if we get here, temporaries contain consistent set of properties P_ = ptmp; From 1de10f9e46f77612d641296e6a879f0e7d7da8a9 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Sun, 24 Sep 2023 14:21:52 +0200 Subject: [PATCH 20/93] Converted astrocyte examples and tests to new syntax --- pynest/examples/astrocyte_brunel.py | 32 +++++++------- pynest/examples/astrocyte_small_network.py | 22 +++++----- .../test_connect_pairwise_bernoulli_astro.py | 44 +++++-------------- 3 files changed, 37 insertions(+), 61 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index 9f6a17e295..e8a6a960b7 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -112,8 +112,7 @@ } syn_params = { - "synapse_model": "tsodyks_synapse", # model of neuron-to-neuron and neuron-to-astrocyte connections - "astro2post": "sic_connection", # model of astrocyte-to-neuron connection + "synapse_model": "tsodyks_synapse", "w_a2n": 0.01, # weight of astrocyte-to-neuron connection "w_e": 1.0, # weight of excitatory connection in nS "w_i": -4.0, # weight of inhibitory connection in nS @@ -172,22 +171,23 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. nest.Connect(nodes_noise, nodes_ex + nodes_in, syn_spec=syn_params_noise) print("Connecting neurons and astrocytes ...") conn_params_e = { - "rule": "pairwise_bernoulli_astro", - "astrocyte": nodes_astro, - "p": network_params["p"] / scale, - "p_syn_astro": network_params["p_syn_astro"], - "max_astro_per_target": network_params["max_astro_per_target"], - "astro_pool_by_index": network_params["astro_pool_by_index"], + "rule": "tripartite_bernoulli_with_pool", + "p_primary": network_params["p"] / scale, + "p_cond_third": network_params["p_syn_astro"], + "pool_size": network_params["max_astro_per_target"], + "random_pool": not network_params["astro_pool_by_index"], } syn_params_e = { - "synapse_model": syn_params["synapse_model"], - "weight_pre2post": syn_params["w_e"], - "tau_psc": tau_syn_ex, - "astro2post": syn_params["astro2post"], - "weight_astro2post": syn_params["w_a2n"], - "delay_pre2post": syn_params["d_e"], - "delay_pre2astro": syn_params["d_e"], + "primary": { + "synapse_model": syn_params["synapse_model"], + "weight": syn_params["w_e"], + "tau_psc": tau_syn_ex, + "delay": syn_params["d_e"], + }, + "third_in": {"delay": syn_params["d_e"]}, + "third_out": {"synapse_model": "sic_connection", "weight": syn_params["w_a2n"]}, } + conn_params_i = {"rule": "pairwise_bernoulli", "p": network_params["p"] / scale} syn_params_i = { "synapse_model": syn_params["synapse_model"], @@ -195,7 +195,7 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. "tau_psc": tau_syn_in, "delay": syn_params["d_i"], } - nest.Connect(nodes_ex, nodes_ex + nodes_in, conn_params_e, syn_params_e) + nest.Connect(nodes_ex, nodes_ex + nodes_in, third=nodes_astro, conn_spec=conn_params_e, syn_spec=syn_params_e) nest.Connect(nodes_in, nodes_ex + nodes_in, conn_params_i, syn_params_i) return nodes_ex, nodes_in, nodes_astro, nodes_noise diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 11d4c21ca3..d059710e7a 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -112,24 +112,22 @@ nest.Connect( pre_neurons, post_neurons, + third=astrocytes, conn_spec={ - "rule": "pairwise_bernoulli_astro", - "astrocyte": astrocytes, - "p": 1.0, - "p_syn_astro": 1.0, - "max_astro_per_target": 3, - "astro_pool_by_index": True, + "rule": "tripartite_bernoulli_with_pool", + "p_primary": 1.0, + "p_cond_third": 1.0, + "pool_size": 3, + "random_pool": False, }, syn_spec={ - "weight_pre2post": 1.0, - "weight_pre2astro": 1.0, - "weight_astro2post": 1.0, - "delay_pre2post": 1.0, - "delay_pre2astro": 1.0, - "delay_astro2post": 1.0, + "primary": {"model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, + "third_in": {"model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, + "third_out": {"model": "sic_connection", "weight": 1.0, "delay": 1.0}, }, ) + mm_pre_neurons = nest.Create("multimeter", params={"record_from": ["V_m"]}) mm_post_neurons = nest.Create("multimeter", params={"record_from": ["V_m", "I_SIC"]}) mm_astrocytes = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) diff --git a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py index 9fa74183b0..c96732695e 100644 --- a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py +++ b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py @@ -42,8 +42,7 @@ def setup_network( pop1 = nest.Create(neuron_model, N1) pop2 = nest.Create(neuron_model, N2) pop_astro = nest.Create(astrocyte_model, N2) - conn_dict["astrocyte"] = pop_astro - nest.Connect(pop1, pop2, conn_dict, syn_dict) + nest.Connect(pop1, pop2, third=pop_astro, conn_spec=conn_dict, syn_spec=syn_dict) return pop1, pop2, pop_astro @@ -242,7 +241,7 @@ def test_statistics(p_n2n): N1 = 50 N2 = 50 max_astro_per_target = 5 - conn_dict = {"rule": "pairwise_bernoulli_astro", "p": p_n2n, "max_astro_per_target": max_astro_per_target} + conn_dict = {"rule": "tripartite_bernoulli_with_pool", "p_primary": p_n2n, "pool_size": max_astro_per_target} # set test parameters stat_dict = {"alpha2": 0.05, "n_runs": 20} @@ -255,7 +254,7 @@ def test_statistics(p_n2n): # 1. p yields the correct indegree and outdegree # 2. max_astro_per_target limits the number of astrocytes connected to each target neuron for fan in ["in", "out"]: - expected = get_expected_degrees_bernoulli(conn_dict["p"], fan, N1, N2) + expected = get_expected_degrees_bernoulli(conn_dict["p_primary"], fan, N1, N2) pvalues = [] n_astrocytes = [] for i in range(stat_dict["n_runs"]): @@ -289,13 +288,14 @@ def test_statistics(p_n2n): # adapted from test_connect_pairwise_bernoulli -def test_autapses_true(): +@pytest.mark.parametrize("autapses", [True, False]) +def test_autapses_true(autapses): # set connection parameters N = 50 conn_dict = { - "rule": "pairwise_bernoulli_astro", - "p": 1.0, - "allow_autapses": True, + "rule": "tripartite_bernoulli_with_pool", + "p_primary": 1.0, + "allow_autapses": autapses, } # set NEST verbosity @@ -304,31 +304,9 @@ def test_autapses_true(): # test that autapses exist pop = nest.Create("aeif_cond_alpha_astro", N) pop_astro = nest.Create("astrocyte_lr_1994", N) - conn_dict["astrocyte"] = pop_astro - nest.Connect(pop, pop, conn_dict) + nest.Connect( + pop, pop, third=pop_astro, conn_spec=conn_dict, syn_spec={"third_out": {"synapse_model": "sic_connection"}} + ) # make sure all connections do exist M = get_connectivity_matrix(pop, pop) mpi_assert(np.diag(M), np.ones(N)) - - -# adapted from test_connect_pairwise_bernoulli -def test_autapses_false(): - # set connection parameters - N = 50 - conn_dict = { - "rule": "pairwise_bernoulli_astro", - "p": 1.0, - "allow_autapses": False, - } - - # set NEST verbosity - nest.set_verbosity("M_FATAL") - - # test that autapses were excluded - pop = nest.Create("aeif_cond_alpha_astro", N) - pop_astro = nest.Create("astrocyte_lr_1994", N) - conn_dict["astrocyte"] = pop_astro - nest.Connect(pop, pop, conn_dict) - # make sure all connections do exist - M = get_connectivity_matrix(pop, pop) - mpi_assert(np.diag(M), np.zeros(N)) From b19002ac89b12fcffa3e67e3af7b5693e028c7fe Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 00:39:12 +0200 Subject: [PATCH 21/93] Working towards revised tripartite connectivity; not compiling at present --- nestkernel/conn_builder.cpp | 442 +++++---------------- nestkernel/conn_builder.h | 64 +-- nestkernel/conn_builder_factory.h | 76 +++- nestkernel/connection_manager.cpp | 64 +++ nestkernel/connection_manager.h | 18 + nestkernel/nest.cpp | 10 + nestkernel/nest.h | 11 + nestkernel/nest_names.cpp | 19 +- nestkernel/nest_names.h | 19 +- nestkernel/nestmodule.cpp | 28 +- nestkernel/nestmodule.h | 6 + pynest/examples/astrocyte_brunel.py | 2 +- pynest/examples/astrocyte_small_network.py | 2 +- pynest/nest/lib/hl_api_connections.py | 113 ++++++ 14 files changed, 483 insertions(+), 391 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 5c82aab6d6..cfe23c0aae 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -39,6 +39,9 @@ #include "fdstream.h" #include "name.h" +// Includes from C++: +#include + nest::ConnBuilder::ConnBuilder( NodeCollectionPTR sources, NodeCollectionPTR targets, const DictionaryDatum& conn_spec, @@ -1585,411 +1588,174 @@ nest::BernoulliBuilder::inner_connect_( const int tid, RngPtr rng, Node* target, } -nest::BernoulliAstroBuilder::BernoulliAstroBuilder( NodeCollectionPTR sources, +nest::AuxiliaryBuilder::AuxiliaryBuilder( NodeCollectionPTR sources, NodeCollectionPTR targets, const DictionaryDatum& conn_spec, - const std::vector< DictionaryDatum >& syn_specs ) - : ConnBuilder( sources, targets, conn_spec, syn_specs ) + const std::vector< DictionaryDatum >& syn_spec ) + : ConnBuilder( sources, targets, conn_spec, syn_spec ) { - // Initialize parameters for astrocyte connectivity - astrocytes_ = getValue< NodeCollectionDatum >( conn_spec, names::astrocyte ); - weights_n2n_.resize( syn_specs.size() ); - weights_n2a_.resize( syn_specs.size() ); - delays_n2n_.resize( syn_specs.size() ); - delays_n2a_.resize( syn_specs.size() ); - synapse_model_id_a2n_.resize( syn_specs.size() ); - weights_a2n_.resize( syn_specs.size() ); - delays_a2n_.resize( syn_specs.size() ); +} - p_ = ( *conn_spec )[ names::p ]; - if ( p_ < 0 or 1 < p_ ) - { - throw BadProperty( "Probability of neuron-neuron connections 0 <= p <= 1 required." ); - } +void +nest::AuxiliaryBuilder::single_connect( size_t snode_id, Node& tgt, size_t tid, RngPtr rng ) +{ + single_connect_( snode_id, tgt, tid, rng ); +} - if ( conn_spec->known( names::p_syn_astro ) ) - { - p_syn_astro_ = ( *conn_spec )[ names::p_syn_astro ]; - if ( p_syn_astro_ < 0 or 1 < p_syn_astro_ ) - { - throw BadProperty( - "Probability of astrocyte pairing per neuron-neuron connection 0 <= p_syn_astro <= 1 required." ); - } - } - else - { - LOG( M_WARNING, - "BernoulliAstroBuilder::connect", - "Probability of astrocyte pairing per neuron-neuron connection not given. " - "A default value of 1.0 is used. " ); - p_syn_astro_ = 1.0; - } - // deterministic (astro_pool_by_index = true) or probabilistic selection of astrocyte pool per target neuron - if ( conn_spec->known( names::astro_pool_by_index ) ) - { - astro_pool_by_index_ = ( *conn_spec )[ names::astro_pool_by_index ]; - } - else +nest::TripartiteBernoulliWithPoolBuilder::TripartiteBernoulliWithPoolBuilder( NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& conn_spec, + const DictionaryDatum& syn_specs ) + : ConnBuilder( sources, targets, conn_spec, { ( *syn_specs )[ names::primary ] } ) + , + , third_( third ) + , third_in_builder_( sources, third, conn_spec, { ( *syn_specs )[ names::third_in ] } ) + , third_out_builder_( third, targets, conn_spec, { ( *syn_specs )[ names::third_out ] } ) + , p_primary_( 1.0 ) + , p_cond_third_( 1.0 ) + , random_pool_( true ) + , pool_size_( third->size() ) + , targets_per_third_( targets->size() / third->size() ) +{ + updateValue< double >( conn_spec, names::p_primary, p_primary_ ); + updateValue< double >( conn_spec, names::p_cond_third, p_cond_third_ ); + updateValue< bool >( conn_spec, names::random_pool, random_pool_ ); + updateValue< long >( conn_spec, names::pool_size, pool_size_ ); + + if ( p_primary_ < 0 or 1 < p_primary_ ) { - astro_pool_by_index_ = false; + throw BadProperty( "Probability of neuron-neuron connections 0 ≤ p ≤ 1 required" ); } - // maximal number of astrocytes per target neuron - if ( conn_spec->known( names::max_astro_per_target ) ) + if ( p_cond_third_ < 0 or 1 < p_cond_third_ ) { - max_astro_per_target_ = ( *conn_spec )[ names::max_astro_per_target ]; - if ( max_astro_per_target_ < 1 or max_astro_per_target_ > astrocytes_->size() ) - { - throw BadProperty( - "Maximal number of astrocytes per target neuron cannot be smaller than 1 or larger than the number of " - "astrocytes." ); - } + throw BadProperty( "Conditional probability of third-factor connection 0 ≤ p_cond_third ≤ 1 required" ); } - else + + if ( pool_size_ < 1 or third->size() < pool_size_ ) { - // when not given, put 0 and determine later by: - // numbers of astrocytes and target neurons, if astro_pool_by_index = true - // number of astrocytes, if astro_pool_by_index = false - max_astro_per_target_ = 0; + throw BadProperty( "Pool size 1 ≤ pool_size ≤ size of third-factor population required" ); } - // determine parameters for individual connections - size_t num_threads = kernel().vp_manager.get_num_threads(); - for ( size_t synapse_indx = 0; synapse_indx < syn_specs.size(); ++synapse_indx ) + if ( not random_pool_ + and ( ( targets->size() * pool_size_ != third->size() ) + or ( pool_size_ == 1 and targets->size() % third->size() == 0 ) ) ) { - // neuron to neuron, neuron to astrocyte - // weights and delays - DictionaryDatum syn_params = syn_specs[ synapse_indx ]; - DictionaryDatum syn_defaults = kernel().model_manager.get_connector_defaults( synapse_model_id_[ synapse_indx ] ); - weights_n2n_[ synapse_indx ] = syn_params->known( names::weight_pre2post ) - ? ConnParameter::create( ( *syn_params )[ names::weight_pre2post ], num_threads ) - : ConnParameter::create( ( *syn_defaults )[ names::weight ], num_threads ); - weights_n2a_[ synapse_indx ] = syn_params->known( names::weight_pre2astro ) - ? ConnParameter::create( ( *syn_params )[ names::weight_pre2astro ], num_threads ) - : ConnParameter::create( ( *syn_defaults )[ names::weight ], num_threads ); - delays_n2n_[ synapse_indx ] = syn_params->known( names::delay_pre2post ) - ? ConnParameter::create( ( *syn_params )[ names::delay_pre2post ], num_threads ) - : ConnParameter::create( ( *syn_defaults )[ names::delay ], num_threads ); - delays_n2a_[ synapse_indx ] = syn_params->known( names::delay_pre2astro ) - ? ConnParameter::create( ( *syn_params )[ names::delay_pre2astro ], num_threads ) - : ConnParameter::create( ( *syn_defaults )[ names::delay ], num_threads ); - - // astrocyte to neuron - // connection model(s) - if ( syn_specs[ synapse_indx ]->known( names::astro2post ) ) - { - std::string syn_name = ( *syn_specs[ synapse_indx ] )[ names::astro2post ]; - // currently, it has to be sic_connection - // in the future, examine if it belongs to a set of connection type - if ( syn_name == "sic_connection" ) - { - synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( syn_name ); - } - else - { - LOG( M_WARNING, - "BernoulliAstroBuilder::connect", - "An incompatible model is requested for connections from astrocytes to neurons. " - "The compatible model is: sic_connection. Using sic_connection. " ); - synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( "sic_connection" ); - } - } - else - { - synapse_model_id_a2n_[ synapse_indx ] = kernel().model_manager.get_synapse_model_id( "sic_connection" ); - } - // weights and delays - DictionaryDatum syn_defaults_a2n = - kernel().model_manager.get_connector_defaults( synapse_model_id_a2n_[ synapse_indx ] ); - weights_a2n_[ synapse_indx ] = syn_params->known( names::weight_astro2post ) - ? ConnParameter::create( ( *syn_params )[ names::weight_astro2post ], num_threads ) - : ConnParameter::create( ( *syn_defaults_a2n )[ names::weight ], num_threads ); - delays_a2n_[ synapse_indx ] = syn_params->known( names::delay_astro2post ) - ? ConnParameter::create( ( *syn_params )[ names::delay_astro2post ], num_threads ) - : ConnParameter::create( ( *syn_defaults_a2n )[ names::delay ], num_threads ); + throw BadProperty( + "The sizes of target and third-factor populations and the chosen pool size do not fit." + " If pool_size == 1, the target population size must be a multiple of the third-factor" + " population size. For pool_size > 1, size(targets) * pool_size == size(third factor)" + " is required. For all other cases, use random pools." ); } } -int -nest::BernoulliAstroBuilder::get_start_astro_index( const int max_astro_per_target, - const int default_n_astro_per_target, - const int targets_size, - const int astrocytes_size, - const int target_index ) +size_t +nest::TripartiteBernoulliWithPoolBuilder::get_first_pool_index_( const size_t target_index ) const { - // helper function to determine the starting astrocyte for the astrocyte pool - // of a target neuron, in the deterministic (astro_pool_by_index = true) case - int start_astro_index = 0; // index of the starting astrocyte - // number of possible overlapping (with other neurons) astrocytes because of requested max_astro_per_target - int n_astro_overlap = max_astro_per_target - default_n_astro_per_target; - // shifting, for even pairings - int shift = std::ceil( n_astro_overlap / 2.0 ); - if ( targets_size > astrocytes_size ) - { - if ( max_astro_per_target % 2 == 0 - and target_index % default_n_astro_per_target >= default_n_astro_per_target / 2.0 ) - { - shift -= 1; // rule with even max_astro_per_target (reduce shifting in the latter half) - } - start_astro_index = ( target_index / default_n_astro_per_target ) - shift; - } - else + if ( pool_size_ > 1 ) { - start_astro_index = target_index * default_n_astro_per_target - shift; + return target_index * pool_size_; } - // the starting index cannot be smaller than 0 or larger than ( astrocytes_size - max_astro_per_target ) - if ( start_astro_index < 0 ) - { - start_astro_index = 0; - } - else if ( start_astro_index > int( astrocytes_size - max_astro_per_target ) ) - { - start_astro_index = astrocytes_size - max_astro_per_target; - } - return start_astro_index; -} -void -nest::BernoulliAstroBuilder::single_connect_astro_( const size_t snode_id, - Node* target, - const size_t target_thread, - RngPtr synced_rng, - RngPtr rng, - const std::vector< ConnParameter* >& weights, - const std::vector< ConnParameter* >& delays, - const std::vector< size_t >& synapse_model_id ) -{ - // function to make single connections - assert( target != NULL ); - for ( size_t synapse_indx = 0; synapse_indx < synapse_model_id.size(); ++synapse_indx ) - { - update_param_dict_( snode_id, *target, target_thread, synced_rng, synapse_indx ); - double weight = weights[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); - double delay = delays[ synapse_indx ]->value_double( target_thread, rng, snode_id, target ); - kernel().connection_manager.connect( snode_id, - target, - target_thread, - synapse_model_id[ synapse_indx ], - param_dicts_[ synapse_indx ][ target_thread ], - delay, - weight ); - } + return target_index / targets_per_third_; // intentional integer division } void -nest::BernoulliAstroBuilder::connect_() +nest::TripartiteBernoulliWithPoolBuilder::connect_() { #pragma omp parallel { const size_t tid = kernel().vp_manager.get_thread_id(); - // use RNG generating same number sequence on all threads - RngPtr synced_rng = get_vp_synced_rng( tid ); - RngPtr rng = get_vp_specific_rng( tid ); - try { - // variables for source neurons - size_t snode_id; - std::set< size_t > connected_snode_ids; - // variables for target neurons - Node* target; - size_t target_thread; - unsigned long indegree; - // variables for astrocytes - Node* astrocyte; - size_t astrocyte_thread; - size_t anode_id; - // variables for astrocyte pool selection - // astro_pool_this_target is the astrocyte pool of a target neuron - // i.e. astrocytes that can (but not necessarily) be connected with this target neuron - std::vector< size_t > astro_pool_this_target; - size_t astrocytes_size = astrocytes_->size(); - size_t targets_size = targets_->size(); - int default_n_astro_per_target = astrocytes_size / targets_size > 1 ? astrocytes_size / targets_size : 1; - int default_n_target_per_astro = targets_size / astrocytes_size > 1 ? targets_size / astrocytes_size : 1; - int target_index = 0; - int astro_index = 0; - // for binomial distribution - binomial_distribution bino_dist; - binomial_distribution::param_type param( sources_->size(), p_ ); - - // user warnings in the case of astro_pool_by_index = true - if ( astro_pool_by_index_ == true ) - { - // when number of target neurons are larger than but cannot be exactly divided by number of astrocytes - if ( targets_size > astrocytes_size ) - { - if ( targets_size % astrocytes_size != 0 ) - { - LOG( M_WARNING, - "BernoulliAstroBuilder::connect", - "Number of target neurons cannot be exactly divided by number of astrocytes. " - "Some neurons will be excluded from neuron-astrocyte pairings. " ); - } - } - // when number of astrocytes are larger than but cannot be exactly divided by number of target neurons - else - { - if ( astrocytes_size % targets_size != 0 ) - { - LOG( M_WARNING, - "BernoulliAstroBuilder::connect", - "Number of astrocytes cannot be exactly divided by number of target neurons. " - "Some astrocytes will be excluded from neuron-astrocyte pairings. " ); - } - } - // // uneven pairings in a special case - // if ( max_astro_per_target_ > 0 and max_astro_per_target_ % 2 == 0 and default_n_target_per_astro % 2 == 1) - // { - // LOG( M_WARNING, - // "BernoulliAstroBuilder::connect", - // "Uneven pairings could exist with the assigned neuron and astrocyte numbers. "); - // } - } + // use RNG generating same number sequence on all threads + RngPtr synced_rng = get_vp_synced_rng( tid ); + RngPtr rng = get_vp_specific_rng( tid ); - // defaults for max_astro_per_target_ when not given - if ( max_astro_per_target_ == 0 ) - { - max_astro_per_target_ = astro_pool_by_index_ == true ? default_n_astro_per_target : astrocytes_size; - } - // assert max_astro_per_target_ > 0 and <= astrocytes_size - assert( max_astro_per_target_ > 0 ); - assert( max_astro_per_target_ <= astrocytes_size ); + binomial_distribution bino_dist; + binomial_distribution::param_type bino_param( sources_->size(), p_primary_ ); // Iterate through target neurons. For each, three steps are done: // 1. draw indegree 2. select astrocyte pool 3. make connections - for ( NodeCollection::const_iterator target_it = targets_->begin(); target_it != targets_->end(); ++target_it ) + for ( const auto& target : *targets_ ) { - // get target node - const size_t tnode_id = ( *target_it ).node_id; - target = kernel().node_manager.get_node_or_proxy( tnode_id, tid ); - target_index = targets_->get_lid( tnode_id ); + const size_t tnode_id = target.node_id; + Node* target_node = kernel().node_manager.get_node_or_proxy( tnode_id, tid ); + const bool local_target = not target_node->is_proxy(); - // check if the target is on the current thread - if ( target->is_proxy() ) - { - target_thread = invalid_thread; - } - else + // step 1, draw indegree for this target + const auto indegree = bino_dist( synced_rng, bino_param ); + if ( indegree == 0 ) { - target_thread = tid; + continue; // no connections for this target } - // step 1, draw indegree for this target - indegree = bino_dist( synced_rng, param ); - // if targets overlap with sources and p=1 and autapses are not allowed, indegree cannot be reached - // in this case, indegree should be forced to reduced by 1 - if ( not allow_autapses_ and indegree == sources_->size() and sources_->get_lid( tnode_id ) >= 0 ) + // step 2, build pool for target + std::vector< size_t > pool; + pool.reserve( pool_size_ ); + if ( random_pool_ ) { - LOG( M_WARNING, - "BernoulliAstroBuilder::connect", - "The indegree equals source size and targets overlap with sources, but autapses are not allowed. " - "The indegree has to be decreased by 1 (source size - 1). " ); - indegree -= 1; + // std::sample( third_->begin(), third_->end(), std::back_inserter( pool ), pool_size_, + // synced_rng ); } - - // step 2, select astrocyte pool for this target - // not required if max_astro_per_target_ = astrocytes_size - if ( max_astro_per_target_ < astrocytes_size ) + else { - // reset the list of astrocyte pool - astro_pool_this_target.clear(); - // deterministic case - if ( astro_pool_by_index_ == true ) - { - // exclude neurons that are remainder - if ( astro_pool_by_index_ == true and target_index >= int( astrocytes_size * default_n_target_per_astro ) ) - { - break; - } - // determine the starting astrocyte - astro_index = get_start_astro_index( - max_astro_per_target_, default_n_astro_per_target, targets_size, astrocytes_size, target_index ); - // iterate from the starting astrocyte and add astrocytes until desired pool size reached - for ( size_t i = 0; i < max_astro_per_target_; i++ ) - { - anode_id = ( *astrocytes_ )[ astro_index ]; - astro_pool_this_target.push_back( anode_id ); - astro_index++; - } - } - // probabilistic case - else + auto first_index = get_first_pool_index_( targets_->get_lid( tnode_id ) ); + for ( size_t i = 0; i < pool_size_; ++i ) { - for ( size_t i = 0; i < max_astro_per_target_; i++ ) - { - // randomly draw astrocytes without repetition until desired pool size reached - do - { - anode_id = ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ]; - } while ( std::find( astro_pool_this_target.begin(), astro_pool_this_target.end(), anode_id ) - != astro_pool_this_target.end() ); - astro_pool_this_target.push_back( anode_id ); - } + pool.push_back( ( *third_ )[ first_index + i ] ); } } // step 3, iterate through indegree to make connections for this target - // reset the lists of connected sources - connected_snode_ids.clear(); - size_t i = 0; - while ( i < indegree ) + // - by construction, we cannot get multapses + // - if the target is also among sources, it can be drawn at most once; + // we ignore it then connecting if no autapses are wanted + std::vector< NodeIDTriple > sources_to_connect_; + sources_to_connect_.reserve( indegree ); + // std::sample( sources_->begin(), sources_->end(), std::back_inserter( sources_to_connect_ ), + // indegree, synced_rng ); + + for ( const auto source : *sources_ ) { - // choose source node randomly - snode_id = ( *sources_ )[ synced_rng->ulrand( sources_->size() ) ]; - - // block multapses and autapses (if autapses not allowed) - if ( connected_snode_ids.find( snode_id ) != connected_snode_ids.end() ) - { - continue; - } + const auto snode_id = source.node_id; if ( not allow_autapses_ and snode_id == tnode_id ) { continue; } - // add source node to list - connected_snode_ids.insert( snode_id ); - - // increase i which counts the number of incoming connections - ++i; - - // if the target is local, connect the source to the target - if ( target_thread == tid ) + if ( local_target ) { - single_connect_astro_( - snode_id, target, target_thread, synced_rng, rng, weights_n2n_, delays_n2n_, synapse_model_id_ ); + // plain connect now with thread-local rng for randomized parameters + single_connect_( snode_id, *target_node, tid, rng ); } - // Bernoulli trial to determine if the considered neuron-neuron connection should be paired with an astrocyte - if ( synced_rng->drand() >= p_syn_astro_ ) + // conditionally connect third factor + if ( not( p_cond_third_ < synced_rng->drand() ) ) { continue; } - // if a pairing should be done, select an astrocyte - // select from all the astrocytes, if max_astro_per_target_ = astrocytes_size - // otherwise, select from the astrocyte pool - anode_id = max_astro_per_target_ == astrocytes_size - ? ( *astrocytes_ )[ synced_rng->ulrand( astrocytes_size ) ] - : astro_pool_this_target.at( synced_rng->ulrand( astro_pool_this_target.size() ) ); + // select third-factor neuron randomly from pool for this target + const auto third_index = pool_size_ == 1 ? 0 : synced_rng->ulrand( pool_size_ ); + const auto third_node_id = pool[ third_index ]; + Node* third_node = kernel().node_manager.get_node_or_proxy( third_node_id, tid ); + const bool local_third_node = not third_node->is_proxy(); - // if the astrocyte is local, connect the source to the astrocyte - astrocyte = kernel().node_manager.get_node_or_proxy( anode_id, tid ); - if ( !astrocyte->is_proxy() ) + if ( local_third_node ) { - astrocyte_thread = tid; - single_connect_astro_( - snode_id, astrocyte, astrocyte_thread, synced_rng, rng, weights_n2a_, delays_n2a_, synapse_model_id_ ); + // route via auxiliary builder who handles parameters + third_in_builder_.single_connect( snode_id, *third_node, tid, rng ); } - // if the target is local, connect the astrocyte to the target - if ( target_thread == tid ) + // connection third-factor node to target if local + if ( local_target ) { - single_connect_astro_( - anode_id, target, target_thread, synced_rng, rng, weights_a2n_, delays_a2n_, synapse_model_id_a2n_ ); + // route via auxiliary builder who handles parameters + third_out_builder_.single_connect( third_node_id, *target_node, tid, rng ); } } } diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index 3b514e83c2..b97392cccf 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -76,6 +76,8 @@ class ConnBuilder const std::vector< DictionaryDatum >& syn_specs ); virtual ~ConnBuilder(); + static constexpr bool is_tripartite = false; + size_t get_synapse_model() const { @@ -447,39 +449,55 @@ class BernoulliBuilder : public ConnBuilder ParameterDatum p_; //!< connection probability }; -class BernoulliAstroBuilder : public ConnBuilder +/** + * Helper class to support parameter handlig for tripartite builders. + */ +class AuxiliaryBuilder : public ConnBuilder { public: - BernoulliAstroBuilder( NodeCollectionPTR, + AuxiliaryBuilder( NodeCollectionPTR, NodeCollectionPTR, const DictionaryDatum&, const std::vector< DictionaryDatum >& ); + //! forwards to single_connect_() in underlying ConnBuilder + void single_connect( size_t, Node&, size_t, RngPtr ); + +protected: + void + connect_() override + { + assert( false ); + } +}; + +class TripartiteBernoulliWithPoolBuilder : public ConnBuilder +{ +public: + TripartiteBernoulliWithPoolBuilder( NodeCollectionPTR, + NodeCollectionPTR, + NodeCollectionPTR, + const DictionaryDatum&, + const DictionaryDatum& ); + + static constexpr bool is_tripartite = true; + protected: - int get_start_astro_index( const int, const int, const int, const int, const int ); - void single_connect_astro_( const size_t, - Node*, - const size_t, - RngPtr, - RngPtr, - const std::vector< ConnParameter* >&, - const std::vector< ConnParameter* >&, - const std::vector< size_t >& ); void connect_() override; private: - NodeCollectionPTR astrocytes_; - double p_; //!< connection probability for neuron-neuron connections - double p_syn_astro_; //!< probability of astrocyte pairing per neuron-neuron connection - bool astro_pool_by_index_; //!< if true, select astrocyte pool per target by index - size_t max_astro_per_target_; //!< maximal number of astrocytes per tartget neuron - std::vector< ConnParameter* > weights_n2n_; //!< synaptic weights for neuron-neuron connections - std::vector< ConnParameter* > weights_n2a_; //!< synaptic weights for neuron-astrocyte connections - std::vector< ConnParameter* > delays_n2n_; //!< synaptic delays for neuron-neuron and neuron-astrocyte connections - std::vector< ConnParameter* > delays_n2a_; //!< synaptic delays for neuron-neuron and neuron-astrocyte connections - std::vector< size_t > synapse_model_id_a2n_; //!< synapse models for astrocyte-neuron connections - std::vector< ConnParameter* > weights_a2n_; //!< synaptic weights for astrocyte-neuron connections - std::vector< ConnParameter* > delays_a2n_; //!< synaptic weights for astrocyte-neuron connections + size_t get_first_pool_index_( const size_t ) const; + + NodeCollectionPTR third_; + + AuxiliaryBuilder third_in_builder_; + AuxiliaryBuilder third_out_builder_; + + double p_primary_; //!< connection probability for pre-post connections + double p_cond_third_; //!< probability third-factor connection if primary connection created + bool random_pool_; //!< if true, select astrocyte pool at random + size_t pool_size_; //!< size of third-factor pool + size_t targets_per_third_; //!< target nodes per third-factor node }; class SymmetricBernoulliBuilder : public ConnBuilder diff --git a/nestkernel/conn_builder_factory.h b/nestkernel/conn_builder_factory.h index 1cd041ca96..d375acfc98 100644 --- a/nestkernel/conn_builder_factory.h +++ b/nestkernel/conn_builder_factory.h @@ -53,18 +53,46 @@ class GenericConnBuilderFactory NodeCollectionPTR, const DictionaryDatum&, const std::vector< DictionaryDatum >& ) const = 0; + virtual ConnBuilder* create( NodeCollectionPTR, + NodeCollectionPTR, + NodeCollectionPTR, + const DictionaryDatum&, + const DictionaryDatum& ) const = 0; }; /** - * Factory class for generating objects of type ConnBuilder + * Factory class for normal ConnBuilders */ - -template < typename ConnBuilderType > +template < typename ConnBuilderType, bool is_tripartite = ConnBuilderType::is_tripartite > class ConnBuilderFactory : public GenericConnBuilderFactory { - public: - //! create conn builder + ConnBuilder* + create( NodeCollectionPTR sources, + NodeCollectionPTR targets, + const DictionaryDatum& conn_spec, + const std::vector< DictionaryDatum >& syn_specs ) const override + { + assert( false ); // only specialisations should be called + } + + //! create tripartite builder + ConnBuilder* + create( NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& conn_spec, + const DictionaryDatum& syn_specs ) const override + { + assert( false ); // only specialisations should be called + } +}; + + +// Specialisation for normal ConnBuilders +template < typename ConnBuilderType > +class ConnBuilderFactory< ConnBuilderType, false > : public GenericConnBuilderFactory +{ ConnBuilder* create( NodeCollectionPTR sources, NodeCollectionPTR targets, @@ -73,6 +101,44 @@ class ConnBuilderFactory : public GenericConnBuilderFactory { return new ConnBuilderType( sources, targets, conn_spec, syn_specs ); } + + //! create tripartite builder + ConnBuilder* + create( NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& conn_spec, + const DictionaryDatum& syn_specs ) const override + { + throw BadProperty( + String::compose( "Connection rule %1 does not support tripartite connections.", ( *conn_spec )[ names::rule ] ) ); + } +}; + +// Specialisation for tripartite ConnBuilders +template < typename ConnBuilderType > +class ConnBuilderFactory< ConnBuilderType, true > : public GenericConnBuilderFactory +{ + ConnBuilder* + create( NodeCollectionPTR sources, + NodeCollectionPTR targets, + const DictionaryDatum& conn_spec, + const std::vector< DictionaryDatum >& syn_specs ) const override + { + throw BadProperty( + String::compose( "Connection rule %1 only supports tripartite connections.", ( *conn_spec )[ names::rule ] ) ); + } + + //! create tripartite builder + ConnBuilder* + create( NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& conn_spec, + const DictionaryDatum& syn_specs ) const override + { + return new ConnBuilderType( sources, targets, third, conn_spec, syn_specs ); + } }; } // namespace nest diff --git a/nestkernel/connection_manager.cpp b/nestkernel/connection_manager.cpp index a0925ff3e9..919aa4fa06 100644 --- a/nestkernel/connection_manager.cpp +++ b/nestkernel/connection_manager.cpp @@ -363,6 +363,20 @@ nest::ConnectionManager::get_conn_builder( const std::string& name, return cb; } +nest::ConnBuilder* +nest::ConnectionManager::get_conn_builder( const std::string& name, + NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& conn_spec, + const DictionaryDatum& syn_specs ) +{ + const size_t rule_id = connruledict_->lookup( name ); + ConnBuilder* cb = connbuilder_factories_.at( rule_id )->create( sources, targets, targets, conn_spec, syn_specs ); + assert( cb ); + return cb; +} + void nest::ConnectionManager::calibrate( const TimeConverter& tc ) { @@ -421,6 +435,7 @@ nest::ConnectionManager::connect( NodeCollectionPTR sources, delete cb; } + void nest::ConnectionManager::connect( TokenArray sources, TokenArray targets, const DictionaryDatum& syn_spec ) { @@ -446,6 +461,7 @@ nest::ConnectionManager::connect( TokenArray sources, TokenArray targets, const } } + void nest::ConnectionManager::update_delay_extrema_() { @@ -743,6 +759,54 @@ nest::ConnectionManager::connect_sonata( const DictionaryDatum& graph_specs, con #endif } +void +nest::ConnectionManager::connect_tripartite( NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& conn_spec, + const DictionaryDatum& syn_specs ) +{ + if ( sources->empty() ) + { + throw IllegalConnection( "Presynaptic nodes cannot be an empty NodeCollection" ); + } + if ( targets->empty() ) + { + throw IllegalConnection( "Postsynaptic nodes cannot be an empty NodeCollection" ); + } + if ( third->empty() ) + { + throw IllegalConnection( "Third-factor nodes cannot be an empty NodeCollection" ); + } + + conn_spec->clear_access_flags(); + syn_specs->clear_access_flags(); + + if ( not conn_spec->known( names::rule ) ) + { + throw BadProperty( "The connection specification must contain a connection rule." ); + } + const std::string rule_name = static_cast< const std::string >( ( *conn_spec )[ names::rule ] ); + + if ( not connruledict_->known( rule_name ) ) + { + throw BadProperty( String::compose( "Unknown connection rule: %1", rule_name ) ); + } + + ConnBuilder* cb = get_conn_builder( rule_name, sources, targets, third, conn_spec, syn_specs ); + + // at this point, all entries in conn_spec and syn_spec have been checked + ALL_ENTRIES_ACCESSED( *conn_spec, "Connect", "Unread dictionary entries in conn_spec: " ); + ALL_ENTRIES_ACCESSED( *syn_specs, "Connect", "Unread dictionary entries in syn_specs: " ); + + // Set flag before calling cb->connect() in case exception is thrown after some connections have been created. + set_connections_have_changed(); + + cb->connect(); + delete cb; +} + + void nest::ConnectionManager::connect_( Node& source, Node& target, diff --git a/nestkernel/connection_manager.h b/nestkernel/connection_manager.h index 53fbdc2963..f36ba9cd2a 100644 --- a/nestkernel/connection_manager.h +++ b/nestkernel/connection_manager.h @@ -101,6 +101,13 @@ class ConnectionManager : public ManagerInterface const DictionaryDatum& conn_spec, const std::vector< DictionaryDatum >& syn_specs ); + ConnBuilder* get_conn_builder( const std::string& name, + NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& conn_spec, + const DictionaryDatum& syn_specs ); + /** * Create connections. */ @@ -174,6 +181,17 @@ class ConnectionManager : public ManagerInterface */ void connect_sonata( const DictionaryDatum& graph_specs, const long hyberslab_size ); + /** + * @brief Create tripartite connections + * + * @note `synapse_specs` is dictionary `{"primary": , "third_in": , "third_out": }` + */ + void connect_tripartite( NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& connectivity, + const DictionaryDatum& synapse_specs ); + size_t find_connection( const size_t tid, const synindex syn_id, const size_t snode_id, const size_t tnode_id ); void disconnect( const size_t tid, const synindex syn_id, const size_t snode_id, const size_t tnode_id ); diff --git a/nestkernel/nest.cpp b/nestkernel/nest.cpp index 31c5eaa2d7..d2cef37fbb 100644 --- a/nestkernel/nest.cpp +++ b/nestkernel/nest.cpp @@ -187,6 +187,16 @@ connect( NodeCollectionPTR sources, kernel().connection_manager.connect( sources, targets, connectivity, synapse_params ); } +void +connect_tripartite( NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& connectivity, + const DictionaryDatum& synapse_specs ) +{ + kernel().connection_manager.connect_tripartite( sources, targets, third, connectivity, synapse_specs ); +} + void connect_arrays( long* sources, long* targets, diff --git a/nestkernel/nest.h b/nestkernel/nest.h index 6901cc3a40..3b7da53be4 100644 --- a/nestkernel/nest.h +++ b/nestkernel/nest.h @@ -83,6 +83,17 @@ void connect( NodeCollectionPTR sources, const DictionaryDatum& connectivity, const std::vector< DictionaryDatum >& synapse_params ); +/** + * @brief Create tripartite connections + * + * @note `synapse_specs` is dictionary `{"primary": , "third_in": , "third_out": }` + */ +void connect_tripartite( NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& connectivity, + const DictionaryDatum& synapse_specs ); + /** * @brief Connect arrays of node IDs one-to-one * diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp index 5e87e3d63c..7f5daa9b65 100644 --- a/nestkernel/nest_names.cpp +++ b/nestkernel/nest_names.cpp @@ -70,9 +70,6 @@ const Name asc_amps( "asc_amps" ); const Name asc_decay( "asc_decay" ); const Name asc_init( "asc_init" ); const Name asc_r( "asc_r" ); -const Name astro_pool_by_index( "astro_pool_by_index" ); -const Name astro2post( "astro2post" ); -const Name astrocyte( "astrocyte" ); const Name available( "available" ); const Name azimuth_angle( "azimuth_angle" ); @@ -120,9 +117,6 @@ const Name dead_time( "dead_time" ); const Name dead_time_random( "dead_time_random" ); const Name dead_time_shape( "dead_time_shape" ); const Name delay( "delay" ); -const Name delay_astro2post( "delay_astro2post" ); -const Name delay_pre2astro( "delay_pre2astro" ); -const Name delay_pre2post( "delay_pre2post" ); const Name delay_u_bars( "delay_u_bars" ); const Name deliver_interval( "deliver_interval" ); const Name delta( "delta" ); @@ -291,7 +285,6 @@ const Name major_axis( "major_axis" ); const Name make_symmetric( "make_symmetric" ); const Name mask( "mask" ); const Name max( "max" ); -const Name max_astro_per_target( "max_astro_per_target" ); const Name max_buffer_size_target_data( "max_buffer_size_target_data" ); const Name max_delay( "max_delay" ); const Name max_num_syn_models( "max_num_syn_models" ); @@ -350,8 +343,9 @@ const Name overwrite_files( "overwrite_files" ); const Name P( "P" ); const Name p( "p" ); +const Name p_cond_third( "p_cond_third " ); const Name p_copy( "p_copy" ); -const Name p_syn_astro( "p_syn_astro" ); +const Name p_primary( "p_primary" ); const Name p_transmit( "p_transmit" ); const Name pairwise_bernoulli_on_source( "pairwise_bernoulli_on_source" ); const Name pairwise_bernoulli_on_target( "pairwise_bernoulli_on_target" ); @@ -361,6 +355,7 @@ const Name phase( "phase" ); const Name phi_max( "phi_max" ); const Name polar_angle( "polar_angle" ); const Name polar_axis( "polar_axis" ); +const Name pool_size( "pool_size" ); const Name port( "port" ); const Name port_name( "port_name" ); const Name port_width( "port_width" ); @@ -372,6 +367,7 @@ const Name pre_synaptic_element( "pre_synaptic_element" ); const Name precise_times( "precise_times" ); const Name precision( "precision" ); const Name prepared( "prepared" ); +const Name primary( "primary" ); const Name print_time( "print_time" ); const Name proximal_curr( "proximal_curr" ); const Name proximal_exc( "proximal_exc" ); @@ -385,6 +381,7 @@ const Name q_sfa( "q_sfa" ); const Name q_stc( "q_stc" ); const Name radius( "radius" ); +const Name random_pool( "random_pool" ); const Name rate( "rate" ); const Name rate_IP3R( "rate_IP3R" ); const Name rate_L( "rate_L" ); @@ -551,6 +548,9 @@ const Name theta_ex( "theta_ex" ); const Name theta_in( "theta_in" ); const Name theta_minus( "theta_minus" ); const Name theta_plus( "theta_plus" ); +const Name third( "third" ); +const Name third_in( "third_in" ); +const Name third_out( "third_out" ); const Name thread( "thread" ); const Name thread_local_id( "thread_local_id" ); const Name threshold( "threshold" ); @@ -613,10 +613,7 @@ const Name Wmax( "Wmax" ); const Name Wmin( "Wmin" ); const Name w( "w" ); const Name weight( "weight" ); -const Name weight_astro2post( "weight_astro2post" ); const Name weight_per_lut_entry( "weight_per_lut_entry" ); -const Name weight_pre2astro( "weight_pre2astro" ); -const Name weight_pre2post( "weight_pre2post" ); const Name weight_recorder( "weight_recorder" ); const Name weights( "weights" ); const Name wfr_comm_interval( "wfr_comm_interval" ); diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h index bda9b7f1f3..60a400197a 100644 --- a/nestkernel/nest_names.h +++ b/nestkernel/nest_names.h @@ -96,9 +96,6 @@ extern const Name asc_amps; extern const Name asc_decay; extern const Name asc_init; extern const Name asc_r; -extern const Name astro_pool_by_index; -extern const Name astro2post; -extern const Name astrocyte; extern const Name available; extern const Name azimuth_angle; @@ -146,9 +143,6 @@ extern const Name dead_time; extern const Name dead_time_random; extern const Name dead_time_shape; extern const Name delay; -extern const Name delay_astro2post; -extern const Name delay_pre2astro; -extern const Name delay_pre2post; extern const Name delay_u_bars; extern const Name deliver_interval; extern const Name delta; @@ -317,7 +311,6 @@ extern const Name major_axis; extern const Name make_symmetric; extern const Name mask; extern const Name max; -extern const Name max_astro_per_target; extern const Name max_buffer_size_target_data; extern const Name max_delay; extern const Name max_num_syn_models; @@ -376,8 +369,9 @@ extern const Name overwrite_files; extern const Name P; extern const Name p; +extern const Name p_cond_third; extern const Name p_copy; -extern const Name p_syn_astro; +extern const Name p_primary; extern const Name p_transmit; extern const Name pairwise_bernoulli_on_source; extern const Name pairwise_bernoulli_on_target; @@ -387,6 +381,7 @@ extern const Name phase; extern const Name phi_max; extern const Name polar_angle; extern const Name polar_axis; +extern const Name pool_size; extern const Name port; extern const Name port_name; extern const Name port_width; @@ -398,6 +393,7 @@ extern const Name pre_synaptic_element; extern const Name precise_times; extern const Name precision; extern const Name prepared; +extern const Name primary; extern const Name print_time; extern const Name proximal_curr; extern const Name proximal_exc; @@ -411,6 +407,7 @@ extern const Name q_sfa; extern const Name q_stc; extern const Name radius; +extern const Name random_pool; extern const Name rate; extern const Name rate_IP3R; extern const Name rate_L; @@ -577,6 +574,9 @@ extern const Name theta_ex; extern const Name theta_in; extern const Name theta_minus; extern const Name theta_plus; +extern const Name third; +extern const Name third_in; +extern const Name third_out; extern const Name thread; extern const Name thread_local_id; extern const Name threshold; @@ -639,10 +639,7 @@ extern const Name Wmax; extern const Name Wmin; extern const Name w; extern const Name weight; -extern const Name weight_astro2post; extern const Name weight_per_lut_entry; -extern const Name weight_pre2astro; -extern const Name weight_pre2post; extern const Name weight_recorder; extern const Name weights; extern const Name wfr_comm_interval; diff --git a/nestkernel/nestmodule.cpp b/nestkernel/nestmodule.cpp index 79674cbef2..c905d5b44c 100644 --- a/nestkernel/nestmodule.cpp +++ b/nestkernel/nestmodule.cpp @@ -767,6 +767,30 @@ NestModule::Connect_g_g_D_aFunction::execute( SLIInterpreter* i ) const kernel().connection_manager.sw_construction_connect.stop(); } + +void +NestModule::ConnectTripartite_g_g_g_D_DFunction::execute( SLIInterpreter* i ) const +{ + kernel().connection_manager.sw_construction_connect.start(); + + i->assert_stack_load( 5 ); + + NodeCollectionDatum sources = getValue< NodeCollectionDatum >( i->OStack.pick( 4 ) ); + NodeCollectionDatum targets = getValue< NodeCollectionDatum >( i->OStack.pick( 3 ) ); + NodeCollectionDatum third = getValue< NodeCollectionDatum >( i->OStack.pick( 2 ) ); + DictionaryDatum connectivity = getValue< DictionaryDatum >( i->OStack.pick( 1 ) ); + DictionaryDatum synapse_specs = getValue< DictionaryDatum >( i->OStack.pick( 0 ) ); + + // dictionary access checking is handled by connect + connect_tripartite( sources, targets, third, connectivity, synapse_specs ); + + i->OStack.pop( 5 ); + i->EStack.pop(); + + kernel().connection_manager.sw_construction_connect.stop(); +} + + void NestModule::ConnectSonata_D_Function::execute( SLIInterpreter* i ) const { @@ -2083,6 +2107,7 @@ NestModule::init( SLIInterpreter* i ) i->createcommand( "Connect_g_g_D_D", &connect_g_g_D_Dfunction ); i->createcommand( "Connect_g_g_D_a", &connect_g_g_D_afunction ); i->createcommand( "ConnectSonata_D", &ConnectSonata_D_Function ); + i->createcommand( "ConnectTripartite_g_g_g_D_D", &connect_tripartite_g_g_g_D_Dfunction ); i->createcommand( "ResetKernel", &resetkernelfunction ); @@ -2164,7 +2189,8 @@ NestModule::init( SLIInterpreter* i ) kernel().connection_manager.register_conn_builder< FixedInDegreeBuilder >( "fixed_indegree" ); kernel().connection_manager.register_conn_builder< FixedOutDegreeBuilder >( "fixed_outdegree" ); kernel().connection_manager.register_conn_builder< BernoulliBuilder >( "pairwise_bernoulli" ); - kernel().connection_manager.register_conn_builder< BernoulliAstroBuilder >( "pairwise_bernoulli_astro" ); + kernel().connection_manager.register_conn_builder< TripartiteBernoulliWithPoolBuilder >( + "tripartite_bernoulli_with_pool" ); kernel().connection_manager.register_conn_builder< SymmetricBernoulliBuilder >( "symmetric_pairwise_bernoulli" ); kernel().connection_manager.register_conn_builder< FixedTotalNumberBuilder >( "fixed_total_number" ); #ifdef HAVE_LIBNEUROSIM diff --git a/nestkernel/nestmodule.h b/nestkernel/nestmodule.h index 3163224449..3381593fa8 100644 --- a/nestkernel/nestmodule.h +++ b/nestkernel/nestmodule.h @@ -545,6 +545,12 @@ class NestModule : public SLIModule void execute( SLIInterpreter* ) const; } ConnectSonata_D_Function; + class ConnectTripartite_g_g_g_D_DFunction : public SLIFunction + { + public: + void execute( SLIInterpreter* ) const override; + } connect_tripartite_g_g_g_D_Dfunction; + class ResetKernelFunction : public SLIFunction { public: diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index e8a6a960b7..abe150638f 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -195,7 +195,7 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. "tau_psc": tau_syn_in, "delay": syn_params["d_i"], } - nest.Connect(nodes_ex, nodes_ex + nodes_in, third=nodes_astro, conn_spec=conn_params_e, syn_spec=syn_params_e) + nest.Connect(nodes_ex, nodes_ex + nodes_in, nodes_astro, conn_spec=conn_params_e, syn_spec=syn_params_e) nest.Connect(nodes_in, nodes_ex + nodes_in, conn_params_i, syn_params_i) return nodes_ex, nodes_in, nodes_astro, nodes_noise diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index d059710e7a..09e7581dbb 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -112,7 +112,7 @@ nest.Connect( pre_neurons, post_neurons, - third=astrocytes, + astrocytes, conn_spec={ "rule": "tripartite_bernoulli_with_pool", "p_primary": 1.0, diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index 5c1673cf74..0e3c27d5c2 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -293,6 +293,119 @@ def Connect(pre, post, conn_spec=None, syn_spec=None, return_synapsecollection=F return GetConnections(pre, post) +@check_stack +def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None, return_synapsecollection=False): + """ + Connect `pre` nodes to `post` nodes and a `third` population. + + Nodes in `pre` and `post` are connected using the specified tripartite connectivity rule + and the given synapse types (all :cpp:class:`static_synapse ` by default). + Details depend on the connectivity rule. + + Lists of synapse models and connection rules are available as + ``nest.synapse_models`` and ``nest.connection_rules``, respectively. Note that only tripartite + connection rules can be used. + + Parameters + ---------- + pre : NodeCollection + Presynaptic nodes + post : NodeCollection + Postsynaptic nodes + third : NodeCollection + Third population to include in connection + conn_spec : dict + Specifies connectivity rule, which must support tripartite connections, see below + syn_spec : dict, optional + Specifies synapse models to be used, see below + return_synapsecollection: bool + Specifies whether or not we should return a :py:class:`.SynapseCollection` of pre and post connections + + Raises + ------ + kernel.NESTError + + Notes + ----- + **Connectivity specification (conn_spec)** + + Available tripartite rules:: + + - 'tripartite_bernoulli_with_pool' + + See :ref:`tripartite_conn_rules` for more details, including example usage. + + **Synapse specifications (syn_specs)** + + Synapse specifications for tripartite connections are given as a dictionary with specifications + for each of the three connections to be created + + ``` + {"primary": , + "third_in": , + "third_out": } + ``` + + Here, `"primary"` marks the synapse specification for the connections between `pre` and `post` neurons, + `"third_in"` for connections between `pre` and `third` neurons and `"third_out"` for connections between + `third` and `post` neurons. + + Each `` entry can be any entry that would be possible as synapse specification + in a normal `Connect()` call. Any missing entries default to `static_synapse`. If no `syn_specs` argument + is given at all, all three entries default to `static_synapse`. + + The synapse model and its properties can be given either as a string + identifying a specific synapse model (default: :cpp:class:`static_synapse `) or + as a dictionary specifying the synapse model and its parameters. + + Available keys in the synapse specification dictionary are:: + + - 'synapse_model' + - 'weight' + - 'delay' + - 'receptor_type' + - any parameters specific to the selected synapse model. + + + See Also + --------- + :ref:`connection_management` + """ + + # Confirm that we got node collections + if not isinstance(pre, NodeCollection): + raise TypeError("Presynaptic nodes must be a NodeCollection") + if not isinstance(post, NodeCollection): + raise TypeError("Postsynaptic nodes must be a NodeCollection") + if not isinstance(third, NodeCollection): + raise TypeError("Third-factor nodes must be a NodeCollection") + + # Normalize syn_specs: ensure all three entries are in place and do not contain lists + syn_specs = syn_specs if syn_specs is not None else dict() + SYN_KEYS = {"primary", "third_in", "third_out"} + for key in SYN_KEYS: + if key not in syn_specs: + syn_specs[key] = {"synapse_model": "static_synapse"} + elif isinstance(syn_specs[key], str): + syn_specs[key] = {"synapse_model": syn_specs[key]} + else: + for entry, value in syn_specs[key].items(): + if isinstance(value, (list, tuple, np.ndarray)): + raise ValueError( + f"Tripartite connections do not accept parameter lists, but 'syn_specs[{key}][{entry}]' is a list or similar." + ) + + sps(pre) + sps(post) + sps(third) + sps(conn_spec) + sps(syn_specs) + sr("ConnectTripartite_g_g_g_D_D") + + if return_synapsecollection: + return GetConnections(pre, post) + + @check_stack def Disconnect(*args, conn_spec=None, syn_spec=None): """Disconnect connections in a SynapseCollection, or `pre` neurons from `post` neurons. From 6bca8cd6612d5ae5fe09938ce413d1f929a471ab Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 20:56:46 +0200 Subject: [PATCH 22/93] Extend NodeCollection interface to bring it closer to standard STL iterators --- nestkernel/node_collection.cpp | 8 ++++++++ nestkernel/node_collection.h | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/nestkernel/node_collection.cpp b/nestkernel/node_collection.cpp index e1ae755c4b..355831a666 100644 --- a/nestkernel/node_collection.cpp +++ b/nestkernel/node_collection.cpp @@ -194,6 +194,14 @@ nc_const_iterator::operator++() return *this; } +nc_const_iterator +nc_const_iterator::operator++( int ) +{ + nc_const_iterator tmp = *this; + ++( *this ); + return tmp; +} + NodeCollectionPTR operator+( NodeCollectionPTR lhs, NodeCollectionPTR rhs ) { diff --git a/nestkernel/node_collection.h b/nestkernel/node_collection.h index 69c1385de7..840a23d2ae 100644 --- a/nestkernel/node_collection.h +++ b/nestkernel/node_collection.h @@ -148,15 +148,23 @@ class nc_const_iterator void composite_update_indices_(); public: + using iterator_category = std::forward_iterator_tag; + using difference_type = long; + using value_type = NodeIDTriple; + using pointer = NodeIDTriple*; + using reference = NodeIDTriple&; + nc_const_iterator( const nc_const_iterator& nci ) = default; void get_current_part_offset( size_t&, size_t& ) const; NodeIDTriple operator*() const; + bool operator==( const nc_const_iterator& rhs ) const; bool operator!=( const nc_const_iterator& rhs ) const; bool operator<( const nc_const_iterator& rhs ) const; bool operator<=( const nc_const_iterator& rhs ) const; nc_const_iterator& operator++(); + nc_const_iterator operator++( int ); // post-fix nc_const_iterator& operator+=( const size_t ); nc_const_iterator operator+( const size_t ) const; @@ -710,6 +718,12 @@ nc_const_iterator::operator+( const size_t n ) const return it += n; } +inline bool +nc_const_iterator::operator==( const nc_const_iterator& rhs ) const +{ + return part_idx_ == rhs.part_idx_ and element_idx_ == rhs.element_idx_; +} + inline bool nc_const_iterator::operator!=( const nc_const_iterator& rhs ) const { From 2cf0310fc0508e808dddedcfb37dca3a58e6d5db Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 20:58:29 +0200 Subject: [PATCH 23/93] Add interface to C++17 std::sample() to random generators and hike default to C++17 --- CMakeLists.txt | 2 +- nestkernel/random_generators.h | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a924f3ebff..07633c092c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ option( static-libraries "Build static executable and libraries [default=OFF]" O set( with-optimize ON CACHE STRING "Enable user defined optimizations [default=ON (uses '-O2')]. When OFF, no '-O' flag is passed to the compiler. Explicit compiler flags can be given; separate multiple flags by ';'." ) set( with-warning ON CACHE STRING "Enable user defined warnings [default=ON (uses '-Wall')]. Separate multiple flags by ';'." ) set( with-debug OFF CACHE STRING "Enable user defined debug flags [default=OFF]. When ON, '-g' is used. Separate multiple flags by ';'." ) -set( with-cpp-std "c++11" CACHE STRING "C++ standard to use for compilation [default='c++11']." ) +set( with-cpp-std "c++17" CACHE STRING "C++ standard to use for compilation [default='c++17']." ) set( with-intel-compiler-flags OFF CACHE STRING "User defined flags for the Intel compiler [default='-fp-model strict']. Separate multiple flags by ';'." ) set( with-libraries OFF CACHE STRING "Link additional libraries [default=OFF]. Give full path. Separate multiple libraries by ';'." ) set( with-includes OFF CACHE STRING "Add additional include paths [default=OFF]. Give full path without '-I'. Separate multiple include paths by ';'." ) diff --git a/nestkernel/random_generators.h b/nestkernel/random_generators.h index f2e98f32bf..3091c9aa3a 100644 --- a/nestkernel/random_generators.h +++ b/nestkernel/random_generators.h @@ -24,7 +24,9 @@ #define RANDOM_GENERATORS_H // C++ includes: +#include #include +#include #include #include #include @@ -33,6 +35,9 @@ // libnestutil includes: #include "randutils.hpp" +// nestkernel includes: +#include "node_collection.h" + namespace nest { @@ -115,6 +120,14 @@ class BaseRandomGenerator * @param N Maximum value that can be drawn. */ virtual unsigned long ulrand( unsigned long N ) = 0; + + /** + * @brief Wrap std::sample for selection from NodeCollection + */ + virtual void sample( NodeCollection::const_iterator first, + NodeCollection::const_iterator last, + std::back_insert_iterator< std::vector< NodeIDTriple > > dest, + size_t n ) = 0; }; /** @@ -266,6 +279,16 @@ class RandomGenerator final : public BaseRandomGenerator return uniform_ulong_dist_( rng_, param ); } + //! Wrapper for std::sample() from C++17 + inline void + sample( NodeCollection::const_iterator first, + NodeCollection::const_iterator last, + std::back_insert_iterator< std::vector< NodeIDTriple > > dest, + size_t n ) override + { + std::sample( first, last, dest, n, rng_ ); + } + private: RandomEngineT rng_; //!< Wrapped RNG engine. std::uniform_int_distribution< unsigned long > uniform_ulong_dist_; From e28b57372dc509d05f51a5363a69fbe28d0bf844 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 20:59:00 +0200 Subject: [PATCH 24/93] Compiling version of revised tripartite builder --- nestkernel/conn_builder.cpp | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index cfe23c0aae..fa7c83d8de 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1608,11 +1608,13 @@ nest::TripartiteBernoulliWithPoolBuilder::TripartiteBernoulliWithPoolBuilder( No NodeCollectionPTR third, const DictionaryDatum& conn_spec, const DictionaryDatum& syn_specs ) - : ConnBuilder( sources, targets, conn_spec, { ( *syn_specs )[ names::primary ] } ) - , + : ConnBuilder( sources, targets, conn_spec, { getValue< DictionaryDatum >( ( *syn_specs )[ names::primary ] ) } ) , third_( third ) - , third_in_builder_( sources, third, conn_spec, { ( *syn_specs )[ names::third_in ] } ) - , third_out_builder_( third, targets, conn_spec, { ( *syn_specs )[ names::third_out ] } ) + , third_in_builder_( sources, third, conn_spec, { getValue< DictionaryDatum >( ( *syn_specs )[ names::third_in ] ) } ) + , third_out_builder_( third, + targets, + conn_spec, + { getValue< DictionaryDatum >( ( *syn_specs )[ names::third_out ] ) } ) , p_primary_( 1.0 ) , p_cond_third_( 1.0 ) , random_pool_( true ) @@ -1694,20 +1696,15 @@ nest::TripartiteBernoulliWithPoolBuilder::connect_() } // step 2, build pool for target - std::vector< size_t > pool; + std::vector< NodeIDTriple > pool; pool.reserve( pool_size_ ); if ( random_pool_ ) { - // std::sample( third_->begin(), third_->end(), std::back_inserter( pool ), pool_size_, - // synced_rng ); + synced_rng->sample( third_->begin(), third_->end(), std::back_inserter( pool ), pool_size_ ); } else { - auto first_index = get_first_pool_index_( targets_->get_lid( tnode_id ) ); - for ( size_t i = 0; i < pool_size_; ++i ) - { - pool.push_back( ( *third_ )[ first_index + i ] ); - } + std::copy_n( third_->begin() + get_first_pool_index_( target.lid ), pool_size_, std::back_inserter( pool ) ); } // step 3, iterate through indegree to make connections for this target @@ -1716,10 +1713,9 @@ nest::TripartiteBernoulliWithPoolBuilder::connect_() // we ignore it then connecting if no autapses are wanted std::vector< NodeIDTriple > sources_to_connect_; sources_to_connect_.reserve( indegree ); - // std::sample( sources_->begin(), sources_->end(), std::back_inserter( sources_to_connect_ ), - // indegree, synced_rng ); + synced_rng->sample( sources_->begin(), sources_->end(), std::back_inserter( sources_to_connect_ ), indegree ); - for ( const auto source : *sources_ ) + for ( const auto source : sources_to_connect_ ) { const auto snode_id = source.node_id; if ( not allow_autapses_ and snode_id == tnode_id ) @@ -1741,7 +1737,7 @@ nest::TripartiteBernoulliWithPoolBuilder::connect_() // select third-factor neuron randomly from pool for this target const auto third_index = pool_size_ == 1 ? 0 : synced_rng->ulrand( pool_size_ ); - const auto third_node_id = pool[ third_index ]; + const auto third_node_id = pool[ third_index ].node_id; Node* third_node = kernel().node_manager.get_node_or_proxy( third_node_id, tid ); const bool local_third_node = not third_node->is_proxy(); From 3106f558f4a59361a1b58cfacbd40efe7357694c Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 21:45:43 +0200 Subject: [PATCH 25/93] Revise modelsmodule generation to include models derived from StructuralPlasticityNode --- build_support/generate_modelsmodule.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/build_support/generate_modelsmodule.py b/build_support/generate_modelsmodule.py index 7540499a29..e317547b7f 100644 --- a/build_support/generate_modelsmodule.py +++ b/build_support/generate_modelsmodule.py @@ -100,16 +100,17 @@ def get_models_from_file(model_file): """ model_patterns = { - "neuron": "public ArchivingNode", - "stimulator": "public StimulationDevice", - "recorder": "public RecordingDevice", - "devicelike": "public DeviceNode", - "connection": "public Connection", - "node": "public Node", - "clopath": "public ClopathArchivingNode", - "urbanczik": "public UrbanczikArchivingNode", - "binary": "typedef binary_neuron", - "rate": "typedef rate_", + "public ArchivingNode": "neuron", + "public StructuralPlasticityNode": "neuron", + "public StimulationDevice": "stimulator", + "public RecordingDevice": "recorder", + "public DeviceNode": "devicelike", + "public Connection": "connection", + "public Node": "node", + "public ClopathArchivingNode": "clopath", + "public UrbanczikArchivingNode": "urbanczik", + "typedef binary_neuron": "binary", + "typedef rate_": "rate", } fname = Path(srcdir) / "models" / f"{model_file}.h" @@ -125,7 +126,7 @@ def get_models_from_file(model_file): if line.startswith("#ifdef HAVE_"): guards.append(line.strip().split()[1]) if line.startswith(f"class {model_file} : "): - for mtype, pattern in model_patterns.items(): + for pattern, mtype in model_patterns.items(): if pattern in line: names.append(model_file) types.append(mtype) @@ -138,7 +139,7 @@ def get_models_from_file(model_file): except (ValueError, KeyError) as e: types.append("node") if line.startswith("typedef "): - for mtype, pattern in model_patterns.items(): + for pattern, mtype in model_patterns.items(): if pattern in line: names.append(line.rsplit(" ", 1)[-1].strip()[:-1]) types.append(mtype) From eb268c8f0c4a765f5ff7060203322702b1f78871 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 22:36:02 +0200 Subject: [PATCH 26/93] Initial test for tripartite connectivity --- testsuite/pytests/test_tripartite_connect.py | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 testsuite/pytests/test_tripartite_connect.py diff --git a/testsuite/pytests/test_tripartite_connect.py b/testsuite/pytests/test_tripartite_connect.py new file mode 100644 index 0000000000..50ae0e7a5e --- /dev/null +++ b/testsuite/pytests/test_tripartite_connect.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# +# test_tripartite_connect.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +import nest + + +def test_connect_all(): + n_pre, n_post, n_third = 4, 2, 3 + pre = nest.Create("parrot_neuron", n_pre) + post = nest.Create("parrot_neuron", n_post) + third = nest.Create("parrot_neuron", n_third) + + nest.TripartiteConnect( + pre, post, third, {"rule": "tripartite_bernoulli_with_pool", "p_primary": 1.0, "p_cond_third": 1} + ) + + n_primary = n_pre * n_post + assert len(nest.GetConnections(pre, post)) == n_primary + assert len(nest.GetConnections(pre, third)) == n_primary + assert len(nest.GetConnections(third, post)) == n_primary From 4de624fdc2ee77c071fea76e6ccf1d7b05ffc17c Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 22:36:25 +0200 Subject: [PATCH 27/93] Fix typo --- nestkernel/nest_names.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp index 7f5daa9b65..45914a746e 100644 --- a/nestkernel/nest_names.cpp +++ b/nestkernel/nest_names.cpp @@ -343,7 +343,7 @@ const Name overwrite_files( "overwrite_files" ); const Name P( "P" ); const Name p( "p" ); -const Name p_cond_third( "p_cond_third " ); +const Name p_cond_third( "p_cond_third" ); const Name p_copy( "p_copy" ); const Name p_primary( "p_primary" ); const Name p_transmit( "p_transmit" ); From b4aff42dca0d16f44a4a200be1c364b5670e419e Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 22:37:03 +0200 Subject: [PATCH 28/93] Remove return_syncoll option from TripartiteConnect --- pynest/nest/lib/hl_api_connections.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index 0e3c27d5c2..722a53286e 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -40,6 +40,7 @@ __all__ = [ "Connect", + "TripartiteConnect", "Disconnect", "GetConnections", ] @@ -294,7 +295,7 @@ def Connect(pre, post, conn_spec=None, syn_spec=None, return_synapsecollection=F @check_stack -def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None, return_synapsecollection=False): +def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): """ Connect `pre` nodes to `post` nodes and a `third` population. @@ -318,8 +319,6 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None, return_synaps Specifies connectivity rule, which must support tripartite connections, see below syn_spec : dict, optional Specifies synapse models to be used, see below - return_synapsecollection: bool - Specifies whether or not we should return a :py:class:`.SynapseCollection` of pre and post connections Raises ------ @@ -390,7 +389,7 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None, return_synaps syn_specs[key] = {"synapse_model": syn_specs[key]} else: for entry, value in syn_specs[key].items(): - if isinstance(value, (list, tuple, np.ndarray)): + if isinstance(value, (list, tuple, numpy.ndarray)): raise ValueError( f"Tripartite connections do not accept parameter lists, but 'syn_specs[{key}][{entry}]' is a list or similar." ) @@ -402,9 +401,6 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None, return_synaps sps(syn_specs) sr("ConnectTripartite_g_g_g_D_D") - if return_synapsecollection: - return GetConnections(pre, post) - @check_stack def Disconnect(*args, conn_spec=None, syn_spec=None): From 984001092ba59cea04e1b11b7f924b2609a485f5 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 22:37:34 +0200 Subject: [PATCH 29/93] Adapt test to new tripartite syntax --- testsuite/pytests/test_connect_pairwise_bernoulli_astro.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py index c96732695e..f8521ab199 100644 --- a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py +++ b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py @@ -42,7 +42,7 @@ def setup_network( pop1 = nest.Create(neuron_model, N1) pop2 = nest.Create(neuron_model, N2) pop_astro = nest.Create(astrocyte_model, N2) - nest.Connect(pop1, pop2, third=pop_astro, conn_spec=conn_dict, syn_spec=syn_dict) + nest.TripartiteConnect(pop1, pop2, pop_astro, conn_spec=conn_dict, syn_specs=syn_dict) return pop1, pop2, pop_astro @@ -304,8 +304,8 @@ def test_autapses_true(autapses): # test that autapses exist pop = nest.Create("aeif_cond_alpha_astro", N) pop_astro = nest.Create("astrocyte_lr_1994", N) - nest.Connect( - pop, pop, third=pop_astro, conn_spec=conn_dict, syn_spec={"third_out": {"synapse_model": "sic_connection"}} + nest.TripartiteConnect( + pop, pop, pop_astro, conn_spec=conn_dict, syn_specs={"third_out": {"synapse_model": "sic_connection"}} ) # make sure all connections do exist M = get_connectivity_matrix(pop, pop) From 4c9ed664ec59617602cc6b8193a0b5452d0ac9d2 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 4 Oct 2023 22:38:08 +0200 Subject: [PATCH 30/93] Fix typos --- nestkernel/conn_builder.cpp | 2 +- nestkernel/connection_manager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index fa7c83d8de..0688d9ceef 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1730,7 +1730,7 @@ nest::TripartiteBernoulliWithPoolBuilder::connect_() } // conditionally connect third factor - if ( not( p_cond_third_ < synced_rng->drand() ) ) + if ( not( synced_rng->drand() < p_cond_third_ ) ) { continue; } diff --git a/nestkernel/connection_manager.cpp b/nestkernel/connection_manager.cpp index 919aa4fa06..da87bd38b0 100644 --- a/nestkernel/connection_manager.cpp +++ b/nestkernel/connection_manager.cpp @@ -372,7 +372,7 @@ nest::ConnectionManager::get_conn_builder( const std::string& name, const DictionaryDatum& syn_specs ) { const size_t rule_id = connruledict_->lookup( name ); - ConnBuilder* cb = connbuilder_factories_.at( rule_id )->create( sources, targets, targets, conn_spec, syn_specs ); + ConnBuilder* cb = connbuilder_factories_.at( rule_id )->create( sources, targets, third, conn_spec, syn_specs ); assert( cb ); return cb; } From 677328210567e8617f0c7256c40997ad964b6c6e Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Fri, 6 Oct 2023 12:30:18 +0200 Subject: [PATCH 31/93] Correct and extend tripartite tests --- .../test_connect_pairwise_bernoulli_astro.py | 7 ++++--- testsuite/pytests/test_tripartite_connect.py | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py index f8521ab199..2bbfd482f2 100644 --- a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py +++ b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py @@ -229,7 +229,7 @@ def mpi_assert(data_original, data_test): if isinstance(data_original, (np.ndarray, np.generic)) and isinstance(data_test, (np.ndarray, np.generic)): assert data_original == pytest.approx(data_test) else: - TestCase.assertTrue(data_original == data_test) + assert data_original == data_test # adapted from test_connect_pairwise_bernoulli.py @@ -262,7 +262,7 @@ def test_statistics(p_n2n): nest.ResetKernel() nest.local_num_threads = nr_threads nest.rng_seed = i + 1 - pop1, pop2, pop_astro = setup_network(conn_dict, None, N1, N2) + pop1, pop2, pop_astro = setup_network(conn_dict, {"third_out": {"synapse_model": "sic_connection"}}, N1, N2) # get indegree or outdegree degrees = get_degrees(fan, pop1, pop2) # gather data from MPI processes @@ -309,4 +309,5 @@ def test_autapses_true(autapses): ) # make sure all connections do exist M = get_connectivity_matrix(pop, pop) - mpi_assert(np.diag(M), np.ones(N)) + mpi_assert(np.diag(M), autapses * np.ones(N)) + mpi_assert(np.sum(M), N**2 - (1 - autapses) * N) diff --git a/testsuite/pytests/test_tripartite_connect.py b/testsuite/pytests/test_tripartite_connect.py index 50ae0e7a5e..c910a430d3 100644 --- a/testsuite/pytests/test_tripartite_connect.py +++ b/testsuite/pytests/test_tripartite_connect.py @@ -36,3 +36,23 @@ def test_connect_all(): assert len(nest.GetConnections(pre, post)) == n_primary assert len(nest.GetConnections(pre, third)) == n_primary assert len(nest.GetConnections(third, post)) == n_primary + + +def test_connect_astro(): + n_pre, n_post, n_third = 4, 2, 3 + pre = nest.Create("aeif_cond_alpha_astro", n_pre) + post = nest.Create("aeif_cond_alpha_astro", n_post) + third = nest.Create("astrocyte_lr_1994", n_third) + + nest.TripartiteConnect( + pre, + post, + third, + {"rule": "tripartite_bernoulli_with_pool", "p_primary": 1.0, "p_cond_third": 1}, + {"third_out": {"synapse_model": "sic_connection"}}, + ) + + n_primary = n_pre * n_post + assert len(nest.GetConnections(pre, post)) == n_primary + assert len(nest.GetConnections(pre, third)) == n_primary + assert len(nest.GetConnections(third, post)) == n_primary From db8fb17366bab1dfdc02452ef6c102bb6e5a1ba9 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Fri, 6 Oct 2023 13:51:30 +0200 Subject: [PATCH 32/93] Remove unused name --- nestkernel/nest_names.cpp | 1 - nestkernel/nest_names.h | 1 - 2 files changed, 2 deletions(-) diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp index 45914a746e..00a70035d8 100644 --- a/nestkernel/nest_names.cpp +++ b/nestkernel/nest_names.cpp @@ -548,7 +548,6 @@ const Name theta_ex( "theta_ex" ); const Name theta_in( "theta_in" ); const Name theta_minus( "theta_minus" ); const Name theta_plus( "theta_plus" ); -const Name third( "third" ); const Name third_in( "third_in" ); const Name third_out( "third_out" ); const Name thread( "thread" ); diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h index 60a400197a..7cb031f86a 100644 --- a/nestkernel/nest_names.h +++ b/nestkernel/nest_names.h @@ -574,7 +574,6 @@ extern const Name theta_ex; extern const Name theta_in; extern const Name theta_minus; extern const Name theta_plus; -extern const Name third; extern const Name third_in; extern const Name third_out; extern const Name thread; From 7bb98713537185960800a0880d8b1b61cf7f274a Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Fri, 6 Oct 2023 13:53:26 +0200 Subject: [PATCH 33/93] Fix import order --- pynest/examples/astrocyte_brunel.py | 10 +++++----- pynest/examples/astrocyte_small_network.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index abe150638f..ab9cb47c9c 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -72,17 +72,17 @@ # Import all necessary modules for simulation, analysis and plotting. Scipy # should be imported before nest. +import hashlib +import json import os +import random import sys import time -import numpy as np -import random -import hashlib -import json +import matplotlib.pyplot as plt import nest import nest.raster_plot -import matplotlib.pyplot as plt +import numpy as np plt.rcParams.update({"font.size": 13}) diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 09e7581dbb..34ddc6fc04 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -59,14 +59,14 @@ ############################################################################### # Import all necessary modules. -import os import hashlib as hl +import os -from mpi4py import MPI +import matplotlib.pyplot as plt import nest import numpy as np import pandas as pd -import matplotlib.pyplot as plt +from mpi4py import MPI plt.rcParams.update({"font.size": 13}) pd.set_option("display.max_rows", None) From 15989ac1d9ed7dcff13bc4b1b372c8d06533d59c Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Fri, 6 Oct 2023 13:59:40 +0200 Subject: [PATCH 34/93] Fix Flake8 issues --- pynest/examples/astrocyte_brunel.py | 8 ++++---- pynest/examples/astrocyte_small_network.py | 4 ++-- pynest/nest/lib/hl_api_connections.py | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index ab9cb47c9c..f31d8d5bb5 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -125,8 +125,8 @@ astrocyte_model = "astrocyte_lr_1994" astrocyte_params = { - "IP3": 0.4, # IP3 initial value in uM - "delta_IP3": 0.5, # Step increase in IP3 concentration with each unit synaptic weight received by the astrocyte in uM + "IP3": 0.4, # IP3 initial value in µM + "delta_IP3": 0.5, # Step increase in IP3 concentration per unit synaptic weight received by the astrocyte in µM "tau_IP3": 2.0, # Time constant of astrocytic IP3 degradation in ms } @@ -257,7 +257,7 @@ def plot_synchrony(neuron_spikes, data_path, start, end, N=100, bw=10): plt.hist(coefs) title = ( f"Firing rate={rate:.2f} spikes/s (n={len(set(senders))}) \n" - + f"Local sync.={lsync_mu:.3f}$\pm${np.std(coefs):.3f}, Global sync.={gsync:.3f}\n" + + f"Local sync.={lsync_mu:.3f}$\\pm${np.std(coefs):.3f}, Global sync.={gsync:.3f}\n" + f"(n={n_sample}, total n of pairs={n_pass_})\n" ) plt.title(title) @@ -340,7 +340,7 @@ def run_simulation(data_path): nest.overwrite_files = True try: nest.local_num_threads = int(sys.argv[1]) - except: + except Exception: nest.local_num_threads = 4 # Simulation settings diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 34ddc6fc04..990d7fbba3 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -89,8 +89,8 @@ astrocyte_model = "astrocyte_lr_1994" astrocyte_params = { - "IP3": 0.4, # IP3 initial value in uM - "delta_IP3": 0.5, # Step increase in IP3 concentration with each unit synaptic weight received by the astrocyte in uM + "IP3": 0.4, # IP3 initial value in µM + "delta_IP3": 0.5, # Step increase in IP3 concentration per unit synaptic weight received by the astrocyte in µM "tau_IP3": 10.0, # Time constant of astrocytic IP3 degradation in ms } diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index 722a53286e..89310bcc4a 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -391,7 +391,8 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): for entry, value in syn_specs[key].items(): if isinstance(value, (list, tuple, numpy.ndarray)): raise ValueError( - f"Tripartite connections do not accept parameter lists, but 'syn_specs[{key}][{entry}]' is a list or similar." + f"Tripartite connections do not accept parameter lists," + f"but 'syn_specs[{key}][{entry}]' is a list or similar." ) sps(pre) From 937f907f28e0f65bbd8b374793c1adbc0ceb9c05 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Fri, 6 Oct 2023 14:02:50 +0200 Subject: [PATCH 35/93] Fix one more isort problem --- testsuite/pytests/test_connect_pairwise_bernoulli_astro.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py index 2bbfd482f2..cbdbd5cd61 100644 --- a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py +++ b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py @@ -20,11 +20,10 @@ # along with NEST. If not, see . +import nest import numpy as np -import scipy.stats import pytest - -import nest +import scipy.stats # copied from connect_test_base.py try: From 5660e417fa9859beee1474096f60276b2524f462 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Fri, 6 Oct 2023 14:09:25 +0200 Subject: [PATCH 36/93] Keep pylint happy if no matplotlib installed --- testsuite/pytests/test_stdp_pl_synapse_hom.py | 3 +++ testsuite/pytests/test_stdp_synapse.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/testsuite/pytests/test_stdp_pl_synapse_hom.py b/testsuite/pytests/test_stdp_pl_synapse_hom.py index a9dcb6507d..f681d37cf7 100644 --- a/testsuite/pytests/test_stdp_pl_synapse_hom.py +++ b/testsuite/pytests/test_stdp_pl_synapse_hom.py @@ -332,6 +332,9 @@ def plot_weight_evolution( fname_snip="", title_snip="", ): + if not DEBUG_PLOTS: # make pylint happy if no matplotlib + return + fig, ax = plt.subplots(nrows=3) n_spikes = len(pre_spikes) diff --git a/testsuite/pytests/test_stdp_synapse.py b/testsuite/pytests/test_stdp_synapse.py index 22e64a8209..46ff0d3f9a 100644 --- a/testsuite/pytests/test_stdp_synapse.py +++ b/testsuite/pytests/test_stdp_synapse.py @@ -366,6 +366,9 @@ def plot_weight_evolution( fname_snip="", title_snip="", ): + if not DEBUG_PLOTS: # make pylint happy if no matplotlib + return + fig, ax = plt.subplots(nrows=3) n_spikes = len(pre_spikes) From c1192991327add95129bed445659ee77ff31c8f3 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Sat, 7 Oct 2023 17:30:12 +0200 Subject: [PATCH 37/93] Properly suppress pylint complaints about unused plt in tests --- testsuite/pytests/test_stdp_pl_synapse_hom.py | 1 + testsuite/pytests/test_stdp_synapse.py | 1 + 2 files changed, 2 insertions(+) diff --git a/testsuite/pytests/test_stdp_pl_synapse_hom.py b/testsuite/pytests/test_stdp_pl_synapse_hom.py index f681d37cf7..4243ccc004 100644 --- a/testsuite/pytests/test_stdp_pl_synapse_hom.py +++ b/testsuite/pytests/test_stdp_pl_synapse_hom.py @@ -335,6 +335,7 @@ def plot_weight_evolution( if not DEBUG_PLOTS: # make pylint happy if no matplotlib return + # pylint: disable=E0601 fig, ax = plt.subplots(nrows=3) n_spikes = len(pre_spikes) diff --git a/testsuite/pytests/test_stdp_synapse.py b/testsuite/pytests/test_stdp_synapse.py index 46ff0d3f9a..ccd0981c6d 100644 --- a/testsuite/pytests/test_stdp_synapse.py +++ b/testsuite/pytests/test_stdp_synapse.py @@ -369,6 +369,7 @@ def plot_weight_evolution( if not DEBUG_PLOTS: # make pylint happy if no matplotlib return + # pylint: disable=E0601 fig, ax = plt.subplots(nrows=3) n_spikes = len(pre_spikes) From 68fe8d36c06ba44d4ff7e92e6ab701b455e0471a Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Sat, 7 Oct 2023 18:07:45 +0200 Subject: [PATCH 38/93] Fix typo in error message --- nestkernel/vp_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nestkernel/vp_manager.cpp b/nestkernel/vp_manager.cpp index cf75afdf56..4f7d0d33e1 100644 --- a/nestkernel/vp_manager.cpp +++ b/nestkernel/vp_manager.cpp @@ -147,7 +147,7 @@ nest::VPManager::set_status( const DictionaryDatum& d ) } if ( force_singlethreading_ and n_threads > 1 ) { - errors.push_back( "This installation of NEST does not support for multiple threads" ); + errors.push_back( "This installation of NEST does not support multiple threads" ); } if ( not errors.empty() ) From e8446a6bf41f58c35e504d020a8be2cc7b1bbdc8 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Sat, 7 Oct 2023 18:08:12 +0200 Subject: [PATCH 39/93] Skip test if threading not available --- testsuite/pytests/test_connect_pairwise_bernoulli_astro.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py index cbdbd5cd61..744922ec68 100644 --- a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py +++ b/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py @@ -234,6 +234,7 @@ def mpi_assert(data_original, data_test): # adapted from test_connect_pairwise_bernoulli.py # a test for parameters "p" and "max_astro_per_target" # run for three levels of neuron-neuron connection probabilities +@pytest.mark.skipif_missing_threads @pytest.mark.parametrize("p_n2n", [0.1, 0.3, 0.5]) def test_statistics(p_n2n): # set connection parameters From 87beb4f8563f270dcf2dd0b38bdce305267c792d Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Sat, 7 Oct 2023 19:02:41 +0200 Subject: [PATCH 40/93] Select pool type via string variable and fix check for proper pool size --- nestkernel/conn_builder.cpp | 22 +++++++-- nestkernel/nest_names.cpp | 2 +- nestkernel/nest_names.h | 2 +- testsuite/pytests/test_tripartite_connect.py | 48 ++++++++++++++++++++ 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 0688d9ceef..15a10c19ad 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1623,8 +1623,23 @@ nest::TripartiteBernoulliWithPoolBuilder::TripartiteBernoulliWithPoolBuilder( No { updateValue< double >( conn_spec, names::p_primary, p_primary_ ); updateValue< double >( conn_spec, names::p_cond_third, p_cond_third_ ); - updateValue< bool >( conn_spec, names::random_pool, random_pool_ ); updateValue< long >( conn_spec, names::pool_size, pool_size_ ); + std::string pool_type; + if ( updateValue< std::string >( conn_spec, names::pool_type, pool_type ) ) + { + if ( pool_type == "random" ) + { + random_pool_ = true; + } + else if ( pool_type == "block" ) + { + random_pool_ = false; + } + else + { + throw BadProperty( "pool_type must be 'random' or 'block'" ); + } + } if ( p_primary_ < 0 or 1 < p_primary_ ) { @@ -1641,9 +1656,8 @@ nest::TripartiteBernoulliWithPoolBuilder::TripartiteBernoulliWithPoolBuilder( No throw BadProperty( "Pool size 1 ≤ pool_size ≤ size of third-factor population required" ); } - if ( not random_pool_ - and ( ( targets->size() * pool_size_ != third->size() ) - or ( pool_size_ == 1 and targets->size() % third->size() == 0 ) ) ) + if ( not( random_pool_ or ( targets->size() * pool_size_ == third->size() ) + or ( pool_size_ == 1 and targets->size() % third->size() == 0 ) ) ) { throw BadProperty( "The sizes of target and third-factor populations and the chosen pool size do not fit." diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp index 00a70035d8..7b63f11516 100644 --- a/nestkernel/nest_names.cpp +++ b/nestkernel/nest_names.cpp @@ -356,6 +356,7 @@ const Name phi_max( "phi_max" ); const Name polar_angle( "polar_angle" ); const Name polar_axis( "polar_axis" ); const Name pool_size( "pool_size" ); +const Name pool_type( "pool_type" ); const Name port( "port" ); const Name port_name( "port_name" ); const Name port_width( "port_width" ); @@ -381,7 +382,6 @@ const Name q_sfa( "q_sfa" ); const Name q_stc( "q_stc" ); const Name radius( "radius" ); -const Name random_pool( "random_pool" ); const Name rate( "rate" ); const Name rate_IP3R( "rate_IP3R" ); const Name rate_L( "rate_L" ); diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h index 7cb031f86a..c3c282c7f1 100644 --- a/nestkernel/nest_names.h +++ b/nestkernel/nest_names.h @@ -382,6 +382,7 @@ extern const Name phi_max; extern const Name polar_angle; extern const Name polar_axis; extern const Name pool_size; +extern const Name pool_type; extern const Name port; extern const Name port_name; extern const Name port_width; @@ -407,7 +408,6 @@ extern const Name q_sfa; extern const Name q_stc; extern const Name radius; -extern const Name random_pool; extern const Name rate; extern const Name rate_IP3R; extern const Name rate_L; diff --git a/testsuite/pytests/test_tripartite_connect.py b/testsuite/pytests/test_tripartite_connect.py index c910a430d3..013c85b443 100644 --- a/testsuite/pytests/test_tripartite_connect.py +++ b/testsuite/pytests/test_tripartite_connect.py @@ -56,3 +56,51 @@ def test_connect_astro(): assert len(nest.GetConnections(pre, post)) == n_primary assert len(nest.GetConnections(pre, third)) == n_primary assert len(nest.GetConnections(third, post)) == n_primary + + +def test_explicit_random_pool(): + n_pre, n_post, n_third = 4, 2, 3 + pre = nest.Create("parrot_neuron", n_pre) + post = nest.Create("parrot_neuron", n_post) + third = nest.Create("parrot_neuron", n_third) + + nest.TripartiteConnect( + pre, post, third, {"rule": "tripartite_bernoulli_with_pool", "pool_type": "random", "pool_size": 2} + ) + + n_primary = n_pre * n_post + assert len(nest.GetConnections(pre, post)) == n_primary + assert len(nest.GetConnections(pre, third)) == n_primary + assert len(nest.GetConnections(third, post)) == n_primary + + +def test_block_pool_single(): + n_pre, n_post, n_third = 4, 4, 2 + pre = nest.Create("parrot_neuron", n_pre) + post = nest.Create("parrot_neuron", n_post) + third = nest.Create("parrot_neuron", n_third) + + nest.TripartiteConnect( + pre, post, third, {"rule": "tripartite_bernoulli_with_pool", "pool_type": "block", "pool_size": 1} + ) + + n_primary = n_pre * n_post + assert len(nest.GetConnections(pre, post)) == n_primary + assert len(nest.GetConnections(pre, third)) == n_primary + assert len(nest.GetConnections(third, post)) == n_primary + + +def test_block_pool_wide(): + n_pre, n_post, n_third = 4, 2, 8 + pre = nest.Create("parrot_neuron", n_pre) + post = nest.Create("parrot_neuron", n_post) + third = nest.Create("parrot_neuron", n_third) + + nest.TripartiteConnect( + pre, post, third, {"rule": "tripartite_bernoulli_with_pool", "pool_type": "block", "pool_size": 4} + ) + + n_primary = n_pre * n_post + assert len(nest.GetConnections(pre, post)) == n_primary + assert len(nest.GetConnections(pre, third)) == n_primary + assert len(nest.GetConnections(third, post)) == n_primary From 5b53f6fa59b50b4b34d35de301bc871dfdd3afaf Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Mon, 9 Oct 2023 17:14:25 +0200 Subject: [PATCH 41/93] Add test against calling TripartiteConnect with bipartite rule --- nestkernel/conn_builder_factory.h | 4 ++-- testsuite/pytests/test_tripartite_connect.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/nestkernel/conn_builder_factory.h b/nestkernel/conn_builder_factory.h index d375acfc98..679f93c518 100644 --- a/nestkernel/conn_builder_factory.h +++ b/nestkernel/conn_builder_factory.h @@ -110,8 +110,8 @@ class ConnBuilderFactory< ConnBuilderType, false > : public GenericConnBuilderFa const DictionaryDatum& conn_spec, const DictionaryDatum& syn_specs ) const override { - throw BadProperty( - String::compose( "Connection rule %1 does not support tripartite connections.", ( *conn_spec )[ names::rule ] ) ); + throw IllegalConnection( String::compose( + "Connection rule '%1' does not support tripartite connections.", ( *conn_spec )[ names::rule ] ) ); } }; diff --git a/testsuite/pytests/test_tripartite_connect.py b/testsuite/pytests/test_tripartite_connect.py index 013c85b443..ef423fddd7 100644 --- a/testsuite/pytests/test_tripartite_connect.py +++ b/testsuite/pytests/test_tripartite_connect.py @@ -20,6 +20,7 @@ # along with NEST. If not, see . import nest +import pytest def test_connect_all(): @@ -104,3 +105,13 @@ def test_block_pool_wide(): assert len(nest.GetConnections(pre, post)) == n_primary assert len(nest.GetConnections(pre, third)) == n_primary assert len(nest.GetConnections(third, post)) == n_primary + + +def test_bipartitet_raises(): + n_pre, n_post, n_third = 4, 2, 8 + pre = nest.Create("parrot_neuron", n_pre) + post = nest.Create("parrot_neuron", n_post) + third = nest.Create("parrot_neuron", n_third) + + with pytest.raises(nest.kernel.NESTErrors.IllegalConnection): + nest.TripartiteConnect(pre, post, third, {"rule": "one_to_one"}) From 37c9533c69a1debec84e02dab06def6c20a8f161 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sun, 15 Oct 2023 10:24:05 +0200 Subject: [PATCH 42/93] Reorganize and improve astrocyte_brunel.py --- pynest/examples/astrocyte_brunel.py | 141 ++++++++++++++-------------- 1 file changed, 69 insertions(+), 72 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index f31d8d5bb5..48e8686965 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -28,14 +28,15 @@ to [1]_, [2]_, and [3]_. The ``aeif_cond_alpha_astro`` model is an adaptive exponential integrate and fire neuron supporting neuron-astrocyte interactions. -The network is created using the ``pairwise_bernoulli_astro`` rule, with the -``tsodyks_synapse`` as the connections from neurons to neruons and astrocytes, -and the ``sic_connection`` as the connections from astrocytes to neurons. +The network is created using the ``tripartite_bernoulli_with_pool`` rule, with +the ``tsodyks_synapse`` as the connections from neurons to neruons and +astrocytes, and the ``sic_connection`` as the connections from astrocytes to +neurons. This network is an example of an astrocytic effect on neuronal excitabitlity. With the slow inward current (SIC) delivered from the astrocytes through the ``sic_connection``, the neurons show higher firing rates and more synchronized -bursting actitivity. The degrees of local and global synchrony are quantitized +bursting actitivity. The degrees of local and global synchrony are quantified by pairwise spike count correlations and a measure of global synchrony in [4]_ respectively, as shown in a plot made in this script (neuron_synchrony.png). Plots of astrocytic dynamics and SIC in neurons are also made in this script. @@ -92,7 +93,7 @@ sim_params = { "dt": 0.1, # simulation resolution in ms "pre_sim_time": 100.0, # pre-simulation time in ms - "sim_time": 1000.0, # simulation time in ms + "sim_time": 100.0, # simulation time in ms "N_rec_spk": 100, # number of samples (neuron) for spike detector "N_rec_mm": 50, # number of samples (neuron, astrocyte) for multimeter } @@ -107,7 +108,7 @@ "p": 0.1, # neuron-neuron connection probability. "p_syn_astro": 0.5, # synapse-astrocyte pairing probability "max_astro_per_target": 10, # max number of astrocytes per target neuron - "astro_pool_by_index": False, # Astrocyte pool selection by index + "astro_pool_type": "random", # astrocyte pool type "poisson_rate": 2000, # rate of Poisson input } @@ -126,8 +127,8 @@ astrocyte_model = "astrocyte_lr_1994" astrocyte_params = { "IP3": 0.4, # IP3 initial value in µM - "delta_IP3": 0.5, # Step increase in IP3 concentration per unit synaptic weight received by the astrocyte in µM - "tau_IP3": 2.0, # Time constant of astrocytic IP3 degradation in ms + "delta_IP3": 0.5, # Parameter determining the increase in astrocytic IP3 concentration induced by synaptic input + "tau_IP3": 2.0, # Time constant of the exponential decay of astrocytic IP3 } ############################################################################### @@ -167,15 +168,15 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. """ print("Connecting Poisson generator ...") assert scale >= 1.0, "scale must be >= 1.0" - syn_params_noise = {"synapse_model": "static_synapse", "weight": syn_params["w_e"]} - nest.Connect(nodes_noise, nodes_ex + nodes_in, syn_spec=syn_params_noise) + nest.Connect( + nodes_noise, nodes_ex + nodes_in, syn_spec={"synapse_model": "static_synapse", "weight": syn_params["w_e"]}) print("Connecting neurons and astrocytes ...") conn_params_e = { "rule": "tripartite_bernoulli_with_pool", "p_primary": network_params["p"] / scale, "p_cond_third": network_params["p_syn_astro"], "pool_size": network_params["max_astro_per_target"], - "random_pool": not network_params["astro_pool_by_index"], + "pool_type": network_params["astro_pool_type"], } syn_params_e = { "primary": { @@ -184,8 +185,16 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. "tau_psc": tau_syn_ex, "delay": syn_params["d_e"], }, - "third_in": {"delay": syn_params["d_e"]}, - "third_out": {"synapse_model": "sic_connection", "weight": syn_params["w_a2n"]}, + "third_in": { + "synapse_model": syn_params["synapse_model"], + "weight": syn_params["w_e"], + "tau_psc": tau_syn_ex, + "delay": syn_params["d_e"], + }, + "third_out": { + "synapse_model": "sic_connection", + "weight": syn_params["w_a2n"] + }, } conn_params_i = {"rule": "pairwise_bernoulli", "p": network_params["p"] / scale} @@ -195,21 +204,19 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. "tau_psc": tau_syn_in, "delay": syn_params["d_i"], } - nest.Connect(nodes_ex, nodes_ex + nodes_in, nodes_astro, conn_spec=conn_params_e, syn_spec=syn_params_e) + nest.TripartiteConnect(nodes_ex, nodes_ex + nodes_in, nodes_astro, conn_spec=conn_params_e, syn_specs=syn_params_e) nest.Connect(nodes_in, nodes_ex + nodes_in, conn_params_i, syn_params_i) - return nodes_ex, nodes_in, nodes_astro, nodes_noise - ############################################################################### # Function for calculating correlation. def get_corr(hlist): - """Calculate pairwise correlation coefficients for a list of histograms.""" + """Calculate pairwise correlations for a list of histograms.""" coef_list = [] - n_pass = 0 - n_fail = 0 + n_pair_pass = 0 + n_pair_fail = 0 for i, hist1 in enumerate(hlist): idxs = list(range(i + 1, len(hlist))) for j in idxs: @@ -217,59 +224,58 @@ def get_corr(hlist): if np.sum(hist1) != 0 and np.sum(hist2) != 0: coef = np.corrcoef(hist1, hist2)[0, 1] coef_list.append(coef) - n_pass += 1 + n_pair_pass += 1 else: - n_fail += 1 + n_pair_fail += 1 + if n_pair_fail > 0: + print(f"n_pair_fail = {n_pair_fail}!") - return coef_list, n_pass, n_fail + return coef_list, n_pair_pass, n_pair_fail ############################################################################### -# Function for calculating and plotting neuronal synchrony. +# Functions for calculating and plotting synchrony of neuron firings. -def plot_synchrony(neuron_spikes, data_path, start, end, N=100, bw=10): +def calc_synchrony(neuron_spikes, n_neuron_rec_spk, start, end, data_path, N=100, bw=10): # get data senders = neuron_spikes["senders"][neuron_spikes["times"] > start] times = neuron_spikes["times"][neuron_spikes["times"] > start] - rate = len(senders) / (end - start) * 1000.0 / len(set(senders)) - print(f"Mean neuronal firing rate = {rate} (n = {len(set(senders))})") + rate = len(senders) / (end - start) * 1000.0 / n_neuron_rec_spk + print(f"Mean neuron firing rate = {rate:.2f} spikes/s (n = {n_neuron_rec_spk})") # sample neurons - n_sample = min(len(set(senders)), N) - print(f"Sampling {n_sample} neurons for synchrony analysis ...") - sampled = random.sample(list(set(senders)), n_sample) + set_senders = list(set(senders)) + n_for_sync = min(len(set_senders), N) + print(f"n of neurons for synchrony analysis = {n_for_sync}") + sampled = random.sample(set_senders, n_for_sync) times = times[np.isin(senders, sampled)] senders = senders[np.isin(senders, sampled)] - # make histogram + # make spiking histograms of individual neurons bins = np.arange(start, end + 0.1, bw) # time bins hists = [ np.histogram(times[senders == x], bins)[0].tolist() for x in set(senders) - ] # spiking histograms of individual neurons + ] + # make spiking histogram of all sampled neurons hist_global = ( np.histogram(times, bins)[0] / len(set(senders)) - ).tolist() # spiking histogram of all neurons sampled - # calculate local and global synchrony of neurons sampled + ).tolist() + # calculate local and global synchrony print("Calculating neuronal local and global synchrony ...") - coefs, n_pass_, n_fail_ = get_corr(hists) # local (spike count correlation) + coefs, n_pair_pass, n_pair_fail = get_corr(hists) # local (spike count correlation) lsync_mu, lsync_sd = np.mean(coefs), np.std(coefs) gsync = np.var(hist_global) / np.mean(np.var(hists, axis=1)) # global (variance of all/variance of individual) + return rate, coefs, lsync_mu, lsync_sd, gsync, n_for_sync + +def plot_synchrony(coefs, title, data_path): # make plot + print("Plotting synchrony ...") plt.hist(coefs) - title = ( - f"Firing rate={rate:.2f} spikes/s (n={len(set(senders))}) \n" - + f"Local sync.={lsync_mu:.3f}$\\pm${np.std(coefs):.3f}, Global sync.={gsync:.3f}\n" - + f"(n={n_sample}, total n of pairs={n_pass_})\n" - ) plt.title(title) plt.xlabel("Pairwise spike count correlation (Pearson's r)") plt.ylabel("n of pairs") plt.tight_layout() plt.savefig(os.path.join(data_path, "neuron_synchrony.png")) plt.close() - print(f"Local synchrony = {lsync_mu:.3f}+-{lsync_sd:.3f}") - print(f"Global synchrony = {gsync:.3f}") - print(f"n of neurons sampled for synchrony analysis = {len(hists)}") - print(f"n of pairs included/excluded = {n_pass_}/{n_fail_}\n") ############################################################################### @@ -346,8 +352,6 @@ def run_simulation(data_path): # Simulation settings pre_sim_time = sim_params["pre_sim_time"] sim_time = sim_params["sim_time"] - # Time before building - startbuild = time.time() # Create and connect nodes exc, inh, astro, noise = create_astro_network() @@ -358,38 +362,25 @@ def run_simulation(data_path): mm_neuron = nest.Create("multimeter", params={"record_from": ["I_SIC"]}) mm_astro = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) - # Time after building - endbuild = time.time() - # Run pre-simulation and simulation print("Running pre-simulation ...") nest.Simulate(pre_sim_time) print("Connecting recorders ...") - n_spk_neuro = min(len(exc + inh), sim_params["N_rec_spk"]) - n_mm_neuro = min(len(exc + inh), sim_params["N_rec_mm"]) - n_mm_astro = min(len(astro), sim_params["N_rec_mm"]) - nest.Connect((exc + inh)[:n_spk_neuro], sr_neuron) - nest.Connect(mm_neuron, (exc + inh)[:n_mm_neuro]) - nest.Connect(mm_astro, astro[:n_mm_astro]) + n_neuron_rec_spk = min(len(exc + inh), sim_params["N_rec_spk"]) + n_neuron_rec_mm = min(len(exc + inh), sim_params["N_rec_mm"]) + n_astrocyte_rec = min(len(astro), sim_params["N_rec_mm"]) + nest.Connect((exc + inh)[:n_neuron_rec_spk], sr_neuron) + nest.Connect(mm_neuron, (exc + inh)[:n_neuron_rec_mm]) + nest.Connect(mm_astro, astro[:n_astrocyte_rec]) print("Running simulation ...") nest.Simulate(sim_time) - # Time after simulation - endsimulate = time.time() - # Read out recordings neuron_spikes = sr_neuron.events neuron_data = mm_neuron.events astro_data = mm_astro.events - # Report firing rates and building/running time - build_time = endbuild - startbuild - run_time = endsimulate - endbuild - print("Brunel network with astrocytes:") - print(f"Building time = {build_time:.2f} s") - print(f"Simulation time = {run_time:.2f} s\n") - - # Data saving + # Data saving (for debug) os.system(f"mkdir -p {data_path}") os.system(f"cp astrocyte_brunel.py {data_path}") params = { @@ -406,20 +397,26 @@ def run_simulation(data_path): json.dump(params, f, indent=4) # Make raster plot and histogram - nest.raster_plot.from_device(sr_neuron, hist=True) - plt.title("") + nest.raster_plot.from_device(sr_neuron, hist=True, title="") plt.savefig(os.path.join(data_path, "neuron_raster.png"), bbox_inches="tight") plt.close() - # Calculate and plot neuronal spiking synchrony - plot_synchrony(neuron_spikes, data_path, pre_sim_time, pre_sim_time + sim_time) + # Calculate and plot synchrony + rate, coefs, lsync_mu, lsync_sd, gsync, n_for_sync = \ + calc_synchrony(neuron_spikes, n_neuron_rec_spk, pre_sim_time, pre_sim_time + sim_time, data_path) + title = ( + f"Firing rate={rate:.2f} spikes/s (n={n_neuron_rec_spk}) \n" + + f"Local sync.={lsync_mu:.3f}$\\pm${lsync_sd:.3f}, Global sync.={gsync:.3f}\n" + + f"(n for synchrony analysis={n_for_sync})\n" + ) + plot_synchrony(coefs, title, data_path) + # print results + print(f"Local synchrony = {lsync_mu:.3f}+-{lsync_sd:.3f}") + print(f"Global synchrony = {gsync:.3f}") # Plot dynamics in astrocytes and neurons plot_dynamics(astro_data, neuron_data, data_path, pre_sim_time) - # Copy files - os.system(f"cp run.jdf out.* err*. {data_path}") - print("Done!") From 51ec3656d63e130469ad6ee650b7c9e0bd072485 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sun, 15 Oct 2023 19:49:49 +0200 Subject: [PATCH 43/93] Add an approach in astrocyte_brunel.py for reproducible node sampling for recordings --- pynest/examples/astrocyte_brunel.py | 45 +++++++++++++++-------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index 48e8686965..2cab23849d 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -77,8 +77,6 @@ import json import os import random -import sys -import time import matplotlib.pyplot as plt import nest @@ -96,6 +94,8 @@ "sim_time": 100.0, # simulation time in ms "N_rec_spk": 100, # number of samples (neuron) for spike detector "N_rec_mm": 50, # number of samples (neuron, astrocyte) for multimeter + "n_threads": 4, # number of threads for NEST + "seed": 100, # seed for the "random" module (not NEST) } ############################################################################### @@ -252,13 +252,9 @@ def calc_synchrony(neuron_spikes, n_neuron_rec_spk, start, end, data_path, N=100 senders = senders[np.isin(senders, sampled)] # make spiking histograms of individual neurons bins = np.arange(start, end + 0.1, bw) # time bins - hists = [ - np.histogram(times[senders == x], bins)[0].tolist() for x in set(senders) - ] + hists = [np.histogram(times[senders == x], bins)[0].tolist() for x in set(senders)] # make spiking histogram of all sampled neurons - hist_global = ( - np.histogram(times, bins)[0] / len(set(senders)) - ).tolist() + hist_global = (np.histogram(times, bins)[0] / len(set(senders))).tolist() # calculate local and global synchrony print("Calculating neuronal local and global synchrony ...") coefs, n_pair_pass, n_pair_fail = get_corr(hists) # local (spike count correlation) @@ -342,12 +338,9 @@ def run_simulation(data_path): # NEST configuration nest.ResetKernel() nest.resolution = sim_params["dt"] + nest.local_num_threads = sim_params["n_threads"] nest.print_time = True nest.overwrite_files = True - try: - nest.local_num_threads = int(sys.argv[1]) - except Exception: - nest.local_num_threads = 4 # Simulation settings pre_sim_time = sim_params["pre_sim_time"] @@ -362,16 +355,25 @@ def run_simulation(data_path): mm_neuron = nest.Create("multimeter", params={"record_from": ["I_SIC"]}) mm_astro = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) - # Run pre-simulation and simulation + # Run pre-simulation print("Running pre-simulation ...") nest.Simulate(pre_sim_time) + + # Sample and connect nodes with recorders print("Connecting recorders ...") - n_neuron_rec_spk = min(len(exc + inh), sim_params["N_rec_spk"]) - n_neuron_rec_mm = min(len(exc + inh), sim_params["N_rec_mm"]) - n_astrocyte_rec = min(len(astro), sim_params["N_rec_mm"]) - nest.Connect((exc + inh)[:n_neuron_rec_spk], sr_neuron) - nest.Connect(mm_neuron, (exc + inh)[:n_neuron_rec_mm]) - nest.Connect(mm_astro, astro[:n_astrocyte_rec]) + neuron_list = (exc + inh).tolist() + astro_list = astro.tolist() + n_neuron_rec_spk = min(len(neuron_list), sim_params["N_rec_spk"]) + n_neuron_rec_mm = min(len(neuron_list), sim_params["N_rec_mm"]) + n_astro_rec = min(len(astro), sim_params["N_rec_mm"]) + neuron_list_for_sr = sorted(random.sample(neuron_list, n_neuron_rec_spk)) + neuron_list_for_mm = sorted(random.sample(neuron_list, n_neuron_rec_mm)) + astro_list_for_mm = sorted(random.sample(astro_list, n_astro_rec)) + nest.Connect(neuron_list_for_sr, sr_neuron) + nest.Connect(mm_neuron, neuron_list_for_mm) + nest.Connect(mm_astro, astro_list_for_mm) + + # Run simulation print("Running simulation ...") nest.Simulate(sim_time) @@ -410,7 +412,6 @@ def run_simulation(data_path): + f"(n for synchrony analysis={n_for_sync})\n" ) plot_synchrony(coefs, title, data_path) - # print results print(f"Local synchrony = {lsync_mu:.3f}+-{lsync_sd:.3f}") print(f"Global synchrony = {gsync:.3f}") @@ -423,5 +424,7 @@ def run_simulation(data_path): ############################################################################### # Run simulation. +random.seed(sim_params["seed"]) hash = hashlib.md5(os.urandom(16)).hexdigest() -run_simulation(os.path.join("astrocyte_brunel", hash)) +run_simulation("astrocyte_brunel") +# run_simulation(os.path.join("astrocyte_brunel", hash)) From dc6eede8ecc6bf25afdf57fec9f7a32243ed2b6d Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 16 Oct 2023 00:53:54 +0200 Subject: [PATCH 44/93] Update documentation of astrocyte_brunel.py --- pynest/examples/astrocyte_brunel.py | 101 +++++++++++++++------------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index 2cab23849d..b3b99b55b9 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -70,8 +70,7 @@ """ ############################################################################### -# Import all necessary modules for simulation, analysis and plotting. Scipy -# should be imported before nest. +# Import all necessary modules for simulation, analysis and plotting. import hashlib import json @@ -90,7 +89,7 @@ sim_params = { "dt": 0.1, # simulation resolution in ms - "pre_sim_time": 100.0, # pre-simulation time in ms + "pre_sim_time": 100.0, # pre-simulation time in ms (excluded from analysis) "sim_time": 100.0, # simulation time in ms "N_rec_spk": 100, # number of samples (neuron) for spike detector "N_rec_mm": 50, # number of samples (neuron, astrocyte) for multimeter @@ -106,10 +105,10 @@ "N_in": 2000, # number of inhibitory neurons "N_astro": 10000, # number of astrocytes "p": 0.1, # neuron-neuron connection probability. - "p_syn_astro": 0.5, # synapse-astrocyte pairing probability - "max_astro_per_target": 10, # max number of astrocytes per target neuron - "astro_pool_type": "random", # astrocyte pool type - "poisson_rate": 2000, # rate of Poisson input + "p_syn_astro": 0.5, # The probability of each neuron-neuron connection to be paired with an astrocyte + "max_astro_per_target": 10, # The maximal number of astrocytes (pool size) that can be connected with each target neuron + "astro_pool_type": "random", # "random": astrocyte pool will be chosen randomly for each target neuron + "poisson_rate": 2000, # Poisson input rate for neurons } syn_params = { @@ -149,19 +148,22 @@ } ############################################################################### -# Function for network building. - +# This function creates the nodes and build the network. The astrocytes only +# respond to excitatory synaptic inputs; therefore, only the excitatory +# neuron-neuron connections are paired with the astrocytes. The +# TripartiteConnect() function and the "tripartite_bernoulli_with_pool" rule are +# used to create the connectivity of the network. def create_astro_network(scale=1.0): """Create nodes for a neuron-astrocyte network.""" print("Creating nodes ...") + assert scale >= 1.0, "scale must be >= 1.0" nodes_ex = nest.Create(neuron_model, int(network_params["N_ex"] * scale), params=neuron_params_ex) nodes_in = nest.Create(neuron_model, int(network_params["N_in"] * scale), params=neuron_params_in) nodes_astro = nest.Create(astrocyte_model, int(network_params["N_astro"] * scale), params=astrocyte_params) nodes_noise = nest.Create("poisson_generator", params={"rate": network_params["poisson_rate"]}) return nodes_ex, nodes_in, nodes_astro, nodes_noise - def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1.0): """Connect the nodes in a neuron-astrocyte network. The astrocytes are paired with excitatory connections only. @@ -170,7 +172,10 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. assert scale >= 1.0, "scale must be >= 1.0" nest.Connect( nodes_noise, nodes_ex + nodes_in, syn_spec={"synapse_model": "static_synapse", "weight": syn_params["w_e"]}) + print("Connecting neurons and astrocytes ...") + # excitatory connections are paired with astrocytes + # conn_spec and syn_spec according to the "tripartite_bernoulli_with_pool" rule conn_params_e = { "rule": "tripartite_bernoulli_with_pool", "p_primary": network_params["p"] / scale, @@ -196,7 +201,8 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. "weight": syn_params["w_a2n"] }, } - + nest.TripartiteConnect(nodes_ex, nodes_ex + nodes_in, nodes_astro, conn_spec=conn_params_e, syn_specs=syn_params_e) + # inhibitory connections are not paired with astrocytes conn_params_i = {"rule": "pairwise_bernoulli", "p": network_params["p"] / scale} syn_params_i = { "synapse_model": syn_params["synapse_model"], @@ -204,13 +210,12 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. "tau_psc": tau_syn_in, "delay": syn_params["d_i"], } - nest.TripartiteConnect(nodes_ex, nodes_ex + nodes_in, nodes_astro, conn_spec=conn_params_e, syn_specs=syn_params_e) nest.Connect(nodes_in, nodes_ex + nodes_in, conn_params_i, syn_params_i) - ############################################################################### -# Function for calculating correlation. - +# This function calculates the pairwise spike count correlations. For each pair +# of neurons, the correlation coefficient (Pearson's r) of their spike count +# histograms is calculated. The result of all pairs are returned. def get_corr(hlist): """Calculate pairwise correlations for a list of histograms.""" @@ -232,17 +237,19 @@ def get_corr(hlist): return coef_list, n_pair_pass, n_pair_fail - ############################################################################### -# Functions for calculating and plotting synchrony of neuron firings. - +# These two functions calculate and plot the synchrony of neuronal firings. +# Histograms of spike counts of all neurons are obtained to evaluate local and +# global synchrony. The local synchrony is evaluated with average pairwise spike +# count correlation, and the global synchrony is evaluated with the variance of +# average spike count/average of variance of individual spike count. def calc_synchrony(neuron_spikes, n_neuron_rec_spk, start, end, data_path, N=100, bw=10): # get data senders = neuron_spikes["senders"][neuron_spikes["times"] > start] times = neuron_spikes["times"][neuron_spikes["times"] > start] rate = len(senders) / (end - start) * 1000.0 / n_neuron_rec_spk - print(f"Mean neuron firing rate = {rate:.2f} spikes/s (n = {n_neuron_rec_spk})") + print(f"Mean neuronal firing rate = {rate:.2f} spikes/s (n = {n_neuron_rec_spk})") # sample neurons set_senders = list(set(senders)) n_for_sync = min(len(set_senders), N) @@ -250,21 +257,20 @@ def calc_synchrony(neuron_spikes, n_neuron_rec_spk, start, end, data_path, N=100 sampled = random.sample(set_senders, n_for_sync) times = times[np.isin(senders, sampled)] senders = senders[np.isin(senders, sampled)] - # make spiking histograms of individual neurons + # make spike count histograms of individual neurons bins = np.arange(start, end + 0.1, bw) # time bins hists = [np.histogram(times[senders == x], bins)[0].tolist() for x in set(senders)] # make spiking histogram of all sampled neurons hist_global = (np.histogram(times, bins)[0] / len(set(senders))).tolist() # calculate local and global synchrony print("Calculating neuronal local and global synchrony ...") - coefs, n_pair_pass, n_pair_fail = get_corr(hists) # local (spike count correlation) + coefs, n_pair_pass, n_pair_fail = get_corr(hists) # local lsync_mu, lsync_sd = np.mean(coefs), np.std(coefs) - gsync = np.var(hist_global) / np.mean(np.var(hists, axis=1)) # global (variance of all/variance of individual) + gsync = np.var(hist_global) / np.mean(np.var(hists, axis=1)) # global return rate, coefs, lsync_mu, lsync_sd, gsync, n_for_sync def plot_synchrony(coefs, title, data_path): - # make plot - print("Plotting synchrony ...") + print("Plotting pairwise spike count correlations ...") plt.hist(coefs) plt.title(title) plt.xlabel("Pairwise spike count correlation (Pearson's r)") @@ -273,42 +279,39 @@ def plot_synchrony(coefs, title, data_path): plt.savefig(os.path.join(data_path, "neuron_synchrony.png")) plt.close() - ############################################################################### -# Function for plotting dynamics. - +# This function plots the dynamics in the astrocytes and their resultant output +# to the neurons.The IP3 and calcium in the astrocytes and the SIC in neurons +# are plotted. Means and standard deviations across sampled nodes are indicated +# by lines and shaded areas, respectively. def plot_dynamics(astro_data, neuron_data, data_path, start): # plot dynamics print("Plotting dynamics ...") # astrocyte data - a = astro_data - a_mask = a["times"] > start - a_ip3 = a["IP3"][a_mask] - a_cal = a["Ca"][a_mask] - a_t = a["times"][a_mask] + a_mask = astro_data["times"] > start + a_ip3 = astro_data["IP3"][a_mask] + a_cal = astro_data["Ca"][a_mask] + a_t = astro_data["times"][a_mask] t_astro = list(set(a_t)) m_ip3 = np.array([np.mean(a_ip3[a_t == t]) for t in t_astro]) s_ip3 = np.array([np.std(a_ip3[a_t == t]) for t in t_astro]) m_cal = np.array([np.mean(a_cal[a_t == t]) for t in t_astro]) s_cal = np.array([np.std(a_cal[a_t == t]) for t in t_astro]) # neuron data - b = neuron_data - b_mask = b["times"] > start - b_sic = b["I_SIC"][b["times"] > start] - b_t = b["times"][b["times"] > start] + b_mask = neuron_data["times"] > start + b_sic = neuron_data["I_SIC"][neuron_data["times"] > start] + b_t = neuron_data["times"][neuron_data["times"] > start] t_neuro = list(set(b_t)) m_sic = np.array([np.mean(b_sic[b_t == t]) for t in t_neuro]) s_sic = np.array([np.std(b_sic[b_t == t]) for t in t_neuro]) # plots - str_ip3 = r"IP$_{3}$" - str_cal = r"Ca$^{2+}$" + fig, axes = plt.subplots(2, 1, sharex=True) color_ip3 = "tab:blue" color_cal = "tab:green" color_sic = "tab:purple" - fig, axes = plt.subplots(2, 1, sharex=True) # astrocyte plot - axes[0].set_title(f"{str_ip3} and {str_cal} in astrocytes (n={len(set(a['senders']))})") + axes[0].set_title(f"{r'IP$_{3}$'} and {r'Ca$^{2+}$'} in astrocytes (n={len(set(astro_data['senders']))})") axes[0].set_ylabel(r"IP$_{3}$ ($\mu$M)") axes[0].tick_params(axis="y", labelcolor=color_ip3) axes[0].fill_between(t_astro, m_ip3 + s_ip3, m_ip3 - s_ip3, alpha=0.3, linewidth=0.0, color=color_ip3) @@ -319,7 +322,7 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): ax.fill_between(t_astro, m_cal + s_cal, m_cal - s_cal, alpha=0.3, linewidth=0.0, color=color_cal) ax.plot(t_astro, m_cal, linewidth=2, color=color_cal) # neuron plot - axes[1].set_title(f"SIC in neurons (n={len(set(a['senders']))})") + axes[1].set_title(f"SIC in neurons (n={len(set(neuron_data['senders']))})") axes[1].set_ylabel("SIC (pA)") axes[1].set_xlabel("Time (ms)") axes[1].fill_between(t_neuro, m_sic + s_sic, m_sic - s_sic, alpha=0.3, linewidth=0.0, color=color_sic) @@ -329,10 +332,11 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): plt.savefig(os.path.join(data_path, "dynamics.png")) plt.close() - ############################################################################### -# Main function for simulation. - +# This is the main function for simulation. Here, the network is created and the +# neurons and astrocytes are randomly chosen to be connected with the recording +# devices. The sample numbers are determined by sim_params. After simulation, +# neuron and astrocyte data are analyzed and plotted. def run_simulation(data_path): # NEST configuration @@ -342,6 +346,9 @@ def run_simulation(data_path): nest.print_time = True nest.overwrite_files = True + # Create data folder + os.system(f"mkdir -p {data_path}") + # Simulation settings pre_sim_time = sim_params["pre_sim_time"] sim_time = sim_params["sim_time"] @@ -359,7 +366,7 @@ def run_simulation(data_path): print("Running pre-simulation ...") nest.Simulate(pre_sim_time) - # Sample and connect nodes with recorders + # Select nodes randomly and connect them with recorders print("Connecting recorders ...") neuron_list = (exc + inh).tolist() astro_list = astro.tolist() @@ -382,8 +389,7 @@ def run_simulation(data_path): neuron_data = mm_neuron.events astro_data = mm_astro.events - # Data saving (for debug) - os.system(f"mkdir -p {data_path}") + # Save script and parameters (debug) os.system(f"cp astrocyte_brunel.py {data_path}") params = { "sim_params": sim_params, @@ -420,7 +426,6 @@ def run_simulation(data_path): print("Done!") - ############################################################################### # Run simulation. From bdb9e670540004a3bd0f5a885031b3d0bfd77a80 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 16 Oct 2023 11:50:17 +0200 Subject: [PATCH 45/93] Improve the documentation of astrocyte_brunel.py --- pynest/examples/astrocyte_brunel.py | 39 +++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index b3b99b55b9..785cc724ab 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -28,11 +28,6 @@ to [1]_, [2]_, and [3]_. The ``aeif_cond_alpha_astro`` model is an adaptive exponential integrate and fire neuron supporting neuron-astrocyte interactions. -The network is created using the ``tripartite_bernoulli_with_pool`` rule, with -the ``tsodyks_synapse`` as the connections from neurons to neruons and -astrocytes, and the ``sic_connection`` as the connections from astrocytes to -neurons. - This network is an example of an astrocytic effect on neuronal excitabitlity. With the slow inward current (SIC) delivered from the astrocytes through the ``sic_connection``, the neurons show higher firing rates and more synchronized @@ -41,6 +36,36 @@ respectively, as shown in a plot made in this script (neuron_synchrony.png). Plots of astrocytic dynamics and SIC in neurons are also made in this script. +The network is created with the ``tripartite_bernoulli_with_pool`` rule. This +rule creates a tripartite connectivity with the following steps: + +1. Connections are made between each pair of neurons with a certain probability +(p). + +2. For each neuron-neuron connection created, another probability (p_syn_astro) +determines if it is paired with one astrocyte. The selection of this astrocyte +is confined by two parameters, astro_pool_size and astro_pool_type (see below). + +3. If paired, connections are made from the presynaptic (source) neurons to the +astrocyte, with the ``tsodyks_synapse``, and from the astrocyte to the +postsynaptic (target) neuron, with the ``sic_connection``. + +The parameter definitions are as follows: + +p: connection probability between pairs of neurons. + +p_syn_astro: probability of each created neuron-neuron connection to be paired +with one astrocyte. + +astro_pool_size: The size of the astrocyte pool for each target neuron. The +astrocytes to be connected with this target neuron can only be selected from its +pool (repetition is allowed). + +astro_pool_type: Defines the way to determine the astrocyte pool for each target +neuron. If "random", a number of astrocytes (=astro_pool_size) are randomly +chosen and assigned as the pool. If "block", the same number of astrocytes are +assigned as the pool but according to their relative indices. + References ~~~~~~~~~~ @@ -106,7 +131,7 @@ "N_astro": 10000, # number of astrocytes "p": 0.1, # neuron-neuron connection probability. "p_syn_astro": 0.5, # The probability of each neuron-neuron connection to be paired with an astrocyte - "max_astro_per_target": 10, # The maximal number of astrocytes (pool size) that can be connected with each target neuron + "astro_pool_size": 10, # The maximal number of astrocytes (pool size) that can be connected with each target neuron "astro_pool_type": "random", # "random": astrocyte pool will be chosen randomly for each target neuron "poisson_rate": 2000, # Poisson input rate for neurons } @@ -180,7 +205,7 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. "rule": "tripartite_bernoulli_with_pool", "p_primary": network_params["p"] / scale, "p_cond_third": network_params["p_syn_astro"], - "pool_size": network_params["max_astro_per_target"], + "pool_size": network_params["astro_pool_size"], "pool_type": network_params["astro_pool_type"], } syn_params_e = { From 90df13a67ff905e3755284e34d852eda3b1cbf0e Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 16 Oct 2023 13:04:27 +0200 Subject: [PATCH 46/93] Change connection_management.rst back --- .../synapses/connection_management.rst | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 317c002775..88263bb3e4 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -238,25 +238,6 @@ created with probability ``p``. conn_spec_dict = {'rule': 'pairwise_bernoulli', 'p': p} nest.Connect(A, B, conn_spec_dict) -pairwise bernoulli astro -~~~~~~~~~~~~~~~~~~ - -A pairwise Bernoulli rule for neuron-astrocyte networks. For each possible pair -of nodes from ``A`` and ``B``, a connection is created with probability ``p``. -For each connection created between neurons, an astrocyte is paired with it with -probability ``p_syn_astro``. A connection from the presynaptic neuron and a -connection to the postsynaptic neuron are made for the paired astrocyte. - -.. code-block:: python - - n, m, x, p, p_syn_astro = 10, 12, 5, 0.2, 0.5 - A = nest.Create('aeif_cond_alpha_astro', n) - B = nest.Create('aeif_cond_alpha_astro', m) - C = nest.Create('astrocyte_lr_1994', x) - conn_spec_dict = {'rule': 'pairwise_bernoulli_astro', 'astrocyte': C, - 'p': p, 'p_syn_astro': p_syn_astro} - nest.Connect(A, B, conn_spec_dict) - symmetric pairwise bernoulli ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From e2512a8de8fb435a8f6d42bc5ce3989c4cfb9388 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 16 Oct 2023 17:38:42 +0200 Subject: [PATCH 47/93] Improve astrocyte_brunel.py --- pynest/examples/astrocyte_brunel.py | 76 +++++++++++++++++------------ 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index 785cc724ab..e71f3e33e3 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -24,47 +24,61 @@ ------------------------------------------------------------ This script simulates a random balanced network with excitatory and inhibitory -neurons and astrocytes. The ``astrocyte_lr_1994`` model is implemented according -to [1]_, [2]_, and [3]_. The ``aeif_cond_alpha_astro`` model is an adaptive -exponential integrate and fire neuron supporting neuron-astrocyte interactions. +neurons and astrocytes. The astrocyte is modeled with ``astrocyte_lr_1994``, +implemented according to [1]_, [2]_, and [3]_. The neuron is modeled with +``aeif_cond_alpha_astro``, an adaptive exponential integrate and fire neuron +supporting neuron-astrocyte interactions. -This network is an example of an astrocytic effect on neuronal excitabitlity. -With the slow inward current (SIC) delivered from the astrocytes through the -``sic_connection``, the neurons show higher firing rates and more synchronized -bursting actitivity. The degrees of local and global synchrony are quantified -by pairwise spike count correlations and a measure of global synchrony in [4]_ -respectively, as shown in a plot made in this script (neuron_synchrony.png). -Plots of astrocytic dynamics and SIC in neurons are also made in this script. +The simulation results of this network demonstrate an astrocytic effect on +neuronal excitabitlity. With the slow inward current (SIC) delivered from the +astrocytes through the ``sic_connection``, the neurons show higher firing rates +and more synchronized bursting actitivity. The degrees of local and global +synchrony are quantified by pairwise spike count correlations and a measure of +global synchrony in [4]_ respectively, as shown in a plot made in this script +(neuron_synchrony.png). Plots of astrocytic dynamics and SIC in neurons are also +made in this script. -The network is created with the ``tripartite_bernoulli_with_pool`` rule. This -rule creates a tripartite connectivity with the following steps: +The network is created with the TripartiteConnect() function and the +``tripartite_bernoulli_with_pool`` rule. This rule creates a tripartite +Bernoulli connectivity with the following steps: -1. Connections are made between each pair of neurons with a certain probability -(p). +1. ``tsodyks_synapse`` connections between pair of neurons are created with a +probability defined by the parameter ``p_primary``. -2. For each neuron-neuron connection created, another probability (p_syn_astro) -determines if it is paired with one astrocyte. The selection of this astrocyte -is confined by two parameters, astro_pool_size and astro_pool_type (see below). +2. For each neuron-neuron connection created, a probability defined by another +parameter ``p_cond_third`` determines if it is paired with one astrocyte. The +selection of this astrocyte is confined by two parameters, ``pool_size`` and +``pool_type``. -3. If paired, connections are made from the presynaptic (source) neurons to the -astrocyte, with the ``tsodyks_synapse``, and from the astrocyte to the -postsynaptic (target) neuron, with the ``sic_connection``. +3. If paired, a ``tsodyks_synapse`` connection from the presynaptic (source) +neuron to the astrocyte is created, and a ``sic_connection`` from the astrocyte +to the postsynaptic (target) neuron is created. -The parameter definitions are as follows: +The connectivity parameters are as follows: -p: connection probability between pairs of neurons. +* ``conn_spec`` parameters -p_syn_astro: probability of each created neuron-neuron connection to be paired -with one astrocyte. + * ``p_primary``: Connection probability between neurons. -astro_pool_size: The size of the astrocyte pool for each target neuron. The -astrocytes to be connected with this target neuron can only be selected from its -pool (repetition is allowed). + * ``p_cond_third``: Probability of each created neuron-neuron connection to be + paired with one astrocyte. -astro_pool_type: Defines the way to determine the astrocyte pool for each target -neuron. If "random", a number of astrocytes (=astro_pool_size) are randomly -chosen and assigned as the pool. If "block", the same number of astrocytes are -assigned as the pool but according to their relative indices. + * ``pool_size``: The size of astrocyte pool for each target neuron. The + astrocytes to be connected with a target neuron can only be selected from + its pool. If not specified, the pool includes all astrocytes. + + * ``pool_type``: The way to determine the astrocyte pool for each target + neuron. If "random", a number of astrocytes (=pool_size) are randomly chosen + and assigned as the pool. If "block", the same number of astrocytes are + assigned as the pool, but they are chosen based on their relative indices. + +* ``syn_specs`` parameters + + * ``primary``: specifications of the connections between neurons. + + * ``third_in``: specifications of the connections from neurons to astrocytes. + + * ``third_out``: specifications of the connections from astrocytes to neurons. References ~~~~~~~~~~ From 7f4974575641c4129f44f102edc337a88e5ea53e Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 16 Oct 2023 23:10:51 +0200 Subject: [PATCH 48/93] Improve text in astrocyte_brunel.py and astrocyte_small_network.py --- pynest/examples/astrocyte_brunel.py | 52 +------ pynest/examples/astrocyte_small_network.py | 165 +++++++++++++-------- 2 files changed, 108 insertions(+), 109 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index e71f3e33e3..28fbe99036 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -38,48 +38,6 @@ (neuron_synchrony.png). Plots of astrocytic dynamics and SIC in neurons are also made in this script. -The network is created with the TripartiteConnect() function and the -``tripartite_bernoulli_with_pool`` rule. This rule creates a tripartite -Bernoulli connectivity with the following steps: - -1. ``tsodyks_synapse`` connections between pair of neurons are created with a -probability defined by the parameter ``p_primary``. - -2. For each neuron-neuron connection created, a probability defined by another -parameter ``p_cond_third`` determines if it is paired with one astrocyte. The -selection of this astrocyte is confined by two parameters, ``pool_size`` and -``pool_type``. - -3. If paired, a ``tsodyks_synapse`` connection from the presynaptic (source) -neuron to the astrocyte is created, and a ``sic_connection`` from the astrocyte -to the postsynaptic (target) neuron is created. - -The connectivity parameters are as follows: - -* ``conn_spec`` parameters - - * ``p_primary``: Connection probability between neurons. - - * ``p_cond_third``: Probability of each created neuron-neuron connection to be - paired with one astrocyte. - - * ``pool_size``: The size of astrocyte pool for each target neuron. The - astrocytes to be connected with a target neuron can only be selected from - its pool. If not specified, the pool includes all astrocytes. - - * ``pool_type``: The way to determine the astrocyte pool for each target - neuron. If "random", a number of astrocytes (=pool_size) are randomly chosen - and assigned as the pool. If "block", the same number of astrocytes are - assigned as the pool, but they are chosen based on their relative indices. - -* ``syn_specs`` parameters - - * ``primary``: specifications of the connections between neurons. - - * ``third_in``: specifications of the connections from neurons to astrocytes. - - * ``third_out``: specifications of the connections from astrocytes to neurons. - References ~~~~~~~~~~ @@ -143,10 +101,10 @@ "N_ex": 8000, # number of excitatory neurons "N_in": 2000, # number of inhibitory neurons "N_astro": 10000, # number of astrocytes - "p": 0.1, # neuron-neuron connection probability. - "p_syn_astro": 0.5, # The probability of each neuron-neuron connection to be paired with an astrocyte - "astro_pool_size": 10, # The maximal number of astrocytes (pool size) that can be connected with each target neuron - "astro_pool_type": "random", # "random": astrocyte pool will be chosen randomly for each target neuron + "p": 0.1, # connection probability between neurons + "p_syn_astro": 0.5, # probability of each created neuron-neuron connection to be paired with one astrocyte + "astro_pool_size": 10, # astrocyte pool size for each target neuron + "astro_pool_type": "random", # astrocyte pool will be chosen randomly for each target neuron "poisson_rate": 2000, # Poisson input rate for neurons } @@ -463,8 +421,6 @@ def run_simulation(data_path): # Plot dynamics in astrocytes and neurons plot_dynamics(astro_data, neuron_data, data_path, pre_sim_time) - print("Done!") - ############################################################################### # Run simulation. diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 990d7fbba3..c2385dc7e6 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -23,13 +23,72 @@ A small neuron-astrocyte network ------------------------------------------------------------ -This script demonstrates how to use the NEST connection builder and the -"pairwise_bernoulli_astro" rule to create a small neuron-astrocyte network with -20 neurons and 10 astrocytes. This connection rule creates the tripartite -connectivity between neurons and astrocytes. The ``astrocyte_lr_1994`` model is -implemented according to [1]_, [2]_, and [3]_. The ``aeif_cond_alpha_astro`` -model is an adaptive exponential integrate and fire neuron supporting -neuron-astrocyte interactions. +This script demonstrates an aproach with NEST to create a neuron-astrocyte +network. The network in this script includes 20 neurons and 5 astrocytes. The +astrocyte is modeled with ``astrocyte_lr_1994``, implemented according to [1]_, +[2]_, and [3]_. The neuron is modeled with ``aeif_cond_alpha_astro``, an +adaptive exponential integrate and fire neuron supporting neuron-astrocyte +interactions. + +The network is created with the TripartiteConnect() function and the +``tripartite_bernoulli_with_pool`` rule. This rule creates a tripartite +Bernoulli connectivity with the following principles: + +1. For each pair of neurons, a Bernoulli trial with a probability ``p_primary`` +determines if they will be connected. + +2. For each neuron-neuron connection created, a Bernoulli trial with a probability +``p_cond_third`` determines if it will be paired with one astrocyte. The +selection of this particular astrocyte is confined by ``pool_size`` and +``pool_type`` (see below). + +3. If a neuron-neuron connection is to be paired with an astrocyte, a connection +from the presynaptic (source) neuron to the astrocyte is created, and a +connection (``sic_connection``) from the astrocyte to the postsynaptic (target) +neuron is created. + +The available connectivity parameters are as follows: + +* ``conn_spec`` parameters + + * ``p_primary``: Connection probability between neurons. + + * ``p_cond_third``: Probability of each created neuron-neuron connection to be + paired with one astrocyte. + + * ``pool_size``: The size of astrocyte pool for each target neuron. The + target neuron can only be connected to astrocytes selected from its pool. + + * ``pool_type``: The way to determine the astrocyte pool for each target + neuron. If "random", a number of astrocytes (``pool_size``) are randomly + chosen from all astrocytes and assigned as the pool. If "block", the + astrocytes pool is evenly distributed to the neurons in blocks without + overlapping, and the ``pool_size`` has to be compatible with this arrangement. + +* ``syn_specs`` parameters + + * ``primary``: specifications for the connections between neurons. + + * ``third_in``: specifications for the connections from neurons to astrocytes. + + * ``third_out``: specifications for the connections from astrocytes to neurons. + +In this script, the network is created with the ``pool_type`` being "block". It +can be seen from the plot "connections.png" that this approach distributes the +astrocytes evenly to the postsynaptic neurons in blocks without overlapping. The +``pool_size`` should be compatible with this arrangement. In the case here, a +``pool_size`` of one is required. + +With the created network, neuron-astrocyte interactions can be observed. The +presynaptic spikes induce the generation of IP3, which then changes the calcium +concentration in the astrocytes. This change in calcium then induces the slow +inward current (SIC) in the neurons through the ``sic_connection``. These +dynamics are shown in the plot "dynamics.png". The changes in membrane potential +in the presynaptic and postsynaptic neurons are shown in the plot "V_m.png". + +The ``pool_type`` can be changed to "random" to see the results with random +astrocyte pools. In that case, the ``pool_size`` can be any from one to the +number of all astrocytes. References ~~~~~~~~~~ @@ -65,24 +124,27 @@ import matplotlib.pyplot as plt import nest import numpy as np -import pandas as pd -from mpi4py import MPI plt.rcParams.update({"font.size": 13}) -pd.set_option("display.max_rows", None) ############################################################################### -# Initialize kernel. +# Initialize NEST kernel and create folder to save data. nest.ResetKernel() -comm = MPI.COMM_WORLD -rank = comm.Get_rank() +spath = "astrocyte_small_network" +# spath = os.path.join("astrocyte_small_network", hl.md5(os.urandom(16)).hexdigest()) +os.system(f"mkdir -p {spath}") +os.system(f"cp astrocyte_small_network.py {spath}") # (debug) ############################################################################### -# Create save path. +# Network parameters. -spath = os.path.join("astrocyte_small_network", hl.md5(os.urandom(16)).hexdigest()) -os.system(f"mkdir -p {spath}") +n_neurons = 10 # number of source and target neurons +n_astrocytes = 5 # number of astrocytes +p_primary = 1.0 # connection probability between neurons +p_cond_third = 1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte +pool_size = 1 # astrocyte pool size for each target neuron +pool_type = "block" # the way to determine the astrocyte pool for each target neuron (change to "random" to see different results) ############################################################################### # Astrocyte parameters. @@ -90,8 +152,8 @@ astrocyte_model = "astrocyte_lr_1994" astrocyte_params = { "IP3": 0.4, # IP3 initial value in µM - "delta_IP3": 0.5, # Step increase in IP3 concentration per unit synaptic weight received by the astrocyte in µM - "tau_IP3": 10.0, # Time constant of astrocytic IP3 degradation in ms + "delta_IP3": 2.0, # parameter determining the increase in astrocytic IP3 concentration induced by synaptic input + "tau_IP3": 10.0, # time constant of the exponential decay of astrocytic IP3 } ############################################################################### @@ -104,30 +166,29 @@ } ############################################################################### -# Create and connect populations and devices. +# Create and connect populations and devices. The neurons and astrocytes are +# connected with multimeters to record their dynamics. -pre_neurons = nest.Create(neuron_model, 10, params=neuron_params) -post_neurons = nest.Create(neuron_model, 10, params=neuron_params) -astrocytes = nest.Create(astrocyte_model, 10, params=astrocyte_params) -nest.Connect( +pre_neurons = nest.Create(neuron_model, n_neurons, params=neuron_params) +post_neurons = nest.Create(neuron_model, n_neurons, params=neuron_params) +astrocytes = nest.Create(astrocyte_model, n_astrocytes, params=astrocyte_params) +nest.TripartiteConnect( pre_neurons, post_neurons, astrocytes, conn_spec={ "rule": "tripartite_bernoulli_with_pool", - "p_primary": 1.0, - "p_cond_third": 1.0, - "pool_size": 3, - "random_pool": False, + "p_primary": p_primary, + "p_cond_third": p_cond_third, + "pool_size": pool_size, + "pool_type": pool_type, }, - syn_spec={ - "primary": {"model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, - "third_in": {"model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, - "third_out": {"model": "sic_connection", "weight": 1.0, "delay": 1.0}, + syn_specs={ + "primary": {"synapse_model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, + "third_in": {"synapse_model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, + "third_out": {"synapse_model": "sic_connection", "weight": 1.0, "delay": 1.0}, }, ) - - mm_pre_neurons = nest.Create("multimeter", params={"record_from": ["V_m"]}) mm_post_neurons = nest.Create("multimeter", params={"record_from": ["V_m", "I_SIC"]}) mm_astrocytes = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) @@ -136,28 +197,16 @@ nest.Connect(mm_astrocytes, astrocytes) ############################################################################### -# Print and save population and connection data. - -pre_loc = np.array(nest.GetLocalNodeCollection(pre_neurons)) -print(f"pre_neurons on rank {rank}:\n{pre_loc}") -post_loc = np.array(nest.GetLocalNodeCollection(post_neurons)) -print(f"post_neurons on rank {rank}:\n{post_loc}") -astrocytes_loc = np.array(nest.GetLocalNodeCollection(astrocytes)) -print(f"astrocytes on rank {rank}:\n{astrocytes_loc}") +# Get connection data. The data are used to plot the network connectivity. conns_a2n = nest.GetConnections(astrocytes, post_neurons) conns_n2n = nest.GetConnections(pre_neurons, post_neurons) conns_n2a = nest.GetConnections(pre_neurons, astrocytes) -print_list = ["source", "target", "weight", "delay", "synapse_model"] -pd.DataFrame(conns_n2n.get())[print_list].to_csv(os.path.join(spath, f"connections_n2n_rank={rank}.csv"), index=False) -pd.DataFrame(conns_n2a.get())[print_list].to_csv(os.path.join(spath, f"connections_n2a_rank={rank}.csv"), index=False) -pd.DataFrame(conns_a2n.get())[print_list].to_csv(os.path.join(spath, f"connections_a2n_rank={rank}.csv"), index=False) ############################################################################### # Functions for plotting. - -def plot_connections(conn_n2n, conn_n2a, conn_a2n, rank=0): # Doesn't work with MPI yet +def plot_connections(conn_n2n, conn_n2a, conn_a2n): print("Plotting connections ...") # Get data dict_n2n = conns_n2n.get() @@ -204,11 +253,9 @@ def set_frame_invisible(ax): axs.legend(bbox_to_anchor=(0.5, 1.1), loc="upper center", ncol=3) set_frame_invisible(axs) plt.tight_layout() - plt.savefig(os.path.join(spath, f"connections_rank={rank}.png")) + plt.savefig(os.path.join(spath, f"connections.png")) - -def plot_vm(pre_data, post_data, data_path, start, rank=0): - # plot dynamics +def plot_vm(pre_data, post_data, data_path, start): print("Plotting V_m ...") # presynaptic data a = pre_data @@ -243,12 +290,10 @@ def plot_vm(pre_data, post_data, data_path, start, rank=0): axes[1].plot(t_b, m_post_vm, linewidth=2, color=color_post) # save plt.tight_layout() - plt.savefig(os.path.join(data_path, f"Vm_rank={rank}.png")) + plt.savefig(os.path.join(data_path, f"V_m.png")) plt.close() - -def plot_dynamics(astro_data, neuron_data, data_path, start, rank=0): - # plot dynamics +def plot_dynamics(astro_data, neuron_data, data_path, start): print("Plotting dynamics ...") # astrocyte data a = astro_data @@ -295,15 +340,13 @@ def plot_dynamics(astro_data, neuron_data, data_path, start, rank=0): axes[1].plot(t_neuro, m_sic, linewidth=2, color=color_sic) # save plt.tight_layout() - plt.savefig(os.path.join(data_path, f"dynamics_rank={rank}.png")) + plt.savefig(os.path.join(data_path, f"dynamics.png")) plt.close() - ############################################################################### # Run simulation and save results. nest.Simulate(1000.0) -os.system(f"cp astrocyte_small_network.py {spath}") -plot_connections(conns_n2n, conns_n2a, conns_a2n, rank) -plot_vm(mm_pre_neurons.events, mm_post_neurons.events, spath, 0.0, rank) -plot_dynamics(mm_astrocytes.events, mm_post_neurons.events, spath, 0.0, rank) +plot_connections(conns_n2n, conns_n2a, conns_a2n) +plot_vm(mm_pre_neurons.events, mm_post_neurons.events, spath, 0.0) +plot_dynamics(mm_astrocytes.events, mm_post_neurons.events, spath, 0.0) From 0146cc479adf90aaf50e6747cfc750c462681c8b Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Tue, 17 Oct 2023 10:02:47 +0200 Subject: [PATCH 49/93] Improve plot_connections() in astrocyte_small_network.py --- pynest/examples/astrocyte_small_network.py | 67 ++++++++++------------ 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index c2385dc7e6..7e9b343ac7 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -60,10 +60,11 @@ target neuron can only be connected to astrocytes selected from its pool. * ``pool_type``: The way to determine the astrocyte pool for each target - neuron. If "random", a number of astrocytes (``pool_size``) are randomly + neuron. If "random", a number (=``pool_size``) of astrocytes are randomly chosen from all astrocytes and assigned as the pool. If "block", the astrocytes pool is evenly distributed to the neurons in blocks without - overlapping, and the ``pool_size`` has to be compatible with this arrangement. + overlapping, and the defined ``pool_size`` has to be compatible with this + arrangement. * ``syn_specs`` parameters @@ -73,11 +74,12 @@ * ``third_out``: specifications for the connections from astrocytes to neurons. -In this script, the network is created with the ``pool_type`` being "block". It -can be seen from the plot "connections.png" that this approach distributes the -astrocytes evenly to the postsynaptic neurons in blocks without overlapping. The -``pool_size`` should be compatible with this arrangement. In the case here, a -``pool_size`` of one is required. +In this script, the network is created with the ``pool_type`` being "block". +``p_primary`` and ``p_cond_third`` are both chosen to be 1, so that all possible +connections are made. It can be seen from the result plot "connections.png" that +"block" distributes the astrocytes evenly to the postsynaptic neurons in blocks +without overlapping. The ``pool_size`` should be compatible with this +arrangement. In the case here, a ``pool_size`` of one is required. With the created network, neuron-astrocyte interactions can be observed. The presynaptic spikes induce the generation of IP3, which then changes the calcium @@ -207,31 +209,13 @@ # Functions for plotting. def plot_connections(conn_n2n, conn_n2a, conn_a2n): - print("Plotting connections ...") - # Get data - dict_n2n = conns_n2n.get() - dict_n2a = conns_n2a.get() - dict_a2n = conns_a2n.get() - # Set of cells - sset_n2n = np.unique(dict_n2n["source"]) - tset_n2n = np.unique(dict_n2n["target"]) - sset_n2a = np.unique(dict_n2a["source"]) - aset_n2a = np.unique(dict_n2a["target"]) - aset_a2n = np.unique(dict_a2n["source"]) - tset_a2n = np.unique(dict_a2n["target"]) - # List of cells in connections - slist_n2n = dict_n2n["source"] - sset_n2n.mean() - tlist_n2n = dict_n2n["target"] - tset_n2n.mean() - slist_n2a = dict_n2a["source"] - sset_n2a.mean() - alist_n2a = dict_n2a["target"] - aset_n2a.mean() - alist_a2n = dict_a2n["source"] - aset_a2n.mean() - tlist_a2n = dict_a2n["target"] - tset_a2n.mean() - # Shift sets - sset_n2a = sset_n2a - sset_n2a.mean() - aset_n2a = aset_n2a - aset_n2a.mean() - aset_a2n = aset_a2n - aset_a2n.mean() - tset_a2n = tset_a2n - tset_a2n.mean() + # helper function to create lists of positions for source and target nodes, for plotting + def get_set_and_list(dict_in): + source_list = dict_in["source"] - np.unique(dict_in["source"]).mean() + target_list = dict_in["target"] - np.unique(dict_in["target"]).mean() + return source_list, target_list + # helper function to set plot frames invisible def set_frame_invisible(ax): ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) @@ -240,20 +224,29 @@ def set_frame_invisible(ax): ax.spines["left"].set_visible(False) ax.spines["right"].set_visible(False) + print("Plotting connections ...") + # prepare data (lists of node positions) + slist_n2n, tlist_n2n = get_set_and_list(conns_n2n.get()) + slist_n2a, alist_n2a = get_set_and_list(conns_n2a.get()) + alist_a2n, tlist_a2n = get_set_and_list(conns_a2n.get()) + # make plot fig, axs = plt.subplots(1, 1, figsize=(10, 8)) - axs.scatter(sset_n2a, [2] * len(sset_n2a), s=400, color="gray", marker="^", label="pre_neurons", zorder=3) - axs.scatter(aset_a2n, [1] * len(aset_a2n), s=400, color="g", marker="o", label="astrocyte", zorder=3) - axs.scatter(tset_a2n, [0] * len(tset_a2n), s=400, color="k", marker="^", label="post_neurons", zorder=3) + # plot nodes (need the sets of node positions) + axs.scatter(list(set(slist_n2a)), [2] * len(set(slist_n2a)), s=400, color="gray", marker="^", label="pre_neurons", zorder=3) + axs.scatter(list(set(alist_a2n)), [1] * len(set(alist_a2n)), s=400, color="g", marker="o", label="astrocytes", zorder=3) + axs.scatter(list(set(tlist_a2n)), [0] * len(set(tlist_a2n)), s=400, color="k", marker="^", label="post_neurons", zorder=3) + # plot connections for sx, tx in zip(slist_n2n, tlist_n2n): axs.plot([sx, tx], [2, 0], linestyle=":", color="b", alpha=0.5, linewidth=1) for sx, tx in zip(slist_n2a, alist_n2a): axs.plot([sx, tx], [2, 1], linestyle="-", color="orange", alpha=0.5, linewidth=2) for sx, tx in zip(alist_a2n, tlist_a2n): axs.plot([sx, tx], [1, 0], linestyle="-", color="g", alpha=0.8, linewidth=4) + # tweak and save axs.legend(bbox_to_anchor=(0.5, 1.1), loc="upper center", ncol=3) set_frame_invisible(axs) plt.tight_layout() - plt.savefig(os.path.join(spath, f"connections.png")) + plt.savefig(os.path.join(spath, "connections.png")) def plot_vm(pre_data, post_data, data_path, start): print("Plotting V_m ...") @@ -290,7 +283,7 @@ def plot_vm(pre_data, post_data, data_path, start): axes[1].plot(t_b, m_post_vm, linewidth=2, color=color_post) # save plt.tight_layout() - plt.savefig(os.path.join(data_path, f"V_m.png")) + plt.savefig(os.path.join(data_path, "V_m.png")) plt.close() def plot_dynamics(astro_data, neuron_data, data_path, start): @@ -340,7 +333,7 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): axes[1].plot(t_neuro, m_sic, linewidth=2, color=color_sic) # save plt.tight_layout() - plt.savefig(os.path.join(data_path, f"dynamics.png")) + plt.savefig(os.path.join(data_path, "dynamics.png")) plt.close() ############################################################################### From cb5ed3f542bd8a3994c96fd7133a8f517f595af9 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Tue, 17 Oct 2023 14:16:40 +0200 Subject: [PATCH 50/93] Reorganize and improve the plot code in astrocyte_small_network.py --- pynest/examples/astrocyte_small_network.py | 120 ++++++++++----------- 1 file changed, 56 insertions(+), 64 deletions(-) diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 7e9b343ac7..09cedcf635 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -210,7 +210,7 @@ def plot_connections(conn_n2n, conn_n2a, conn_a2n): # helper function to create lists of positions for source and target nodes, for plotting - def get_set_and_list(dict_in): + def get_node_positions(dict_in): source_list = dict_in["source"] - np.unique(dict_in["source"]).mean() target_list = dict_in["target"] - np.unique(dict_in["target"]).mean() return source_list, target_list @@ -226,9 +226,9 @@ def set_frame_invisible(ax): print("Plotting connections ...") # prepare data (lists of node positions) - slist_n2n, tlist_n2n = get_set_and_list(conns_n2n.get()) - slist_n2a, alist_n2a = get_set_and_list(conns_n2a.get()) - alist_a2n, tlist_a2n = get_set_and_list(conns_a2n.get()) + slist_n2n, tlist_n2n = get_node_positions(conns_n2n.get()) + slist_n2a, alist_n2a = get_node_positions(conns_n2a.get()) + alist_a2n, tlist_a2n = get_node_positions(conns_a2n.get()) # make plot fig, axs = plt.subplots(1, 1, figsize=(10, 8)) # plot nodes (need the sets of node positions) @@ -248,39 +248,43 @@ def set_frame_invisible(ax): plt.tight_layout() plt.savefig(os.path.join(spath, "connections.png")) +def mask_data_by_start(data_in, start): + data_out = {} + times = data_in["times"] + for key, value in data_in.items(): + data = data_in[key] + data_out[key] = data[times>start] + return data_out + +def get_plot_data(data_in, variable): + times_all = data_in["times"] + ts = list(set(data_in["times"])) + means = np.array([np.mean(data_in[variable][times_all == t]) for t in ts]) + sds = np.array([np.std(data_in[variable][times_all == t]) for t in ts]) + return ts, means, sds + def plot_vm(pre_data, post_data, data_path, start): print("Plotting V_m ...") - # presynaptic data - a = pre_data - a_mask = a["times"] > start - a_vm = a["V_m"][a_mask] - a_t = a["times"][a_mask] - t_a = list(set(a_t)) - m_pre_vm = np.array([np.mean(a_vm[a_t == t]) for t in t_a]) - s_pre_vm = np.array([np.std(a_vm[a_t == t]) for t in t_a]) - # postsynaptic data - b = post_data - b_mask = b["times"] > start - b_vm = b["V_m"][b_mask] - b_t = b["times"][b_mask] - t_b = list(set(b_t)) - m_post_vm = np.array([np.mean(b_vm[b_t == t]) for t in t_b]) - s_post_vm = np.array([np.std(b_vm[b_t == t]) for t in t_b]) - # plots - color_pre = "tab:blue" - color_post = "tab:blue" + # get presynaptic data + pre_data_masked = mask_data_by_start(pre_data, start) + pre_times, pre_vm_mean, pre_vm_sd = get_plot_data(pre_data_masked, "V_m") + # get postsynaptic data + post_data_masked = mask_data_by_start(post_data, start) + post_times, post_vm_mean, post_vm_sd = get_plot_data(post_data_masked, "V_m") + # set plots fig, axes = plt.subplots(2, 1, sharex=True) - # presynaptic - axes[0].set_title(f"presynaptic neurons (n={len(set(a['senders']))})") + color_pre = color_post = "tab:blue" + # plot presynaptic membrane potential + axes[0].set_title(f"presynaptic neurons (n={len(set(pre_data['senders']))})") axes[0].set_ylabel(r"$V_{m}$ (mV)") - axes[0].fill_between(t_a, m_pre_vm + s_pre_vm, m_pre_vm - s_pre_vm, alpha=0.3, linewidth=0.0, color=color_pre) - axes[0].plot(t_a, m_pre_vm, linewidth=2, color=color_pre) - # postsynaptic - axes[1].set_title(f"postsynaptic neurons (n={len(set(b['senders']))})") + axes[0].fill_between(pre_times, pre_vm_mean + pre_vm_sd, pre_vm_mean - pre_vm_sd, alpha=0.3, linewidth=0.0, color=color_pre) + axes[0].plot(pre_times, pre_vm_mean, linewidth=2, color=color_pre) + # plot postsynaptic membrane potential + axes[1].set_title(f"postsynaptic neurons (n={len(set(post_data['senders']))})") axes[1].set_ylabel(r"$V_{m}$ (mV)") axes[1].set_xlabel("Time (ms)") - axes[1].fill_between(t_b, m_post_vm + s_post_vm, m_post_vm - s_post_vm, alpha=0.3, linewidth=0.0, color=color_post) - axes[1].plot(t_b, m_post_vm, linewidth=2, color=color_post) + axes[1].fill_between(post_times, post_vm_mean + post_vm_sd, post_vm_mean - post_vm_sd, alpha=0.3, linewidth=0.0, color=color_post) + axes[1].plot(post_times, post_vm_mean, linewidth=2, color=color_post) # save plt.tight_layout() plt.savefig(os.path.join(data_path, "V_m.png")) @@ -288,49 +292,37 @@ def plot_vm(pre_data, post_data, data_path, start): def plot_dynamics(astro_data, neuron_data, data_path, start): print("Plotting dynamics ...") - # astrocyte data - a = astro_data - a_mask = a["times"] > start - a_ip3 = a["IP3"][a_mask] - a_cal = a["Ca"][a_mask] - a_t = a["times"][a_mask] - t_astro = list(set(a_t)) - m_ip3 = np.array([np.mean(a_ip3[a_t == t]) for t in t_astro]) - s_ip3 = np.array([np.std(a_ip3[a_t == t]) for t in t_astro]) - m_cal = np.array([np.mean(a_cal[a_t == t]) for t in t_astro]) - s_cal = np.array([np.std(a_cal[a_t == t]) for t in t_astro]) - # neuron data - b = neuron_data - b_mask = b["times"] > start - b_sic = b["I_SIC"][b_mask] - b_t = b["times"][b_mask] - t_neuro = list(set(b_t)) - m_sic = np.array([np.mean(b_sic[b_t == t]) for t in t_neuro]) - s_sic = np.array([np.std(b_sic[b_t == t]) for t in t_neuro]) - # plots - str_ip3 = r"IP$_{3}$" - str_cal = r"Ca$^{2+}$" + # get astrocyte data + astro_data_masked = mask_data_by_start(astro_data, start) + astro_times, astro_ip3_mean, astro_ip3_sd = get_plot_data(astro_data_masked, "IP3") + astro_times, astro_ca_mean, astro_ca_sd = get_plot_data(astro_data_masked, "Ca") + # get neuron data + neuron_data_masked = mask_data_by_start(neuron_data, start) + neuron_times, neuron_sic_mean, neuron_sic_sd = get_plot_data(neuron_data_masked, "I_SIC") + # set plots + fig, axes = plt.subplots(2, 1, sharex=True) color_ip3 = "tab:blue" color_cal = "tab:green" color_sic = "tab:purple" - fig, axes = plt.subplots(2, 1, sharex=True) - # astrocyte plot - axes[0].set_title(f"{str_ip3} and {str_cal} in astrocytes (n={len(set(a['senders']))})") + # plot astrocyte data + n_astro = len(set(astro_data_masked['senders'])) + axes[0].set_title(f"IP$_{{3}}$ and Ca$^{{2+}}$ in astrocytes (n={n_astro})") axes[0].set_ylabel(r"IP$_{3}$ ($\mu$M)") axes[0].tick_params(axis="y", labelcolor=color_ip3) - axes[0].fill_between(t_astro, m_ip3 + s_ip3, m_ip3 - s_ip3, alpha=0.3, linewidth=0.0, color=color_ip3) - axes[0].plot(t_astro, m_ip3, linewidth=2, color=color_ip3) + axes[0].fill_between(astro_times, astro_ip3_mean + astro_ip3_sd, astro_ip3_mean - astro_ip3_sd, alpha=0.3, linewidth=0.0, color=color_ip3) + axes[0].plot(astro_times, astro_ip3_mean, linewidth=2, color=color_ip3) ax = axes[0].twinx() ax.set_ylabel(r"Ca$^{2+}$ ($\mu$M)") ax.tick_params(axis="y", labelcolor=color_cal) - ax.fill_between(t_astro, m_cal + s_cal, m_cal - s_cal, alpha=0.3, linewidth=0.0, color=color_cal) - ax.plot(t_astro, m_cal, linewidth=2, color=color_cal) - # neuron plot - axes[1].set_title(f"SIC in postsynaptic neurons (n={len(set(a['senders']))})") + ax.fill_between(astro_times, astro_ca_mean + astro_ca_sd, astro_ca_mean - astro_ca_sd, alpha=0.3, linewidth=0.0, color=color_cal) + ax.plot(astro_times, astro_ca_mean, linewidth=2, color=color_cal) + # plot neuron data + n_neuron = len(set(neuron_data_masked['senders'])) + axes[1].set_title(f"SIC in postsynaptic neurons (n={n_neuron})") axes[1].set_ylabel("SIC (pA)") axes[1].set_xlabel("Time (ms)") - axes[1].fill_between(t_neuro, m_sic + s_sic, m_sic - s_sic, alpha=0.3, linewidth=0.0, color=color_sic) - axes[1].plot(t_neuro, m_sic, linewidth=2, color=color_sic) + axes[1].fill_between(neuron_times, neuron_sic_mean + neuron_sic_sd, neuron_sic_mean - neuron_sic_sd, alpha=0.3, linewidth=0.0, color=color_sic) + axes[1].plot(neuron_times, neuron_sic_mean, linewidth=2, color=color_sic) # save plt.tight_layout() plt.savefig(os.path.join(data_path, "dynamics.png")) From 5b2f346a0fa7f97c7f1937bfaa31dce64b4a39ce Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Tue, 17 Oct 2023 15:43:59 +0200 Subject: [PATCH 51/93] Improve and fix python format for the two example scripts for neuron-astrocyte networks --- pynest/examples/astrocyte_brunel.py | 106 ++++++++++++--------- pynest/examples/astrocyte_small_network.py | 89 ++++++++++++----- 2 files changed, 125 insertions(+), 70 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index 28fbe99036..68756a5209 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -30,13 +30,13 @@ supporting neuron-astrocyte interactions. The simulation results of this network demonstrate an astrocytic effect on -neuronal excitabitlity. With the slow inward current (SIC) delivered from the +neuronal excitability. With the slow inward current (SIC) induced by the astrocytes through the ``sic_connection``, the neurons show higher firing rates -and more synchronized bursting actitivity. The degrees of local and global -synchrony are quantified by pairwise spike count correlations and a measure of -global synchrony in [4]_ respectively, as shown in a plot made in this script -(neuron_synchrony.png). Plots of astrocytic dynamics and SIC in neurons are also -made in this script. +and more synchronized actitivity. Here, the local synchrony is quantified by +pairwise spike count correlations, and the global synchrony by an approach +suggested in [4]_. The result of this synchrony analysis is shown in the plot +"neuron_synchrony.png". The dynamics in the astrocytes and the SIC in the +neurons are shown in the plot "dynamics.png". References ~~~~~~~~~~ @@ -101,10 +101,10 @@ "N_ex": 8000, # number of excitatory neurons "N_in": 2000, # number of inhibitory neurons "N_astro": 10000, # number of astrocytes - "p": 0.1, # connection probability between neurons - "p_syn_astro": 0.5, # probability of each created neuron-neuron connection to be paired with one astrocyte - "astro_pool_size": 10, # astrocyte pool size for each target neuron - "astro_pool_type": "random", # astrocyte pool will be chosen randomly for each target neuron + "p_primary": 0.1, # connection probability between neurons + "p_cond_third": 0.5, # probability of each created neuron-neuron connection to be paired with one astrocyte + "pool_size": 10, # astrocyte pool size for each target neuron + "pool_type": "random", # astrocyte pool will be chosen randomly for each target neuron "poisson_rate": 2000, # Poisson input rate for neurons } @@ -151,6 +151,7 @@ # TripartiteConnect() function and the "tripartite_bernoulli_with_pool" rule are # used to create the connectivity of the network. + def create_astro_network(scale=1.0): """Create nodes for a neuron-astrocyte network.""" print("Creating nodes ...") @@ -161,6 +162,7 @@ def create_astro_network(scale=1.0): nodes_noise = nest.Create("poisson_generator", params={"rate": network_params["poisson_rate"]}) return nodes_ex, nodes_in, nodes_astro, nodes_noise + def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1.0): """Connect the nodes in a neuron-astrocyte network. The astrocytes are paired with excitatory connections only. @@ -168,17 +170,17 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. print("Connecting Poisson generator ...") assert scale >= 1.0, "scale must be >= 1.0" nest.Connect( - nodes_noise, nodes_ex + nodes_in, syn_spec={"synapse_model": "static_synapse", "weight": syn_params["w_e"]}) - + nodes_noise, nodes_ex + nodes_in, syn_spec={"synapse_model": "static_synapse", "weight": syn_params["w_e"]} + ) print("Connecting neurons and astrocytes ...") # excitatory connections are paired with astrocytes # conn_spec and syn_spec according to the "tripartite_bernoulli_with_pool" rule conn_params_e = { "rule": "tripartite_bernoulli_with_pool", - "p_primary": network_params["p"] / scale, - "p_cond_third": network_params["p_syn_astro"], - "pool_size": network_params["astro_pool_size"], - "pool_type": network_params["astro_pool_type"], + "p_primary": network_params["p_primary"] / scale, + "p_cond_third": network_params["p_cond_third"], + "pool_size": network_params["pool_size"], + "pool_type": network_params["pool_type"], } syn_params_e = { "primary": { @@ -193,14 +195,11 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. "tau_psc": tau_syn_ex, "delay": syn_params["d_e"], }, - "third_out": { - "synapse_model": "sic_connection", - "weight": syn_params["w_a2n"] - }, + "third_out": {"synapse_model": "sic_connection", "weight": syn_params["w_a2n"]}, } nest.TripartiteConnect(nodes_ex, nodes_ex + nodes_in, nodes_astro, conn_spec=conn_params_e, syn_specs=syn_params_e) # inhibitory connections are not paired with astrocytes - conn_params_i = {"rule": "pairwise_bernoulli", "p": network_params["p"] / scale} + conn_params_i = {"rule": "pairwise_bernoulli", "p": network_params["p_primary"] / scale} syn_params_i = { "synapse_model": syn_params["synapse_model"], "weight": syn_params["w_i"], @@ -209,11 +208,13 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. } nest.Connect(nodes_in, nodes_ex + nodes_in, conn_params_i, syn_params_i) + ############################################################################### # This function calculates the pairwise spike count correlations. For each pair # of neurons, the correlation coefficient (Pearson's r) of their spike count # histograms is calculated. The result of all pairs are returned. + def get_corr(hlist): """Calculate pairwise correlations for a list of histograms.""" coef_list = [] @@ -234,6 +235,7 @@ def get_corr(hlist): return coef_list, n_pair_pass, n_pair_fail + ############################################################################### # These two functions calculate and plot the synchrony of neuronal firings. # Histograms of spike counts of all neurons are obtained to evaluate local and @@ -241,6 +243,7 @@ def get_corr(hlist): # count correlation, and the global synchrony is evaluated with the variance of # average spike count/average of variance of individual spike count. + def calc_synchrony(neuron_spikes, n_neuron_rec_spk, start, end, data_path, N=100, bw=10): # get data senders = neuron_spikes["senders"][neuron_spikes["times"] > start] @@ -266,6 +269,7 @@ def calc_synchrony(neuron_spikes, n_neuron_rec_spk, start, end, data_path, N=100 gsync = np.var(hist_global) / np.mean(np.var(hists, axis=1)) # global return rate, coefs, lsync_mu, lsync_sd, gsync, n_for_sync + def plot_synchrony(coefs, title, data_path): print("Plotting pairwise spike count correlations ...") plt.hist(coefs) @@ -276,32 +280,34 @@ def plot_synchrony(coefs, title, data_path): plt.savefig(os.path.join(data_path, "neuron_synchrony.png")) plt.close() + ############################################################################### # This function plots the dynamics in the astrocytes and their resultant output -# to the neurons.The IP3 and calcium in the astrocytes and the SIC in neurons +# to the neurons. The IP3 and calcium in the astrocytes and the SIC in neurons # are plotted. Means and standard deviations across sampled nodes are indicated # by lines and shaded areas, respectively. + def plot_dynamics(astro_data, neuron_data, data_path, start): # plot dynamics print("Plotting dynamics ...") # astrocyte data - a_mask = astro_data["times"] > start - a_ip3 = astro_data["IP3"][a_mask] - a_cal = astro_data["Ca"][a_mask] - a_t = astro_data["times"][a_mask] - t_astro = list(set(a_t)) - m_ip3 = np.array([np.mean(a_ip3[a_t == t]) for t in t_astro]) - s_ip3 = np.array([np.std(a_ip3[a_t == t]) for t in t_astro]) - m_cal = np.array([np.mean(a_cal[a_t == t]) for t in t_astro]) - s_cal = np.array([np.std(a_cal[a_t == t]) for t in t_astro]) + astro_mask = astro_data["times"] > start + astro_ip3 = astro_data["IP3"][astro_mask] + astro_cal = astro_data["Ca"][astro_mask] + astro_times = astro_data["times"][astro_mask] + astro_times_set = list(set(astro_times)) + ip3_means = np.array([np.mean(astro_ip3[astro_times == t]) for t in astro_times_set]) + ip3_sds = np.array([np.std(astro_ip3[astro_times == t]) for t in astro_times_set]) + cal_means = np.array([np.mean(astro_cal[astro_times == t]) for t in astro_times_set]) + cal_sds = np.array([np.std(astro_cal[astro_times == t]) for t in astro_times_set]) # neuron data - b_mask = neuron_data["times"] > start - b_sic = neuron_data["I_SIC"][neuron_data["times"] > start] - b_t = neuron_data["times"][neuron_data["times"] > start] - t_neuro = list(set(b_t)) - m_sic = np.array([np.mean(b_sic[b_t == t]) for t in t_neuro]) - s_sic = np.array([np.std(b_sic[b_t == t]) for t in t_neuro]) + neuron_mask = neuron_data["times"] > start + neuron_sic = neuron_data["I_SIC"][neuron_mask] + neuron_times = neuron_data["times"][neuron_mask] + neuron_times_set = list(set(neuron_times)) + sic_means = np.array([np.mean(neuron_sic[neuron_times == t]) for t in neuron_times_set]) + sic_sds = np.array([np.std(neuron_sic[neuron_times == t]) for t in neuron_times_set]) # plots fig, axes = plt.subplots(2, 1, sharex=True) color_ip3 = "tab:blue" @@ -311,30 +317,38 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): axes[0].set_title(f"{r'IP$_{3}$'} and {r'Ca$^{2+}$'} in astrocytes (n={len(set(astro_data['senders']))})") axes[0].set_ylabel(r"IP$_{3}$ ($\mu$M)") axes[0].tick_params(axis="y", labelcolor=color_ip3) - axes[0].fill_between(t_astro, m_ip3 + s_ip3, m_ip3 - s_ip3, alpha=0.3, linewidth=0.0, color=color_ip3) - axes[0].plot(t_astro, m_ip3, linewidth=2, color=color_ip3) + axes[0].fill_between( + astro_times_set, ip3_means + ip3_sds, ip3_means - ip3_sds, alpha=0.3, linewidth=0.0, color=color_ip3 + ) + axes[0].plot(astro_times_set, ip3_means, linewidth=2, color=color_ip3) ax = axes[0].twinx() ax.set_ylabel(r"Ca$^{2+}$ ($\mu$M)") ax.tick_params(axis="y", labelcolor=color_cal) - ax.fill_between(t_astro, m_cal + s_cal, m_cal - s_cal, alpha=0.3, linewidth=0.0, color=color_cal) - ax.plot(t_astro, m_cal, linewidth=2, color=color_cal) + ax.fill_between( + astro_times_set, cal_means + cal_sds, cal_means - cal_sds, alpha=0.3, linewidth=0.0, color=color_cal + ) + ax.plot(astro_times_set, cal_means, linewidth=2, color=color_cal) # neuron plot axes[1].set_title(f"SIC in neurons (n={len(set(neuron_data['senders']))})") axes[1].set_ylabel("SIC (pA)") axes[1].set_xlabel("Time (ms)") - axes[1].fill_between(t_neuro, m_sic + s_sic, m_sic - s_sic, alpha=0.3, linewidth=0.0, color=color_sic) - axes[1].plot(t_neuro, m_sic, linewidth=2, color=color_sic) + axes[1].fill_between( + neuron_times_set, sic_means + sic_sds, sic_means - sic_sds, alpha=0.3, linewidth=0.0, color=color_sic + ) + axes[1].plot(neuron_times_set, sic_means, linewidth=2, color=color_sic) # save plt.tight_layout() plt.savefig(os.path.join(data_path, "dynamics.png")) plt.close() + ############################################################################### # This is the main function for simulation. Here, the network is created and the # neurons and astrocytes are randomly chosen to be connected with the recording # devices. The sample numbers are determined by sim_params. After simulation, # neuron and astrocyte data are analyzed and plotted. + def run_simulation(data_path): # NEST configuration nest.ResetKernel() @@ -407,8 +421,9 @@ def run_simulation(data_path): plt.close() # Calculate and plot synchrony - rate, coefs, lsync_mu, lsync_sd, gsync, n_for_sync = \ - calc_synchrony(neuron_spikes, n_neuron_rec_spk, pre_sim_time, pre_sim_time + sim_time, data_path) + rate, coefs, lsync_mu, lsync_sd, gsync, n_for_sync = calc_synchrony( + neuron_spikes, n_neuron_rec_spk, pre_sim_time, pre_sim_time + sim_time, data_path + ) title = ( f"Firing rate={rate:.2f} spikes/s (n={n_neuron_rec_spk}) \n" + f"Local sync.={lsync_mu:.3f}$\\pm${lsync_sd:.3f}, Global sync.={gsync:.3f}\n" @@ -421,6 +436,7 @@ def run_simulation(data_path): # Plot dynamics in astrocytes and neurons plot_dynamics(astro_data, neuron_data, data_path, pre_sim_time) + ############################################################################### # Run simulation. diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 09cedcf635..024aa21c75 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -24,7 +24,7 @@ ------------------------------------------------------------ This script demonstrates an aproach with NEST to create a neuron-astrocyte -network. The network in this script includes 20 neurons and 5 astrocytes. The +network. The network in this script includes 20 neurons and five astrocytes. The astrocyte is modeled with ``astrocyte_lr_1994``, implemented according to [1]_, [2]_, and [3]_. The neuron is modeled with ``aeif_cond_alpha_astro``, an adaptive exponential integrate and fire neuron supporting neuron-astrocyte @@ -37,9 +37,9 @@ 1. For each pair of neurons, a Bernoulli trial with a probability ``p_primary`` determines if they will be connected. -2. For each neuron-neuron connection created, a Bernoulli trial with a probability -``p_cond_third`` determines if it will be paired with one astrocyte. The -selection of this particular astrocyte is confined by ``pool_size`` and +2. For each neuron-neuron connection created, a Bernoulli trial with a +probability ``p_cond_third`` determines if it will be paired with one astrocyte. +The selection of this particular astrocyte is confined by ``pool_size`` and ``pool_type`` (see below). 3. If a neuron-neuron connection is to be paired with an astrocyte, a connection @@ -60,11 +60,11 @@ target neuron can only be connected to astrocytes selected from its pool. * ``pool_type``: The way to determine the astrocyte pool for each target - neuron. If "random", a number (=``pool_size``) of astrocytes are randomly - chosen from all astrocytes and assigned as the pool. If "block", the - astrocytes pool is evenly distributed to the neurons in blocks without - overlapping, and the defined ``pool_size`` has to be compatible with this - arrangement. + neuron. If specified "random", a number (``pool_size``) of astrocytes are + randomly chosen from all astrocytes and assigned as the pool. If specified + "block", the astrocytes are evenly distributed to the neurons in blocks + without overlapping, and the specified ``pool_size`` has to be compatible + with this arrangement. * ``syn_specs`` parameters @@ -75,7 +75,7 @@ * ``third_out``: specifications for the connections from astrocytes to neurons. In this script, the network is created with the ``pool_type`` being "block". -``p_primary`` and ``p_cond_third`` are both chosen to be 1, so that all possible +``p_primary`` and ``p_cond_third`` are both set to one, so that all possible connections are made. It can be seen from the result plot "connections.png" that "block" distributes the astrocytes evenly to the postsynaptic neurons in blocks without overlapping. The ``pool_size`` should be compatible with this @@ -90,7 +90,7 @@ The ``pool_type`` can be changed to "random" to see the results with random astrocyte pools. In that case, the ``pool_size`` can be any from one to the -number of all astrocytes. +total number of astrocytes. References ~~~~~~~~~~ @@ -136,7 +136,7 @@ spath = "astrocyte_small_network" # spath = os.path.join("astrocyte_small_network", hl.md5(os.urandom(16)).hexdigest()) os.system(f"mkdir -p {spath}") -os.system(f"cp astrocyte_small_network.py {spath}") # (debug) +os.system(f"cp astrocyte_small_network.py {spath}") # (debug) ############################################################################### # Network parameters. @@ -154,7 +154,7 @@ astrocyte_model = "astrocyte_lr_1994" astrocyte_params = { "IP3": 0.4, # IP3 initial value in µM - "delta_IP3": 2.0, # parameter determining the increase in astrocytic IP3 concentration induced by synaptic input + "delta_IP3": 2.0, # parameter determining the increase in astrocytic IP3 concentration induced by synaptic input "tau_IP3": 10.0, # time constant of the exponential decay of astrocytic IP3 } @@ -206,8 +206,12 @@ conns_n2a = nest.GetConnections(pre_neurons, astrocytes) ############################################################################### -# Functions for plotting. +# Functions for plotting. For neuron and astrocyte data, means and standard +# deviations across sampled nodes are indicated by lines and shaded areas, +# respectively. + +# Plot all connections between neurons and astrocytes def plot_connections(conn_n2n, conn_n2a, conn_a2n): # helper function to create lists of positions for source and target nodes, for plotting def get_node_positions(dict_in): @@ -232,9 +236,15 @@ def set_frame_invisible(ax): # make plot fig, axs = plt.subplots(1, 1, figsize=(10, 8)) # plot nodes (need the sets of node positions) - axs.scatter(list(set(slist_n2a)), [2] * len(set(slist_n2a)), s=400, color="gray", marker="^", label="pre_neurons", zorder=3) - axs.scatter(list(set(alist_a2n)), [1] * len(set(alist_a2n)), s=400, color="g", marker="o", label="astrocytes", zorder=3) - axs.scatter(list(set(tlist_a2n)), [0] * len(set(tlist_a2n)), s=400, color="k", marker="^", label="post_neurons", zorder=3) + axs.scatter( + list(set(slist_n2a)), [2] * len(set(slist_n2a)), s=400, color="gray", marker="^", label="pre_neurons", zorder=3 + ) + axs.scatter( + list(set(alist_a2n)), [1] * len(set(alist_a2n)), s=400, color="g", marker="o", label="astrocytes", zorder=3 + ) + axs.scatter( + list(set(tlist_a2n)), [0] * len(set(tlist_a2n)), s=400, color="k", marker="^", label="post_neurons", zorder=3 + ) # plot connections for sx, tx in zip(slist_n2n, tlist_n2n): axs.plot([sx, tx], [2, 0], linestyle=":", color="b", alpha=0.5, linewidth=1) @@ -248,14 +258,18 @@ def set_frame_invisible(ax): plt.tight_layout() plt.savefig(os.path.join(spath, "connections.png")) + +# Helper function to mask data by start time def mask_data_by_start(data_in, start): data_out = {} times = data_in["times"] for key, value in data_in.items(): data = data_in[key] - data_out[key] = data[times>start] + data_out[key] = data[times > start] return data_out + +# Helper function to get times, means, and standard deviation of data for plotting def get_plot_data(data_in, variable): times_all = data_in["times"] ts = list(set(data_in["times"])) @@ -263,6 +277,8 @@ def get_plot_data(data_in, variable): sds = np.array([np.std(data_in[variable][times_all == t]) for t in ts]) return ts, means, sds + +# Plot membrane potentials of presynaptic and postsynaptic neurons def plot_vm(pre_data, post_data, data_path, start): print("Plotting V_m ...") # get presynaptic data @@ -277,19 +293,25 @@ def plot_vm(pre_data, post_data, data_path, start): # plot presynaptic membrane potential axes[0].set_title(f"presynaptic neurons (n={len(set(pre_data['senders']))})") axes[0].set_ylabel(r"$V_{m}$ (mV)") - axes[0].fill_between(pre_times, pre_vm_mean + pre_vm_sd, pre_vm_mean - pre_vm_sd, alpha=0.3, linewidth=0.0, color=color_pre) + axes[0].fill_between( + pre_times, pre_vm_mean + pre_vm_sd, pre_vm_mean - pre_vm_sd, alpha=0.3, linewidth=0.0, color=color_pre + ) axes[0].plot(pre_times, pre_vm_mean, linewidth=2, color=color_pre) # plot postsynaptic membrane potential axes[1].set_title(f"postsynaptic neurons (n={len(set(post_data['senders']))})") axes[1].set_ylabel(r"$V_{m}$ (mV)") axes[1].set_xlabel("Time (ms)") - axes[1].fill_between(post_times, post_vm_mean + post_vm_sd, post_vm_mean - post_vm_sd, alpha=0.3, linewidth=0.0, color=color_post) + axes[1].fill_between( + post_times, post_vm_mean + post_vm_sd, post_vm_mean - post_vm_sd, alpha=0.3, linewidth=0.0, color=color_post + ) axes[1].plot(post_times, post_vm_mean, linewidth=2, color=color_post) # save plt.tight_layout() plt.savefig(os.path.join(data_path, "V_m.png")) plt.close() + +# Plot dynamics in astrocytes and SIC in neurons def plot_dynamics(astro_data, neuron_data, data_path, start): print("Plotting dynamics ...") # get astrocyte data @@ -305,29 +327,46 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): color_cal = "tab:green" color_sic = "tab:purple" # plot astrocyte data - n_astro = len(set(astro_data_masked['senders'])) + n_astro = len(set(astro_data_masked["senders"])) axes[0].set_title(f"IP$_{{3}}$ and Ca$^{{2+}}$ in astrocytes (n={n_astro})") axes[0].set_ylabel(r"IP$_{3}$ ($\mu$M)") axes[0].tick_params(axis="y", labelcolor=color_ip3) - axes[0].fill_between(astro_times, astro_ip3_mean + astro_ip3_sd, astro_ip3_mean - astro_ip3_sd, alpha=0.3, linewidth=0.0, color=color_ip3) + axes[0].fill_between( + astro_times, + astro_ip3_mean + astro_ip3_sd, + astro_ip3_mean - astro_ip3_sd, + alpha=0.3, + linewidth=0.0, + color=color_ip3, + ) axes[0].plot(astro_times, astro_ip3_mean, linewidth=2, color=color_ip3) ax = axes[0].twinx() ax.set_ylabel(r"Ca$^{2+}$ ($\mu$M)") ax.tick_params(axis="y", labelcolor=color_cal) - ax.fill_between(astro_times, astro_ca_mean + astro_ca_sd, astro_ca_mean - astro_ca_sd, alpha=0.3, linewidth=0.0, color=color_cal) + ax.fill_between( + astro_times, astro_ca_mean + astro_ca_sd, astro_ca_mean - astro_ca_sd, alpha=0.3, linewidth=0.0, color=color_cal + ) ax.plot(astro_times, astro_ca_mean, linewidth=2, color=color_cal) # plot neuron data - n_neuron = len(set(neuron_data_masked['senders'])) + n_neuron = len(set(neuron_data_masked["senders"])) axes[1].set_title(f"SIC in postsynaptic neurons (n={n_neuron})") axes[1].set_ylabel("SIC (pA)") axes[1].set_xlabel("Time (ms)") - axes[1].fill_between(neuron_times, neuron_sic_mean + neuron_sic_sd, neuron_sic_mean - neuron_sic_sd, alpha=0.3, linewidth=0.0, color=color_sic) + axes[1].fill_between( + neuron_times, + neuron_sic_mean + neuron_sic_sd, + neuron_sic_mean - neuron_sic_sd, + alpha=0.3, + linewidth=0.0, + color=color_sic, + ) axes[1].plot(neuron_times, neuron_sic_mean, linewidth=2, color=color_sic) # save plt.tight_layout() plt.savefig(os.path.join(data_path, "dynamics.png")) plt.close() + ############################################################################### # Run simulation and save results. From 41942ee4b9e7259c3a0b20f4021ab1dc537d89f1 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Tue, 17 Oct 2023 16:04:32 +0200 Subject: [PATCH 52/93] Change neuron-astrocyte network scripts --- pynest/examples/astrocyte_brunel.py | 7 ++----- pynest/examples/astrocyte_small_network.py | 16 +++++++--------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index 68756a5209..ed98dbc128 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -87,7 +87,7 @@ sim_params = { "dt": 0.1, # simulation resolution in ms "pre_sim_time": 100.0, # pre-simulation time in ms (excluded from analysis) - "sim_time": 100.0, # simulation time in ms + "sim_time": 1000.0, # simulation time in ms "N_rec_spk": 100, # number of samples (neuron) for spike detector "N_rec_mm": 50, # number of samples (neuron, astrocyte) for multimeter "n_threads": 4, # number of threads for NEST @@ -400,8 +400,7 @@ def run_simulation(data_path): neuron_data = mm_neuron.events astro_data = mm_astro.events - # Save script and parameters (debug) - os.system(f"cp astrocyte_brunel.py {data_path}") + # Save parameters (debug) params = { "sim_params": sim_params, "network_params": network_params, @@ -441,6 +440,4 @@ def run_simulation(data_path): # Run simulation. random.seed(sim_params["seed"]) -hash = hashlib.md5(os.urandom(16)).hexdigest() run_simulation("astrocyte_brunel") -# run_simulation(os.path.join("astrocyte_brunel", hash)) diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 024aa21c75..5af8b5af8b 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -133,10 +133,8 @@ # Initialize NEST kernel and create folder to save data. nest.ResetKernel() -spath = "astrocyte_small_network" -# spath = os.path.join("astrocyte_small_network", hl.md5(os.urandom(16)).hexdigest()) -os.system(f"mkdir -p {spath}") -os.system(f"cp astrocyte_small_network.py {spath}") # (debug) +save_path = "astrocyte_small_network" +os.system(f"mkdir -p {save_path}") ############################################################################### # Network parameters. @@ -212,7 +210,7 @@ # Plot all connections between neurons and astrocytes -def plot_connections(conn_n2n, conn_n2a, conn_a2n): +def plot_connections(conn_n2n, conn_n2a, conn_a2n, data_path): # helper function to create lists of positions for source and target nodes, for plotting def get_node_positions(dict_in): source_list = dict_in["source"] - np.unique(dict_in["source"]).mean() @@ -256,7 +254,7 @@ def set_frame_invisible(ax): axs.legend(bbox_to_anchor=(0.5, 1.1), loc="upper center", ncol=3) set_frame_invisible(axs) plt.tight_layout() - plt.savefig(os.path.join(spath, "connections.png")) + plt.savefig(os.path.join(data_path, "connections.png")) # Helper function to mask data by start time @@ -371,6 +369,6 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): # Run simulation and save results. nest.Simulate(1000.0) -plot_connections(conns_n2n, conns_n2a, conns_a2n) -plot_vm(mm_pre_neurons.events, mm_post_neurons.events, spath, 0.0) -plot_dynamics(mm_astrocytes.events, mm_post_neurons.events, spath, 0.0) +plot_connections(conns_n2n, conns_n2a, conns_a2n, save_path) +plot_vm(mm_pre_neurons.events, mm_post_neurons.events, save_path, 0.0) +plot_dynamics(mm_astrocytes.events, mm_post_neurons.events, save_path, 0.0) From 7f014494327d944d37c01ca9f08a05024e81d476 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Tue, 17 Oct 2023 16:18:56 +0200 Subject: [PATCH 53/93] Fix the line that was too long in astrocyte_small_network.py --- pynest/examples/astrocyte_small_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 5af8b5af8b..9c6b6cacaf 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -144,7 +144,7 @@ p_primary = 1.0 # connection probability between neurons p_cond_third = 1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte pool_size = 1 # astrocyte pool size for each target neuron -pool_type = "block" # the way to determine the astrocyte pool for each target neuron (change to "random" to see different results) +pool_type = "block" # the way to determine the astrocyte pool for each target neuron ############################################################################### # Astrocyte parameters. From a54b8d8dab0ff399d68d593f79de013f77c403ed Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 23 Oct 2023 13:30:01 +0200 Subject: [PATCH 54/93] Correct typo in two astrocyte scripts --- pynest/examples/astrocyte_brunel.py | 2 +- pynest/examples/astrocyte_small_network.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index ed98dbc128..b5dc9557cc 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -26,7 +26,7 @@ This script simulates a random balanced network with excitatory and inhibitory neurons and astrocytes. The astrocyte is modeled with ``astrocyte_lr_1994``, implemented according to [1]_, [2]_, and [3]_. The neuron is modeled with -``aeif_cond_alpha_astro``, an adaptive exponential integrate and fire neuron +``aeif_cond_alpha_astro``, an adaptive exponential integrate-and-fire neuron supporting neuron-astrocyte interactions. The simulation results of this network demonstrate an astrocytic effect on diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 9c6b6cacaf..7a2bfb608b 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -27,7 +27,7 @@ network. The network in this script includes 20 neurons and five astrocytes. The astrocyte is modeled with ``astrocyte_lr_1994``, implemented according to [1]_, [2]_, and [3]_. The neuron is modeled with ``aeif_cond_alpha_astro``, an -adaptive exponential integrate and fire neuron supporting neuron-astrocyte +adaptive exponential integrate-and-fire neuron supporting neuron-astrocyte interactions. The network is created with the TripartiteConnect() function and the From 93414f1a3044dd2295c63d954ad4f57f5eda7f69 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 23 Oct 2023 13:35:59 +0200 Subject: [PATCH 55/93] Change the name "p_cond_third" to "p_third_if_primary" --- nestkernel/conn_builder.cpp | 10 +++++----- nestkernel/conn_builder.h | 2 +- nestkernel/nest_names.cpp | 2 +- nestkernel/nest_names.h | 2 +- pynest/examples/astrocyte_brunel.py | 4 ++-- pynest/examples/astrocyte_small_network.py | 10 +++++----- testsuite/pytests/test_tripartite_connect.py | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 15a10c19ad..5c4ff6cc10 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1616,13 +1616,13 @@ nest::TripartiteBernoulliWithPoolBuilder::TripartiteBernoulliWithPoolBuilder( No conn_spec, { getValue< DictionaryDatum >( ( *syn_specs )[ names::third_out ] ) } ) , p_primary_( 1.0 ) - , p_cond_third_( 1.0 ) + , p_third_if_primary_( 1.0 ) , random_pool_( true ) , pool_size_( third->size() ) , targets_per_third_( targets->size() / third->size() ) { updateValue< double >( conn_spec, names::p_primary, p_primary_ ); - updateValue< double >( conn_spec, names::p_cond_third, p_cond_third_ ); + updateValue< double >( conn_spec, names::p_third_if_primary, p_third_if_primary_ ); updateValue< long >( conn_spec, names::pool_size, pool_size_ ); std::string pool_type; if ( updateValue< std::string >( conn_spec, names::pool_type, pool_type ) ) @@ -1646,9 +1646,9 @@ nest::TripartiteBernoulliWithPoolBuilder::TripartiteBernoulliWithPoolBuilder( No throw BadProperty( "Probability of neuron-neuron connections 0 ≤ p ≤ 1 required" ); } - if ( p_cond_third_ < 0 or 1 < p_cond_third_ ) + if ( p_third_if_primary_ < 0 or 1 < p_third_if_primary_ ) { - throw BadProperty( "Conditional probability of third-factor connection 0 ≤ p_cond_third ≤ 1 required" ); + throw BadProperty( "Conditional probability of third-factor connection 0 ≤ p_third_if_primary ≤ 1 required" ); } if ( pool_size_ < 1 or third->size() < pool_size_ ) @@ -1744,7 +1744,7 @@ nest::TripartiteBernoulliWithPoolBuilder::connect_() } // conditionally connect third factor - if ( not( synced_rng->drand() < p_cond_third_ ) ) + if ( not( synced_rng->drand() < p_third_if_primary_ ) ) { continue; } diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index b97392cccf..cebe931e68 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -494,7 +494,7 @@ class TripartiteBernoulliWithPoolBuilder : public ConnBuilder AuxiliaryBuilder third_out_builder_; double p_primary_; //!< connection probability for pre-post connections - double p_cond_third_; //!< probability third-factor connection if primary connection created + double p_third_if_primary_; //!< probability third-factor connection if primary connection created bool random_pool_; //!< if true, select astrocyte pool at random size_t pool_size_; //!< size of third-factor pool size_t targets_per_third_; //!< target nodes per third-factor node diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp index 7b63f11516..a15351fb57 100644 --- a/nestkernel/nest_names.cpp +++ b/nestkernel/nest_names.cpp @@ -343,9 +343,9 @@ const Name overwrite_files( "overwrite_files" ); const Name P( "P" ); const Name p( "p" ); -const Name p_cond_third( "p_cond_third" ); const Name p_copy( "p_copy" ); const Name p_primary( "p_primary" ); +const Name p_third_if_primary( "p_third_if_primary" ); const Name p_transmit( "p_transmit" ); const Name pairwise_bernoulli_on_source( "pairwise_bernoulli_on_source" ); const Name pairwise_bernoulli_on_target( "pairwise_bernoulli_on_target" ); diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h index c3c282c7f1..b6f7bee2ea 100644 --- a/nestkernel/nest_names.h +++ b/nestkernel/nest_names.h @@ -369,9 +369,9 @@ extern const Name overwrite_files; extern const Name P; extern const Name p; -extern const Name p_cond_third; extern const Name p_copy; extern const Name p_primary; +extern const Name p_third_if_primary; extern const Name p_transmit; extern const Name pairwise_bernoulli_on_source; extern const Name pairwise_bernoulli_on_target; diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index b5dc9557cc..eae57367a1 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -102,7 +102,7 @@ "N_in": 2000, # number of inhibitory neurons "N_astro": 10000, # number of astrocytes "p_primary": 0.1, # connection probability between neurons - "p_cond_third": 0.5, # probability of each created neuron-neuron connection to be paired with one astrocyte + "p_third_if_primary": 0.5, # probability of each created neuron-neuron connection to be paired with one astrocyte "pool_size": 10, # astrocyte pool size for each target neuron "pool_type": "random", # astrocyte pool will be chosen randomly for each target neuron "poisson_rate": 2000, # Poisson input rate for neurons @@ -178,7 +178,7 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. conn_params_e = { "rule": "tripartite_bernoulli_with_pool", "p_primary": network_params["p_primary"] / scale, - "p_cond_third": network_params["p_cond_third"], + "p_third_if_primary": network_params["p_third_if_primary"], "pool_size": network_params["pool_size"], "pool_type": network_params["pool_type"], } diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 7a2bfb608b..376ade5133 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -38,7 +38,7 @@ determines if they will be connected. 2. For each neuron-neuron connection created, a Bernoulli trial with a -probability ``p_cond_third`` determines if it will be paired with one astrocyte. +probability ``p_third_if_primary`` determines if it will be paired with one astrocyte. The selection of this particular astrocyte is confined by ``pool_size`` and ``pool_type`` (see below). @@ -53,7 +53,7 @@ * ``p_primary``: Connection probability between neurons. - * ``p_cond_third``: Probability of each created neuron-neuron connection to be + * ``p_third_if_primary``: Probability of each created neuron-neuron connection to be paired with one astrocyte. * ``pool_size``: The size of astrocyte pool for each target neuron. The @@ -75,7 +75,7 @@ * ``third_out``: specifications for the connections from astrocytes to neurons. In this script, the network is created with the ``pool_type`` being "block". -``p_primary`` and ``p_cond_third`` are both set to one, so that all possible +``p_primary`` and ``p_third_if_primary`` are both set to one, so that all possible connections are made. It can be seen from the result plot "connections.png" that "block" distributes the astrocytes evenly to the postsynaptic neurons in blocks without overlapping. The ``pool_size`` should be compatible with this @@ -142,7 +142,7 @@ n_neurons = 10 # number of source and target neurons n_astrocytes = 5 # number of astrocytes p_primary = 1.0 # connection probability between neurons -p_cond_third = 1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte +p_third_if_primary = 1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte pool_size = 1 # astrocyte pool size for each target neuron pool_type = "block" # the way to determine the astrocyte pool for each target neuron @@ -179,7 +179,7 @@ conn_spec={ "rule": "tripartite_bernoulli_with_pool", "p_primary": p_primary, - "p_cond_third": p_cond_third, + "p_third_if_primary": p_third_if_primary, "pool_size": pool_size, "pool_type": pool_type, }, diff --git a/testsuite/pytests/test_tripartite_connect.py b/testsuite/pytests/test_tripartite_connect.py index ef423fddd7..c06f582792 100644 --- a/testsuite/pytests/test_tripartite_connect.py +++ b/testsuite/pytests/test_tripartite_connect.py @@ -30,7 +30,7 @@ def test_connect_all(): third = nest.Create("parrot_neuron", n_third) nest.TripartiteConnect( - pre, post, third, {"rule": "tripartite_bernoulli_with_pool", "p_primary": 1.0, "p_cond_third": 1} + pre, post, third, {"rule": "tripartite_bernoulli_with_pool", "p_primary": 1.0, "p_third_if_primary": 1} ) n_primary = n_pre * n_post @@ -49,7 +49,7 @@ def test_connect_astro(): pre, post, third, - {"rule": "tripartite_bernoulli_with_pool", "p_primary": 1.0, "p_cond_third": 1}, + {"rule": "tripartite_bernoulli_with_pool", "p_primary": 1.0, "p_third_if_primary": 1}, {"third_out": {"synapse_model": "sic_connection"}}, ) From 7286047fe70494eb1a328b458cf4544ac65d20c8 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Mon, 23 Oct 2023 13:40:30 +0200 Subject: [PATCH 56/93] Fix C++ format after commit 93414f --- nestkernel/conn_builder.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index cebe931e68..6e72fec5eb 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -493,11 +493,11 @@ class TripartiteBernoulliWithPoolBuilder : public ConnBuilder AuxiliaryBuilder third_in_builder_; AuxiliaryBuilder third_out_builder_; - double p_primary_; //!< connection probability for pre-post connections - double p_third_if_primary_; //!< probability third-factor connection if primary connection created - bool random_pool_; //!< if true, select astrocyte pool at random - size_t pool_size_; //!< size of third-factor pool - size_t targets_per_third_; //!< target nodes per third-factor node + double p_primary_; //!< connection probability for pre-post connections + double p_third_if_primary_; //!< probability third-factor connection if primary connection created + bool random_pool_; //!< if true, select astrocyte pool at random + size_t pool_size_; //!< size of third-factor pool + size_t targets_per_third_; //!< target nodes per third-factor node }; class SymmetricBernoulliBuilder : public ConnBuilder From dd15cef8689e5d1f225edf1707d96c1a3fc2e9be Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Tue, 24 Oct 2023 21:56:10 +0200 Subject: [PATCH 57/93] Delete unnecessary parts in two astrocyte scripts --- pynest/examples/astrocyte_brunel.py | 99 +--------------------- pynest/examples/astrocyte_small_network.py | 11 ++- 2 files changed, 8 insertions(+), 102 deletions(-) diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index eae57367a1..d7346ba751 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -29,14 +29,9 @@ ``aeif_cond_alpha_astro``, an adaptive exponential integrate-and-fire neuron supporting neuron-astrocyte interactions. -The simulation results of this network demonstrate an astrocytic effect on -neuronal excitability. With the slow inward current (SIC) induced by the -astrocytes through the ``sic_connection``, the neurons show higher firing rates -and more synchronized actitivity. Here, the local synchrony is quantified by -pairwise spike count correlations, and the global synchrony by an approach -suggested in [4]_. The result of this synchrony analysis is shown in the plot -"neuron_synchrony.png". The dynamics in the astrocytes and the SIC in the -neurons are shown in the plot "dynamics.png". +The simulation results show how astrocytes affect neuronal excitability. The +dynamics in the astrocytes and the SIC in the neurons are shown in the plot +"dynamics.png". References ~~~~~~~~~~ @@ -56,9 +51,6 @@ neurons: a new mechanism for epilepsy?. Physical review letters, 91(26), 268101. DOI: https://doi.org/10.1103/PhysRevLett.91.268101 -.. [4] Golomb, D. (2007). Neuronal synchrony measures. Scholarpedia, 2(1), 1347. - DOI: http://dx.doi.org/10.4249/scholarpedia.1347 - See Also ~~~~~~~~ @@ -209,78 +201,6 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. nest.Connect(nodes_in, nodes_ex + nodes_in, conn_params_i, syn_params_i) -############################################################################### -# This function calculates the pairwise spike count correlations. For each pair -# of neurons, the correlation coefficient (Pearson's r) of their spike count -# histograms is calculated. The result of all pairs are returned. - - -def get_corr(hlist): - """Calculate pairwise correlations for a list of histograms.""" - coef_list = [] - n_pair_pass = 0 - n_pair_fail = 0 - for i, hist1 in enumerate(hlist): - idxs = list(range(i + 1, len(hlist))) - for j in idxs: - hist2 = hlist[j] - if np.sum(hist1) != 0 and np.sum(hist2) != 0: - coef = np.corrcoef(hist1, hist2)[0, 1] - coef_list.append(coef) - n_pair_pass += 1 - else: - n_pair_fail += 1 - if n_pair_fail > 0: - print(f"n_pair_fail = {n_pair_fail}!") - - return coef_list, n_pair_pass, n_pair_fail - - -############################################################################### -# These two functions calculate and plot the synchrony of neuronal firings. -# Histograms of spike counts of all neurons are obtained to evaluate local and -# global synchrony. The local synchrony is evaluated with average pairwise spike -# count correlation, and the global synchrony is evaluated with the variance of -# average spike count/average of variance of individual spike count. - - -def calc_synchrony(neuron_spikes, n_neuron_rec_spk, start, end, data_path, N=100, bw=10): - # get data - senders = neuron_spikes["senders"][neuron_spikes["times"] > start] - times = neuron_spikes["times"][neuron_spikes["times"] > start] - rate = len(senders) / (end - start) * 1000.0 / n_neuron_rec_spk - print(f"Mean neuronal firing rate = {rate:.2f} spikes/s (n = {n_neuron_rec_spk})") - # sample neurons - set_senders = list(set(senders)) - n_for_sync = min(len(set_senders), N) - print(f"n of neurons for synchrony analysis = {n_for_sync}") - sampled = random.sample(set_senders, n_for_sync) - times = times[np.isin(senders, sampled)] - senders = senders[np.isin(senders, sampled)] - # make spike count histograms of individual neurons - bins = np.arange(start, end + 0.1, bw) # time bins - hists = [np.histogram(times[senders == x], bins)[0].tolist() for x in set(senders)] - # make spiking histogram of all sampled neurons - hist_global = (np.histogram(times, bins)[0] / len(set(senders))).tolist() - # calculate local and global synchrony - print("Calculating neuronal local and global synchrony ...") - coefs, n_pair_pass, n_pair_fail = get_corr(hists) # local - lsync_mu, lsync_sd = np.mean(coefs), np.std(coefs) - gsync = np.var(hist_global) / np.mean(np.var(hists, axis=1)) # global - return rate, coefs, lsync_mu, lsync_sd, gsync, n_for_sync - - -def plot_synchrony(coefs, title, data_path): - print("Plotting pairwise spike count correlations ...") - plt.hist(coefs) - plt.title(title) - plt.xlabel("Pairwise spike count correlation (Pearson's r)") - plt.ylabel("n of pairs") - plt.tight_layout() - plt.savefig(os.path.join(data_path, "neuron_synchrony.png")) - plt.close() - - ############################################################################### # This function plots the dynamics in the astrocytes and their resultant output # to the neurons. The IP3 and calcium in the astrocytes and the SIC in neurons @@ -419,19 +339,6 @@ def run_simulation(data_path): plt.savefig(os.path.join(data_path, "neuron_raster.png"), bbox_inches="tight") plt.close() - # Calculate and plot synchrony - rate, coefs, lsync_mu, lsync_sd, gsync, n_for_sync = calc_synchrony( - neuron_spikes, n_neuron_rec_spk, pre_sim_time, pre_sim_time + sim_time, data_path - ) - title = ( - f"Firing rate={rate:.2f} spikes/s (n={n_neuron_rec_spk}) \n" - + f"Local sync.={lsync_mu:.3f}$\\pm${lsync_sd:.3f}, Global sync.={gsync:.3f}\n" - + f"(n for synchrony analysis={n_for_sync})\n" - ) - plot_synchrony(coefs, title, data_path) - print(f"Local synchrony = {lsync_mu:.3f}+-{lsync_sd:.3f}") - print(f"Global synchrony = {gsync:.3f}") - # Plot dynamics in astrocytes and neurons plot_dynamics(astro_data, neuron_data, data_path, pre_sim_time) diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 376ade5133..9907a4f2d7 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -23,12 +23,11 @@ A small neuron-astrocyte network ------------------------------------------------------------ -This script demonstrates an aproach with NEST to create a neuron-astrocyte -network. The network in this script includes 20 neurons and five astrocytes. The -astrocyte is modeled with ``astrocyte_lr_1994``, implemented according to [1]_, -[2]_, and [3]_. The neuron is modeled with ``aeif_cond_alpha_astro``, an -adaptive exponential integrate-and-fire neuron supporting neuron-astrocyte -interactions. +This script shows how to create an astrocyte-neuron network in NEST. The network +in this script includes 20 neurons and five astrocytes. The astrocyte is modeled +with ``astrocyte_lr_1994``, implemented according to [1]_, [2]_, and [3]_. The +neuron is modeled with ``aeif_cond_alpha_astro``, an adaptive exponential +integrate-and-fire neuron supporting neuron-astrocyte interactions. The network is created with the TripartiteConnect() function and the ``tripartite_bernoulli_with_pool`` rule. This rule creates a tripartite From 33fe0a27e43658f300d2c78e71f3450d40edb2f4 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 1 Nov 2023 17:38:15 +0100 Subject: [PATCH 58/93] Fix merge error --- models/astrocyte_lr_1994.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/models/astrocyte_lr_1994.cpp b/models/astrocyte_lr_1994.cpp index ef6d4e5b96..139647106a 100644 --- a/models/astrocyte_lr_1994.cpp +++ b/models/astrocyte_lr_1994.cpp @@ -490,16 +490,6 @@ nest::astrocyte_lr_1994::update( Time const& origin, const long from, const long kernel().event_delivery_manager.send_secondary( *this, sic ); } -/** - * Default implementation of register_stdp_connection( const std::string& name ) - * throws IllegalConnection - */ -void -nest::astrocyte_lr_1994::register_stdp_connection( double, double ) -{ - throw IllegalConnection( "The target node does not support STDP synapses." ); -} - void nest::astrocyte_lr_1994::handle( SpikeEvent& e ) { From 04ec00eaf569d26e08a75ddec0f586df3a222ee9 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 1 Nov 2023 17:54:08 +0100 Subject: [PATCH 59/93] Improved comment --- nestkernel/conn_builder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index 6e72fec5eb..1761e54855 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -494,7 +494,7 @@ class TripartiteBernoulliWithPoolBuilder : public ConnBuilder AuxiliaryBuilder third_out_builder_; double p_primary_; //!< connection probability for pre-post connections - double p_third_if_primary_; //!< probability third-factor connection if primary connection created + double p_third_if_primary_; //!< probability of third-factor connection if primary connection created bool random_pool_; //!< if true, select astrocyte pool at random size_t pool_size_; //!< size of third-factor pool size_t targets_per_third_; //!< target nodes per third-factor node From fb741a947dace411b7615d831de0ce6236259768 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 1 Nov 2023 19:54:14 +0100 Subject: [PATCH 60/93] Improve developer documentation for tripartite connectivity --- nestkernel/conn_builder.cpp | 7 +++-- nestkernel/conn_builder.h | 44 ++++++++++++++++++++++++++----- nestkernel/conn_builder_factory.h | 19 ++++++++++--- nestkernel/connection_manager.h | 5 +++- nestkernel/nest.h | 8 ++++-- nestkernel/node_collection.h | 2 +- nestkernel/random_generators.h | 3 ++- 7 files changed, 70 insertions(+), 18 deletions(-) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 5c4ff6cc10..8e28f5020a 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1643,7 +1643,7 @@ nest::TripartiteBernoulliWithPoolBuilder::TripartiteBernoulliWithPoolBuilder( No if ( p_primary_ < 0 or 1 < p_primary_ ) { - throw BadProperty( "Probability of neuron-neuron connections 0 ≤ p ≤ 1 required" ); + throw BadProperty( "Probability of primary connection 0 ≤ p_primary ≤ 1 required" ); } if ( p_third_if_primary_ < 0 or 1 < p_third_if_primary_ ) @@ -1687,7 +1687,10 @@ nest::TripartiteBernoulliWithPoolBuilder::connect_() try { - // use RNG generating same number sequence on all threads + /* Random number generators: + * - Use RNG generating same number sequence on all threads to decide which connections to create + * - Use per-thread random number generator to randomize connection properties + */ RngPtr synced_rng = get_vp_synced_rng( tid ); RngPtr rng = get_vp_specific_rng( tid ); diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index 1761e54855..544d26251c 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -76,6 +76,11 @@ class ConnBuilder const std::vector< DictionaryDatum >& syn_specs ); virtual ~ConnBuilder(); + /** + * Mark ConnBuilder subclasses as building tripartite rules or not. + * + * @note This flag is required for template specialisation of ConnBuilderFactory's. + */ static constexpr bool is_tripartite = false; size_t @@ -450,7 +455,12 @@ class BernoulliBuilder : public ConnBuilder }; /** - * Helper class to support parameter handlig for tripartite builders. + * Helper class to support parameter handling for tripartite builders. + * + * In tripartite builders, the actual builder class decides which connections to create and + * handles parameterization of the primary connection. For each third-party connection, + * it maintains an AuxiliaryBuilder which handles the parameterization of the corresponding + * third-party connection. */ class AuxiliaryBuilder : public ConnBuilder { @@ -467,18 +477,37 @@ class AuxiliaryBuilder : public ConnBuilder void connect_() override { + // The auxiliary builder does not create connections, it only parameterizes them. assert( false ); } }; +/** + * Class representing tripartite Bernoulli connector + * + * For each source-target pair, a Bernoulli trial is performed. If a primary connection is created, a third-factor + * connection is created conditionally on a second Bernoulli trial. The third-party neuron to be connected is + * chosen from a pool, which can either be set up in blocks or randomized. The third-party neuron receives + * input from the source neuron and provides output to the target neuron of the primary connection. + */ class TripartiteBernoulliWithPoolBuilder : public ConnBuilder { public: - TripartiteBernoulliWithPoolBuilder( NodeCollectionPTR, - NodeCollectionPTR, - NodeCollectionPTR, - const DictionaryDatum&, - const DictionaryDatum& ); + /** + * Constructor + * + * @param sources Source population for primary connection + * @param targets Target population for primary connection + * @param third Third-party population + * @param conn_spec Connection specification dictionary for tripartite bernoulli rule + * @param syn_specs Dictionary of synapse specifications for the three connections that may be created. Allowed keys + * are `"primary"`, `"third_in"`, `"third_out"` + */ + TripartiteBernoulliWithPoolBuilder( NodeCollectionPTR sources, + NodeCollectionPTR targets, + NodeCollectionPTR third, + const DictionaryDatum& conn_spec, + const DictionaryDatum& syn_specs ); static constexpr bool is_tripartite = true; @@ -486,7 +515,8 @@ class TripartiteBernoulliWithPoolBuilder : public ConnBuilder void connect_() override; private: - size_t get_first_pool_index_( const size_t ) const; + //! Provide index of first third-party node to be assigned to pool for given target node + size_t get_first_pool_index_( const size_t target_index ) const; NodeCollectionPTR third_; diff --git a/nestkernel/conn_builder_factory.h b/nestkernel/conn_builder_factory.h index 679f93c518..388fdef44b 100644 --- a/nestkernel/conn_builder_factory.h +++ b/nestkernel/conn_builder_factory.h @@ -49,10 +49,18 @@ class GenericConnBuilderFactory virtual ~GenericConnBuilderFactory() { } + + /** + * Factory method for builders for bi-partite connection rules (the default). + */ virtual ConnBuilder* create( NodeCollectionPTR, NodeCollectionPTR, const DictionaryDatum&, const std::vector< DictionaryDatum >& ) const = 0; + + /** + * Factory method for builders for tri-partite connection rules. + */ virtual ConnBuilder* create( NodeCollectionPTR, NodeCollectionPTR, NodeCollectionPTR, @@ -61,7 +69,12 @@ class GenericConnBuilderFactory }; /** - * Factory class for normal ConnBuilders + * Factory class for ConnBuilders + * + * This template class provides an interface with bipartite and tripartite `create()` methods. + * Implementation is delegated to explicit template specialisations below, which only implement + * the `create()` method with the proper arity depending on the `is_tripartite` flag of + * the pertaining conn builder. */ template < typename ConnBuilderType, bool is_tripartite = ConnBuilderType::is_tripartite > class ConnBuilderFactory : public GenericConnBuilderFactory @@ -89,7 +102,7 @@ class ConnBuilderFactory : public GenericConnBuilderFactory }; -// Specialisation for normal ConnBuilders +// Specialisation for bipartite ConnBuilders template < typename ConnBuilderType > class ConnBuilderFactory< ConnBuilderType, false > : public GenericConnBuilderFactory { @@ -102,7 +115,6 @@ class ConnBuilderFactory< ConnBuilderType, false > : public GenericConnBuilderFa return new ConnBuilderType( sources, targets, conn_spec, syn_specs ); } - //! create tripartite builder ConnBuilder* create( NodeCollectionPTR sources, NodeCollectionPTR targets, @@ -129,7 +141,6 @@ class ConnBuilderFactory< ConnBuilderType, true > : public GenericConnBuilderFac String::compose( "Connection rule %1 only supports tripartite connections.", ( *conn_spec )[ names::rule ] ) ); } - //! create tripartite builder ConnBuilder* create( NodeCollectionPTR sources, NodeCollectionPTR targets, diff --git a/nestkernel/connection_manager.h b/nestkernel/connection_manager.h index f36ba9cd2a..7905052c21 100644 --- a/nestkernel/connection_manager.h +++ b/nestkernel/connection_manager.h @@ -95,12 +95,14 @@ class ConnectionManager : public ManagerInterface template < typename ConnBuilder > void register_conn_builder( const std::string& name ); + //! Obtain builder for bipartite connections ConnBuilder* get_conn_builder( const std::string& name, NodeCollectionPTR sources, NodeCollectionPTR targets, const DictionaryDatum& conn_spec, const std::vector< DictionaryDatum >& syn_specs ); + //! Obtain builder for tripartite connections ConnBuilder* get_conn_builder( const std::string& name, NodeCollectionPTR sources, NodeCollectionPTR targets, @@ -184,7 +186,8 @@ class ConnectionManager : public ManagerInterface /** * @brief Create tripartite connections * - * @note `synapse_specs` is dictionary `{"primary": , "third_in": , "third_out": }` + * @note `synapse_specs` is dictionary `{"primary": , "third_in": , "third_out": }`; all + * keys are optional */ void connect_tripartite( NodeCollectionPTR sources, NodeCollectionPTR targets, diff --git a/nestkernel/nest.h b/nestkernel/nest.h index 9df0a50b60..77e53b6add 100644 --- a/nestkernel/nest.h +++ b/nestkernel/nest.h @@ -84,15 +84,19 @@ NodeCollectionPTR create( const Name& model_name, const size_t n ); NodeCollectionPTR get_nodes( const DictionaryDatum& dict, const bool local_only ); +/** + * Create bipartite connections. + */ void connect( NodeCollectionPTR sources, NodeCollectionPTR targets, const DictionaryDatum& connectivity, const std::vector< DictionaryDatum >& synapse_params ); /** - * @brief Create tripartite connections + * Create tripartite connections * - * @note `synapse_specs` is dictionary `{"primary": , "third_in": , "third_out": }` + * @note `synapse_specs` is dictionary `{"primary": , "third_in": , "third_out": }`; all + * entries are optional. */ void connect_tripartite( NodeCollectionPTR sources, NodeCollectionPTR targets, diff --git a/nestkernel/node_collection.h b/nestkernel/node_collection.h index 840a23d2ae..98b6394cd3 100644 --- a/nestkernel/node_collection.h +++ b/nestkernel/node_collection.h @@ -164,7 +164,7 @@ class nc_const_iterator bool operator<=( const nc_const_iterator& rhs ) const; nc_const_iterator& operator++(); - nc_const_iterator operator++( int ); // post-fix + nc_const_iterator operator++( int ); // postfix nc_const_iterator& operator+=( const size_t ); nc_const_iterator operator+( const size_t ) const; diff --git a/nestkernel/random_generators.h b/nestkernel/random_generators.h index 3091c9aa3a..3acd2d3665 100644 --- a/nestkernel/random_generators.h +++ b/nestkernel/random_generators.h @@ -123,6 +123,8 @@ class BaseRandomGenerator /** * @brief Wrap std::sample for selection from NodeCollection + * + * Inserts random sample without replacement of size n from range `[first, last)`into `dest`. */ virtual void sample( NodeCollection::const_iterator first, NodeCollection::const_iterator last, @@ -279,7 +281,6 @@ class RandomGenerator final : public BaseRandomGenerator return uniform_ulong_dist_( rng_, param ); } - //! Wrapper for std::sample() from C++17 inline void sample( NodeCollection::const_iterator first, NodeCollection::const_iterator last, From 7c97d865d3572c4bbd929a5a361f8f285a2ba9ae Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 1 Nov 2023 19:55:54 +0100 Subject: [PATCH 61/93] Improve user documentation for tripartite connectivity --- .../synapses/connection_management.rst | 80 +++++++++++++++++++ pynest/examples/astrocyte_brunel.py | 10 +-- pynest/examples/astrocyte_small_network.py | 18 ++--- pynest/nest/lib/hl_api_connections.py | 2 +- 4 files changed, 95 insertions(+), 15 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 88263bb3e4..ad5a7d71a6 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -256,6 +256,86 @@ must be ``True``. 'allow_autapses': False, 'make_symmetric': True} nest.Connect(A, B, conn_spec_dict) +.. _tripartite_connectivity: + +Tripartite connectivity +----------------------- + +NEST supports creating connections of three node populations using the +:py:func:`.TripartiteConnect` function: + +.. code-block:: python + + TripartiteConnect(pre, post, third, conn_spec) + TripartiteConnect(pre, post, third, conn_spec, syn_specs) + +``pre``, ``post``, and ``third`` are ``NodeCollections``, defining the nodes of +origin (`sources`) and termination (`targets`) as well as the third +factor to be included. Details of the connections created depend on +the connection rule used. + +``conn_spec`` must be provided and must specify a tripartite +connection rule. + +``syn_specs`` is a dictionary of the form + +.. code-block:: python + + {"primary": , + "third_in": , + "third_out": } + +where the individual ``syn_spec`` elements follow the same rules as +for the :py:func:`.Connect` function. Any of the three elements can be +left out, in which case the synapse specification defaults to +``"static_synapse"``. The ``"primary"`` synapse specification applies +to connections between ``pre`` and ``post`` nodes, the ``"third_in"`` +specification to connections between ``pre`` and ``third`` nodes and +the ``"third_out"`` specification to connections between ``third`` and +``post`` nodes. + +tripartite_bernoulli_with_pool +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For each possible pair of nodes from ``A`` and ``B``, a connection is +created with probability ``p_primary``, and these connections are +called "primary" connections. For each primary connection, a +third-party connection to a node from ``T`` is created with the +conditional probability ``p_third_if_primary``. The node from ``T`` to +connect to is chosen at random from a pool, a subset of the nodes in +``T``. By default, this pool is all of ``T``. + +Pool formation is controlled by parameters ``pool_type``, which can be ``"random"`` +(default) or ``"block"``, and ``pool_size`` which must be between 1 +and the size of ``T`` (default). For random pools, for each node from +``B``, ``pool_size`` nodes from ``T`` are chosen randomly without +replacement. + +For block pools, two variants exist. Let ``n(B)`` and ``n(T)`` be the number of +nodes in ``B`` and ``T``, respectively. If ``pool_size == 1``, the +first ``n(B)/n(T)`` nodes in ``B`` get are assigned the first node in +``T`` as their pool, the second ``n(B)/n(T)`` nodes in ``B`` the +second node in ``T`` and so forth. In this case, ``n(B)`` must be a +multiple of ``n(T)``. If ``pool_size > 1``, the first ``pool_size`` +elements of ``T`` are the pool for the first node in ``B``, the +second ``pool_size`` elements of ``T`` are the pool for the second +node in ``B`` and so forth. In this case, ``n(B) * pool_size == n(T)`` +is required. + +.. code-block:: python + + A = nest.Create('aeif_cond_alpha_astro', 10) + B = nest.Create('aeif_cond_alpha_astro', 20) + T = nest.Create('astrocyte_lr_1994', 5) + conn_spec_dict = {'rule': 'tripartite_bernoulli_with_pool', + 'p_primary': 0.8, + 'p_third_if_primary': 0.5, + 'pool_type': 'random', + 'pool_size': 3} + syn_specs_dict = {'third_out': 'sic_connection'} + nest.TripartiteConnect(A, B, T, conn_spec_dict, syn_specs_dict) + + .. _synapse_spec: Synapse Specification diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocyte_brunel.py index d7346ba751..f518f1589e 100644 --- a/pynest/examples/astrocyte_brunel.py +++ b/pynest/examples/astrocyte_brunel.py @@ -21,17 +21,17 @@ """ Random balanced network with astrocytes ------------------------------------------------------------- +--------------------------------------- This script simulates a random balanced network with excitatory and inhibitory -neurons and astrocytes. The astrocyte is modeled with ``astrocyte_lr_1994``, -implemented according to [1]_, [2]_, and [3]_. The neuron is modeled with +neurons and astrocytes. The astrocytes are modeled with ``astrocyte_lr_1994``, +implemented according to [1]_, [2]_, and [3]_. The neurons are modeled with ``aeif_cond_alpha_astro``, an adaptive exponential integrate-and-fire neuron supporting neuron-astrocyte interactions. The simulation results show how astrocytes affect neuronal excitability. The -dynamics in the astrocytes and the SIC in the neurons are shown in the plot -"dynamics.png". +dynamics in the astrocytes and the slow inward current in the neurons are +shown in the plot "dynamics.png". References ~~~~~~~~~~ diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocyte_small_network.py index 9907a4f2d7..b4eed0e6b7 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocyte_small_network.py @@ -21,12 +21,12 @@ """ A small neuron-astrocyte network ------------------------------------------------------------- +-------------------------------- This script shows how to create an astrocyte-neuron network in NEST. The network -in this script includes 20 neurons and five astrocytes. The astrocyte is modeled +in this script includes 20 neurons and five astrocytes. The astrocytes are modeled with ``astrocyte_lr_1994``, implemented according to [1]_, [2]_, and [3]_. The -neuron is modeled with ``aeif_cond_alpha_astro``, an adaptive exponential +neurons are modeled with ``aeif_cond_alpha_astro``, an adaptive exponential integrate-and-fire neuron supporting neuron-astrocyte interactions. The network is created with the TripartiteConnect() function and the @@ -59,9 +59,9 @@ target neuron can only be connected to astrocytes selected from its pool. * ``pool_type``: The way to determine the astrocyte pool for each target - neuron. If specified "random", a number (``pool_size``) of astrocytes are - randomly chosen from all astrocytes and assigned as the pool. If specified - "block", the astrocytes are evenly distributed to the neurons in blocks + neuron. If ``"random"``, a number (``pool_size``) of astrocytes are + randomly chosen from all astrocytes (without replacement) and assigned as the pool. If + ``"block"``, the astrocytes are evenly distributed to the neurons in blocks without overlapping, and the specified ``pool_size`` has to be compatible with this arrangement. @@ -73,11 +73,11 @@ * ``third_out``: specifications for the connections from astrocytes to neurons. -In this script, the network is created with the ``pool_type`` being "block". +In this script, the network is created with the ``pool_type`` being ``"block"``. ``p_primary`` and ``p_third_if_primary`` are both set to one, so that all possible connections are made. It can be seen from the result plot "connections.png" that -"block" distributes the astrocytes evenly to the postsynaptic neurons in blocks -without overlapping. The ``pool_size`` should be compatible with this +``"block"`` distributes the astrocytes evenly to the postsynaptic neurons in blocks +without overlapping. The ``pool_size`` must be compatible with this arrangement. In the case here, a ``pool_size`` of one is required. With the created network, neuron-astrocyte interactions can be observed. The diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index 89310bcc4a..94cf7b395c 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -332,7 +332,7 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): - 'tripartite_bernoulli_with_pool' - See :ref:`tripartite_conn_rules` for more details, including example usage. + See :ref:`tripartite_connectivity` for more details and :doc:`astrocyte_small_network` and :doc:`astrocyte_brunel` for examples. **Synapse specifications (syn_specs)** From 2120aa081b64dd5d5c02401e8b3d20b09957924f Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 1 Nov 2023 19:56:45 +0100 Subject: [PATCH 62/93] Create Whats new file for NEST 3.7 --- doc/htmldoc/whats_new/v3.7/index.rst | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 doc/htmldoc/whats_new/v3.7/index.rst diff --git a/doc/htmldoc/whats_new/v3.7/index.rst b/doc/htmldoc/whats_new/v3.7/index.rst new file mode 100644 index 0000000000..cef8c10b92 --- /dev/null +++ b/doc/htmldoc/whats_new/v3.7/index.rst @@ -0,0 +1,42 @@ +.. _release_3.7: + +What's new in NEST 3.7 +====================== + +This page contains a summary of important breaking and non-breaking +changes from NEST 3.6 to NEST 3.7. In addition to the `release notes +on GitHub `_, this +page also contains transition information that helps you to update +your simulation scripts when you come from an older version of NEST. + +If you transition from an earlier version, please see our extensive +:ref:`transition guide from NEST 2.x to 3.0 ` and the +:ref:`list of updates for previous releases in the 3.x series `. + + +NEST requires C++17 +------------------- + +From NEST 3.7 on, we use some C++17 features in NEST code. Therefore, +NEST needs to be built with a compiler that supports C++17. Most +recent C++ compilers should do so. + +Tripartite connectivity in NEST +------------------------------- + +NEST now supports creation of connections involving three populations +of neurons, a pre-synaptic, a post-synaptic and a third-factor +population. At present, as single tripartite connection rule is +available, ``tripartite_bernoulli_with_pool``. Tripartite connections +are created with the new :py:func:`.TripartiteConnect` function. The first +use case for tripartite connections are networks containing astrocyte +populations. + +See examples using astrocyte models: + +* :doc:`../../../auto_examples/astrocyte_small_network` +* :doc:`../../../auto_examples/astrocyte_brunel` + +See connectivity documentation: + +* :ref:`tripartite_connectivity` From 9b507f76c680672fa99f6b61521b4bd143996159 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 1 Nov 2023 23:30:23 +0100 Subject: [PATCH 63/93] Provide support for CollocatedSynapses --- nestkernel/conn_builder.cpp | 14 ++-- nestkernel/conn_builder.h | 2 +- nestkernel/conn_builder_factory.h | 8 +-- nestkernel/connection_manager.cpp | 20 ++++-- nestkernel/connection_manager.h | 4 +- nestkernel/nest.cpp | 2 +- nestkernel/nest.h | 2 +- nestkernel/nestmodule.cpp | 16 ++++- pynest/nest/lib/hl_api_connections.py | 12 ++-- ...y => test_connect_tripartite_bernoulli.py} | 2 +- testsuite/pytests/test_tripartite_connect.py | 67 +++++++++++++++++++ 11 files changed, 126 insertions(+), 23 deletions(-) rename testsuite/pytests/{test_connect_pairwise_bernoulli_astro.py => test_connect_tripartite_bernoulli.py} (99%) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index 8e28f5020a..fc06556adf 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1607,14 +1607,20 @@ nest::TripartiteBernoulliWithPoolBuilder::TripartiteBernoulliWithPoolBuilder( No NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& conn_spec, - const DictionaryDatum& syn_specs ) - : ConnBuilder( sources, targets, conn_spec, { getValue< DictionaryDatum >( ( *syn_specs )[ names::primary ] ) } ) + const std::map< Name, std::vector< DictionaryDatum > >& syn_specs ) + : ConnBuilder( sources, + targets, + conn_spec, + const_cast< std::map< Name, std::vector< DictionaryDatum > >& >( syn_specs )[ names::primary ] ) , third_( third ) - , third_in_builder_( sources, third, conn_spec, { getValue< DictionaryDatum >( ( *syn_specs )[ names::third_in ] ) } ) + , third_in_builder_( sources, + third, + conn_spec, + const_cast< std::map< Name, std::vector< DictionaryDatum > >& >( syn_specs )[ names::third_in ] ) , third_out_builder_( third, targets, conn_spec, - { getValue< DictionaryDatum >( ( *syn_specs )[ names::third_out ] ) } ) + const_cast< std::map< Name, std::vector< DictionaryDatum > >& >( syn_specs )[ names::third_out ] ) , p_primary_( 1.0 ) , p_third_if_primary_( 1.0 ) , random_pool_( true ) diff --git a/nestkernel/conn_builder.h b/nestkernel/conn_builder.h index 544d26251c..05f3b0dcc4 100644 --- a/nestkernel/conn_builder.h +++ b/nestkernel/conn_builder.h @@ -507,7 +507,7 @@ class TripartiteBernoulliWithPoolBuilder : public ConnBuilder NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& conn_spec, - const DictionaryDatum& syn_specs ); + const std::map< Name, std::vector< DictionaryDatum > >& syn_specs ); static constexpr bool is_tripartite = true; diff --git a/nestkernel/conn_builder_factory.h b/nestkernel/conn_builder_factory.h index 388fdef44b..0fe097c7c6 100644 --- a/nestkernel/conn_builder_factory.h +++ b/nestkernel/conn_builder_factory.h @@ -65,7 +65,7 @@ class GenericConnBuilderFactory NodeCollectionPTR, NodeCollectionPTR, const DictionaryDatum&, - const DictionaryDatum& ) const = 0; + const std::map< Name, std::vector< DictionaryDatum > >& ) const = 0; }; /** @@ -95,7 +95,7 @@ class ConnBuilderFactory : public GenericConnBuilderFactory NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& conn_spec, - const DictionaryDatum& syn_specs ) const override + const std::map< Name, std::vector< DictionaryDatum > >& syn_specs ) const override { assert( false ); // only specialisations should be called } @@ -120,7 +120,7 @@ class ConnBuilderFactory< ConnBuilderType, false > : public GenericConnBuilderFa NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& conn_spec, - const DictionaryDatum& syn_specs ) const override + const std::map< Name, std::vector< DictionaryDatum > >& syn_specs ) const override { throw IllegalConnection( String::compose( "Connection rule '%1' does not support tripartite connections.", ( *conn_spec )[ names::rule ] ) ); @@ -146,7 +146,7 @@ class ConnBuilderFactory< ConnBuilderType, true > : public GenericConnBuilderFac NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& conn_spec, - const DictionaryDatum& syn_specs ) const override + const std::map< Name, std::vector< DictionaryDatum > >& syn_specs ) const override { return new ConnBuilderType( sources, targets, third, conn_spec, syn_specs ); } diff --git a/nestkernel/connection_manager.cpp b/nestkernel/connection_manager.cpp index da87bd38b0..8b5008c4a4 100644 --- a/nestkernel/connection_manager.cpp +++ b/nestkernel/connection_manager.cpp @@ -369,7 +369,7 @@ nest::ConnectionManager::get_conn_builder( const std::string& name, NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& conn_spec, - const DictionaryDatum& syn_specs ) + const std::map< Name, std::vector< DictionaryDatum > >& syn_specs ) { const size_t rule_id = connruledict_->lookup( name ); ConnBuilder* cb = connbuilder_factories_.at( rule_id )->create( sources, targets, third, conn_spec, syn_specs ); @@ -764,7 +764,7 @@ nest::ConnectionManager::connect_tripartite( NodeCollectionPTR sources, NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& conn_spec, - const DictionaryDatum& syn_specs ) + const std::map< Name, std::vector< DictionaryDatum > >& syn_specs ) { if ( sources->empty() ) { @@ -780,7 +780,13 @@ nest::ConnectionManager::connect_tripartite( NodeCollectionPTR sources, } conn_spec->clear_access_flags(); - syn_specs->clear_access_flags(); + for ( auto& [ key, syn_spec_array ] : syn_specs ) + { + for ( auto& syn_spec : syn_spec_array ) + { + syn_spec->clear_access_flags(); + } + } if ( not conn_spec->known( names::rule ) ) { @@ -797,7 +803,13 @@ nest::ConnectionManager::connect_tripartite( NodeCollectionPTR sources, // at this point, all entries in conn_spec and syn_spec have been checked ALL_ENTRIES_ACCESSED( *conn_spec, "Connect", "Unread dictionary entries in conn_spec: " ); - ALL_ENTRIES_ACCESSED( *syn_specs, "Connect", "Unread dictionary entries in syn_specs: " ); + for ( auto& [ key, syn_spec_array ] : syn_specs ) + { + for ( auto& syn_spec : syn_spec_array ) + { + ALL_ENTRIES_ACCESSED( *syn_spec, "Connect", "Unread dictionary entries in syn_specs: " ); + } + } // Set flag before calling cb->connect() in case exception is thrown after some connections have been created. set_connections_have_changed(); diff --git a/nestkernel/connection_manager.h b/nestkernel/connection_manager.h index 7905052c21..ffb947c68b 100644 --- a/nestkernel/connection_manager.h +++ b/nestkernel/connection_manager.h @@ -108,7 +108,7 @@ class ConnectionManager : public ManagerInterface NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& conn_spec, - const DictionaryDatum& syn_specs ); + const std::map< Name, std::vector< DictionaryDatum > >& syn_specs ); /** * Create connections. @@ -193,7 +193,7 @@ class ConnectionManager : public ManagerInterface NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& connectivity, - const DictionaryDatum& synapse_specs ); + const std::map< Name, std::vector< DictionaryDatum > >& synapse_specs ); size_t find_connection( const size_t tid, const synindex syn_id, const size_t snode_id, const size_t tnode_id ); diff --git a/nestkernel/nest.cpp b/nestkernel/nest.cpp index d2cef37fbb..00e0e78fc8 100644 --- a/nestkernel/nest.cpp +++ b/nestkernel/nest.cpp @@ -192,7 +192,7 @@ connect_tripartite( NodeCollectionPTR sources, NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& connectivity, - const DictionaryDatum& synapse_specs ) + const std::map< Name, std::vector< DictionaryDatum > >& synapse_specs ) { kernel().connection_manager.connect_tripartite( sources, targets, third, connectivity, synapse_specs ); } diff --git a/nestkernel/nest.h b/nestkernel/nest.h index 77e53b6add..2ecfece57b 100644 --- a/nestkernel/nest.h +++ b/nestkernel/nest.h @@ -102,7 +102,7 @@ void connect_tripartite( NodeCollectionPTR sources, NodeCollectionPTR targets, NodeCollectionPTR third, const DictionaryDatum& connectivity, - const DictionaryDatum& synapse_specs ); + const std::map< Name, std::vector< DictionaryDatum > >& synapse_specs ); /** * @brief Connect arrays of node IDs one-to-one diff --git a/nestkernel/nestmodule.cpp b/nestkernel/nestmodule.cpp index c905d5b44c..0c31df6902 100644 --- a/nestkernel/nestmodule.cpp +++ b/nestkernel/nestmodule.cpp @@ -779,7 +779,21 @@ NestModule::ConnectTripartite_g_g_g_D_DFunction::execute( SLIInterpreter* i ) co NodeCollectionDatum targets = getValue< NodeCollectionDatum >( i->OStack.pick( 3 ) ); NodeCollectionDatum third = getValue< NodeCollectionDatum >( i->OStack.pick( 2 ) ); DictionaryDatum connectivity = getValue< DictionaryDatum >( i->OStack.pick( 1 ) ); - DictionaryDatum synapse_specs = getValue< DictionaryDatum >( i->OStack.pick( 0 ) ); + DictionaryDatum synapse_specs_dict = getValue< DictionaryDatum >( i->OStack.pick( 0 ) ); + + std::map< Name, std::vector< DictionaryDatum > > synapse_specs { + { names::primary, {} }, { names::third_in, {} }, { names::third_out, {} } + }; + + for ( auto& [ key, syn_spec_array ] : synapse_specs ) + { + ArrayDatum spec = getValue< ArrayDatum >( ( *synapse_specs_dict )[ key ] ); + + for ( auto syn_param : spec ) + { + syn_spec_array.push_back( getValue< DictionaryDatum >( syn_param ) ); + } + } // dictionary access checking is handled by connect connect_tripartite( sources, targets, third, connectivity, synapse_specs ); diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index 94cf7b395c..f1bdf07c6a 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -36,7 +36,7 @@ _process_syn_spec, ) from .hl_api_helper import is_string -from .hl_api_types import NodeCollection, SynapseCollection +from .hl_api_types import CollocatedSynapses, NodeCollection, SynapseCollection __all__ = [ "Connect", @@ -379,7 +379,7 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): if not isinstance(third, NodeCollection): raise TypeError("Third-factor nodes must be a NodeCollection") - # Normalize syn_specs: ensure all three entries are in place and do not contain lists + # Normalize syn_specs: ensure all three entries are in place, are collocated synapses and do not contain lists syn_specs = syn_specs if syn_specs is not None else dict() SYN_KEYS = {"primary", "third_in", "third_out"} for key in SYN_KEYS: @@ -387,8 +387,12 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): syn_specs[key] = {"synapse_model": "static_synapse"} elif isinstance(syn_specs[key], str): syn_specs[key] = {"synapse_model": syn_specs[key]} - else: - for entry, value in syn_specs[key].items(): + + if not isinstance(syn_specs[key], CollocatedSynapses): + syn_specs[key] = CollocatedSynapses(syn_specs[key]) + + for synspec in syn_specs[key].syn_specs: + for entry, value in synspec.items(): if isinstance(value, (list, tuple, numpy.ndarray)): raise ValueError( f"Tripartite connections do not accept parameter lists," diff --git a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py b/testsuite/pytests/test_connect_tripartite_bernoulli.py similarity index 99% rename from testsuite/pytests/test_connect_pairwise_bernoulli_astro.py rename to testsuite/pytests/test_connect_tripartite_bernoulli.py index 744922ec68..40d2dbf07b 100644 --- a/testsuite/pytests/test_connect_pairwise_bernoulli_astro.py +++ b/testsuite/pytests/test_connect_tripartite_bernoulli.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# test_connect_pairwise_bernoulli_astro.py +# test_connect_tripartite_bernoulli.py # # This file is part of NEST. # diff --git a/testsuite/pytests/test_tripartite_connect.py b/testsuite/pytests/test_tripartite_connect.py index c06f582792..115c128ecd 100644 --- a/testsuite/pytests/test_tripartite_connect.py +++ b/testsuite/pytests/test_tripartite_connect.py @@ -19,10 +19,25 @@ # You should have received a copy of the GNU General Public License # along with NEST. If not, see . +""" +Basic tests for tripartite connectivity. + +For statistical tests, see `test_connect_tripartite_bernoulli.py`. +""" + import nest import pytest +@pytest.fixture(autouse=True) +def reset_kernel(): + """ + Reset kernel to clear connections before each test. + """ + + nest.ResetKernel() + + def test_connect_all(): n_pre, n_post, n_third = 4, 2, 3 pre = nest.Create("parrot_neuron", n_pre) @@ -115,3 +130,55 @@ def test_bipartitet_raises(): with pytest.raises(nest.kernel.NESTErrors.IllegalConnection): nest.TripartiteConnect(pre, post, third, {"rule": "one_to_one"}) + + +def test_connect_complex_synspecs(): + n_pre, n_post, n_third = 4, 2, 3 + pre = nest.Create("parrot_neuron", n_pre) + post = nest.Create("parrot_neuron", n_post) + third = nest.Create("parrot_neuron", n_third) + + nest.TripartiteConnect( + pre, + post, + third, + {"rule": "tripartite_bernoulli_with_pool", "p_primary": 1.0, "p_third_if_primary": 1}, + { + "primary": nest.CollocatedSynapses( + {"synapse_model": "stdp_synapse", "weight": 2.0}, {"synapse_model": "tsodyks_synapse", "delay": 3.0} + ), + "third_in": nest.CollocatedSynapses( + {"synapse_model": "static_synapse", "weight": nest.random.uniform(0.5, 1.5)}, + {"synapse_model": "tsodyks2_synapse"}, + ), + "third_out": nest.CollocatedSynapses( + {"synapse_model": "static_synapse_lbl"}, + {"synapse_model": "stdp_synapse_hpc", "alpha": nest.random.uniform(0.5, 1.5)}, + ), + }, + ) + + n_primary = n_pre * n_post + c_stdp = nest.GetConnections(synapse_model="stdp_synapse") + assert len(c_stdp) == n_primary + assert set(c_stdp.weight) == {2.0} + + c_tsodyks = nest.GetConnections(synapse_model="tsodyks_synapse") + assert len(c_tsodyks) == n_primary + assert set(c_tsodyks.delay) == {3.0} + + c_static = nest.GetConnections(synapse_model="static_synapse") + assert len(c_static) == n_primary + assert len(set(c_static.weight)) == len(c_static) # random values, all different + + c_tsodyks2 = nest.GetConnections(synapse_model="tsodyks2_synapse") + assert len(c_tsodyks2) == n_primary + assert set(c_tsodyks2.delay) == {1.0} + + c_stat_lbl = nest.GetConnections(synapse_model="static_synapse_lbl") + assert len(c_stat_lbl) == n_primary + assert set(c_stat_lbl.weight) == {1.0} + + c_stdp_hpc = nest.GetConnections(synapse_model="stdp_synapse_hpc") + assert len(c_stdp_hpc) == n_primary + assert len(set(c_stdp_hpc.alpha)) == len(c_stdp_hpc) # random values, all different From e61029d45f954c1b87e6872244c2850328efeb94 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 1 Nov 2023 23:48:17 +0100 Subject: [PATCH 64/93] Fixed formatting --- pynest/nest/lib/hl_api_connections.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index f1bdf07c6a..7f5e8287cf 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -332,7 +332,8 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): - 'tripartite_bernoulli_with_pool' - See :ref:`tripartite_connectivity` for more details and :doc:`astrocyte_small_network` and :doc:`astrocyte_brunel` for examples. + See :ref:`tripartite_connectivity` for more details and :doc:`astrocyte_small_network` + and :doc:`astrocyte_brunel` for examples. **Synapse specifications (syn_specs)** From 51bb8904c5e580d8e59bef04735e9c7561146aa4 Mon Sep 17 00:00:00 2001 From: Hans Ekkehard Plesser Date: Wed, 1 Nov 2023 23:52:15 +0100 Subject: [PATCH 65/93] Added developer comment --- nestkernel/conn_builder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/nestkernel/conn_builder.cpp b/nestkernel/conn_builder.cpp index fc06556adf..18c2d2fa49 100644 --- a/nestkernel/conn_builder.cpp +++ b/nestkernel/conn_builder.cpp @@ -1611,6 +1611,7 @@ nest::TripartiteBernoulliWithPoolBuilder::TripartiteBernoulliWithPoolBuilder( No : ConnBuilder( sources, targets, conn_spec, + // const_cast here seems required, clang complains otherwise; try to clean up when Datums disappear const_cast< std::map< Name, std::vector< DictionaryDatum > >& >( syn_specs )[ names::primary ] ) , third_( third ) , third_in_builder_( sources, From afeb348bff4264861f7588357ed42d35a128f90b Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 3 Nov 2023 15:46:24 +0100 Subject: [PATCH 66/93] Apply suggestions from code review Co-authored-by: jessica-mitchell --- pynest/nest/lib/hl_api_connections.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index 7f5e8287cf..017a30c903 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -328,9 +328,9 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): ----- **Connectivity specification (conn_spec)** - Available tripartite rules:: + Available tripartite rules: - - 'tripartite_bernoulli_with_pool' +- ``tripartite_bernoulli_with_pool`` See :ref:`tripartite_connectivity` for more details and :doc:`astrocyte_small_network` and :doc:`astrocyte_brunel` for examples. @@ -338,21 +338,19 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): **Synapse specifications (syn_specs)** Synapse specifications for tripartite connections are given as a dictionary with specifications - for each of the three connections to be created + for each of the three connections to be created:: - ``` {"primary": , "third_in": , "third_out": } - ``` - Here, `"primary"` marks the synapse specification for the connections between `pre` and `post` neurons, - `"third_in"` for connections between `pre` and `third` neurons and `"third_out"` for connections between - `third` and `post` neurons. + Here, ``"primary"`` marks the synapse specification for the connections between ``pre`` and ``post`` neurons, + ``"third_in"`` for connections between ``pre`` and ``third`` neurons and ``"third_out"`` for connections between + ``third`` and ``post`` neurons. - Each `` entry can be any entry that would be possible as synapse specification - in a normal `Connect()` call. Any missing entries default to `static_synapse`. If no `syn_specs` argument - is given at all, all three entries default to `static_synapse`. + Each ```` entry can be any entry that would be possible as synapse specification + in a normal ``Connect()`` call. Any missing entries default to ``static_synapse``. If no ```` argument + is given at all, all three entries default to ``static_synapse``. The synapse model and its properties can be given either as a string identifying a specific synapse model (default: :cpp:class:`static_synapse `) or From d1299e3000f1584b3c5b3200ff9ebb93a7c7ec05 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 3 Nov 2023 15:54:09 +0100 Subject: [PATCH 67/93] Apply suggestions from code review Co-authored-by: jessica-mitchell --- pynest/nest/lib/hl_api_connections.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index 017a30c903..45cfc46a55 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -332,8 +332,8 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): - ``tripartite_bernoulli_with_pool`` - See :ref:`tripartite_connectivity` for more details and :doc:`astrocyte_small_network` - and :doc:`astrocyte_brunel` for examples. + See :ref:`tripartite_connectivity` for more details and :doc:`/auto_examples/astrocyte_small_network` + and :doc:`/auto_examples/astrocyte_brunel` for examples. **Synapse specifications (syn_specs)** From 9c3ff53879b7f50b7719d3ff3d8ecad0940d11c4 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 3 Nov 2023 20:14:03 +0100 Subject: [PATCH 68/93] Fix format for hl_api_connections.py --- pynest/nest/lib/hl_api_connections.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index 45cfc46a55..a435bb84cb 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -328,9 +328,9 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): ----- **Connectivity specification (conn_spec)** - Available tripartite rules: + Available tripartite rules:: -- ``tripartite_bernoulli_with_pool`` + - ``tripartite_bernoulli_with_pool`` See :ref:`tripartite_connectivity` for more details and :doc:`/auto_examples/astrocyte_small_network` and :doc:`/auto_examples/astrocyte_brunel` for examples. @@ -340,7 +340,7 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): Synapse specifications for tripartite connections are given as a dictionary with specifications for each of the three connections to be created:: - {"primary": , + {"primary": , "third_in": , "third_out": } From fa42ed11215b9e1103d184dbcf9fd955929105a1 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 3 Nov 2023 23:50:14 +0100 Subject: [PATCH 69/93] Move astrocyte example scripts to a dedicated folder --- doc/htmldoc/examples/index.rst | 16 ++++++++-------- doc/htmldoc/whats_new/v3.7/index.rst | 4 ++-- pynest/examples/astrocytes/README.rst | 2 ++ .../{ => astrocytes}/astrocyte_brunel.py | 0 .../{ => astrocytes}/astrocyte_single.py | 0 .../{ => astrocytes}/astrocyte_small_network.py | 8 ++++---- .../{ => astrocytes}/astrocyte_tripartite.py | 0 pynest/nest/lib/hl_api_connections.py | 4 ++-- 8 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 pynest/examples/astrocytes/README.rst rename pynest/examples/{ => astrocytes}/astrocyte_brunel.py (100%) rename pynest/examples/{ => astrocytes}/astrocyte_single.py (100%) rename pynest/examples/{ => astrocytes}/astrocyte_small_network.py (98%) rename pynest/examples/{ => astrocytes}/astrocyte_tripartite.py (100%) diff --git a/doc/htmldoc/examples/index.rst b/doc/htmldoc/examples/index.rst index a517ad53c7..40e6a826b6 100644 --- a/doc/htmldoc/examples/index.rst +++ b/doc/htmldoc/examples/index.rst @@ -57,10 +57,10 @@ PyNEST examples .. grid-item-card:: Astrocytes :img-top: ../static/img/astrocyte_tripartite.png - * :doc:`../auto_examples/astrocyte_single` - * :doc:`../auto_examples/astrocyte_tripartite` - * :doc:`../auto_examples/astrocyte_small_network` - * :doc:`../auto_examples/astrocyte_brunel` + * :doc:`../auto_examples/astrocytes/astrocyte_single` + * :doc:`../auto_examples/astrocytes/astrocyte_tripartite` + * :doc:`../auto_examples/astrocytes/astrocyte_small_network` + * :doc:`../auto_examples/astrocytes/astrocyte_brunel` .. grid:: 1 1 2 3 @@ -327,10 +327,10 @@ PyNEST examples ../auto_examples/csa_example ../auto_examples/csa_spatial_example ../auto_examples/hpc_benchmark - ../auto_examples/astrocyte_single - ../auto_examples/astrocyte_tripartite - ../auto_examples/astrocyte_small_network - ../auto_examples/astrocyte_brunel + ../auto_examples/astrocytes/astrocyte_single + ../auto_examples/astrocytes/astrocyte_tripartite + ../auto_examples/astrocytes/astrocyte_small_network + ../auto_examples/astrocytes/astrocyte_brunel .. toctree:: :hidden: diff --git a/doc/htmldoc/whats_new/v3.7/index.rst b/doc/htmldoc/whats_new/v3.7/index.rst index cef8c10b92..06ca61f5c4 100644 --- a/doc/htmldoc/whats_new/v3.7/index.rst +++ b/doc/htmldoc/whats_new/v3.7/index.rst @@ -34,8 +34,8 @@ populations. See examples using astrocyte models: -* :doc:`../../../auto_examples/astrocyte_small_network` -* :doc:`../../../auto_examples/astrocyte_brunel` +* :doc:`../../../auto_examples/astrocytes/astrocyte_small_network` +* :doc:`../../../auto_examples/astrocytes/astrocyte_brunel` See connectivity documentation: diff --git a/pynest/examples/astrocytes/README.rst b/pynest/examples/astrocytes/README.rst new file mode 100644 index 0000000000..446271b887 --- /dev/null +++ b/pynest/examples/astrocytes/README.rst @@ -0,0 +1,2 @@ +Astrocytes +========== diff --git a/pynest/examples/astrocyte_brunel.py b/pynest/examples/astrocytes/astrocyte_brunel.py similarity index 100% rename from pynest/examples/astrocyte_brunel.py rename to pynest/examples/astrocytes/astrocyte_brunel.py diff --git a/pynest/examples/astrocyte_single.py b/pynest/examples/astrocytes/astrocyte_single.py similarity index 100% rename from pynest/examples/astrocyte_single.py rename to pynest/examples/astrocytes/astrocyte_single.py diff --git a/pynest/examples/astrocyte_small_network.py b/pynest/examples/astrocytes/astrocyte_small_network.py similarity index 98% rename from pynest/examples/astrocyte_small_network.py rename to pynest/examples/astrocytes/astrocyte_small_network.py index b4eed0e6b7..7f129596a9 100644 --- a/pynest/examples/astrocyte_small_network.py +++ b/pynest/examples/astrocytes/astrocyte_small_network.py @@ -138,12 +138,12 @@ ############################################################################### # Network parameters. -n_neurons = 10 # number of source and target neurons -n_astrocytes = 5 # number of astrocytes +n_neurons = 8 # number of source and target neurons +n_astrocytes = 4 # number of astrocytes p_primary = 1.0 # connection probability between neurons p_third_if_primary = 1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte -pool_size = 1 # astrocyte pool size for each target neuron -pool_type = "block" # the way to determine the astrocyte pool for each target neuron +pool_size = 2 # astrocyte pool size for each target neuron +pool_type = "random" # the way to determine the astrocyte pool for each target neuron ############################################################################### # Astrocyte parameters. diff --git a/pynest/examples/astrocyte_tripartite.py b/pynest/examples/astrocytes/astrocyte_tripartite.py similarity index 100% rename from pynest/examples/astrocyte_tripartite.py rename to pynest/examples/astrocytes/astrocyte_tripartite.py diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index a435bb84cb..0cc8990ab7 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -332,8 +332,8 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): - ``tripartite_bernoulli_with_pool`` - See :ref:`tripartite_connectivity` for more details and :doc:`/auto_examples/astrocyte_small_network` - and :doc:`/auto_examples/astrocyte_brunel` for examples. + See :ref:`tripartite_connectivity` for more details and :doc:`/auto_examples/astrocytes/astrocyte_small_network` + and :doc:`/auto_examples/astrocytes/astrocyte_brunel` for examples. **Synapse specifications (syn_specs)** From 430cb2591fe8e9d83a59406c2a77f1d5f79c5710 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sat, 4 Nov 2023 23:37:15 +0100 Subject: [PATCH 70/93] Replace plt.savefig() with plt.show() in two astrocyte scripts --- .../examples/astrocytes/astrocyte_brunel.py | 94 ++++------- .../astrocytes/astrocyte_small_network.py | 147 +++++++++--------- 2 files changed, 108 insertions(+), 133 deletions(-) diff --git a/pynest/examples/astrocytes/astrocyte_brunel.py b/pynest/examples/astrocytes/astrocyte_brunel.py index f518f1589e..7faf7efa09 100644 --- a/pynest/examples/astrocytes/astrocyte_brunel.py +++ b/pynest/examples/astrocytes/astrocyte_brunel.py @@ -30,8 +30,9 @@ supporting neuron-astrocyte interactions. The simulation results show how astrocytes affect neuronal excitability. The -dynamics in the astrocytes and the slow inward current in the neurons are -shown in the plot "dynamics.png". +astrocytic dynamics, the slow inward current in the neurons induced by the +astrocytes, and the raster plot of neuronal firings are shown in the created +figures. References ~~~~~~~~~~ @@ -59,35 +60,29 @@ """ ############################################################################### -# Import all necessary modules for simulation, analysis and plotting. +# Import all necessary modules for simulation and plotting. -import hashlib -import json -import os import random import matplotlib.pyplot as plt import nest -import nest.raster_plot import numpy as np -plt.rcParams.update({"font.size": 13}) - ############################################################################### -# Simulation parameters. +# Set simulation parameters. sim_params = { "dt": 0.1, # simulation resolution in ms - "pre_sim_time": 100.0, # pre-simulation time in ms (excluded from analysis) + "pre_sim_time": 100.0, # pre-simulation time in ms (data not recorded) "sim_time": 1000.0, # simulation time in ms "N_rec_spk": 100, # number of samples (neuron) for spike detector "N_rec_mm": 50, # number of samples (neuron, astrocyte) for multimeter "n_threads": 4, # number of threads for NEST - "seed": 100, # seed for the "random" module (not NEST) + "seed": 100, # seed for the random module } ############################################################################### -# Network parameters. +# Set network parameters. network_params = { "N_ex": 8000, # number of excitatory neurons @@ -101,7 +96,6 @@ } syn_params = { - "synapse_model": "tsodyks_synapse", "w_a2n": 0.01, # weight of astrocyte-to-neuron connection "w_e": 1.0, # weight of excitatory connection in nS "w_i": -4.0, # weight of inhibitory connection in nS @@ -110,7 +104,7 @@ } ############################################################################### -# Astrocyte parameters. +# Set astrocyte parameters. astrocyte_model = "astrocyte_lr_1994" astrocyte_params = { @@ -120,7 +114,7 @@ } ############################################################################### -# Neuron parameters. +# Set neuron parameters. neuron_model = "aeif_cond_alpha_astro" tau_syn_ex = 2.0 @@ -140,8 +134,8 @@ # This function creates the nodes and build the network. The astrocytes only # respond to excitatory synaptic inputs; therefore, only the excitatory # neuron-neuron connections are paired with the astrocytes. The -# TripartiteConnect() function and the "tripartite_bernoulli_with_pool" rule are -# used to create the connectivity of the network. +# TripartiteConnect() function and the "tripartite_bernoulli_with_pool" rule +# are used to create the connectivity of the network. def create_astro_network(scale=1.0): @@ -161,9 +155,7 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. """ print("Connecting Poisson generator ...") assert scale >= 1.0, "scale must be >= 1.0" - nest.Connect( - nodes_noise, nodes_ex + nodes_in, syn_spec={"synapse_model": "static_synapse", "weight": syn_params["w_e"]} - ) + nest.Connect(nodes_noise, nodes_ex + nodes_in, syn_spec={"weight": syn_params["w_e"]}) print("Connecting neurons and astrocytes ...") # excitatory connections are paired with astrocytes # conn_spec and syn_spec according to the "tripartite_bernoulli_with_pool" rule @@ -176,13 +168,13 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. } syn_params_e = { "primary": { - "synapse_model": syn_params["synapse_model"], + "synapse_model": "tsodyks_synapse", "weight": syn_params["w_e"], "tau_psc": tau_syn_ex, "delay": syn_params["d_e"], }, "third_in": { - "synapse_model": syn_params["synapse_model"], + "synapse_model": "tsodyks_synapse", "weight": syn_params["w_e"], "tau_psc": tau_syn_ex, "delay": syn_params["d_e"], @@ -193,7 +185,7 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. # inhibitory connections are not paired with astrocytes conn_params_i = {"rule": "pairwise_bernoulli", "p": network_params["p_primary"] / scale} syn_params_i = { - "synapse_model": syn_params["synapse_model"], + "synapse_model": "tsodyks_synapse", "weight": syn_params["w_i"], "tau_psc": tau_syn_in, "delay": syn_params["d_i"], @@ -208,8 +200,7 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. # by lines and shaded areas, respectively. -def plot_dynamics(astro_data, neuron_data, data_path, start): - # plot dynamics +def plot_dynamics(astro_data, neuron_data, start): print("Plotting dynamics ...") # astrocyte data astro_mask = astro_data["times"] > start @@ -228,7 +219,7 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): neuron_times_set = list(set(neuron_times)) sic_means = np.array([np.mean(neuron_sic[neuron_times == t]) for t in neuron_times_set]) sic_sds = np.array([np.std(neuron_sic[neuron_times == t]) for t in neuron_times_set]) - # plots + # set plots fig, axes = plt.subplots(2, 1, sharex=True) color_ip3 = "tab:blue" color_cal = "tab:green" @@ -256,20 +247,15 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): neuron_times_set, sic_means + sic_sds, sic_means - sic_sds, alpha=0.3, linewidth=0.0, color=color_sic ) axes[1].plot(neuron_times_set, sic_means, linewidth=2, color=color_sic) - # save - plt.tight_layout() - plt.savefig(os.path.join(data_path, "dynamics.png")) - plt.close() ############################################################################### -# This is the main function for simulation. Here, the network is created and the -# neurons and astrocytes are randomly chosen to be connected with the recording -# devices. The sample numbers are determined by sim_params. After simulation, -# neuron and astrocyte data are analyzed and plotted. +# This is the main function for simulation. The network is created and the +# neurons and astrocytes are randomly chosen for recording. After simulation, +# recorded data of neurons and astrocytes are plotted. -def run_simulation(data_path): +def run_simulation(): # NEST configuration nest.ResetKernel() nest.resolution = sim_params["dt"] @@ -277,8 +263,8 @@ def run_simulation(data_path): nest.print_time = True nest.overwrite_files = True - # Create data folder - os.system(f"mkdir -p {data_path}") + # Use random seed for reproducible sampling + random.seed(sim_params["seed"]) # Simulation settings pre_sim_time = sim_params["pre_sim_time"] @@ -304,7 +290,7 @@ def run_simulation(data_path): n_neuron_rec_spk = min(len(neuron_list), sim_params["N_rec_spk"]) n_neuron_rec_mm = min(len(neuron_list), sim_params["N_rec_mm"]) n_astro_rec = min(len(astro), sim_params["N_rec_mm"]) - neuron_list_for_sr = sorted(random.sample(neuron_list, n_neuron_rec_spk)) + neuron_list_for_sr = neuron_list[: min(len(neuron_list), n_neuron_rec_spk)] neuron_list_for_mm = sorted(random.sample(neuron_list, n_neuron_rec_mm)) astro_list_for_mm = sorted(random.sample(astro_list, n_astro_rec)) nest.Connect(neuron_list_for_sr, sr_neuron) @@ -320,31 +306,19 @@ def run_simulation(data_path): neuron_data = mm_neuron.events astro_data = mm_astro.events - # Save parameters (debug) - params = { - "sim_params": sim_params, - "network_params": network_params, - "syn_params": syn_params, - "neuron_model": neuron_model, - "neuron_params_ex": neuron_params_ex, - "neuron_params_in": neuron_params_in, - "astrocyte_model": astrocyte_model, - "astrocyte_params": astrocyte_params, - } - with open(os.path.join(data_path, "params.json"), "w") as f: - json.dump(params, f, indent=4) - - # Make raster plot and histogram - nest.raster_plot.from_device(sr_neuron, hist=True, title="") - plt.savefig(os.path.join(data_path, "neuron_raster.png"), bbox_inches="tight") - plt.close() + # Make raster plot + nest.raster_plot.from_device( + sr_neuron, hist=True, title=f"Raster plot of neuron {neuron_list_for_sr[0]} to {neuron_list_for_sr[-1]}" + ) # Plot dynamics in astrocytes and neurons - plot_dynamics(astro_data, neuron_data, data_path, pre_sim_time) + plot_dynamics(astro_data, neuron_data, pre_sim_time) + + # Show plots + plt.show() ############################################################################### # Run simulation. -random.seed(sim_params["seed"]) -run_simulation("astrocyte_brunel") +run_simulation() diff --git a/pynest/examples/astrocytes/astrocyte_small_network.py b/pynest/examples/astrocytes/astrocyte_small_network.py index 7f129596a9..53d8c10ac1 100644 --- a/pynest/examples/astrocytes/astrocyte_small_network.py +++ b/pynest/examples/astrocytes/astrocyte_small_network.py @@ -23,13 +23,13 @@ A small neuron-astrocyte network -------------------------------- -This script shows how to create an astrocyte-neuron network in NEST. The network +This script shows how to create an neuron-astrocyte network in NEST. The network in this script includes 20 neurons and five astrocytes. The astrocytes are modeled with ``astrocyte_lr_1994``, implemented according to [1]_, [2]_, and [3]_. The neurons are modeled with ``aeif_cond_alpha_astro``, an adaptive exponential integrate-and-fire neuron supporting neuron-astrocyte interactions. -The network is created with the TripartiteConnect() function and the +The network is created with the ``TripartiteConnect()`` function and the ``tripartite_bernoulli_with_pool`` rule. This rule creates a tripartite Bernoulli connectivity with the following principles: @@ -60,37 +60,47 @@ * ``pool_type``: The way to determine the astrocyte pool for each target neuron. If ``"random"``, a number (``pool_size``) of astrocytes are - randomly chosen from all astrocytes (without replacement) and assigned as the pool. If - ``"block"``, the astrocytes are evenly distributed to the neurons in blocks - without overlapping, and the specified ``pool_size`` has to be compatible - with this arrangement. + randomly chosen from all astrocytes (without replacement) and assigned as + the pool. If ``"block"``, the astrocytes are evenly distributed to the + neurons in blocks without overlapping, and the specified ``pool_size`` has + to be compatible with this arrangement. See :ref:`tripartite_connectivity` + for more details about ``pool_type``. * ``syn_specs`` parameters - * ``primary``: specifications for the connections between neurons. + * ``primary``: ``syn_spec`` specifications for the connections between neurons. - * ``third_in``: specifications for the connections from neurons to astrocytes. + * ``third_in``: ``syn_spec`` specifications for the connections from neurons to astrocytes. - * ``third_out``: specifications for the connections from astrocytes to neurons. + * ``third_out``: ``syn_spec`` specifications for the connections from astrocytes to neurons. In this script, the network is created with the ``pool_type`` being ``"block"``. ``p_primary`` and ``p_third_if_primary`` are both set to one, so that all possible connections are made. It can be seen from the result plot "connections.png" that ``"block"`` distributes the astrocytes evenly to the postsynaptic neurons in blocks -without overlapping. The ``pool_size`` must be compatible with this -arrangement. In the case here, a ``pool_size`` of one is required. +without overlapping. The ``pool_size`` must be compatible with this arrangement. +In the case here, a ``pool_size`` of one is required. One of the created figures +shows the connections actually made between neurons and astrocytes as a result +(note that multiple connections may exist between nodes; this cannot be seen +from the figure because of overlapping). Users can try different parameters +(e.g. ``p_primary``=0.5 and ``p_third_if_primary``=0.5) to see changes in the +connections. With the created network, neuron-astrocyte interactions can be observed. The presynaptic spikes induce the generation of IP3, which then changes the calcium concentration in the astrocytes. This change in calcium then induces the slow -inward current (SIC) in the neurons through the ``sic_connection``. These -dynamics are shown in the plot "dynamics.png". The changes in membrane potential -in the presynaptic and postsynaptic neurons are shown in the plot "V_m.png". +inward current (SIC) in the neurons through the ``sic_connection``. The changes +in membrane potential of the presynaptic and postsynaptic neurons are also +recorded. These data are shown in the created figures. The ``pool_type`` can be changed to "random" to see the results with random astrocyte pools. In that case, the ``pool_size`` can be any from one to the total number of astrocytes. +See :ref:`tripartite_connectivity` for more details about the +``TripartiteConnect()`` function and the ``tripartite_bernoulli_with_pool`` +rule. + References ~~~~~~~~~~ @@ -119,34 +129,27 @@ ############################################################################### # Import all necessary modules. -import hashlib as hl -import os - import matplotlib.pyplot as plt import nest import numpy as np -plt.rcParams.update({"font.size": 13}) - ############################################################################### -# Initialize NEST kernel and create folder to save data. +# Initialize NEST kernel. nest.ResetKernel() -save_path = "astrocyte_small_network" -os.system(f"mkdir -p {save_path}") ############################################################################### -# Network parameters. +# Set network parameters. -n_neurons = 8 # number of source and target neurons -n_astrocytes = 4 # number of astrocytes -p_primary = 1.0 # connection probability between neurons -p_third_if_primary = 1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte -pool_size = 2 # astrocyte pool size for each target neuron -pool_type = "random" # the way to determine the astrocyte pool for each target neuron +n_neurons = 10 # number of source and target neurons +n_astrocytes = 5 # number of astrocytes +p_primary = 0.5 #1.0 # connection probability between neurons +p_third_if_primary = 0.5 #1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte +pool_size = 1 # astrocyte pool size for each target neuron +pool_type = "block" # the way to determine the astrocyte pool for each target neuron ############################################################################### -# Astrocyte parameters. +# Set astrocyte parameters. astrocyte_model = "astrocyte_lr_1994" astrocyte_params = { @@ -156,7 +159,7 @@ } ############################################################################### -# Neuron parameters. +# Set neuron parameters. neuron_model = "aeif_cond_alpha_astro" neuron_params = { @@ -203,17 +206,15 @@ conns_n2a = nest.GetConnections(pre_neurons, astrocytes) ############################################################################### -# Functions for plotting. For neuron and astrocyte data, means and standard -# deviations across sampled nodes are indicated by lines and shaded areas, -# respectively. +# Functions for plotting. # Plot all connections between neurons and astrocytes -def plot_connections(conn_n2n, conn_n2a, conn_a2n, data_path): - # helper function to create lists of positions for source and target nodes, for plotting - def get_node_positions(dict_in): - source_list = dict_in["source"] - np.unique(dict_in["source"]).mean() - target_list = dict_in["target"] - np.unique(dict_in["target"]).mean() +def plot_connections(conn_n2n, conn_n2a, conn_a2n): + # helper function to create lists of positions for source and target nodes + def get_node_positions(dict_in, pre_1st_id, pre_last_id, post_1st_id, post_last_id): + source_list = np.array(dict_in["source"]) - (pre_1st_id + pre_last_id)/2 + target_list = np.array(dict_in["target"]) - (post_1st_id + post_last_id)/2 return source_list, target_list # helper function to set plot frames invisible @@ -227,33 +228,40 @@ def set_frame_invisible(ax): print("Plotting connections ...") # prepare data (lists of node positions) - slist_n2n, tlist_n2n = get_node_positions(conns_n2n.get()) - slist_n2a, alist_n2a = get_node_positions(conns_n2a.get()) - alist_a2n, tlist_a2n = get_node_positions(conns_a2n.get()) + pre_id_list = pre_neurons.tolist() + post_id_list = post_neurons.tolist() + astro_id_list = astrocytes.tolist() + slist_n2n, tlist_n2n = get_node_positions(conns_n2n.get(), pre_id_list[0], pre_id_list[-1], post_id_list[0], post_id_list[-1]) + slist_n2a, alist_n2a = get_node_positions(conns_n2a.get(), pre_id_list[0], pre_id_list[-1], astro_id_list[0], astro_id_list[-1]) + alist_a2n, tlist_a2n = get_node_positions(conns_a2n.get(), astro_id_list[0], astro_id_list[-1], post_id_list[0], post_id_list[-1]) # make plot fig, axs = plt.subplots(1, 1, figsize=(10, 8)) - # plot nodes (need the sets of node positions) + # plot nodes and connections axs.scatter( - list(set(slist_n2a)), [2] * len(set(slist_n2a)), s=400, color="gray", marker="^", label="pre_neurons", zorder=3 + np.array(pre_id_list) - (pre_id_list[0] + pre_id_list[-1])/2, + [2] * len(pre_id_list), s=300, color="gray", marker="^", label="pre_neurons", zorder=3 ) + for i, (sx, tx) in enumerate(zip(slist_n2n, tlist_n2n)): + label = "neuron-to-neuron" if i == 0 else None + axs.plot([sx, tx], [2, 0], linestyle=":", color="b", alpha=0.3, linewidth=2, label=label) axs.scatter( - list(set(alist_a2n)), [1] * len(set(alist_a2n)), s=400, color="g", marker="o", label="astrocytes", zorder=3 + np.array(post_id_list) - (post_id_list[0] + post_id_list[-1])/2, + [0] * len(post_id_list), s=300, color="k", marker="^", label="post_neurons", zorder=3 ) + for i, (sx, tx) in enumerate(zip(slist_n2a, alist_n2a)): + label = "neuron-to-astrocyte" if i == 0 else None + axs.plot([sx, tx], [2, 1], linestyle="-", color="orange", alpha=0.5, linewidth=2, label=label) axs.scatter( - list(set(tlist_a2n)), [0] * len(set(tlist_a2n)), s=400, color="k", marker="^", label="post_neurons", zorder=3 + np.array(astro_id_list) - (astro_id_list[0] + astro_id_list[-1])/2, + [1] * len(astro_id_list), s=300, color="g", marker="o", label="astrocytes", zorder=3 ) - # plot connections - for sx, tx in zip(slist_n2n, tlist_n2n): - axs.plot([sx, tx], [2, 0], linestyle=":", color="b", alpha=0.5, linewidth=1) - for sx, tx in zip(slist_n2a, alist_n2a): - axs.plot([sx, tx], [2, 1], linestyle="-", color="orange", alpha=0.5, linewidth=2) - for sx, tx in zip(alist_a2n, tlist_a2n): - axs.plot([sx, tx], [1, 0], linestyle="-", color="g", alpha=0.8, linewidth=4) - # tweak and save - axs.legend(bbox_to_anchor=(0.5, 1.1), loc="upper center", ncol=3) + for i, (sx, tx) in enumerate(zip(alist_a2n, tlist_a2n)): + label = "astrocyte-to-neuron" if i == 0 else None + axs.plot([sx, tx], [1, 0], linestyle="-", color="g", alpha=0.8, linewidth=4, label=label) + # tweak legends and frames + legend = axs.legend(bbox_to_anchor=(0.5, 1.15), loc="upper center", ncol=3, labelspacing=1.5) + legend.set_frame_on(False) set_frame_invisible(axs) - plt.tight_layout() - plt.savefig(os.path.join(data_path, "connections.png")) # Helper function to mask data by start time @@ -276,7 +284,7 @@ def get_plot_data(data_in, variable): # Plot membrane potentials of presynaptic and postsynaptic neurons -def plot_vm(pre_data, post_data, data_path, start): +def plot_vm(pre_data, post_data, start): print("Plotting V_m ...") # get presynaptic data pre_data_masked = mask_data_by_start(pre_data, start) @@ -288,28 +296,24 @@ def plot_vm(pre_data, post_data, data_path, start): fig, axes = plt.subplots(2, 1, sharex=True) color_pre = color_post = "tab:blue" # plot presynaptic membrane potential - axes[0].set_title(f"presynaptic neurons (n={len(set(pre_data['senders']))})") + axes[0].set_title(f"membrane potential of presynaptic neurons (n={len(set(pre_data['senders']))})") axes[0].set_ylabel(r"$V_{m}$ (mV)") axes[0].fill_between( pre_times, pre_vm_mean + pre_vm_sd, pre_vm_mean - pre_vm_sd, alpha=0.3, linewidth=0.0, color=color_pre ) axes[0].plot(pre_times, pre_vm_mean, linewidth=2, color=color_pre) # plot postsynaptic membrane potential - axes[1].set_title(f"postsynaptic neurons (n={len(set(post_data['senders']))})") + axes[1].set_title(f"membrane potential of postsynaptic neurons (n={len(set(post_data['senders']))})") axes[1].set_ylabel(r"$V_{m}$ (mV)") axes[1].set_xlabel("Time (ms)") axes[1].fill_between( post_times, post_vm_mean + post_vm_sd, post_vm_mean - post_vm_sd, alpha=0.3, linewidth=0.0, color=color_post ) axes[1].plot(post_times, post_vm_mean, linewidth=2, color=color_post) - # save - plt.tight_layout() - plt.savefig(os.path.join(data_path, "V_m.png")) - plt.close() # Plot dynamics in astrocytes and SIC in neurons -def plot_dynamics(astro_data, neuron_data, data_path, start): +def plot_dynamics(astro_data, neuron_data, start): print("Plotting dynamics ...") # get astrocyte data astro_data_masked = mask_data_by_start(astro_data, start) @@ -358,16 +362,13 @@ def plot_dynamics(astro_data, neuron_data, data_path, start): color=color_sic, ) axes[1].plot(neuron_times, neuron_sic_mean, linewidth=2, color=color_sic) - # save - plt.tight_layout() - plt.savefig(os.path.join(data_path, "dynamics.png")) - plt.close() ############################################################################### -# Run simulation and save results. +# Run simulation. nest.Simulate(1000.0) -plot_connections(conns_n2n, conns_n2a, conns_a2n, save_path) -plot_vm(mm_pre_neurons.events, mm_post_neurons.events, save_path, 0.0) -plot_dynamics(mm_astrocytes.events, mm_post_neurons.events, save_path, 0.0) +plot_connections(conns_n2n, conns_n2a, conns_a2n) +plot_vm(mm_pre_neurons.events, mm_post_neurons.events, 0.0) +plot_dynamics(mm_astrocytes.events, mm_post_neurons.events, 0.0) +plt.show() From 3cda6f5aa4ef68520853140dbdb54b48cee446b0 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sun, 5 Nov 2023 00:29:56 +0100 Subject: [PATCH 71/93] Improve astrocyte_small_network.py --- .../astrocytes/astrocyte_small_network.py | 126 +++++++++--------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/pynest/examples/astrocytes/astrocyte_small_network.py b/pynest/examples/astrocytes/astrocyte_small_network.py index 53d8c10ac1..32f8bc3ee0 100644 --- a/pynest/examples/astrocytes/astrocyte_small_network.py +++ b/pynest/examples/astrocytes/astrocyte_small_network.py @@ -23,7 +23,7 @@ A small neuron-astrocyte network -------------------------------- -This script shows how to create an neuron-astrocyte network in NEST. The network +This script shows how to create a neuron-astrocyte network in NEST. The network in this script includes 20 neurons and five astrocytes. The astrocytes are modeled with ``astrocyte_lr_1994``, implemented according to [1]_, [2]_, and [3]_. The neurons are modeled with ``aeif_cond_alpha_astro``, an adaptive exponential @@ -75,15 +75,15 @@ * ``third_out``: ``syn_spec`` specifications for the connections from astrocytes to neurons. In this script, the network is created with the ``pool_type`` being ``"block"``. -``p_primary`` and ``p_third_if_primary`` are both set to one, so that all possible -connections are made. It can be seen from the result plot "connections.png" that -``"block"`` distributes the astrocytes evenly to the postsynaptic neurons in blocks -without overlapping. The ``pool_size`` must be compatible with this arrangement. -In the case here, a ``pool_size`` of one is required. One of the created figures -shows the connections actually made between neurons and astrocytes as a result -(note that multiple connections may exist between nodes; this cannot be seen -from the figure because of overlapping). Users can try different parameters -(e.g. ``p_primary``=0.5 and ``p_third_if_primary``=0.5) to see changes in the +``p_primary`` and ``p_third_if_primary`` are both set to one to include as many +connections as possible. One of the created figures shows the connections between +neurons and astrocytes as a result (note that multiple connections may exist +between a pair of nodes; this is not obvious in the figure since connections +drawn later cover previous ones). It can be seen from the figure that ``"block"`` +results in astrocytes being connected to postsynaptic neurons in non-overlapping +blocks. The ``pool_size`` should be compatible with this arrangement; in the case +here, a ``pool_size`` of one is required. Users can try different parameters +(e.g. ``p_primary`` = 0.5 and ``p_third_if_primary`` = 0.5) to see changes in connections. With the created network, neuron-astrocyte interactions can be observed. The @@ -91,7 +91,7 @@ concentration in the astrocytes. This change in calcium then induces the slow inward current (SIC) in the neurons through the ``sic_connection``. The changes in membrane potential of the presynaptic and postsynaptic neurons are also -recorded. These data are shown in the created figures. +recorded. These data are also shown in the created figures. The ``pool_type`` can be changed to "random" to see the results with random astrocyte pools. In that case, the ``pool_size`` can be any from one to the @@ -167,54 +167,16 @@ "I_e": 1000.0, # external current input in pA } -############################################################################### -# Create and connect populations and devices. The neurons and astrocytes are -# connected with multimeters to record their dynamics. - -pre_neurons = nest.Create(neuron_model, n_neurons, params=neuron_params) -post_neurons = nest.Create(neuron_model, n_neurons, params=neuron_params) -astrocytes = nest.Create(astrocyte_model, n_astrocytes, params=astrocyte_params) -nest.TripartiteConnect( - pre_neurons, - post_neurons, - astrocytes, - conn_spec={ - "rule": "tripartite_bernoulli_with_pool", - "p_primary": p_primary, - "p_third_if_primary": p_third_if_primary, - "pool_size": pool_size, - "pool_type": pool_type, - }, - syn_specs={ - "primary": {"synapse_model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, - "third_in": {"synapse_model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, - "third_out": {"synapse_model": "sic_connection", "weight": 1.0, "delay": 1.0}, - }, -) -mm_pre_neurons = nest.Create("multimeter", params={"record_from": ["V_m"]}) -mm_post_neurons = nest.Create("multimeter", params={"record_from": ["V_m", "I_SIC"]}) -mm_astrocytes = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) -nest.Connect(mm_pre_neurons, pre_neurons) -nest.Connect(mm_post_neurons, post_neurons) -nest.Connect(mm_astrocytes, astrocytes) - -############################################################################### -# Get connection data. The data are used to plot the network connectivity. - -conns_a2n = nest.GetConnections(astrocytes, post_neurons) -conns_n2n = nest.GetConnections(pre_neurons, post_neurons) -conns_n2a = nest.GetConnections(pre_neurons, astrocytes) - ############################################################################### # Functions for plotting. # Plot all connections between neurons and astrocytes -def plot_connections(conn_n2n, conn_n2a, conn_a2n): +def plot_connections(conn_n2n, conn_n2a, conn_a2n, pre_id_list, post_id_list, astro_id_list): # helper function to create lists of positions for source and target nodes - def get_node_positions(dict_in, pre_1st_id, pre_last_id, post_1st_id, post_last_id): - source_list = np.array(dict_in["source"]) - (pre_1st_id + pre_last_id)/2 - target_list = np.array(dict_in["target"]) - (post_1st_id + post_last_id)/2 + def get_node_positions(dict_in, source_center, target_center): + source_list = np.array(dict_in["source"]) - source_center + target_list = np.array(dict_in["target"]) - target_center return source_list, target_list # helper function to set plot frames invisible @@ -228,31 +190,31 @@ def set_frame_invisible(ax): print("Plotting connections ...") # prepare data (lists of node positions) - pre_id_list = pre_neurons.tolist() - post_id_list = post_neurons.tolist() - astro_id_list = astrocytes.tolist() - slist_n2n, tlist_n2n = get_node_positions(conns_n2n.get(), pre_id_list[0], pre_id_list[-1], post_id_list[0], post_id_list[-1]) - slist_n2a, alist_n2a = get_node_positions(conns_n2a.get(), pre_id_list[0], pre_id_list[-1], astro_id_list[0], astro_id_list[-1]) - alist_a2n, tlist_a2n = get_node_positions(conns_a2n.get(), astro_id_list[0], astro_id_list[-1], post_id_list[0], post_id_list[-1]) + pre_id_center = (pre_id_list[0] + pre_id_list[-1])/2 + post_id_center = (post_id_list[0] + post_id_list[-1])/2 + astro_id_center = (astro_id_list[0] + astro_id_list[-1])/2 + slist_n2n, tlist_n2n = get_node_positions(conns_n2n.get(), pre_id_center, post_id_center) + slist_n2a, alist_n2a = get_node_positions(conns_n2a.get(), pre_id_center, astro_id_center) + alist_a2n, tlist_a2n = get_node_positions(conns_a2n.get(), astro_id_center, post_id_center) # make plot fig, axs = plt.subplots(1, 1, figsize=(10, 8)) # plot nodes and connections axs.scatter( - np.array(pre_id_list) - (pre_id_list[0] + pre_id_list[-1])/2, + np.array(pre_id_list) - pre_id_center, [2] * len(pre_id_list), s=300, color="gray", marker="^", label="pre_neurons", zorder=3 ) for i, (sx, tx) in enumerate(zip(slist_n2n, tlist_n2n)): label = "neuron-to-neuron" if i == 0 else None axs.plot([sx, tx], [2, 0], linestyle=":", color="b", alpha=0.3, linewidth=2, label=label) axs.scatter( - np.array(post_id_list) - (post_id_list[0] + post_id_list[-1])/2, + np.array(post_id_list) - post_id_center, [0] * len(post_id_list), s=300, color="k", marker="^", label="post_neurons", zorder=3 ) for i, (sx, tx) in enumerate(zip(slist_n2a, alist_n2a)): label = "neuron-to-astrocyte" if i == 0 else None axs.plot([sx, tx], [2, 1], linestyle="-", color="orange", alpha=0.5, linewidth=2, label=label) axs.scatter( - np.array(astro_id_list) - (astro_id_list[0] + astro_id_list[-1])/2, + np.array(astro_id_list) - astro_id_center, [1] * len(astro_id_list), s=300, color="g", marker="o", label="astrocytes", zorder=3 ) for i, (sx, tx) in enumerate(zip(alist_a2n, tlist_a2n)): @@ -364,11 +326,49 @@ def plot_dynamics(astro_data, neuron_data, start): axes[1].plot(neuron_times, neuron_sic_mean, linewidth=2, color=color_sic) +############################################################################### +# Create and connect populations and devices. The neurons and astrocytes are +# connected with multimeters to record their dynamics. + +pre_neurons = nest.Create(neuron_model, n_neurons, params=neuron_params) +post_neurons = nest.Create(neuron_model, n_neurons, params=neuron_params) +astrocytes = nest.Create(astrocyte_model, n_astrocytes, params=astrocyte_params) +nest.TripartiteConnect( + pre_neurons, + post_neurons, + astrocytes, + conn_spec={ + "rule": "tripartite_bernoulli_with_pool", + "p_primary": p_primary, + "p_third_if_primary": p_third_if_primary, + "pool_size": pool_size, + "pool_type": pool_type, + }, + syn_specs={ + "primary": {"synapse_model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, + "third_in": {"synapse_model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, + "third_out": {"synapse_model": "sic_connection", "weight": 1.0, "delay": 1.0}, + }, +) +mm_pre_neurons = nest.Create("multimeter", params={"record_from": ["V_m"]}) +mm_post_neurons = nest.Create("multimeter", params={"record_from": ["V_m", "I_SIC"]}) +mm_astrocytes = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) +nest.Connect(mm_pre_neurons, pre_neurons) +nest.Connect(mm_post_neurons, post_neurons) +nest.Connect(mm_astrocytes, astrocytes) + +############################################################################### +# Get connection data. The data are used to plot the network connectivity. + +conns_a2n = nest.GetConnections(astrocytes, post_neurons) +conns_n2n = nest.GetConnections(pre_neurons, post_neurons) +conns_n2a = nest.GetConnections(pre_neurons, astrocytes) + ############################################################################### # Run simulation. nest.Simulate(1000.0) -plot_connections(conns_n2n, conns_n2a, conns_a2n) +plot_connections(conns_n2n, conns_n2a, conns_a2n, pre_neurons.tolist(), post_neurons.tolist(), astrocytes.tolist()) plot_vm(mm_pre_neurons.events, mm_post_neurons.events, 0.0) plot_dynamics(mm_astrocytes.events, mm_post_neurons.events, 0.0) plt.show() From 45133a3ba218bbbe19550ae7aeb551cf03a0e5c9 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sun, 5 Nov 2023 11:11:28 +0100 Subject: [PATCH 72/93] Improve astrocyte_small_network.py --- .../astrocytes/astrocyte_small_network.py | 138 +++++++++--------- 1 file changed, 73 insertions(+), 65 deletions(-) diff --git a/pynest/examples/astrocytes/astrocyte_small_network.py b/pynest/examples/astrocytes/astrocyte_small_network.py index 32f8bc3ee0..2f01e3c853 100644 --- a/pynest/examples/astrocytes/astrocyte_small_network.py +++ b/pynest/examples/astrocytes/astrocyte_small_network.py @@ -30,21 +30,22 @@ integrate-and-fire neuron supporting neuron-astrocyte interactions. The network is created with the ``TripartiteConnect()`` function and the -``tripartite_bernoulli_with_pool`` rule. This rule creates a tripartite -Bernoulli connectivity with the following principles: +``tripartite_bernoulli_with_pool`` rule (see :ref:`tripartite_connectivity` for +detailed descriptions). This rule creates a tripartite Bernoulli connectivity +with the following principles: 1. For each pair of neurons, a Bernoulli trial with a probability ``p_primary`` -determines if they will be connected. +determines if a ``tsodyks_synapse`` will be created between them. 2. For each neuron-neuron connection created, a Bernoulli trial with a probability ``p_third_if_primary`` determines if it will be paired with one astrocyte. The selection of this particular astrocyte is confined by ``pool_size`` and ``pool_type`` (see below). -3. If a neuron-neuron connection is to be paired with an astrocyte, a connection -from the presynaptic (source) neuron to the astrocyte is created, and a -connection (``sic_connection``) from the astrocyte to the postsynaptic (target) -neuron is created. +3. If a neuron-neuron connection is to be paired with an astrocyte, a +``tsodyks_synapse`` from the presynaptic (source) neuron to the astrocyte +is created, and a ``sic_connection`` from the astrocyte to the postsynaptic +(target) neuron is created. The available connectivity parameters are as follows: @@ -56,7 +57,9 @@ paired with one astrocyte. * ``pool_size``: The size of astrocyte pool for each target neuron. The - target neuron can only be connected to astrocytes selected from its pool. + astrocyte pool of each target neuron is determined before making + connections. Each target neuron can only be connected to astrocytes + in its pool. * ``pool_type``: The way to determine the astrocyte pool for each target neuron. If ``"random"``, a number (``pool_size``) of astrocytes are @@ -91,7 +94,7 @@ concentration in the astrocytes. This change in calcium then induces the slow inward current (SIC) in the neurons through the ``sic_connection``. The changes in membrane potential of the presynaptic and postsynaptic neurons are also -recorded. These data are also shown in the created figures. +recorded. These data are shown in the created figures. The ``pool_type`` can be changed to "random" to see the results with random astrocyte pools. In that case, the ``pool_size`` can be any from one to the @@ -143,8 +146,8 @@ n_neurons = 10 # number of source and target neurons n_astrocytes = 5 # number of astrocytes -p_primary = 0.5 #1.0 # connection probability between neurons -p_third_if_primary = 0.5 #1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte +p_primary = 0.5 # 1.0 # connection probability between neurons +p_third_if_primary = 0.5 # 1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte pool_size = 1 # astrocyte pool size for each target neuron pool_type = "block" # the way to determine the astrocyte pool for each target neuron @@ -173,67 +176,76 @@ # Plot all connections between neurons and astrocytes def plot_connections(conn_n2n, conn_n2a, conn_a2n, pre_id_list, post_id_list, astro_id_list): - # helper function to create lists of positions for source and target nodes - def get_node_positions(dict_in, source_center, target_center): + print("Plotting connections ...") + + # helper function to create lists of connection positions + def get_conn_positions(dict_in, source_center, target_center): source_list = np.array(dict_in["source"]) - source_center target_list = np.array(dict_in["target"]) - target_center - return source_list, target_list - - # helper function to set plot frames invisible - def set_frame_invisible(ax): - ax.get_xaxis().set_visible(False) - ax.get_yaxis().set_visible(False) - ax.spines["top"].set_visible(False) - ax.spines["bottom"].set_visible(False) - ax.spines["left"].set_visible(False) - ax.spines["right"].set_visible(False) - - print("Plotting connections ...") - # prepare data (lists of node positions) - pre_id_center = (pre_id_list[0] + pre_id_list[-1])/2 - post_id_center = (post_id_list[0] + post_id_list[-1])/2 - astro_id_center = (astro_id_list[0] + astro_id_list[-1])/2 - slist_n2n, tlist_n2n = get_node_positions(conns_n2n.get(), pre_id_center, post_id_center) - slist_n2a, alist_n2a = get_node_positions(conns_n2a.get(), pre_id_center, astro_id_center) - alist_a2n, tlist_a2n = get_node_positions(conns_a2n.get(), astro_id_center, post_id_center) - # make plot + return source_list.tolist(), target_list.tolist() + + # prepare data (lists of node positions, list of connection positions) + pre_id_center = (pre_id_list[0] + pre_id_list[-1]) / 2 + post_id_center = (post_id_list[0] + post_id_list[-1]) / 2 + astro_id_center = (astro_id_list[0] + astro_id_list[-1]) / 2 + slist_n2n, tlist_n2n = get_conn_positions(conns_n2n.get(), pre_id_center, post_id_center) + slist_n2a, tlist_n2a = get_conn_positions(conns_n2a.get(), pre_id_center, astro_id_center) + slist_a2n, tlist_a2n = get_conn_positions(conns_a2n.get(), astro_id_center, post_id_center) + # initialize figure fig, axs = plt.subplots(1, 1, figsize=(10, 8)) # plot nodes and connections + # source neuron nodes axs.scatter( np.array(pre_id_list) - pre_id_center, - [2] * len(pre_id_list), s=300, color="gray", marker="^", label="pre_neurons", zorder=3 + [2] * len(pre_id_list), + s=300, + color="gray", + marker="^", + label="pre_neurons", + zorder=3, ) + # neuron-to-neuron connections for i, (sx, tx) in enumerate(zip(slist_n2n, tlist_n2n)): label = "neuron-to-neuron" if i == 0 else None axs.plot([sx, tx], [2, 0], linestyle=":", color="b", alpha=0.3, linewidth=2, label=label) + # target neuron nodes axs.scatter( np.array(post_id_list) - post_id_center, - [0] * len(post_id_list), s=300, color="k", marker="^", label="post_neurons", zorder=3 + [0] * len(post_id_list), + s=300, + color="k", + marker="^", + label="post_neurons", + zorder=3, ) - for i, (sx, tx) in enumerate(zip(slist_n2a, alist_n2a)): + # neuron-to-astrocyte connections + for i, (sx, tx) in enumerate(zip(slist_n2a, tlist_n2a)): label = "neuron-to-astrocyte" if i == 0 else None axs.plot([sx, tx], [2, 1], linestyle="-", color="orange", alpha=0.5, linewidth=2, label=label) + # astrocyte nodes axs.scatter( np.array(astro_id_list) - astro_id_center, - [1] * len(astro_id_list), s=300, color="g", marker="o", label="astrocytes", zorder=3 + [1] * len(astro_id_list), + s=300, + color="g", + marker="o", + label="astrocytes", + zorder=3, ) - for i, (sx, tx) in enumerate(zip(alist_a2n, tlist_a2n)): + # astrocyte-to-neuron connections + for i, (sx, tx) in enumerate(zip(slist_a2n, tlist_a2n)): label = "astrocyte-to-neuron" if i == 0 else None - axs.plot([sx, tx], [1, 0], linestyle="-", color="g", alpha=0.8, linewidth=4, label=label) - # tweak legends and frames + axs.plot([sx, tx], [1, 0], linestyle="-", color="g", linewidth=4, label=label) + # set legends legend = axs.legend(bbox_to_anchor=(0.5, 1.15), loc="upper center", ncol=3, labelspacing=1.5) legend.set_frame_on(False) - set_frame_invisible(axs) - - -# Helper function to mask data by start time -def mask_data_by_start(data_in, start): - data_out = {} - times = data_in["times"] - for key, value in data_in.items(): - data = data_in[key] - data_out[key] = data[times > start] - return data_out + # set axes and frames invisible + axs.get_xaxis().set_visible(False) + axs.get_yaxis().set_visible(False) + axs.spines["top"].set_visible(False) + axs.spines["bottom"].set_visible(False) + axs.spines["left"].set_visible(False) + axs.spines["right"].set_visible(False) # Helper function to get times, means, and standard deviation of data for plotting @@ -249,11 +261,9 @@ def get_plot_data(data_in, variable): def plot_vm(pre_data, post_data, start): print("Plotting V_m ...") # get presynaptic data - pre_data_masked = mask_data_by_start(pre_data, start) - pre_times, pre_vm_mean, pre_vm_sd = get_plot_data(pre_data_masked, "V_m") + pre_times, pre_vm_mean, pre_vm_sd = get_plot_data(pre_data, "V_m") # get postsynaptic data - post_data_masked = mask_data_by_start(post_data, start) - post_times, post_vm_mean, post_vm_sd = get_plot_data(post_data_masked, "V_m") + post_times, post_vm_mean, post_vm_sd = get_plot_data(post_data, "V_m") # set plots fig, axes = plt.subplots(2, 1, sharex=True) color_pre = color_post = "tab:blue" @@ -278,19 +288,17 @@ def plot_vm(pre_data, post_data, start): def plot_dynamics(astro_data, neuron_data, start): print("Plotting dynamics ...") # get astrocyte data - astro_data_masked = mask_data_by_start(astro_data, start) - astro_times, astro_ip3_mean, astro_ip3_sd = get_plot_data(astro_data_masked, "IP3") - astro_times, astro_ca_mean, astro_ca_sd = get_plot_data(astro_data_masked, "Ca") + astro_times, astro_ip3_mean, astro_ip3_sd = get_plot_data(astro_data, "IP3") + astro_times, astro_ca_mean, astro_ca_sd = get_plot_data(astro_data, "Ca") # get neuron data - neuron_data_masked = mask_data_by_start(neuron_data, start) - neuron_times, neuron_sic_mean, neuron_sic_sd = get_plot_data(neuron_data_masked, "I_SIC") + neuron_times, neuron_sic_mean, neuron_sic_sd = get_plot_data(neuron_data, "I_SIC") # set plots fig, axes = plt.subplots(2, 1, sharex=True) color_ip3 = "tab:blue" color_cal = "tab:green" color_sic = "tab:purple" # plot astrocyte data - n_astro = len(set(astro_data_masked["senders"])) + n_astro = len(set(astro_data["senders"])) axes[0].set_title(f"IP$_{{3}}$ and Ca$^{{2+}}$ in astrocytes (n={n_astro})") axes[0].set_ylabel(r"IP$_{3}$ ($\mu$M)") axes[0].tick_params(axis="y", labelcolor=color_ip3) @@ -311,7 +319,7 @@ def plot_dynamics(astro_data, neuron_data, start): ) ax.plot(astro_times, astro_ca_mean, linewidth=2, color=color_cal) # plot neuron data - n_neuron = len(set(neuron_data_masked["senders"])) + n_neuron = len(set(neuron_data["senders"])) axes[1].set_title(f"SIC in postsynaptic neurons (n={n_neuron})") axes[1].set_ylabel("SIC (pA)") axes[1].set_xlabel("Time (ms)") @@ -345,9 +353,9 @@ def plot_dynamics(astro_data, neuron_data, start): "pool_type": pool_type, }, syn_specs={ - "primary": {"synapse_model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, - "third_in": {"synapse_model": "tsodyks_synapse", "weight": 1.0, "delay": 1.0}, - "third_out": {"synapse_model": "sic_connection", "weight": 1.0, "delay": 1.0}, + "primary": {"synapse_model": "tsodyks_synapse"}, + "third_in": {"synapse_model": "tsodyks_synapse"}, + "third_out": {"synapse_model": "sic_connection"}, }, ) mm_pre_neurons = nest.Create("multimeter", params={"record_from": ["V_m"]}) From a27c6210b06a5133e2a826d4b1e55d7251019030 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sun, 5 Nov 2023 12:33:28 +0100 Subject: [PATCH 73/93] Correct parameters in astrocyte_small_network.py --- pynest/examples/astrocytes/astrocyte_small_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pynest/examples/astrocytes/astrocyte_small_network.py b/pynest/examples/astrocytes/astrocyte_small_network.py index 2f01e3c853..47cfe9be48 100644 --- a/pynest/examples/astrocytes/astrocyte_small_network.py +++ b/pynest/examples/astrocytes/astrocyte_small_network.py @@ -146,8 +146,8 @@ n_neurons = 10 # number of source and target neurons n_astrocytes = 5 # number of astrocytes -p_primary = 0.5 # 1.0 # connection probability between neurons -p_third_if_primary = 0.5 # 1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte +p_primary = 1.0 # connection probability between neurons +p_third_if_primary = 1.0 # probability of each created neuron-neuron connection to be paired with one astrocyte pool_size = 1 # astrocyte pool size for each target neuron pool_type = "block" # the way to determine the astrocyte pool for each target neuron From dddd674906e872b0a68f73512b4e79834ea5f94e Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Wed, 8 Nov 2023 11:45:20 +0100 Subject: [PATCH 74/93] Add a illustrative figure for the "tripartite_bernoulli_with_pool" rule --- .../synapses/connection_management.rst | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index ad5a7d71a6..cc64ed92f6 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -261,7 +261,7 @@ must be ``True``. Tripartite connectivity ----------------------- -NEST supports creating connections of three node populations using the +NEST supports creating connections of three-node populations using the :py:func:`.TripartiteConnect` function: .. code-block:: python @@ -289,9 +289,9 @@ where the individual ``syn_spec`` elements follow the same rules as for the :py:func:`.Connect` function. Any of the three elements can be left out, in which case the synapse specification defaults to ``"static_synapse"``. The ``"primary"`` synapse specification applies -to connections between ``pre`` and ``post`` nodes, the ``"third_in"`` -specification to connections between ``pre`` and ``third`` nodes and -the ``"third_out"`` specification to connections between ``third`` and +to connections from ``pre`` to ``post`` nodes, the ``"third_in"`` +specification to connections from ``pre`` to ``third`` nodes and +the ``"third_out"`` specification to connections from ``third`` to ``post`` nodes. tripartite_bernoulli_with_pool @@ -313,7 +313,7 @@ replacement. For block pools, two variants exist. Let ``n(B)`` and ``n(T)`` be the number of nodes in ``B`` and ``T``, respectively. If ``pool_size == 1``, the -first ``n(B)/n(T)`` nodes in ``B`` get are assigned the first node in +first ``n(B)/n(T)`` nodes in ``B`` are assigned the first node in ``T`` as their pool, the second ``n(B)/n(T)`` nodes in ``B`` the second node in ``T`` and so forth. In this case, ``n(B)`` must be a multiple of ``n(T)``. If ``pool_size > 1``, the first ``pool_size`` @@ -336,6 +336,21 @@ is required. nest.TripartiteConnect(A, B, T, conn_spec_dict, syn_specs_dict) +.. figure:: ../static/img/astrocyte_pool_type.svg + :align: center + :alt: Astrocyte pool type. + + This figure illustrates possible outcomes of connectivity with the two + pool types. In the example of ``"random"`` pool type (left), each node in + ``B`` can be connected with up to two randomly selected nodes in ``T`` + (given ``pool_size == 2``). In the example of ``"block"`` pool type (right), + let ``n(B)/n(T)`` = 2, then each node in ``B`` can be connected with one + node in ``T`` (``pool_size == 1`` is required because ``n(T) < n(B)``), and + each node in ``T`` can be connected with up to two nodes in ``B``. Colors + of nodes in ``B`` and ``T`` indicate which node(s) in ``T`` a node in ``B`` + is connected with. + + .. _synapse_spec: Synapse Specification From 44101699cac8370bcd215ad42ecac77d34621f2b Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Wed, 8 Nov 2023 12:01:38 +0100 Subject: [PATCH 75/93] Propose an expansion in the description for "tripartite_bernoulli_with_pool" --- doc/htmldoc/synapses/connection_management.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index cc64ed92f6..7b29f9cd23 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -300,10 +300,13 @@ tripartite_bernoulli_with_pool For each possible pair of nodes from ``A`` and ``B``, a connection is created with probability ``p_primary``, and these connections are called "primary" connections. For each primary connection, a -third-party connection to a node from ``T`` is created with the -conditional probability ``p_third_if_primary``. The node from ``T`` to -connect to is chosen at random from a pool, a subset of the nodes in -``T``. By default, this pool is all of ``T``. +third-party connection pair to a node from ``T`` is created with the +conditional probability ``p_third_if_primary``. This connection pair +includes a connection from the node from ``A`` (i.e. the source) to the +node from ``T`` in question, and a connection from this node from ``T`` +to the node from ``B`` (i.e. the target). The node from ``T`` to connect +to is chosen at random from a pool, a subset of the nodes in ``T``. By +default, this pool is all of ``T``. Pool formation is controlled by parameters ``pool_type``, which can be ``"random"`` (default) or ``"block"``, and ``pool_size`` which must be between 1 From 4fb358fd365b5f407bf84e6247570ec53e247e50 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Wed, 8 Nov 2023 12:57:35 +0100 Subject: [PATCH 76/93] Propose a change from ``T`` to ``C`` and improve some wording --- .../synapses/connection_management.rst | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 7b29f9cd23..60afeed687 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -261,7 +261,7 @@ must be ``True``. Tripartite connectivity ----------------------- -NEST supports creating connections of three-node populations using the +NEST supports creating connections of three node populations using the :py:func:`.TripartiteConnect` function: .. code-block:: python @@ -300,43 +300,44 @@ tripartite_bernoulli_with_pool For each possible pair of nodes from ``A`` and ``B``, a connection is created with probability ``p_primary``, and these connections are called "primary" connections. For each primary connection, a -third-party connection pair to a node from ``T`` is created with the -conditional probability ``p_third_if_primary``. This connection pair -includes a connection from the node from ``A`` (i.e. the source) to the -node from ``T`` in question, and a connection from this node from ``T`` -to the node from ``B`` (i.e. the target). The node from ``T`` to connect -to is chosen at random from a pool, a subset of the nodes in ``T``. By -default, this pool is all of ``T``. +third-party connection pair involving a node from ``C`` (a third +``NodeCollections``) is created with the conditional probability +``p_third_if_primary``. This connection pair includes a connection +from the node from ``A`` (i.e. the source) to the node from ``C`` in +question, and a connection from this node from ``C`` to the node from +``B`` (i.e. the target). The node from ``C`` to connect to is chosen +at random from a pool, a subset of the nodes in ``C``. By default, +this pool is all of ``C``. Pool formation is controlled by parameters ``pool_type``, which can be ``"random"`` (default) or ``"block"``, and ``pool_size`` which must be between 1 -and the size of ``T`` (default). For random pools, for each node from -``B``, ``pool_size`` nodes from ``T`` are chosen randomly without +and the size of ``C`` (default). For random pools, for each node from +``B``, ``pool_size`` nodes from ``C`` are chosen randomly without replacement. -For block pools, two variants exist. Let ``n(B)`` and ``n(T)`` be the number of -nodes in ``B`` and ``T``, respectively. If ``pool_size == 1``, the -first ``n(B)/n(T)`` nodes in ``B`` are assigned the first node in -``T`` as their pool, the second ``n(B)/n(T)`` nodes in ``B`` the -second node in ``T`` and so forth. In this case, ``n(B)`` must be a -multiple of ``n(T)``. If ``pool_size > 1``, the first ``pool_size`` -elements of ``T`` are the pool for the first node in ``B``, the -second ``pool_size`` elements of ``T`` are the pool for the second -node in ``B`` and so forth. In this case, ``n(B) * pool_size == n(T)`` +For block pools, two variants exist. Let ``n(B)`` and ``n(C)`` be the number of +nodes in ``B`` and ``C``, respectively. If ``pool_size == 1``, the +first ``n(B)/n(C)`` nodes in ``B`` are assigned the first node in +``C`` as their pool, the second ``n(B)/n(C)`` nodes in ``B`` the +second node in ``C`` and so forth. In this case, ``n(B)`` must be a +multiple of ``n(C)``. If ``pool_size > 1``, the first ``pool_size`` +elements of ``C`` are the pool for the first node in ``B``, the +second ``pool_size`` elements of ``C`` are the pool for the second +node in ``B`` and so forth. In this case, ``n(B) * pool_size == n(C)`` is required. .. code-block:: python A = nest.Create('aeif_cond_alpha_astro', 10) B = nest.Create('aeif_cond_alpha_astro', 20) - T = nest.Create('astrocyte_lr_1994', 5) + C = nest.Create('astrocyte_lr_1994', 5) conn_spec_dict = {'rule': 'tripartite_bernoulli_with_pool', 'p_primary': 0.8, 'p_third_if_primary': 0.5, 'pool_type': 'random', 'pool_size': 3} syn_specs_dict = {'third_out': 'sic_connection'} - nest.TripartiteConnect(A, B, T, conn_spec_dict, syn_specs_dict) + nest.TripartiteConnect(A, B, C, conn_spec_dict, syn_specs_dict) .. figure:: ../static/img/astrocyte_pool_type.svg @@ -345,12 +346,12 @@ is required. This figure illustrates possible outcomes of connectivity with the two pool types. In the example of ``"random"`` pool type (left), each node in - ``B`` can be connected with up to two randomly selected nodes in ``T`` + ``B`` can be connected with up to two randomly selected nodes in ``C`` (given ``pool_size == 2``). In the example of ``"block"`` pool type (right), - let ``n(B)/n(T)`` = 2, then each node in ``B`` can be connected with one - node in ``T`` (``pool_size == 1`` is required because ``n(T) < n(B)``), and - each node in ``T`` can be connected with up to two nodes in ``B``. Colors - of nodes in ``B`` and ``T`` indicate which node(s) in ``T`` a node in ``B`` + let ``n(B)/n(C)`` = 2, then each node in ``B`` can be connected with one + node in ``C`` (``pool_size == 1`` is required because ``n(C) < n(B)``), and + each node in ``C`` can be connected with up to two nodes in ``B``. Colors + of nodes in ``B`` and ``C`` indicate which node(s) in ``C`` a node in ``B`` is connected with. From 2ced01e4c63db78fdb23733321fa7e4ce2bdc4d3 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Thu, 9 Nov 2023 09:41:48 +0100 Subject: [PATCH 77/93] Add tripartite_pool_type.svg --- .../static/img/tripartite_pool_type.svg | 630 ++++++++++++++++++ .../synapses/connection_management.rst | 2 +- 2 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 doc/htmldoc/static/img/tripartite_pool_type.svg diff --git a/doc/htmldoc/static/img/tripartite_pool_type.svg b/doc/htmldoc/static/img/tripartite_pool_type.svg new file mode 100644 index 0000000000..dc9228829c --- /dev/null +++ b/doc/htmldoc/static/img/tripartite_pool_type.svg @@ -0,0 +1,630 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A1 + + + + + + + A2 + + + + + + + A3 + + + + + + + A4 + + + + + + + A5 + + + + + + + An + + + + + + + B1 + + + + + + + B2 + + + + + + + B4 + + + + + + + B3 + + + + + + + Bm + + + + + + + C1 + + + + + + + C2 + + + + + + + C3 + + + + + + + Cx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pool_type”: “random”“pool_size”: 2 + + + + + + ... + + + + + + ... + + + + + + ... + + + + + + + + + + + + + + + + + + + + + + + + + + + + B5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A1 + + + + + + + A2 + + + + + + + A3 + + + + + + + A4 + + + + + + + A5 + + + + + + + An + + + + + + + B1 + + + + + + + B2 + + + + + + + B4 + + + + + + + B3 + + + + + + + Bm + + + + + + + C1 + + + + + + + C2 + + + + + + + C3 + + + + + + + Cx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ... + + + + + + ... + + + + + + ... + + + + + + + + + + + + + + + + + + + + + B5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pool_type”: “block”“pool_size”: 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 60afeed687..32245022d5 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -340,7 +340,7 @@ is required. nest.TripartiteConnect(A, B, C, conn_spec_dict, syn_specs_dict) -.. figure:: ../static/img/astrocyte_pool_type.svg +.. figure:: ../static/img/tripartite_pool_type.svg :align: center :alt: Astrocyte pool type. From 7d7027dfd7fa2a87633ff6f29179ed4b2eee7fa3 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Thu, 9 Nov 2023 16:16:02 +0100 Subject: [PATCH 78/93] Add a second case for "block" pool type in tripartite_pool_type.svg --- .../static/img/tripartite_pool_type.svg | 874 +++++++++++++----- .../synapses/connection_management.rst | 26 +- 2 files changed, 638 insertions(+), 262 deletions(-) diff --git a/doc/htmldoc/static/img/tripartite_pool_type.svg b/doc/htmldoc/static/img/tripartite_pool_type.svg index dc9228829c..d52b8d843b 100644 --- a/doc/htmldoc/static/img/tripartite_pool_type.svg +++ b/doc/htmldoc/static/img/tripartite_pool_type.svg @@ -1,12 +1,12 @@ - + - + - + @@ -38,6 +38,7 @@ + @@ -48,7 +49,7 @@ - + @@ -95,531 +96,904 @@ - - - + + + - - - + + + - - - + + + - + - - - A1 + + + - + - - - A2 + + + - - - A3 + + + A1 - - - A4 + + + A2 - - - A5 + + + A3 - - - An + + + A4 - - - B1 + + + A5 - - - B2 + + + An - - - B4 + + + B1 - - - B3 + + + B2 - - - Bm + + + B4 - - - C1 + + + B3 - - - C2 + + + Bm - - - C3 + + + C1 - - - Cx + + + C2 - + - - - + + + C3 - + - - - + + + Cx - - - + + + - - - + + + - - - + + + - - - + + + - + - - pool_type”: “random”“pool_size”: 2 + + + - + - - ... + + + - - ... + + pool_type”: “random”“pool_size”: 2 - - ... + + ... - + - - - + + ... - + - - - + + ... - - - + + + - + - - - B5 + + + - - - + + + - + - - - + + + B5 - - - + + + - - - + + + - - - + + + - + - - - A1 + + + - + - - - A2 + + + - - - A3 + + + A1 - - - A4 + + + A2 - - - A5 + + + A3 - - - An + + + A4 - - - B1 + + + A5 - - - B2 + + + An - - - B4 + + + B1 - - - B3 + + + B2 - - - Bm + + + B4 - - - C1 + + + B3 - - - C2 + + + Bm - - - C3 + + + C1 - - - Cx + + + C2 - + - - - + + + C3 - + - - - + + + Cx - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - ... + + + - + - - ... + + + - - ... + + ... - + - - - + + ... - + - - - + + ... - + - - - B5 + + + - - - + + + - + - - - + + + B5 - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - pool_type”: “block”“pool_size”: 1 + + + - + - - + + + - + - - + + pool_type”: “block”“pool_size”: 1 - + - - - + + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A1 + + + + + + + A2 + + + + + + + A3 + + + + + + + A4 + + + + + + + A5 + + + + + + + An + + + + + + + B1 + + + + + + + B2 + + + + + + + Bm + + + + + + + C1 + + + + + + + C3 + + + + + + + C5 + + + + + + + Cx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ... + + + + + + ... + + + + + + ... + + + + + + + + + + + + + + + + + + + + + B3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pool_type”: “block”“pool_size”: 2 + + + + + + + C2 + + + + + + + C4 + + + + + + + + + + + + + + B6 + + + + + + + A6 + + + + + + + A6 + + + + + + + A5 + + + + + + + C6 + + + + + + + B6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 32245022d5..47893f566c 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -340,19 +340,21 @@ is required. nest.TripartiteConnect(A, B, C, conn_spec_dict, syn_specs_dict) -.. figure:: ../static/img/tripartite_pool_type.svg +.. image:: ../static/img/tripartite_pool_type.svg :align: center - :alt: Astrocyte pool type. - - This figure illustrates possible outcomes of connectivity with the two - pool types. In the example of ``"random"`` pool type (left), each node in - ``B`` can be connected with up to two randomly selected nodes in ``C`` - (given ``pool_size == 2``). In the example of ``"block"`` pool type (right), - let ``n(B)/n(C)`` = 2, then each node in ``B`` can be connected with one - node in ``C`` (``pool_size == 1`` is required because ``n(C) < n(B)``), and - each node in ``C`` can be connected with up to two nodes in ``B``. Colors - of nodes in ``B`` and ``C`` indicate which node(s) in ``C`` a node in ``B`` - is connected with. + +Possible outcomes of connectivity with the two pool types. In the example +of ``"random"`` pool type (left), each node in ``B`` can be connected with +up to two randomly selected nodes in ``C`` (given ``pool_size == 2``). In +the first example of ``"block"`` pool type (middle), let ``n(B)/n(C)`` = 2, +then each node in ``B`` can be connected with one node in ``C`` +(``pool_size == 1`` is required because ``n(C) < n(B)``), and each node in +``C`` can be connected with up to two nodes in ``B``. In the second example +of ``"block"`` pool type (right), let ``n(C)/n(B)`` = 2, then each node in +``B`` can be connected with up to two nodes in ``C`` (``pool_size == 2`` is +required because ``n(C)/n(B)`` = 2), and each node in ``C`` can be +connected to one node in ``B``. Colors of nodes in ``B`` and ``C`` indicate +which node(s) in ``C`` a node in ``B`` is connected with. .. _synapse_spec: From 840cd39ba89add58c47a6b518e1d0e9619ee9d75 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 10 Nov 2023 07:57:47 +0100 Subject: [PATCH 79/93] Apply suggestions from code review Co-authored-by: jessica-mitchell --- doc/htmldoc/synapses/connection_management.rst | 6 +++--- pynest/examples/astrocytes/astrocyte_brunel.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 47893f566c..dd4814249e 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -301,11 +301,11 @@ For each possible pair of nodes from ``A`` and ``B``, a connection is created with probability ``p_primary``, and these connections are called "primary" connections. For each primary connection, a third-party connection pair involving a node from ``C`` (a third -``NodeCollections``) is created with the conditional probability +``NodeCollection``) is created with the conditional probability ``p_third_if_primary``. This connection pair includes a connection -from the node from ``A`` (i.e. the source) to the node from ``C`` in +from the node from ``A`` (i.e., the source) to the node from ``C`` in question, and a connection from this node from ``C`` to the node from -``B`` (i.e. the target). The node from ``C`` to connect to is chosen +``B`` (i.e., the target). The node from ``C`` to connect to is chosen at random from a pool, a subset of the nodes in ``C``. By default, this pool is all of ``C``. diff --git a/pynest/examples/astrocytes/astrocyte_brunel.py b/pynest/examples/astrocytes/astrocyte_brunel.py index 7faf7efa09..c2416b9330 100644 --- a/pynest/examples/astrocytes/astrocyte_brunel.py +++ b/pynest/examples/astrocytes/astrocyte_brunel.py @@ -134,7 +134,7 @@ # This function creates the nodes and build the network. The astrocytes only # respond to excitatory synaptic inputs; therefore, only the excitatory # neuron-neuron connections are paired with the astrocytes. The -# TripartiteConnect() function and the "tripartite_bernoulli_with_pool" rule +# ``TripartiteConnect()`` function and the ``tripartite_bernoulli_with_pool`` rule # are used to create the connectivity of the network. From 7b906b120fe68eb104a315e6f050f23ff4e51391 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Thu, 16 Nov 2023 22:17:58 +0100 Subject: [PATCH 80/93] Apply suggestions from code review Co-authored-by: Johanna Senk --- .../synapses/connection_management.rst | 6 ++--- doc/htmldoc/whats_new/v3.7/index.rst | 2 +- nestkernel/conn_builder_factory.h | 4 ++-- .../examples/astrocytes/astrocyte_brunel.py | 24 +++++++++---------- pynest/nest/lib/hl_api_connections.py | 16 ++++++------- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index dd4814249e..4f722ebbde 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -266,12 +266,12 @@ NEST supports creating connections of three node populations using the .. code-block:: python - TripartiteConnect(pre, post, third, conn_spec) + nest.TripartiteConnect(pre, post, third, conn_spec) TripartiteConnect(pre, post, third, conn_spec, syn_specs) ``pre``, ``post``, and ``third`` are ``NodeCollections``, defining the nodes of origin (`sources`) and termination (`targets`) as well as the third -factor to be included. Details of the connections created depend on +factor population to be included. Connections will be established from ``pre`` to both ``third`` and ``post``, and from ``third`` to ``post``. Details of the connections created depend on the connection rule used. ``conn_spec`` must be provided and must specify a tripartite @@ -294,7 +294,7 @@ specification to connections from ``pre`` to ``third`` nodes and the ``"third_out"`` specification to connections from ``third`` to ``post`` nodes. -tripartite_bernoulli_with_pool +Tripartite Bernoulli with pool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For each possible pair of nodes from ``A`` and ``B``, a connection is diff --git a/doc/htmldoc/whats_new/v3.7/index.rst b/doc/htmldoc/whats_new/v3.7/index.rst index 06ca61f5c4..4f4446d5dc 100644 --- a/doc/htmldoc/whats_new/v3.7/index.rst +++ b/doc/htmldoc/whats_new/v3.7/index.rst @@ -25,7 +25,7 @@ Tripartite connectivity in NEST ------------------------------- NEST now supports creation of connections involving three populations -of neurons, a pre-synaptic, a post-synaptic and a third-factor +of neurons: a pre-synaptic, a post-synaptic and a third-factor population. At present, as single tripartite connection rule is available, ``tripartite_bernoulli_with_pool``. Tripartite connections are created with the new :py:func:`.TripartiteConnect` function. The first diff --git a/nestkernel/conn_builder_factory.h b/nestkernel/conn_builder_factory.h index 0fe097c7c6..a9937611a5 100644 --- a/nestkernel/conn_builder_factory.h +++ b/nestkernel/conn_builder_factory.h @@ -51,7 +51,7 @@ class GenericConnBuilderFactory } /** - * Factory method for builders for bi-partite connection rules (the default). + * Factory method for builders for bipartite connection rules (the default). */ virtual ConnBuilder* create( NodeCollectionPTR, NodeCollectionPTR, @@ -59,7 +59,7 @@ class GenericConnBuilderFactory const std::vector< DictionaryDatum >& ) const = 0; /** - * Factory method for builders for tri-partite connection rules. + * Factory method for builders for tripartite connection rules. */ virtual ConnBuilder* create( NodeCollectionPTR, NodeCollectionPTR, diff --git a/pynest/examples/astrocytes/astrocyte_brunel.py b/pynest/examples/astrocytes/astrocyte_brunel.py index c2416b9330..cd30f393bd 100644 --- a/pynest/examples/astrocytes/astrocyte_brunel.py +++ b/pynest/examples/astrocytes/astrocyte_brunel.py @@ -75,8 +75,8 @@ "dt": 0.1, # simulation resolution in ms "pre_sim_time": 100.0, # pre-simulation time in ms (data not recorded) "sim_time": 1000.0, # simulation time in ms - "N_rec_spk": 100, # number of samples (neuron) for spike detector - "N_rec_mm": 50, # number of samples (neuron, astrocyte) for multimeter + "N_rec_spk": 100, # number of neurons to record from with spike recorder + "N_rec_mm": 50, # number of nodes (neurons, astrocytes) to record from with multimeter "n_threads": 4, # number of threads for NEST "seed": 100, # seed for the random module } @@ -263,18 +263,18 @@ def run_simulation(): nest.print_time = True nest.overwrite_files = True - # Use random seed for reproducible sampling + # use random seed for reproducible sampling random.seed(sim_params["seed"]) - # Simulation settings + # simulation settings pre_sim_time = sim_params["pre_sim_time"] sim_time = sim_params["sim_time"] - # Create and connect nodes + # create and connect nodes exc, inh, astro, noise = create_astro_network() connect_astro_network(exc, inh, astro, noise) - # Create and connect recorders (multimeter default resolution = 1 ms) + # create and connect recorders (multimeter default resolution = 1 ms) sr_neuron = nest.Create("spike_recorder") mm_neuron = nest.Create("multimeter", params={"record_from": ["I_SIC"]}) mm_astro = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) @@ -283,7 +283,7 @@ def run_simulation(): print("Running pre-simulation ...") nest.Simulate(pre_sim_time) - # Select nodes randomly and connect them with recorders + # select nodes randomly and connect them with recorders print("Connecting recorders ...") neuron_list = (exc + inh).tolist() astro_list = astro.tolist() @@ -297,24 +297,24 @@ def run_simulation(): nest.Connect(mm_neuron, neuron_list_for_mm) nest.Connect(mm_astro, astro_list_for_mm) - # Run simulation + # run simulation print("Running simulation ...") nest.Simulate(sim_time) - # Read out recordings + # read out recordings neuron_spikes = sr_neuron.events neuron_data = mm_neuron.events astro_data = mm_astro.events - # Make raster plot + # make raster plot nest.raster_plot.from_device( sr_neuron, hist=True, title=f"Raster plot of neuron {neuron_list_for_sr[0]} to {neuron_list_for_sr[-1]}" ) - # Plot dynamics in astrocytes and neurons + # plot dynamics in astrocytes and neurons plot_dynamics(astro_data, neuron_data, pre_sim_time) - # Show plots + # show plots plt.show() diff --git a/pynest/nest/lib/hl_api_connections.py b/pynest/nest/lib/hl_api_connections.py index 0cc8990ab7..7a2fc8b47d 100644 --- a/pynest/nest/lib/hl_api_connections.py +++ b/pynest/nest/lib/hl_api_connections.py @@ -297,11 +297,11 @@ def Connect(pre, post, conn_spec=None, syn_spec=None, return_synapsecollection=F @check_stack def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): """ - Connect `pre` nodes to `post` nodes and a `third` population. + Connect `pre` nodes to `post` nodes and a `third`-factor nodes. - Nodes in `pre` and `post` are connected using the specified tripartite connectivity rule + Nodes in `pre` and `post` are connected using the specified tripartite connection rule and the given synapse types (all :cpp:class:`static_synapse ` by default). - Details depend on the connectivity rule. + Details depend on the connection rule. Lists of synapse models and connection rules are available as ``nest.synapse_models`` and ``nest.connection_rules``, respectively. Note that only tripartite @@ -316,7 +316,7 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): third : NodeCollection Third population to include in connection conn_spec : dict - Specifies connectivity rule, which must support tripartite connections, see below + Specifies connection rule, which must support tripartite connections, see below syn_spec : dict, optional Specifies synapse models to be used, see below @@ -338,15 +338,15 @@ def TripartiteConnect(pre, post, third, conn_spec, syn_specs=None): **Synapse specifications (syn_specs)** Synapse specifications for tripartite connections are given as a dictionary with specifications - for each of the three connections to be created:: + for each of the three projections to be created:: {"primary": , "third_in": , "third_out": } - Here, ``"primary"`` marks the synapse specification for the connections between ``pre`` and ``post`` neurons, - ``"third_in"`` for connections between ``pre`` and ``third`` neurons and ``"third_out"`` for connections between - ``third`` and ``post`` neurons. + Here, ``"primary"`` marks the synapse specification for the projections between ``pre`` and ``post`` nodes, + ``"third_in"`` for connections between ``pre`` and ``third`` nodes and ``"third_out"`` for connections between + ``third`` and ``post`` nodes. Each ```` entry can be any entry that would be possible as synapse specification in a normal ``Connect()`` call. Any missing entries default to ``static_synapse``. If no ```` argument From 8b4324fbeddbf89e42c91a7dcf992b83fb0c58e5 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 07:50:11 +0100 Subject: [PATCH 81/93] Apply suggestions from code review Co-authored-by: Johanna Senk --- doc/htmldoc/synapses/connection_management.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 4f722ebbde..2a1d98264e 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -267,7 +267,7 @@ NEST supports creating connections of three node populations using the .. code-block:: python nest.TripartiteConnect(pre, post, third, conn_spec) - TripartiteConnect(pre, post, third, conn_spec, syn_specs) + nest.TripartiteConnect(pre, post, third, conn_spec, syn_specs) ``pre``, ``post``, and ``third`` are ``NodeCollections``, defining the nodes of origin (`sources`) and termination (`targets`) as well as the third @@ -331,13 +331,13 @@ is required. A = nest.Create('aeif_cond_alpha_astro', 10) B = nest.Create('aeif_cond_alpha_astro', 20) C = nest.Create('astrocyte_lr_1994', 5) - conn_spec_dict = {'rule': 'tripartite_bernoulli_with_pool', + conn_spec = {'rule': 'tripartite_bernoulli_with_pool', 'p_primary': 0.8, 'p_third_if_primary': 0.5, 'pool_type': 'random', 'pool_size': 3} - syn_specs_dict = {'third_out': 'sic_connection'} - nest.TripartiteConnect(A, B, C, conn_spec_dict, syn_specs_dict) + syn_specs = {'third_out': 'sic_connection'} + nest.TripartiteConnect(A, B, C, conn_spec, syn_specs) .. image:: ../static/img/tripartite_pool_type.svg From 174026f5c23e7bf0e162fc96207966a99d62cfa6 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 08:57:28 +0100 Subject: [PATCH 82/93] Change a few notations according to review suggestions --- .../synapses/connection_management.rst | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 2a1d98264e..81829b996b 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -275,31 +275,33 @@ factor population to be included. Connections will be established from ``pre`` t the connection rule used. ``conn_spec`` must be provided and must specify a tripartite -connection rule. +connection rule, e.g., :ref:`tripartite Bernoulli with pool `. ``syn_specs`` is a dictionary of the form .. code-block:: python - {"primary": , - "third_in": , - "third_out": } + {'primary': , + 'third_in': , + 'third_out': } where the individual ``syn_spec`` elements follow the same rules as for the :py:func:`.Connect` function. Any of the three elements can be left out, in which case the synapse specification defaults to -``"static_synapse"``. The ``"primary"`` synapse specification applies -to connections from ``pre`` to ``post`` nodes, the ``"third_in"`` +``'static_synapse'``. The ``'primary'`` synapse specification applies +to connections from ``pre`` to ``post`` nodes, the ``'third_in'`` specification to connections from ``pre`` to ``third`` nodes and -the ``"third_out"`` specification to connections from ``third`` to +the ``'third_out'`` specification to connections from ``third`` to ``post`` nodes. +.. _tripartite_bernoulli_with_pool: + Tripartite Bernoulli with pool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For each possible pair of nodes from ``A`` and ``B``, a connection is created with probability ``p_primary``, and these connections are -called "primary" connections. For each primary connection, a +called 'primary' connections. For each primary connection, a third-party connection pair involving a node from ``C`` (a third ``NodeCollection``) is created with the conditional probability ``p_third_if_primary``. This connection pair includes a connection @@ -309,8 +311,8 @@ question, and a connection from this node from ``C`` to the node from at random from a pool, a subset of the nodes in ``C``. By default, this pool is all of ``C``. -Pool formation is controlled by parameters ``pool_type``, which can be ``"random"`` -(default) or ``"block"``, and ``pool_size`` which must be between 1 +Pool formation is controlled by parameters ``pool_type``, which can be ``'random'`` +(default) or ``'block'``, and ``pool_size`` which must be between 1 and the size of ``C`` (default). For random pools, for each node from ``B``, ``pool_size`` nodes from ``C`` are chosen randomly without replacement. @@ -328,14 +330,15 @@ is required. .. code-block:: python - A = nest.Create('aeif_cond_alpha_astro', 10) - B = nest.Create('aeif_cond_alpha_astro', 20) - C = nest.Create('astrocyte_lr_1994', 5) + n, m, x, p_primary, p_third_if_primary, pool_size = 10, 10, 5, 0.25, 1.0, 2 + A = nest.Create('aeif_cond_alpha_astro', n) + B = nest.Create('aeif_cond_alpha_astro', m) + C = nest.Create('astrocyte_lr_1994', x) conn_spec = {'rule': 'tripartite_bernoulli_with_pool', - 'p_primary': 0.8, - 'p_third_if_primary': 0.5, + 'p_primary': p_primary, + 'p_third_if_primary': p_third_if_primary, 'pool_type': 'random', - 'pool_size': 3} + 'pool_size': pool_size} syn_specs = {'third_out': 'sic_connection'} nest.TripartiteConnect(A, B, C, conn_spec, syn_specs) @@ -344,13 +347,13 @@ is required. :align: center Possible outcomes of connectivity with the two pool types. In the example -of ``"random"`` pool type (left), each node in ``B`` can be connected with +of ``'random'`` pool type (left), each node in ``B`` can be connected with up to two randomly selected nodes in ``C`` (given ``pool_size == 2``). In -the first example of ``"block"`` pool type (middle), let ``n(B)/n(C)`` = 2, +the first example of ``'block'`` pool type (middle), let ``n(B)/n(C)`` = 2, then each node in ``B`` can be connected with one node in ``C`` (``pool_size == 1`` is required because ``n(C) < n(B)``), and each node in ``C`` can be connected with up to two nodes in ``B``. In the second example -of ``"block"`` pool type (right), let ``n(C)/n(B)`` = 2, then each node in +of ``'block'`` pool type (right), let ``n(C)/n(B)`` = 2, then each node in ``B`` can be connected with up to two nodes in ``C`` (``pool_size == 2`` is required because ``n(C)/n(B)`` = 2), and each node in ``C`` can be connected to one node in ``B``. Colors of nodes in ``B`` and ``C`` indicate From 963423c8aecf30d9ffd65f70e597442992369193 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 09:51:50 +0100 Subject: [PATCH 83/93] Reorganize astrocyte_brunel.py according to review suggestions --- .../examples/astrocytes/astrocyte_brunel.py | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/pynest/examples/astrocytes/astrocyte_brunel.py b/pynest/examples/astrocytes/astrocyte_brunel.py index cd30f393bd..4807844584 100644 --- a/pynest/examples/astrocytes/astrocyte_brunel.py +++ b/pynest/examples/astrocytes/astrocyte_brunel.py @@ -139,7 +139,18 @@ def create_astro_network(scale=1.0): - """Create nodes for a neuron-astrocyte network.""" + """Create nodes for a neuron-astrocyte network. + + Nodes in a neuron-astrocyte network are created according to the give scale + of the model. The nodes created include excitatory and inhibitory neruons, + astrocytes, and a Poisson generator. + + Parameters + --------- + scale + Scale of the model. + + """ print("Creating nodes ...") assert scale >= 1.0, "scale must be >= 1.0" nodes_ex = nest.Create(neuron_model, int(network_params["N_ex"] * scale), params=neuron_params_ex) @@ -151,7 +162,25 @@ def create_astro_network(scale=1.0): def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1.0): """Connect the nodes in a neuron-astrocyte network. - The astrocytes are paired with excitatory connections only. + + Nodes in a neuron-astrocyte network are connected. The connection + probability between neurons is divided by a the given scale to preserve + the expected number of connections for each node. The astrocytes are paired + with excitatory connections only. + + Parameters + --------- + nodes_ex + Nodes of excitatory neurons. + nodes_in + Nodes of inhibitory neurons. + nodes_astro + Nodes of astrocytes. + node_noise + Poisson generator. + scale + Scale of the model. + """ print("Connecting Poisson generator ...") assert scale >= 1.0, "scale must be >= 1.0" @@ -201,6 +230,22 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. def plot_dynamics(astro_data, neuron_data, start): + """Plot the dynamics in neurons and astrocytes. + + The dynamics in the given neuron and astrocyte nodes are plotted. The + dynamics in clude IP3 and calcium in the astrocytes, and the SIC input to + the neurons. + + Parameters + --------- + astro_data + Data of IP3 and calcium dynamics in the astrocytes. + neuron_data + Data of SIC input to the neurons. + start + Start time of the plotted dynamics. + + """ print("Plotting dynamics ...") # astrocyte data astro_mask = astro_data["times"] > start @@ -256,6 +301,7 @@ def plot_dynamics(astro_data, neuron_data, start): def run_simulation(): + """Run simulation of a neuron-astrocyte network.""" # NEST configuration nest.ResetKernel() nest.resolution = sim_params["dt"] @@ -279,10 +325,6 @@ def run_simulation(): mm_neuron = nest.Create("multimeter", params={"record_from": ["I_SIC"]}) mm_astro = nest.Create("multimeter", params={"record_from": ["IP3", "Ca"]}) - # Run pre-simulation - print("Running pre-simulation ...") - nest.Simulate(pre_sim_time) - # select nodes randomly and connect them with recorders print("Connecting recorders ...") neuron_list = (exc + inh).tolist() @@ -297,6 +339,10 @@ def run_simulation(): nest.Connect(mm_neuron, neuron_list_for_mm) nest.Connect(mm_astro, astro_list_for_mm) + # run pre-simulation + print("Running pre-simulation ...") + nest.Simulate(pre_sim_time) + # run simulation print("Running simulation ...") nest.Simulate(sim_time) @@ -312,7 +358,7 @@ def run_simulation(): ) # plot dynamics in astrocytes and neurons - plot_dynamics(astro_data, neuron_data, pre_sim_time) + plot_dynamics(astro_data, neuron_data, 0.0) # show plots plt.show() From 72ea489c0e74d6f0926999cec2b5abd1b700903f Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 16:01:09 +0100 Subject: [PATCH 84/93] Improve test_connect_tripartite_bernoulli.py according to review suggestions --- .../examples/astrocytes/astrocyte_brunel.py | 4 + .../test_connect_tripartite_bernoulli.py | 264 ++++++++++++------ 2 files changed, 182 insertions(+), 86 deletions(-) diff --git a/pynest/examples/astrocytes/astrocyte_brunel.py b/pynest/examples/astrocytes/astrocyte_brunel.py index 4807844584..e9b97dd18b 100644 --- a/pynest/examples/astrocytes/astrocyte_brunel.py +++ b/pynest/examples/astrocytes/astrocyte_brunel.py @@ -150,6 +150,10 @@ def create_astro_network(scale=1.0): scale Scale of the model. + Return values + ------------- + Created nodes and Poisson generator. + """ print("Creating nodes ...") assert scale >= 1.0, "scale must be >= 1.0" diff --git a/testsuite/pytests/test_connect_tripartite_bernoulli.py b/testsuite/pytests/test_connect_tripartite_bernoulli.py index 40d2dbf07b..154d6c2ee8 100644 --- a/testsuite/pytests/test_connect_tripartite_bernoulli.py +++ b/testsuite/pytests/test_connect_tripartite_bernoulli.py @@ -25,7 +25,6 @@ import pytest import scipy.stats -# copied from connect_test_base.py try: from mpi4py import MPI @@ -34,38 +33,69 @@ haveMPI4Py = False -# adapted from connect_test_base.py -def setup_network( - conn_dict, syn_dict, N1, N2, neuron_model="aeif_cond_alpha_astro", astrocyte_model="astrocyte_lr_1994" -): - pop1 = nest.Create(neuron_model, N1) - pop2 = nest.Create(neuron_model, N2) - pop_astro = nest.Create(astrocyte_model, N2) - nest.TripartiteConnect(pop1, pop2, pop_astro, conn_spec=conn_dict, syn_specs=syn_dict) - return pop1, pop2, pop_astro +def setup_network(conn_dict, syn_dict, N1, N2, primary_model="aeif_cond_alpha_astro", third_model="astrocyte_lr_1994"): + """Setup the network for the statistical test. + A three-population network is created for a statictical test of the + "tripartite_bernoulli_with_pool" rule. + + Parameters + --------- + conn_dict + Dictionary for the connectivity specifications (conn_spec). + syn_dict + Dictionary for the synapse specifications (syn_spec). + N1 + Number of nodes in the source population. + N2 + Number of nodes in the target population. + primary_model + Model name for the nodes of the primary (source and target) populations. + third_model + Model name for the nodes of the third population. + + Return values + ------------- + Nodes of the three populations created. -# copied from connect_test_base.py -def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): """ - Calculate expected degree distribution. + pop1 = nest.Create(primary_model, N1) + pop2 = nest.Create(primary_model, N2) + pop3 = nest.Create(third_model, N2) + nest.TripartiteConnect(pop1, pop2, pop3, conn_spec=conn_dict, syn_specs=syn_dict) + return pop1, pop2, pop3 + + +def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): + """Calculate expected degree distribution. Degrees with expected number of observations below e_min are combined into larger bins. + Parameters + --------- + p + Connection probability between the source and target populations. + fan + Type of degree to be calculated. + len_source_pop + Number of nodes in the source population. + len_source_pop + Number of nodes in the target population. + Return values ------------- 2D array. The four columns contain degree, expected number of observation, actual number observations, and the number of bins combined. - """ + """ n = len_source_pop if fan == "in" else len_target_pop n_p = len_target_pop if fan == "in" else len_source_pop mid = int(round(n * p)) e_min = 5 - # Combine from front. + # combine from front data_front = [] cumexp = 0.0 bins_combined = 0 @@ -85,7 +115,7 @@ def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): cumexp = 0.0 bins_combined = 0 - # Combine from back. + # combine from back data_back = [] cumexp = 0.0 bins_combined = 0 @@ -109,15 +139,34 @@ def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): expected = np.array(data_front + data_back) if fan == "out": assert sum(expected[:, 3]) == len_target_pop + 1 - else: # , 'Something is wrong' + else: assert sum(expected[:, 3]) == len_source_pop + 1 - # np.hstack((np.asarray(data_front)[0], np.asarray(data_back)[0])) return expected -# copied from connect_test_base.py def get_degrees(fan, pop1, pop2): + """Get total indegree or outdegree in a connectivity matrix. + + The total indegree or outdegree in the connectivity matrix of two + connected populations is calculated. + + Parameters + --------- + p + Connection probability between the source and target populations. + fan + Type of degree to be calculated. + pop1 + Source population. + pop2 + Target population. + + Return values + ------------- + Total indegree or outdegree. + + """ M = get_connectivity_matrix(pop1, pop2) if fan == "in": degrees = np.sum(M, axis=1) @@ -126,14 +175,25 @@ def get_degrees(fan, pop1, pop2): return degrees -# copied from connect_test_base.py def gather_data(data_array): - """ - Gathers data from all mpi processes by collecting all element in a list if + """Gather data of a given array from all MPI processes. + + Gathers data from all MPI processes by collecting all element in a list if data is a list and summing all elements to one numpy-array if data is one - numpy-array. Returns gathered data if rank of current mpi node is zero and + numpy-array. Returns gathered data if rank of current MPI node is zero and None otherwise. + Parameters + --------- + data_array + The array for which data are to be obtained. + + Return values + ------------- + If MPI is available, the array with data from all MPI processes + (MPI node is zero) or None (otherwise) is returned. If MPI is not + available, the original array is returned. + """ if haveMPI4Py: data_array_list = MPI.COMM_WORLD.gather(data_array, root=0) @@ -149,59 +209,67 @@ def gather_data(data_array): return data_array -# copied from connect_test_base.py -def chi_squared_check(degrees, expected, distribution=None): +def chi_squared_check(degrees, expected): """ - Create a single network and compare the resulting degree distribution - with the expected distribution using Pearson's chi-squared GOF test. + Compare the actual degree distribution with the expected distribution using + Pearson's chi-squared GOF test. Parameters ---------- - seed : PRNG seed value. - control: Boolean value. If True, _generate_multinomial_degrees will - be used instead of _get_degrees. + degrees + The actual degree distribution to be evaluated. + expected + The expected degree distribution. Return values ------------- chi-squared statistic. p-value from chi-squared test. + """ + observed = {} + for degree in degrees: + if degree not in observed: + observed[degree] = 1 + else: + observed[degree] += 1 - if distribution in ("pairwise_bernoulli", "symmetric_pairwise_bernoulli"): - observed = {} - for degree in degrees: - if degree not in observed: - observed[degree] = 1 - else: - observed[degree] += 1 - # Add observations to data structure, combining multiple observations - # where necessary. - expected[:, 2] = 0.0 - for row in expected: - for i in range(int(row[3])): - deg = int(row[0]) + i - if deg in observed: - row[2] += observed[deg] - - # ddof: adjustment to the degrees of freedom. df = k-1-ddof - return scipy.stats.chisquare(np.array(expected[:, 2]), np.array(expected[:, 1])) - else: - # ddof: adjustment to the degrees of freedom. df = k-1-ddof - return scipy.stats.chisquare(np.array(degrees), np.array(expected)) + # add observations to data structure + # combine multiple observations where necessary + expected[:, 2] = 0.0 + for row in expected: + for i in range(int(row[3])): + deg = int(row[0]) + i + if deg in observed: + row[2] += observed[deg] + + # adjustment to the degrees of freedom + return scipy.stats.chisquare(np.array(expected[:, 2]), np.array(expected[:, 1])) -# copied from connect_test_base.py def mpi_barrier(): + """Forms a barrier for MPI processes until all of them call the function.""" if haveMPI4Py: MPI.COMM_WORLD.Barrier() -# copied from connect_test_base.py def get_connectivity_matrix(pop1, pop2): """ Returns a connectivity matrix describing all connections from pop1 to pop2 - such that M_ij describes the connection between the jth neuron in pop1 to - the ith neuron in pop2. + such that M_ij describes the connection between the jth node in pop1 to + the ith node in pop2. + + Parameters + --------- + pop1 + Source population. + pop2 + Target population. + + Return values + ------------- + A connectivity matrix describing all connections from pop1 to pop2. + """ M = np.zeros((len(pop2), len(pop1))) @@ -216,14 +284,18 @@ def get_connectivity_matrix(pop1, pop2): return M -# adapted from connect_test_base.py def mpi_assert(data_original, data_test): """ - Compares data_original and data_test. - """ + Compares two arrays with assert and pytest. + Parameters + --------- + data_original + The original data. + data_test + The data to be compared with. + """ data_original = gather_data(data_original) - # only test if on rank 0 if data_original is not None: if isinstance(data_original, (np.ndarray, np.generic)) and isinstance(data_test, (np.ndarray, np.generic)): assert data_original == pytest.approx(data_test) @@ -231,17 +303,24 @@ def mpi_assert(data_original, data_test): assert data_original == data_test -# adapted from test_connect_pairwise_bernoulli.py -# a test for parameters "p" and "max_astro_per_target" -# run for three levels of neuron-neuron connection probabilities @pytest.mark.skipif_missing_threads -@pytest.mark.parametrize("p_n2n", [0.1, 0.3, 0.5]) -def test_statistics(p_n2n): - # set connection parameters +@pytest.mark.parametrize("p_primary", [0.1, 0.3, 0.5]) +def test_statistics(p_primary): + """ + A test for the parameters "p_primary" and "pool_size" in the + "tripartite_bernoulli_with_pool" rule. + + Parameters + --------- + p_primary + Connection probability between the primary populations. + + """ + # set network and connectivity parameters N1 = 50 N2 = 50 - max_astro_per_target = 5 - conn_dict = {"rule": "tripartite_bernoulli_with_pool", "p_primary": p_n2n, "pool_size": max_astro_per_target} + pool_size = 5 + conn_dict = {"rule": "tripartite_bernoulli_with_pool", "p_primary": p_primary, "pool_size": pool_size} # set test parameters stat_dict = {"alpha2": 0.05, "n_runs": 20} @@ -251,46 +330,54 @@ def test_statistics(p_n2n): nest.set_verbosity("M_FATAL") # here we test - # 1. p yields the correct indegree and outdegree - # 2. max_astro_per_target limits the number of astrocytes connected to each target neuron + # 1. p_primary yields the correct indegree and outdegree + # 2. pool_size limits the number of third-population nodes connected to each target nodes for fan in ["in", "out"]: expected = get_expected_degrees_bernoulli(conn_dict["p_primary"], fan, N1, N2) pvalues = [] - n_astrocytes = [] + n_third_nodes = [] for i in range(stat_dict["n_runs"]): # setup network and connect nest.ResetKernel() nest.local_num_threads = nr_threads nest.rng_seed = i + 1 - pop1, pop2, pop_astro = setup_network(conn_dict, {"third_out": {"synapse_model": "sic_connection"}}, N1, N2) + pop1, pop2, pop3 = setup_network(conn_dict, {"third_out": {"synapse_model": "sic_connection"}}, N1, N2) # get indegree or outdegree degrees = get_degrees(fan, pop1, pop2) # gather data from MPI processes degrees = gather_data(degrees) # do chi-square test for indegree or outdegree if degrees is not None: - chi, p_degrees = chi_squared_check(degrees, expected, "pairwise_bernoulli") + chi, p_degrees = chi_squared_check(degrees, expected) pvalues.append(p_degrees) mpi_barrier() - # get number of astrocytes connected to each target neuron + # get number of third_nodes connected to each target nodes conns_n2n = nest.GetConnections(pop1, pop2).get() - conns_a2n = nest.GetConnections(pop_astro, pop2).get() + conns_a2n = nest.GetConnections(pop3, pop2).get() for id in list(set(conns_n2n["target"])): - astrocytes = np.array(conns_a2n["source"]) + third_nodes = np.array(conns_a2n["source"]) targets = np.array(conns_a2n["target"]) - n_astrocytes.append(len(set(astrocytes[targets == id]))) + n_third_nodes.append(len(set(third_nodes[targets == id]))) # assert that the p-values are uniformly distributed if degrees is not None: ks, p_uniform = scipy.stats.kstest(pvalues, "uniform") assert p_uniform > stat_dict["alpha2"] - # assert that for each target neuron, number of astrocytes is smaller than max_astro_per_target - assert all(n <= max_astro_per_target for n in n_astrocytes) + # assert that for each target nodes, number of third_nodes is smaller than pool_size + assert all(n <= pool_size for n in n_third_nodes) -# adapted from test_connect_pairwise_bernoulli @pytest.mark.parametrize("autapses", [True, False]) def test_autapses_true(autapses): - # set connection parameters + """ + A test for autapses in the "tripartite_bernoulli_with_pool" rule. + + Parameters + --------- + autapses + If True, autapses are allowed. If False, not allowed. + + """ + # set network and connectivity parameters N = 50 conn_dict = { "rule": "tripartite_bernoulli_with_pool", @@ -301,13 +388,18 @@ def test_autapses_true(autapses): # set NEST verbosity nest.set_verbosity("M_FATAL") - # test that autapses exist - pop = nest.Create("aeif_cond_alpha_astro", N) - pop_astro = nest.Create("astrocyte_lr_1994", N) + # create the network + pop_primay = nest.Create("aeif_cond_alpha_astro", N) + pop_third = nest.Create("astrocyte_lr_1994", N) nest.TripartiteConnect( - pop, pop, pop_astro, conn_spec=conn_dict, syn_specs={"third_out": {"synapse_model": "sic_connection"}} + pop_primay, + pop_primay, + pop_third, + conn_spec=conn_dict, + syn_specs={"third_out": {"synapse_model": "sic_connection"}}, ) - # make sure all connections do exist - M = get_connectivity_matrix(pop, pop) + + # make sure autapses do (or do not) exist + M = get_connectivity_matrix(pop_primay, pop_primay) mpi_assert(np.diag(M), autapses * np.ones(N)) mpi_assert(np.sum(M), N**2 - (1 - autapses) * N) From 2c624672c4d530d81184688000fc7b2231be98b0 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 16:15:10 +0100 Subject: [PATCH 85/93] Add docstrings for functions in astrocyte_small_network.py --- .../astrocytes/astrocyte_small_network.py | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/pynest/examples/astrocytes/astrocyte_small_network.py b/pynest/examples/astrocytes/astrocyte_small_network.py index 47cfe9be48..52b42a3443 100644 --- a/pynest/examples/astrocytes/astrocyte_small_network.py +++ b/pynest/examples/astrocytes/astrocyte_small_network.py @@ -174,8 +174,25 @@ # Functions for plotting. -# Plot all connections between neurons and astrocytes def plot_connections(conn_n2n, conn_n2a, conn_a2n, pre_id_list, post_id_list, astro_id_list): + """Plot all connections between neurons and astrocytes. + + Parameters + --------- + conn_n2n + Data of neuron-to-neuron connections. + conn_n2a + Data of neuron-to-astrocyte connections. + conn_a2n + Data of astrocyte-to-neuron connections. + pre_id_list + ID list of presynaptic neurons. + post_id_list + ID list of postsynaptic neurons. + astro_id_list + ID list of astrocytes. + + """ print("Plotting connections ...") # helper function to create lists of connection positions @@ -248,8 +265,21 @@ def get_conn_positions(dict_in, source_center, target_center): axs.spines["right"].set_visible(False) -# Helper function to get times, means, and standard deviation of data for plotting def get_plot_data(data_in, variable): + """Helper function to get times, means, and standard deviations of data for plotting. + + Parameters + --------- + data_in + Data containing the variable to be plotted. + Variable + Variable to be plotted. + + Return values + ------------- + Times, means, and standard deviations of the variable to be plotted. + + """ times_all = data_in["times"] ts = list(set(data_in["times"])) means = np.array([np.mean(data_in[variable][times_all == t]) for t in ts]) @@ -257,8 +287,20 @@ def get_plot_data(data_in, variable): return ts, means, sds -# Plot membrane potentials of presynaptic and postsynaptic neurons def plot_vm(pre_data, post_data, start): + """Plot membrane potentials of presynaptic and postsynaptic neurons. + + Parameters + --------- + pre_data + Data of the presynaptic neurons. + post_data + Data of the postsynaptic neurons. + start + Start time of the data to be plotted. + + """ + print("Plotting V_m ...") # get presynaptic data pre_times, pre_vm_mean, pre_vm_sd = get_plot_data(pre_data, "V_m") @@ -284,8 +326,19 @@ def plot_vm(pre_data, post_data, start): axes[1].plot(post_times, post_vm_mean, linewidth=2, color=color_post) -# Plot dynamics in astrocytes and SIC in neurons def plot_dynamics(astro_data, neuron_data, start): + """Plot dynamics in astrocytes and SIC in neurons. + + Parameters + --------- + astro_data + Data of the astrocytes. + neuron_data + Data of the neurons. + start + Start time of the data to be plotted. + + """ print("Plotting dynamics ...") # get astrocyte data astro_times, astro_ip3_mean, astro_ip3_sd = get_plot_data(astro_data, "IP3") From 6b3e6efa9e90a3876f6f13871dd21983fb703177 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 16:33:12 +0100 Subject: [PATCH 86/93] Rename astrocyte_tripartite.py to astrocyte_interaction.py --- doc/htmldoc/examples/index.rst | 6 +++--- doc/htmldoc/static/img/astrocyte_tripartite.png | Bin 58914 -> 0 bytes doc/htmldoc/whats_new/v3.6/index.rst | 2 +- ...e_tripartite.py => astrocyte_interaction.py} | 2 +- pynest/examples/astrocytes/astrocyte_single.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 doc/htmldoc/static/img/astrocyte_tripartite.png rename pynest/examples/astrocytes/{astrocyte_tripartite.py => astrocyte_interaction.py} (99%) diff --git a/doc/htmldoc/examples/index.rst b/doc/htmldoc/examples/index.rst index 40e6a826b6..b7977d0076 100644 --- a/doc/htmldoc/examples/index.rst +++ b/doc/htmldoc/examples/index.rst @@ -55,10 +55,10 @@ PyNEST examples * :doc:`../auto_examples/pong/generate_gif` .. grid-item-card:: Astrocytes - :img-top: ../static/img/astrocyte_tripartite.png + :img-top: ../static/img/astrocyte_interaction.png * :doc:`../auto_examples/astrocytes/astrocyte_single` - * :doc:`../auto_examples/astrocytes/astrocyte_tripartite` + * :doc:`../auto_examples/astrocytes/astrocyte_interaction` * :doc:`../auto_examples/astrocytes/astrocyte_small_network` * :doc:`../auto_examples/astrocytes/astrocyte_brunel` @@ -328,7 +328,7 @@ PyNEST examples ../auto_examples/csa_spatial_example ../auto_examples/hpc_benchmark ../auto_examples/astrocytes/astrocyte_single - ../auto_examples/astrocytes/astrocyte_tripartite + ../auto_examples/astrocytes/astrocyte_interaction ../auto_examples/astrocytes/astrocyte_small_network ../auto_examples/astrocytes/astrocyte_brunel diff --git a/doc/htmldoc/static/img/astrocyte_tripartite.png b/doc/htmldoc/static/img/astrocyte_tripartite.png deleted file mode 100644 index bffc8da56f4114532ba2b9bd2c3e038f8a59e7a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58914 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 T8u-K$`0tR`QO#@(vupng=#&_D diff --git a/doc/htmldoc/whats_new/v3.6/index.rst b/doc/htmldoc/whats_new/v3.6/index.rst index d95a8d9e8c..789c5e9e12 100644 --- a/doc/htmldoc/whats_new/v3.6/index.rst +++ b/doc/htmldoc/whats_new/v3.6/index.rst @@ -27,7 +27,7 @@ neuron-astrocyte circuits. See examples using astrocyte models: * :doc:`../../../auto_examples/astrocyte_single` -* :doc:`../../../auto_examples/astrocyte_tripartite` +* :doc:`../../../auto_examples/astrocyte_interaction` See model docs: diff --git a/pynest/examples/astrocytes/astrocyte_tripartite.py b/pynest/examples/astrocytes/astrocyte_interaction.py similarity index 99% rename from pynest/examples/astrocytes/astrocyte_tripartite.py rename to pynest/examples/astrocytes/astrocyte_interaction.py index bbdb5cfe9c..ce91b0e56d 100644 --- a/pynest/examples/astrocytes/astrocyte_tripartite.py +++ b/pynest/examples/astrocytes/astrocyte_interaction.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# astrocyte_tripartite.py +# astrocyte_interaction.py # # This file is part of NEST. # diff --git a/pynest/examples/astrocytes/astrocyte_single.py b/pynest/examples/astrocytes/astrocyte_single.py index 2cc8de3d9d..7de0865210 100644 --- a/pynest/examples/astrocytes/astrocyte_single.py +++ b/pynest/examples/astrocytes/astrocyte_single.py @@ -34,7 +34,7 @@ See Also ~~~~~~~~ -:doc:`astrocyte_tripartite` +:doc:`astrocyte_interaction` References ~~~~~~~~~~ From 44b8fabff938e17247f33cafa0d64d2a8a06b9f7 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 16:39:28 +0100 Subject: [PATCH 87/93] Add a comment for the scaling of "p_third_if_primary" --- pynest/examples/astrocytes/astrocyte_brunel.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pynest/examples/astrocytes/astrocyte_brunel.py b/pynest/examples/astrocytes/astrocyte_brunel.py index e9b97dd18b..ec8c46097c 100644 --- a/pynest/examples/astrocytes/astrocyte_brunel.py +++ b/pynest/examples/astrocytes/astrocyte_brunel.py @@ -195,7 +195,9 @@ def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise, scale=1. conn_params_e = { "rule": "tripartite_bernoulli_with_pool", "p_primary": network_params["p_primary"] / scale, - "p_third_if_primary": network_params["p_third_if_primary"], + "p_third_if_primary": network_params[ + "p_third_if_primary" + ], # "p_third_if_primary" is scaled along with "p_primary", so no further scaling is required "pool_size": network_params["pool_size"], "pool_type": network_params["pool_type"], } From ac8fd3a623378d400096ad501867084aea5e075b Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 17:42:06 +0100 Subject: [PATCH 88/93] Add comments for get_expected_degrees_bernoulli() --- .../pytests/test_connect_tripartite_bernoulli.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/testsuite/pytests/test_connect_tripartite_bernoulli.py b/testsuite/pytests/test_connect_tripartite_bernoulli.py index 154d6c2ee8..50907065eb 100644 --- a/testsuite/pytests/test_connect_tripartite_bernoulli.py +++ b/testsuite/pytests/test_connect_tripartite_bernoulli.py @@ -95,13 +95,15 @@ def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): mid = int(round(n * p)) e_min = 5 - # combine from front data_front = [] - cumexp = 0.0 + cumexp = 0.0 # expected number of observations bins_combined = 0 + # iterate from degree=0 to degree=mid-1, where mid is the expected degree for degree in range(mid): + # for each degree, generate expected number of observations, with binomial distribution cumexp += scipy.stats.binom.pmf(degree, n, p) * n_p bins_combined += 1 + # if cumexp is < e_min, keep it and combine it into bins of larger degrees later if cumexp < e_min: if degree == mid - 1: if len(data_front) == 0: @@ -110,15 +112,17 @@ def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): data_front[-1] = (deg, exp + cumexp, obs, num + bins_combined) else: continue + # if cumexp is >= e_min, save the data (along with previously unsaved data) else: data_front.append((degree - bins_combined + 1, cumexp, 0, bins_combined)) + # reset number of observations and move on to the next degree cumexp = 0.0 bins_combined = 0 - # combine from back data_back = [] cumexp = 0.0 bins_combined = 0 + # do the same iteration but from degree=n to degree=mid for degree in reversed(range(mid, n + 1)): cumexp += scipy.stats.binom.pmf(degree, n, p) * n_p bins_combined += 1 @@ -136,6 +140,7 @@ def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): bins_combined = 0 data_back.reverse() + # combine to obtain the expected degree distribution expected = np.array(data_front + data_back) if fan == "out": assert sum(expected[:, 3]) == len_target_pop + 1 From 5b0c1576ab6636e66f6bf677d3f14453797d2e0d Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 18:14:16 +0100 Subject: [PATCH 89/93] Improve comments in get_expected_degrees_bernoulli() --- testsuite/pytests/test_connect_tripartite_bernoulli.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testsuite/pytests/test_connect_tripartite_bernoulli.py b/testsuite/pytests/test_connect_tripartite_bernoulli.py index 50907065eb..04ee1f003e 100644 --- a/testsuite/pytests/test_connect_tripartite_bernoulli.py +++ b/testsuite/pytests/test_connect_tripartite_bernoulli.py @@ -95,15 +95,15 @@ def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): mid = int(round(n * p)) e_min = 5 - data_front = [] + data_front = [] # data from degree=0 to degree=mid-1 (mid is the expected degree in average) cumexp = 0.0 # expected number of observations bins_combined = 0 - # iterate from degree=0 to degree=mid-1, where mid is the expected degree + # iterate from degree=0 to degree=mid-1 for degree in range(mid): # for each degree, generate expected number of observations, with binomial distribution cumexp += scipy.stats.binom.pmf(degree, n, p) * n_p bins_combined += 1 - # if cumexp is < e_min, keep it and combine it into bins of larger degrees later + # if cumexp is < e_min, keep it and combine it into another bin later if cumexp < e_min: if degree == mid - 1: if len(data_front) == 0: @@ -112,14 +112,14 @@ def get_expected_degrees_bernoulli(p, fan, len_source_pop, len_target_pop): data_front[-1] = (deg, exp + cumexp, obs, num + bins_combined) else: continue - # if cumexp is >= e_min, save the data (along with previously unsaved data) + # if cumexp is >= e_min, append the data (along with previous data to be combined) else: data_front.append((degree - bins_combined + 1, cumexp, 0, bins_combined)) # reset number of observations and move on to the next degree cumexp = 0.0 bins_combined = 0 - data_back = [] + data_back = [] # data from degree=mid to degree=n cumexp = 0.0 bins_combined = 0 # do the same iteration but from degree=n to degree=mid From 44b8270625cac7b26ab5e3fe1cf11930bc6948a3 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 19:21:41 +0100 Subject: [PATCH 90/93] Add astrocyte_interaction.png --- .../static/img/astrocyte_interaction.png | Bin 0 -> 58914 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/htmldoc/static/img/astrocyte_interaction.png diff --git a/doc/htmldoc/static/img/astrocyte_interaction.png b/doc/htmldoc/static/img/astrocyte_interaction.png new file mode 100644 index 0000000000000000000000000000000000000000..bffc8da56f4114532ba2b9bd2c3e038f8a59e7a1 GIT binary patch literal 58914 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 T8u-K$`0tR`QO#@(vupng=#&_D literal 0 HcmV?d00001 From bb945f78b4b49f1e92d5b2ebf67a1cadedc214be Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Fri, 17 Nov 2023 19:48:12 +0100 Subject: [PATCH 91/93] Update paths for astrocyte examples in what's new --- doc/htmldoc/whats_new/v3.6/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/htmldoc/whats_new/v3.6/index.rst b/doc/htmldoc/whats_new/v3.6/index.rst index 789c5e9e12..73ea9ada08 100644 --- a/doc/htmldoc/whats_new/v3.6/index.rst +++ b/doc/htmldoc/whats_new/v3.6/index.rst @@ -26,8 +26,8 @@ neuron-astrocyte circuits. See examples using astrocyte models: -* :doc:`../../../auto_examples/astrocyte_single` -* :doc:`../../../auto_examples/astrocyte_interaction` +* :doc:`../../../auto_examples/astrocytes/astrocyte_single` +* :doc:`../../../auto_examples/astrocytes/astrocyte_interaction` See model docs: From de9c48aabbd243c3ae3cd63f2ba6f71d5471dc69 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sun, 19 Nov 2023 17:15:57 +0100 Subject: [PATCH 92/93] Redo the tripartite_bernoulli_with_pool figure and the relevant text --- .../static/img/tripartite_pool_type.svg | 1276 ++++++++++------- .../synapses/connection_management.rst | 105 +- 2 files changed, 843 insertions(+), 538 deletions(-) diff --git a/doc/htmldoc/static/img/tripartite_pool_type.svg b/doc/htmldoc/static/img/tripartite_pool_type.svg index d52b8d843b..ff936b1e3c 100644 --- a/doc/htmldoc/static/img/tripartite_pool_type.svg +++ b/doc/htmldoc/static/img/tripartite_pool_type.svg @@ -1,55 +1,78 @@ - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -96,904 +119,1155 @@ - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - - A1 + + + - + - - - A2 + + + - + - - - A3 + + + - + - - - A4 + + + - + - - - A5 + + + - + - - - An + + + - + - - - B1 + + + - + - - - B2 + + + - + - - - B4 + + + - + - - - B3 + + + - + - - - Bm + + + - + - - - C1 + + + - + - - - C2 + + + - + - - - C3 + + + - + - - - Cx + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - pool_type”: “random”“pool_size”: 2 + + + - + - - ... + + + - + - - ... + + + - + - - ... + + + + t0 - + - - - + + + + t1 - + - - - + + + + t2 - + - - - + + + + t3 - - - B5 + + + + t4 - + - - - + + + + t5 - - - + + + - - - + + + - - - + + + - - - + + + - + - - - A1 + + + - + - - - A2 + + + - - - A3 + + + + s0 - - - A4 + + + + s1 - - - A5 + + + + s3 - - - An + + + + s4 - - - B1 + + + + s5 - - - B2 + + + + s2 - - - B4 + + + + a2 - - - B3 + + + + a0 - - - Bm + + + + a1 - + - - - C1 + + + - + - - - C2 + + + - + - - - C3 + + + - + - - - Cx + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - ... + + + - + - - ... + + + + t0 - + - - ... + + + + t1 - + - - - + + + + t2 - + - - - + + + + t4 - - - B5 + + + + t5 - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - - + + + + s0 - + - - - + + + + s1 - + - - pool_type”: “block”“pool_size”: 1 + + + + s3 - + - - + + + + s4 - + - - + + + + s5 - + - - - + + + + s2 - + - - - + + + + a2 - + - - - + + + + a0 - + - - - A1 + + + - + - - - A2 + + + - + - - - A3 + + + - + - - - A4 + + + - + - - - A5 + + + - + - - - An + + + - + - - - B1 + + + - + - - - B2 + + + - + - - - Bm + + + - + - - - C1 + + + - + - - - C3 + + + - - - C5 + + + + s0 - - - Cx + + + + s1 - + - - - + + + + s3 - + - - - + + + + s4 - + - - - + + + + s5 - + - - - + + + + s2 - + - - - + + + + a0 - + - - - + + + + a1 - + - - - + + + + a3 - + - - ... + + + + a4 - + - - ... + + + + a5 - + - - ... + + + + t0 - + - - - + + + + t2 - - - + + + - + - - - B3 + + + - + - - - + + 2 - + - - - + + 4 - + - - - + + 2 - + - - - + + 4 - - pool_type”: “block”“pool_size”: 2 + + 2 - + - - - C2 + + 2 - + - - - C4 + + 1 - + - - - + + 2 - + - - - B6 + + 4 - + - - - A6 + + 2 - + - - - A6 + + 2 - + - - - A5 + + 2 - + - - - C6 + + 2 - + - - - B6 + + 2 - + - - + + 2 - + - - - + + 2 - + - - - + + 2 - + - - - + + 4 - + - - - + + 2 - + - - - + + 1 - + - - - + + 1 - + - - - + + 1 - + - - - + + 2 - + - - - + + 2 - + - - - + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 2 + + + + + + 2 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 2 + + + + + + 2 + + + + + + 1 + + + + + + 2 + + + + + + 1 + + + + + + 1 + + + + + + 2 + + + + + + 2 + + + + + + 3 + + + + + + 3 + + + + + + 2 + + + + + + 2 + + + + + + + + + + + + + + + a1 + + + + + + + + t3 + + + + + + + + + + + + + + + a2 + + + + + + + + t1 + + + + + + Tripartite Bernoulli with poolp_primary = 0.2p_third_if_primary = 1.0pool_type = randompool_size = 2 + + + + + + Tripartite Bernoulli with poolp_primary = 0.2p_third_if_primary = 1.0pool_type = blockpool_size = 1 + + + + + + Tripartite Bernoulli with poolp_primary = 0.2p_third_if_primary = 1.0pool_type = blockpool_size = 2 + + + + + + A + + + + + + B + + + + + + C diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 81829b996b..1298f763be 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -299,65 +299,96 @@ the ``'third_out'`` specification to connections from ``third`` to Tripartite Bernoulli with pool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For each possible pair of nodes from ``A`` and ``B``, a connection is +For each possible pair of nodes from a source ``NodeCollection`` (e.g., a neuron population ``S``) +and a target ``NodeCollection`` (e.g., a neuron population ``T``), a connection is created with probability ``p_primary``, and these connections are called 'primary' connections. For each primary connection, a -third-party connection pair involving a node from ``C`` (a third -``NodeCollection``) is created with the conditional probability +third-party connection pair involving a node from a third ``NodeCollection`` +(e.g., an astrocyte population ``A``) is created with the conditional probability ``p_third_if_primary``. This connection pair includes a connection -from the node from ``A`` (i.e., the source) to the node from ``C`` in -question, and a connection from this node from ``C`` to the node from -``B`` (i.e., the target). The node from ``C`` to connect to is chosen -at random from a pool, a subset of the nodes in ``C``. By default, -this pool is all of ``C``. +from the ``S`` node to the ``A`` node, and a connection from the ``A`` node to the +``T`` node. The ``A`` node to connect to is chosen +at random from a pool, a subset of the nodes in ``A``. By default, +this pool is all of ``A``. Pool formation is controlled by parameters ``pool_type``, which can be ``'random'`` -(default) or ``'block'``, and ``pool_size`` which must be between 1 -and the size of ``C`` (default). For random pools, for each node from -``B``, ``pool_size`` nodes from ``C`` are chosen randomly without +(default) or ``'block'``, and ``pool_size``, which must be between 1 +and the size of ``A`` (default). For random pools, for each node from +``T``, ``pool_size`` nodes from ``A`` are chosen randomly without replacement. -For block pools, two variants exist. Let ``n(B)`` and ``n(C)`` be the number of -nodes in ``B`` and ``C``, respectively. If ``pool_size == 1``, the -first ``n(B)/n(C)`` nodes in ``B`` are assigned the first node in -``C`` as their pool, the second ``n(B)/n(C)`` nodes in ``B`` the -second node in ``C`` and so forth. In this case, ``n(B)`` must be a -multiple of ``n(C)``. If ``pool_size > 1``, the first ``pool_size`` -elements of ``C`` are the pool for the first node in ``B``, the -second ``pool_size`` elements of ``C`` are the pool for the second -node in ``B`` and so forth. In this case, ``n(B) * pool_size == n(C)`` +For block pools, two variants exist. Let ``N_T`` and ``N_A`` be the number of +nodes in ``T`` and ``A``, respectively. If ``pool_size == 1``, the +first ``N_T/N_A`` nodes in ``T`` are assigned the first node in +``A`` as their pool, the second ``N_T/N_A`` nodes in ``T`` the +second node in ``A`` and so forth. In this case, ``N_T`` must be a +multiple of ``N_A``. If ``pool_size > 1``, the first ``pool_size`` +elements of ``A`` are the pool for the first node in ``T``, the +second ``pool_size`` elements of ``A`` are the pool for the second +node in ``T`` and so forth. In this case, ``N_T * pool_size == N_A`` is required. +The following code and figure demonstrate three possible use cases with +``pool_type`` being ``'random'`` or ``'block'``: + .. code-block:: python - n, m, x, p_primary, p_third_if_primary, pool_size = 10, 10, 5, 0.25, 1.0, 2 - A = nest.Create('aeif_cond_alpha_astro', n) - B = nest.Create('aeif_cond_alpha_astro', m) - C = nest.Create('astrocyte_lr_1994', x) + N_S, N_T, N_A, p_primary, p_third_if_primary, pool_size = 6, 6, 3, 0.2, 1.0, 2 + S = nest.Create('aeif_cond_alpha_astro', N_S) + T = nest.Create('aeif_cond_alpha_astro', N_T) + A = nest.Create('astrocyte_lr_1994', N_A) conn_spec = {'rule': 'tripartite_bernoulli_with_pool', 'p_primary': p_primary, 'p_third_if_primary': p_third_if_primary, 'pool_type': 'random', 'pool_size': pool_size} syn_specs = {'third_out': 'sic_connection'} - nest.TripartiteConnect(A, B, C, conn_spec, syn_specs) + nest.TripartiteConnect(S, T, A, conn_spec, syn_specs) + + +.. code-block:: python + + N_S, N_T, N_A, p_primary, p_third_if_primary, pool_size = 6, 6, 3, 0.2, 1.0, 1 + S = nest.Create('aeif_cond_alpha_astro', N_S) + T = nest.Create('aeif_cond_alpha_astro', N_T) + A = nest.Create('astrocyte_lr_1994', N_A) + conn_spec = {'rule': 'tripartite_bernoulli_with_pool', + 'p_primary': p_primary, + 'p_third_if_primary': p_third_if_primary, + 'pool_type': 'block', + 'pool_size': pool_size} + syn_specs = {'third_out': 'sic_connection'} + nest.TripartiteConnect(S, T, A, conn_spec, syn_specs) + + +.. code-block:: python + + N_S, N_T, N_A, p_primary, p_third_if_primary, pool_size = 6, 3, 6, 0.2, 1.0, 2 + S = nest.Create('aeif_cond_alpha_astro', N_S) + T = nest.Create('aeif_cond_alpha_astro', N_T) + A = nest.Create('astrocyte_lr_1994', N_A) + conn_spec = {'rule': 'tripartite_bernoulli_with_pool', + 'p_primary': p_primary, + 'p_third_if_primary': p_third_if_primary, + 'pool_type': 'block', + 'pool_size': pool_size} + syn_specs = {'third_out': 'sic_connection'} + nest.TripartiteConnect(S, T, A, conn_spec, syn_specs) .. image:: ../static/img/tripartite_pool_type.svg :align: center -Possible outcomes of connectivity with the two pool types. In the example -of ``'random'`` pool type (left), each node in ``B`` can be connected with -up to two randomly selected nodes in ``C`` (given ``pool_size == 2``). In -the first example of ``'block'`` pool type (middle), let ``n(B)/n(C)`` = 2, -then each node in ``B`` can be connected with one node in ``C`` -(``pool_size == 1`` is required because ``n(C) < n(B)``), and each node in -``C`` can be connected with up to two nodes in ``B``. In the second example -of ``'block'`` pool type (right), let ``n(C)/n(B)`` = 2, then each node in -``B`` can be connected with up to two nodes in ``C`` (``pool_size == 2`` is -required because ``n(C)/n(B)`` = 2), and each node in ``C`` can be -connected to one node in ``B``. Colors of nodes in ``B`` and ``C`` indicate -which node(s) in ``C`` a node in ``B`` is connected with. +(A) In the example of ``'random'`` pool type, each node in ``T`` can be connected with +up to two randomly selected nodes in ``A`` (given ``pool_size == 2``). (B) In +the first example of ``'block'`` pool type, let ``N_T/N_A`` = 2, +then each node in ``T`` can be connected with one node in ``A`` +(``pool_size == 1`` is required because ``N_A < N_T``), and each node in +``A`` can be connected with up to two nodes in ``T``. (C) In the second example +of ``'block'`` pool type, let ``N_A/N_T`` = 2, then each node in +``T`` can be connected with up to two nodes in ``A`` (``pool_size == 2`` is +required because ``N_A/N_T`` = 2), and each node in ``A`` can be +connected to one node in ``T``. .. _synapse_spec: From 31e0ce9ba8c43c3477b3c856e645f50573646d22 Mon Sep 17 00:00:00 2001 From: HanjiaJiang Date: Sun, 19 Nov 2023 22:38:27 +0100 Subject: [PATCH 93/93] Improve wording and code for the tripartite_bernoulli_with_pool documentation --- doc/htmldoc/synapses/connection_management.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/htmldoc/synapses/connection_management.rst b/doc/htmldoc/synapses/connection_management.rst index 1298f763be..f9a919febc 100644 --- a/doc/htmldoc/synapses/connection_management.rst +++ b/doc/htmldoc/synapses/connection_management.rst @@ -328,19 +328,20 @@ second ``pool_size`` elements of ``A`` are the pool for the second node in ``T`` and so forth. In this case, ``N_T * pool_size == N_A`` is required. -The following code and figure demonstrate three possible use cases with +The following code and figure demonstrate three use case examples with ``pool_type`` being ``'random'`` or ``'block'``: .. code-block:: python - N_S, N_T, N_A, p_primary, p_third_if_primary, pool_size = 6, 6, 3, 0.2, 1.0, 2 + N_S, N_T, N_A, p_primary, p_third_if_primary = 6, 6, 3, 0.2, 1.0 + pool_type, pool_size = 'random', 2 S = nest.Create('aeif_cond_alpha_astro', N_S) T = nest.Create('aeif_cond_alpha_astro', N_T) A = nest.Create('astrocyte_lr_1994', N_A) conn_spec = {'rule': 'tripartite_bernoulli_with_pool', 'p_primary': p_primary, 'p_third_if_primary': p_third_if_primary, - 'pool_type': 'random', + 'pool_type': pool_type, 'pool_size': pool_size} syn_specs = {'third_out': 'sic_connection'} nest.TripartiteConnect(S, T, A, conn_spec, syn_specs) @@ -348,14 +349,15 @@ The following code and figure demonstrate three possible use cases with .. code-block:: python - N_S, N_T, N_A, p_primary, p_third_if_primary, pool_size = 6, 6, 3, 0.2, 1.0, 1 + N_S, N_T, N_A, p_primary, p_third_if_primary = 6, 6, 3, 0.2, 1.0 + pool_type, pool_size = 'block', 1 S = nest.Create('aeif_cond_alpha_astro', N_S) T = nest.Create('aeif_cond_alpha_astro', N_T) A = nest.Create('astrocyte_lr_1994', N_A) conn_spec = {'rule': 'tripartite_bernoulli_with_pool', 'p_primary': p_primary, 'p_third_if_primary': p_third_if_primary, - 'pool_type': 'block', + 'pool_type': pool_type, 'pool_size': pool_size} syn_specs = {'third_out': 'sic_connection'} nest.TripartiteConnect(S, T, A, conn_spec, syn_specs) @@ -363,14 +365,15 @@ The following code and figure demonstrate three possible use cases with .. code-block:: python - N_S, N_T, N_A, p_primary, p_third_if_primary, pool_size = 6, 3, 6, 0.2, 1.0, 2 + N_S, N_T, N_A, p_primary, p_third_if_primary = 6, 3, 6, 0.2, 1.0 + pool_type, pool_size = 'block', 2 S = nest.Create('aeif_cond_alpha_astro', N_S) T = nest.Create('aeif_cond_alpha_astro', N_T) A = nest.Create('astrocyte_lr_1994', N_A) conn_spec = {'rule': 'tripartite_bernoulli_with_pool', 'p_primary': p_primary, 'p_third_if_primary': p_third_if_primary, - 'pool_type': 'block', + 'pool_type': pool_type, 'pool_size': pool_size} syn_specs = {'third_out': 'sic_connection'} nest.TripartiteConnect(S, T, A, conn_spec, syn_specs)