__author__ = 'Aaron D. Milstein'
from specify_cells import *
import random
import os
from ipyparallel import interactive
"""
Builds a cell locally so each engine is ready to receive jobs one at a time, specified by an index corresponding to
which synapse to optimize (coarse sampling of the full set of spines).
"""
morph_filename = 'EB2-late-bifurcation.swc'
mech_filename = '043016 Type A - km2_NMDA_KIN5_Pr'
param_names = ['f', 'tau_F', 'd1', 'tau_D1']
x = [] # placeholder for optimization parameters, must be pushed to each engine at each iteration
num_stims = 3
interp_dt = 0.02
P0 = 0.2 # set as static to reduce difficulty of optimization
@interactive
def sim_stim_train_dynamics(ISI):
"""
Called by controller, mapped to each engine. Simultaneously activates n synapses in the syn_list at the specified
ISI with the dynamics stored in x and returns the resulting EPSP waveform.
:param ISI: int
:return: np.array
"""
N = len(syn_list.syns_to_stim)
this_Pr = Pr(P0, x[0], x[1], x[2], x[3])
stim_time_array = [[] for i in range(len(syn_list.syns_to_stim))]
if ISI == 10:
duration = equilibrate + ISI * (num_stims - 1) + 110. + 101.
stim_times = [equilibrate + ISI * i for i in range(num_stims + 2)]
stim_times.append(stim_times[-1] + 110.)
else:
duration = equilibrate + ISI * (num_stims - 1) + 101.
stim_times = [equilibrate + ISI * i for i in range(num_stims)]
# P_list = []
for stim_time in stim_times:
P = this_Pr.stim(stim_time)
# P_list.append(P)
for j in local_random.sample(range(N), int(P * N)):
stim_time_array[j].append(stim_time)
stim_time_vector_array = [h.Vector(stim_time_list) for stim_time_list in stim_time_array]
interp_t = np.arange(0., duration, interp_dt)
sim.tstop = duration
for i, syn in enumerate(syn_list.syns_to_stim):
syn.source.play(stim_time_vector_array[i])
start_time = time.time()
sim.run(v_init)
del stim_time_vector_array
for syn in syn_list.syns_to_stim:
syn.source.play(h.Vector())
t = np.array(sim.tvec)
left, right = time2index(t, equilibrate-2.0, equilibrate)
vm = np.array(sim.rec_list[0]['vec'])
baseline = np.average(vm[left:right])
vm -= baseline
rec = np.interp(interp_t, t, vm)
print 'Process:', os.getpid(), 'ISI:', ISI, 'synapses:', N, 'took', time.time() - start_time, 's'
left, right = time2index(interp_t, equilibrate-2.0, duration)
return {ISI: rec[left:right]}
@interactive
def sim_stim_train_basal(x):
"""
Called by controller, mapped to each engine. Simultaneously activates N synapses in the syn_list at 300 ms ISI and
returns the resulting EPSP waveform.
:param x: array of float
:return: np.array
"""
N = int(x[0] * 10000.)
ISI = 300.
duration = equilibrate + ISI * (num_stims - 1) + 101.
syn_list.choose_syns_to_stim(N)
N = len(syn_list.syns_to_stim)
stim_time_array = [[] for i in range(N)]
for stim_time in [equilibrate + ISI * i for i in range(num_stims)]:
for j in local_random.sample(range(N), int(P0 * N)):
stim_time_array[j].append(stim_time)
stim_time_vector_array = [h.Vector(stim_time_list) for stim_time_list in stim_time_array]
interp_t = np.arange(0., duration, interp_dt)
sim.tstop = duration
for i, syn in enumerate(syn_list.syns_to_stim):
syn.source.play(stim_time_vector_array[i])
start_time = time.time()
sim.run(v_init)
del stim_time_vector_array
for syn in syn_list.syns_to_stim:
syn.source.play(h.Vector())
t = np.array(sim.tvec)
left, right = time2index(t, equilibrate-2.0, equilibrate)
vm = np.array(sim.rec_list[0]['vec'])
baseline = np.average(vm[left:right])
vm -= baseline
rec = np.interp(interp_t, t, vm)
print 'Process:', os.getpid(), 'ISI:', ISI, 'synapses:', N, 'took', time.time() - start_time, 's'
left, right = time2index(interp_t, equilibrate-2.0, duration)
return rec[left:right]
class Pr(object):
"""
This object contains internal variables to track the evolution in time of parameters governing synaptic release
probability, used during optimization, and then exported to pr.mod for use during patterned input simulations.
"""
def __init__(self, P0, f, tau_F, d, tau_D):
self.P0 = P0
self.f = f
self.tau_F = tau_F
self.d = d
self.tau_D = tau_D
self.P = P0
self.tlast = None
self.F = 1.
self.D = 1.
def stim(self, stim_time):
"""
Evolve the dynamics until the current stim_time, report the current P, and update the internal parameters.
:param stim_time: float
:return: float
"""
if self.tlast is not None:
self.F = 1. + (self.F - 1.) * np.exp(-(stim_time - self.tlast) / self.tau_F)
self.D = 1. - (1. - self.D) * np.exp(-(stim_time - self.tlast) / self.tau_D)
self.P = min(1., self.P0 * self.F * self.D)
self.tlast = stim_time
self.F += self.f
self.D *= self.d
return self.P
class SynList(object):
"""
This object contains internal variables that will allow a subset of synapses to be chosen according to various
criterion, and can easily be modified by calling internal methods within functions during optimization.
"""
def __init__(self, cell, start=100., end=300., sec_type_list=['trunk', 'apical']):
"""
:param cell: :class: 'SHocCell'
:param start: float
:param end: float
:param sec_type_list: list of str
"""
self.cell = cell
self.internal_random = random.Random()
self.internal_random.seed(0)
self.syns_to_stim = []
self.sec_type_list = sec_type_list
self.all_syns_in_range = {sec_type: [] for sec_type in self.sec_type_list}
for sec_type in self.sec_type_list:
for node in self.cell.get_nodes_of_subtype(sec_type):
if (start is None) or self.within_distance_range(node, start, end):
for spine in node.spines:
for syn in spine.synapses:
self.all_syns_in_range[sec_type].append(syn)
self.num_syns_in_range = {sec_type: len(self.all_syns_in_range[sec_type]) for sec_type in self.sec_type_list}
self.fraction_syns_in_range = {sec_type: float(self.num_syns_in_range[sec_type]) /
float(np.sum(self.num_syns_in_range.values())) for sec_type in
self.sec_type_list}
def choose_syns_to_stim(self, N):
"""
Based on the proportion of synapses in the dendritic sections pre-initialized in the SynList object, choose N
random synapses.
:param N: int
"""
self.internal_random.seed(0)
self.syns_to_stim = []
for sec_type in self.sec_type_list:
for syn in self.internal_random.sample(self.all_syns_in_range[sec_type],
int(N * self.fraction_syns_in_range[sec_type])):
self.syns_to_stim.append(syn)
def within_distance_range(self, node, start, end):
"""
Check if dendrite origin of specified node is within the specified distance from the soma.
:param node: :class:'SHocNode'
:param start: float
:param end: float
:return: boolean
"""
sec_type = node.type
if sec_type == 'trunk':
distance = self.cell.get_distance_to_node(self.cell.tree.root, node, loc=0.)
else:
distance = self.cell.get_distance_to_node(self.cell.tree.root, self.cell.get_dendrite_origin(node), loc=1.)
if end is None:
end = np.Infinity
return start <= distance <= end
equilibrate = 250. # time to steady-state
duration = 550.
v_init = -67.
syn_types = ['AMPA_KIN', 'NMDA_KIN5']
cell = CA1_Pyr(morph_filename, mech_filename, full_spines=True)
cell.zero_na()
local_random = random.Random()
local_random.seed(os.getpid())
for branch in cell.trunk+cell.apical:
for node in branch.spines:
syn = Synapse(cell, node, syn_types, stochastic=0) # stochasticity will be implemented in python rather than
# in neuron (hoc) during optimization
cell.init_synaptic_mechanisms()
sim = QuickSim(duration, verbose=0)
# look for a trunk bifurcation
trunk_bifurcation = [trunk for trunk in cell.trunk if cell.is_bifurcation(trunk, 'trunk')]
if trunk_bifurcation:
trunk_branches = [branch for branch in trunk_bifurcation[0].children if branch.type == 'trunk']
# get where the thickest trunk branch gives rise to the tuft
trunk = max(trunk_branches, key=lambda node: node.sec(0.).diam)
trunk = (node for node in cell.trunk if cell.node_in_subtree(trunk, node) and 'tuft' in (child.type
for child in node.children)).next()
else:
trunk_bifurcation = [node for node in cell.trunk if 'tuft' in (child.type for child in node.children)]
trunk = trunk_bifurcation[0]
sim.append_rec(cell, trunk, description='trunk', loc=0.)
syn_list = SynList(cell)