Audit frozen LIF semantics and real recurrent motifs without learning changes

This commit is contained in:
Digital Drosophila Archive
2026-09-30 01:00:16 +08:00
parent 3213aada79
commit 6fc326ccd1
55 changed files with 2809 additions and 26 deletions
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Philip Shiu and Nico Spiller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,109 @@
name: brian2
channels:
- defaults
- conda-forge
dependencies:
- abseil-cpp=20211102.0=h0e60522_0
- arrow-cpp=11.0.0=py310h7cee713_0
- asttokens=2.2.1=pyhd8ed1ab_0
- aws-c-common=0.4.57=ha925a31_1
- aws-c-event-stream=0.1.6=hd77b12b_5
- aws-checksums=0.1.9=ha925a31_0
- aws-sdk-cpp=1.8.185=hd77b12b_0
- backcall=0.2.0=pyh9f0ad1d_0
- backports=1.0=pyhd8ed1ab_3
- backports.functools_lru_cache=1.6.4=pyhd8ed1ab_0
- boost-cpp=1.78.0=h5b4e17d_0
- brian2=2.5.1=py310hc781a3c_0
- bzip2=1.0.8=he774522_0
- c-ares=1.19.0=h2bbff1b_0
- ca-certificates=2023.5.7=h56e8100_0
- colorama=0.4.6=pyhd8ed1ab_0
- cython=0.29.33=py310hd77b12b_0
- debugpy=1.5.1=py310hd77b12b_0
- decorator=5.1.1=pyhd8ed1ab_0
- executing=1.2.0=pyhd8ed1ab_0
- gflags=2.2.2=ha925a31_1004
- glog=0.5.0=h4797de2_0
- gsl=2.7=hdfb1a43_0
- importlib-metadata=6.6.0=pyha770c72_0
- importlib_metadata=6.6.0=hd8ed1ab_0
- intel-openmp=2023.1.0=h57928b3_46319
- ipykernel=6.15.0=pyh025b116_0
- ipython=8.14.0=pyh08f2357_0
- jedi=0.18.2=pyhd8ed1ab_0
- jinja2=2.11.3=pyhd8ed1ab_2
- joblib=1.2.0=pyhd8ed1ab_0
- jupyter_client=8.2.0=pyhd8ed1ab_0
- jupyter_core=5.3.0=py310h5588dad_0
- libblas=3.9.0=8_mkl
- libbrotlicommon=1.0.9=h8ffe710_7
- libbrotlidec=1.0.9=h8ffe710_7
- libbrotlienc=1.0.9=h8ffe710_7
- libcblas=3.9.0=8_mkl
- libcurl=7.88.1=h86230a5_0
- libffi=3.4.4=hd77b12b_0
- liblapack=3.9.0=8_mkl
- libprotobuf=3.20.3=h23ce68f_0
- libsodium=1.0.18=h8d14728_1
- libssh2=1.10.0=hcd4344a_0
- libthrift=0.15.0=h636ae23_0
- lz4-c=1.9.4=h2bbff1b_0
- m2w64-gcc-libgfortran=5.3.0=6
- m2w64-gcc-libs=5.3.0=7
- m2w64-gcc-libs-core=5.3.0=7
- m2w64-gmp=6.1.0=2
- m2w64-libwinpthread-git=5.0.0.4634.697f757=2
- markupsafe=1.1.1=py310he2412df_4
- matplotlib-inline=0.1.6=pyhd8ed1ab_0
- mkl=2020.4=hb70f87d_311
- mpmath=1.3.0=pyhd8ed1ab_0
- msys2-conda-epoch=20160418=1
- nest-asyncio=1.5.6=pyhd8ed1ab_0
- numpy=1.22.3=py310hed7ac4c_2
- openssl=1.1.1t=h2bbff1b_0
- orc=1.7.4=h623e30f_1
- packaging=23.1=pyhd8ed1ab_0
- pandas=1.4.3=py310hf5e1058_0
- parso=0.8.3=pyhd8ed1ab_0
- pickleshare=0.7.5=py_1003
- pip=23.0.1=py310haa95532_0
- platformdirs=3.5.1=pyhd8ed1ab_0
- prompt-toolkit=3.0.38=pyha770c72_0
- prompt_toolkit=3.0.38=hd8ed1ab_0
- psutil=5.9.0=py310h2bbff1b_0
- pure_eval=0.2.2=pyhd8ed1ab_0
- py-cpuinfo=9.0.0=pyhd8ed1ab_0
- pyarrow=11.0.0=py310h790e06d_0
- pygments=2.15.1=pyhd8ed1ab_0
- pyparsing=3.0.9=pyhd8ed1ab_0
- python=3.10.11=h966fe2a_2
- python-dateutil=2.8.2=pyhd8ed1ab_0
- python_abi=3.10=2_cp310
- pytz=2023.3=pyhd8ed1ab_0
- pywin32=305=py310h2bbff1b_0
- pyzmq=25.0.2=py310hd77b12b_0
- re2=2022.04.01=h0e60522_0
- scipy=1.8.1=py310h7c00807_2
- setuptools=67.8.0=py310haa95532_0
- six=1.16.0=pyh6c4a22f_0
- snappy=1.1.9=h6c2663c_0
- sqlite=3.41.2=h2bbff1b_0
- stack_data=0.6.2=pyhd8ed1ab_0
- sympy=1.12=pyh04b8f61_3
- tk=8.6.12=h2bbff1b_0
- tornado=6.2=py310he2412df_0
- traitlets=5.9.0=pyhd8ed1ab_0
- typing-extensions=4.6.3=hd8ed1ab_0
- typing_extensions=4.6.3=pyha770c72_0
- tzdata=2023c=h04d1e81_0
- utf8proc=2.6.1=h2bbff1b_0
- vc=14.2=h21ff451_1
- vs2015_runtime=14.27.29016=h5e58377_2
- wcwidth=0.2.6=pyhd8ed1ab_0
- wheel=0.38.4=py310haa95532_0
- xz=5.4.2=h8cc25b3_0
- zeromq=4.3.4=h0e60522_1
- zipp=3.15.0=pyhd8ed1ab_0
- zlib=1.2.13=h8cc25b3_0
- zstd=1.5.5=hd43e919_0
@@ -0,0 +1,373 @@
import pandas as pd
from textwrap import dedent
# brian 2
from brian2 import NeuronGroup, Synapses, PoissonInput, SpikeMonitor, Network
from brian2 import mV, ms, Hz
# file handling
from pathlib import Path
# parallelization
from joblib import Parallel, delayed, parallel_backend
from time import time
default_params = {
# trials
't_run' : 1000 * ms, # duration of trial
'n_run' : 30, # number of runs
# network constants
# Kakaria and de Bivort 2017 https://doi.org/10.3389/fnbeh.2017.00008
'v_0' : -52 * mV, # resting potential
'v_rst' : -52 * mV, # reset potential after spike
'v_th' : -45 * mV, # threshold for spiking
't_mbr' : 20 * ms, # membrane time scale (capacitance * resistance = .002 * uF * 10. * Mohm)
# Jürgensen et al https://doi.org/10.1088/2634-4386/ac3ba6
'tau' : 5 * ms, # time constant
# Lazar et al https://doi.org/10.7554/eLife.62362
't_rfc' : 2.2 * ms, # refractory period
# Paul et al 2015 doi: 10.3389/fncel.2015.00029
't_dly' : 1.8*ms, # delay for changes in post-synaptic neuron
# Free parameter
'w_syn' : .275 * mV, # weight per synapse (note: modulated by exponential decay)
# Default activation rates
'r_poi' : 150*Hz, # default rate of the Poisson inputs
'r_poi2' : 0*Hz, # default rate of a 2nd class of Poisson inputs
'f_poi' : 250, # scaling factor for Poisson synapse; 250 is sufficient to cause spiking
# equations for neurons # alpha synapse https://doi.org/10.1017/CBO9780511815706; See https://brian2.readthedocs.io/en/stable/user/converting_from_integrated_form.html
'eqs' : dedent('''
dv/dt = (v_0 - v + g) / t_mbr : volt (unless refractory)
dg/dt = -g / tau : volt (unless refractory)
rfc : second
'''),
# condition for spike
'eq_th' : 'v > v_th',
# rules for spike
'eq_rst' : 'v = v_rst; w = 0; g = 0 * mV',
}
#######################
# brian2 model setup
def poi(neu, exc, exc2, params):
'''Create PoissonInput for neurons.
For each neuron in 'names' a PoissonInput is generated and
the refractory period of that neuron is set to 0 in NeuronGroup.
Parameters
----------
neu : NeuronGroup
Defined brian2.NeuronGroup object
exc : list
Indices of neurons for which to create Poisson input
exc2 : list
Indices of neurons for which to create Poisson input with `r_poi`
params : dict
Constants and equations that are used to construct the brian2 network model
Returns
-------
pois : list
PoissonInput objects for each neuron in 'exc'
neu : NeuronGroup
NeuronGroup with adjusted refractory periods
'''
pois = []
for i in exc:
p = PoissonInput(
target=neu[i],
target_var='v',
N=1,
rate=params['r_poi'],
weight=params['w_syn']*params['f_poi']
)
neu[i].rfc = 0 * ms # no refractory period for Poisson targets
pois.append(p)
for i in exc2:
p = PoissonInput(
target=neu[i],
target_var='v',
N=1,
rate=params['r_poi2'],
weight=params['w_syn']*params['f_poi']
)
neu[i].rfc = 0 * ms # no refractory period for Poisson targets
pois.append(p)
return pois, neu
def silence(slnc, syn):
'''Silence neuron by setting weights of all synapses from it to 0
Parameters
----------
slnc : list
List of neuron indices to silence
syn : brian2.Synapses
Defined synapses object
Returns
-------
syn : brian2.Synapses
Synapses with modified weights
'''
for i in slnc:
syn.w[' {} == i'.format(i)] = 0*mV
return syn
def create_model(path_comp, path_con, params):
'''Create default network model.
Convert the "completeness materialization" and "connectivity" dataframes
into a brian2 neural network model. Network constants and equations
are defined at the beginning of this file.
Parameters
----------
path_comp : str
path to "completeness materialization" dataframe
path_con : str
path to "connectivity" dataframe
params : dict
Constants and equations that are used to construct the brian2 network model
Returns
-------
neu : NeuronGroup
brian2.NeuronGroup object with neurons as in 'path_comp'
syn : Synapses
brian2.Synapses object with connections as in 'path_con'
spk_mon : SpikeMonitor
brian2.SpikeMonitor object, which records time of spike events
'''
# load neuron connectivity dataframes
df_comp = pd.read_csv(path_comp, index_col=0)
df_con = pd.read_parquet(path_con)
neu = NeuronGroup( # create neurons
N=len(df_comp),
model=params['eqs'],
method='linear',
threshold=params['eq_th'],
reset=params['eq_rst'],
refractory='rfc',
name='default_neurons',
namespace=params,
)
neu.v = params['v_0'] # initialize values
neu.g = 0
neu.rfc = params['t_rfc']
# create synapses
syn = Synapses(neu, neu, 'w : volt', on_pre='g += w', delay=params['t_dly'], name='default_synapses')
# connect synapses
i_pre = df_con.loc[:, 'Presynaptic_Index'].values
i_post = df_con.loc[:, 'Postsynaptic_Index'].values
syn.connect(i=i_pre, j=i_post)
# define connection weight
syn.w = df_con.loc[:,'Excitatory x Connectivity'].values * params['w_syn']
# object to record spikes
spk_mon = SpikeMonitor(neu)
return neu, syn, spk_mon
#####################
# running simulations
def get_spk_trn(spk_mon):
'''Extracts spike times from 'spk_mon'
The spike times recorded in the SpikeMonitor object during
simulation are converted to a list of times for each neurons.
Returns dict with "brian ID": "list of spike times".
Parameters
----------
spk_mon : SpikeMonitor
Contains recorded spike times
Returns
-------
spk_trn : dict
Mapping between brian neuron IDs and spike times
'''
spk_trn = {k: v for k, v in spk_mon.spike_trains().items() if len(v)}
return spk_trn
def construct_dataframe(res, exp_name, i2flyid):
'''Take spike time dict and collects spikes in pandas dataframe
Parameters
----------
res : list
List with spike time dicts for each trial
exp_name : str
Name of the experiment
i2flyid : dict
Mapping between Brian IDs and flywire IDs
Returns
-------
df : pandas.DataFrame
Dataframe where each row is one spike
'''
ids, ts, nrun = [], [], []
for n, i in enumerate(res):
for j, k in i.items():
ids.extend([j for _ in k])
nrun.extend([n for _ in k])
ts.extend([float(l) for l in k])
d = {
't': ts,
'trial': nrun,
'flywire_id': [i2flyid[i] for i in ids],
'exp_name': exp_name
}
df = pd.DataFrame(d)
return df
def run_trial(exc, exc2, slnc, path_comp, path_con, params):
'''Run single trial of coactivation/silencng experiment
During the coactivation experiment, the neurons in 'exc' are
Poisson inputs. The simulation runs for 't_run'.
Parameters
----------
exc: list
contains indices of neurons for PoissonInput
exc2: list
contains indices of neurons for PoissonInput of different frequency `r_poi2`
slnc: list
contains indices of neurons to silence
path_comp: Path
path to "completeness materialization" dataframe
path_con: Path
path to "connectivity" dataframe
params : dict
Constants and equations that are used to construct the brian2 network model
Returns
-------
spk_trn : dict
Mapping between brian neuron IDs and spike times
'''
# get default network
neu, syn, spk_mon = create_model(path_comp, path_con, params)
# define Poisson input for excitation
poi_inp, neu = poi(neu, exc, exc2, params)
# silence neurons
syn = silence(slnc, syn)
# collect in Network object
net = Network(neu, syn, spk_mon, *poi_inp)
# run simulation
net.run(duration=params['t_run'])
# spike times
spk_trn = get_spk_trn(spk_mon)
return spk_trn
def run_exp(exp_name, neu_exc, path_res, path_comp, path_con,
params=default_params, neu_slnc=[], neu_exc2=[],
n_proc=-1, force_overwrite=False):
'''
Run default network experiment
Neurons in `neu_exc` are Poisson external inputs
Neurons in `neu_slnc` are silenced
Parameters
----------
exp_name: str
name of the experiment
neu_exc: list
contains custom names or flywire IDs of neurons to be excited
path_res: str
path to the output folder where spike data is stored
path_comp: str
path to "completeness materialization" dataframe
path_con: str
path to "connectivity" dataframe
params : dict
Constants and equations that are used to construct the brian2 network model
neu_slnc : list (optional)
contains custom names or flywire IDs of neurons to be silenced
neu_exc2 : list (optional)
contains custom names or flywire IDs of neurons to be excited with different frequency
requires `r_poi2` to be defined in `params`
n_proc: int (optional)
number of cores to be used for parallel runs
default: -1 (use all available cores)
n_proc=1 is equivalent serial code
force_overwrite : bool (optional)
If True, overwrite output files, else skip simulation, by default False
'''
# convert to Path objects
path_res, path_comp, path_con = [ Path(i) for i in [path_res, path_comp, path_con] ]
# define output files
path_save = path_res / '{}.parquet'.format(exp_name)
if path_save.is_file() and not force_overwrite:
print('>>> Skipping experiment {} because {} exists and force_overwrite = {}'.format(exp_name, path_save, force_overwrite))
return
# load name/id mappings
df_comp = pd.read_csv(path_comp, index_col=0) # load completeness dataframe
flyid2i = {j: i for i, j in enumerate(df_comp.index)} # flywire id: biran ID
i2flyid = {j: i for i, j in flyid2i.items()} # brian ID: flywire ID
# print info
print('>>> Experiment: {}'.format(exp_name))
print(' Output file: {}'.format(path_save))
print(' Excited neurons: {}'.format(len(neu_exc + neu_exc2)))
if neu_slnc:
print(' Silenced neurons: {}'.format(len(neu_slnc)))
# start parallel calculation
n_run = params['n_run']
start = time()
with parallel_backend('loky', n_jobs=n_proc):
exc = [ flyid2i[n] for n in neu_exc ]
exc2 = [ flyid2i[n] for n in neu_exc2 ]
slnc = [ flyid2i[n] for n in neu_slnc ]
res = Parallel()(
delayed(
run_trial)(exc, exc2, slnc, path_comp, path_con, params) for _ in range(n_run))
# print simulation time
walltime = time() - start
print(' Elapsed time: {} s'.format(int(walltime)))
# dataframe with spike times
df = construct_dataframe(res, exp_name, i2flyid)
# store spike data
df.to_parquet(path_save, compression='brotli')
@@ -0,0 +1,29 @@
{
"commit": "91bdd1e7dcf193f3e7ca5a8933497fcef63b7960",
"downloaded_code_executed": false,
"files": [
{
"bytes": 11900,
"license": "MIT",
"path": "model.py",
"sha256": "fc45837d7122c6ce2a7f3f2f23c515992e4b232aadb919efabb72337fac88e4e",
"url": "https://raw.githubusercontent.com/philshiu/Drosophila_brain_model/91bdd1e7dcf193f3e7ca5a8933497fcef63b7960/model.py"
},
{
"bytes": 1085,
"license": "MIT",
"path": "LICENSE",
"sha256": "3621f6d6476189190e2960fa43f11b275ba4eca848fdac50a1ba2925de6223e8",
"url": "https://raw.githubusercontent.com/philshiu/Drosophila_brain_model/91bdd1e7dcf193f3e7ca5a8933497fcef63b7960/LICENSE"
},
{
"bytes": 3445,
"license": "MIT",
"path": "environment_full.yml",
"sha256": "402fcef4968c99397b0e908b3323ab9e6ef86af5583e46e6427f96f3d1091adf",
"url": "https://raw.githubusercontent.com/philshiu/Drosophila_brain_model/91bdd1e7dcf193f3e7ca5a8933497fcef63b7960/environment_full.yml"
}
],
"new_dependencies_installed": false,
"paper": "https://www.nature.com/articles/s41586-024-07763-9"
}