Demo of bmtool chemical synaptic tuner using only Neuron¶
By Gregory Glickert
First we must define some general settings and the settings for the connection we would like to tune. Below is an example of what this could look like for excitatory and inhibitory connections. Currently all of these settings, but the ones in the spec_syn_param are needed in order to use the tuner. If you dont include the general settting when initalizing the tuner then by default it will use these settings. The spec_settings are going to depend on your exact use case and connection type.
If you are using the tuner for a BMTK network you can look at this notebook, but make sure to also check out the bmtk_tuner notebook in this same directory.
general_settings = {
'vclamp': True, # if vclamp should start on or off used mostly for singleEventv
'rise_interval': (0.1, 0.9), #10-90%
'tstart': 500., # when the singleEvent should start
'tdur': 100., # Dur of sim after single synaptic event has occured
'threshold': -15., #threshold for spike in mV
'delay': 1.3, # netcon delay
'weight': 1., # netcon weight
'dt': 0.025, # simulation dt
'celsius': 20 # temp of sim
}
conn_type_settings = {
'Fac2FSI': { # facilitating synapse
'spec_settings': {
'post_cell': 'FSI_Cell',
'vclamp_amp' : -70., # voltage clamp amps
'sec_x': 0.5, # location of synapse
'sec_id': 1, # location of synapse
"level_of_detail": "AMPA_NMDA_STP", # name of mechanism from the modfile
},
'spec_syn_param': { # synaptic parameters from modfile
'initW': 0.76,
'tau_r_AMPA': 0.45,
'tau_d_AMPA': 7.5,
'Use': 0.13,
'Dep': 0.,
'Fac': 200.
},
},
'Dep2FSI': { # depressing synapse
'spec_settings': {
'post_cell': 'FSI_Cell',
'vclamp_amp': -55,
'sec_x': 0.5,
'sec_id':0,
"level_of_detail": "GABA_A_STP",
},
'spec_syn_param': {
'initW': 20,
'tau_r_GABAA': 0.9,
'tau_d_GABAA': 15,
'e_GABAA':-75,
'Use': 0.4,
'Dep': 190.,
'Fac': 0.
},
},
}
Then the modfiles must be compiled in order for the tuner to work properly
import os
# if already compiled then lets delete the folder and force a recompile
if os.path.isdir('modfiles/x86_64'):
os.system("rm -rf modfiles/x86_64 ")
# compile the mod files
if not os.path.isdir("modfiles/x86_64"):
os.chdir('modfiles')
os.system("nrnivmodl")
os.chdir("..")
/Users/gregglickert/miniconda3/envs/bmtk/bin/nrnivmodl:10: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html from pkg_resources import working_set
/usr/bin/xcrun /Users/gregglickert/Documents/GitHub/bmtool/docs/examples/notebooks/synapses/synaptic_tuner/modfiles Mod files: "./AMPA_NMDA_STP.mod" "./GABA_A_STP.mod" "./Gfluct.mod" "./cadad.mod" "./cal2.mod" "./can_mig.mod" "./exp2syn_stp.mod" "./gap.mod" "./h_kole.mod" "./imCA3.mod" "./kBK.mod" "./kap_BS.mod" "./kdmc_BS.mod" "./kdrCA3.mod" "./kdr_BS.mod" "./kdrinter.mod" "./leak.mod" "./nainter.mod" "./napCA3.mod" "./natCA3.mod" "./nax_BS.mod" "./vecevent_coreneuron.mod" -> Compiling mod_func.cpp => LINKING shared library ./libnrnmech.dylib Successfully created arm64/special
initialize tuner¶
Now we can initialize the synaptic tuner. When initializing you will have to change a few arguments depending on your use case. other_vars_to_record can be any variable in your synaptic mechanism, while slider_vars can be any range variable you wish to tune in the synapse. If the variable is not defined in the spec_syn_param than the tuner will get the value from the mechanism and try to set up some sliders to tune it.
mechanisms_dir = 'modfiles'
templates_file = 'templates.hoc'
# Initialize our tuner
from bmtool.synapses import SynapseTuner
tuner = SynapseTuner(mechanisms_dir=mechanisms_dir, # where x86_64 is located
templates_dir=templates_file, # where the neuron templates are located
conn_type_settings=conn_type_settings, # dict of connection settings
general_settings = general_settings, # dict of general settings
connection = 'Dep2FSI', # key in connection settings for which connection you want to tune
current_name = 'i', # name of current variable in synapase
other_vars_to_record = ['record_Pr', 'record_use'], # Other synaptic variables you wish to record besides the normal ones
slider_vars=['initW','Dep','Fac','Use']) # Range variables you want to tune to adjust synaptic response.
Warning: no DISPLAY environment variable. --No graphics will be displayed.
SingleEvent¶
The SingleEvent method will run a short pulse and then print out the synaptic properties for the synapse.
tuner.SingleEvent()
('baseline', 0.03107559632675816)
('sign', 1.0)
('latency', 1.35)
('amp', 0.16000855570656114)
('rise_time', 1.35)
('decay_time', 15.182347895738655)
('half_width', 14.850000000000001)
Current Integral in pA*ms: -15777.35
InteractiveTuner¶
The InteractiveTuner will deliver an input to the cell at a desired weight and frequency. The frequency by default will be 8 spikes then a 250ms delay and then 4 more spikes.
Paired-pulse ratio is (Avg 2nd pulse - Avg 1st pulse) ÷ 90th percentile amplitude.
Induction is (Avg (6th, 7th, 8th pulses) - Avg 1st pulse) ÷ 90th percentile amplitude.
Recovery is (Avg (9th, 10th, 11th, 12th pulses) - Avg (1st, 2nd, 3rd, 4th pulses)) ÷ 90th percentile amplitude
tuner.InteractiveTuner()
VBox(children=(HBox(children=(Dropdown(description='Connection:', options=('Dep2FSI', 'Fac2FSI'), style=Descri…
Output()
Frequency reponse¶
We can also see how the STP parameters vary with different train frequencies
results = tuner.stp_frequency_response(log_plot=False)
Analyzing frequencies: 0%| | 0/16 [00:00<?, ?it/s]
SynapseOptimizer¶
If we don't feel like tuner by hand we can also try to optimize an output of our model. In this example we will optimize and find the best STP parameters that give the induction and paired pulse response we want. Something to note is that the optimizer does not know what the trace should look like and only knows the features. So it might get some wild trace that happens to work. Also if you are using the random init_guess and don't like the voltage trace then run it again. The seed is different each time so the optimization will be different and can result in a better fit.
from bmtool.synapses import SynapseOptimizer
# Create the optimizer
optimizer = SynapseOptimizer(tuner)
# Define parameter bounds these can be any range variable you wish to tune
param_bounds = {
'Dep': (0, 800.0),
'Fac': (0, 100.0),
'Use': (0.1, 1.0),
}
# Define target metrics these are the metrics that the tuner will try to automatic get the synapse to respond with
# max amps is an absolute value
target_metrics = {
'induction': -0.75,
'simple_ppr': 0.8,
'recovery': 0.0,
}
# currently the metrics in the SynapseOptimizer are
# - induction: measure of synaptic facilitation/depression
# - ppr: paired-pulse ratio from allen
# - simple_ppr ration of first to second amplitude
# - recovery: recovery from facilitation/depression
# - max_amplitude: maximum synaptic response amplitude
# - rise_time: time for synaptic response to rise from 20% to 80% of peak
# - decay_time: time constant of synaptic response decay
# - latency: synaptic response latency
# - half_width: synaptic response half-width
# - baseline: baseline current
# - amp: peak amplitude from syn_props
def custom_cost(metrics, targets):
# equal zero unless using train input
induction_error = (metrics['induction'] - targets['induction']) ** 2
ppr_error = (metrics['simple_ppr'] - targets['simple_ppr']) ** 2
recovery_error = (metrics['recovery'] - targets['recovery']) ** 2
return induction_error + 3 * ppr_error + recovery_error
# Run optimization with custom cost function
result = optimizer.optimize_parameters(
target_metrics=target_metrics,
param_bounds=param_bounds,
run_single_event=True, # Run and use parameters from SingleEvent
run_train_input=True, # Run and use parameters from train input
train_frequency=50, # Freq in Hz of train input
train_delay=250, # delay in ms of second train
init_guess='middle_guess', # either random or middle_guess. Random will start the synapse witha random value in the param_bound. Middle guess will pick the middle value in the param_bounds
cost_function=custom_cost,
method='SLSQP') # check out https://docs.scipy.org/doc/scipy-1.15.0/reference/generated/scipy.optimize.minimize.html
# Plot results
optimizer.plot_optimization_results(result)
Optimization Results: Final Error: 2.91e-07 Target Metrics: induction: -0.750 (target: -0.750) simple_ppr: 0.800 (target: 0.800) recovery: 0.000 (target: 0.000) Optimal Parameters: Dep: 144.855 Fac: 99.077 Use: 0.680
========================================
Short Term Plasticity Results for 50Hz with 250 Delay
========================================
Simple PPR: Above 1 is facilitating, below 1 is depressing
PPR: Above 0 is facilitating, below 0 is depressing.
Induction: Above 0 is facilitating, below 0 is depressing.
Recovery: A measure of how fast STP decays.
Simple Paired Pulse Ratio (PPR)
Calculation: Avg 2nd pulse / Avg 1st pulse
Values: 54.576 / 68.223 = 0.800
Paired Pulse Response (PPR)
Calculation: (Avg 2nd pulse - Avg 1st pulse) / 90th percentile amplitude
Values: (54.576 - 68.223) / 66.858 = -0.204
Induction
Calculation: (Avg(6th, 7th, 8th pulses) - Avg 1st pulse) / 90th percentile amplitude
Values: 18.053 - 68.223 / 66.858 = -0.750
Recovery
Calculation: (Avg(9th, 10th, 11th, 12th pulses) - Avg(1st to 4th pulses)) / 90th percentile amplitude
Values: 44.122 - 44.122 / 66.858 = 0.000
========================================
('baseline', -0.0002407732493736603)
('sign', 1.0)
('latency', 1.35)
('amp', 0.06798172722533025)
('rise_time', 1.35)
('decay_time', 15.182348785123999)
('half_width', 14.850000000000001)
Current Integral in pA*ms: 1362.98