def update_section(sec, args): """Update section properties. args: {name: value} for parameters. The names `length`, `dia` and `RA` are mapped to L, diam and Ra respectively. `length`: section.L `dia`: section.diam `nseg`: section.nseg for all mechanisms, the entries should be: name: {field: value, ...} """ sec.L = args.pop('length', sec.L) sec.diam = args.pop('dia', Q_(sec.diam, 'um')).to('um').m sec.Ra = args.pop('RA', Q_(sec.Ra, 'ohm*cm')).to('ohm*cm').m sec.nseg = args.pop('nseg', sec.nseg) for mech, params in args.items(): for pname, pvalue in params.items(): if pname.startswith('g'): val = pvalue.to('S/cm**2').m elif pname.startswith('e'): val = pvalue.to('mV').m else: raise Exception('Do not know how to handle {} {}'.format( pname, pvalue)) set_mech_param(sec, mech, pname, val)
def setup_voltage_clamp(cable, vhold, thold, vpre, tpre, vclamp, tclamp, pos=0.0, rs=Q_('0.001Mohm')): """Create a voltage clamp at `pos` location on `cable`. vhold: holding voltage (with unit, converted to mV) thold: holding period (with unit, converted to ms) vpre: prepulse voltage (with unit, converted to mV) tpre: prepulse period (with unit, converted to ms) vclamp: clamping voltage (with unit, converted to mV) tclamp: clamping period (with unit, converted to ms) rs: series resistance with clamp circuit (with unit, converted to Mohm) """ clamp = h.SEClamp(pos, sec=cable) clamp.rs = rs.to(ur.Mohm).m clamp.amp1 = vhold.to(ur.mV).m clamp.dur1 = thold.to(ur.ms).m clamp.amp2 = vpre.to(ur.mV).m clamp.dur2 = tpre.to(ur.ms).m clamp.amp3 = vclamp.to(ur.mV).m clamp.dur3 = tclamp.to(ur.ms).m return clamp
def make_kc_with_dynaclamp(kc_name, kc_file, inject, tstart, tend, ggn_vm=None): """Read KC model from `kc_file`, inject current `inject` nA, apply dynamic clamp `ggn_vm`, which should be a 2D array with time (ms) in column 0, and voltage (mV) in column 1. """ global model_dict kc = nu.create_cell(kc_name, filename=kc_file) model_dict[kc] = None iclamp = ephys.setup_current_clamp(kc.soma, pos=0.5, delay=Q_(tstart, 'ms'), duration=Q_((tend - tstart), 'ms'), amplitude=Q_(inject, 'nA')) model_dict[iclamp] = None ggn_g_vec = None if ggn_vm is not None: syn = h.GradedSyn(kc.soma(0.5)) for attr, value in GGN_KC_SYN_PARAMS.items(): setattr(syn, attr, value) model_dict[syn] = None ggn_comp = h.Section('ggn') model_dict[ggn_comp] = None h.setpointer(ggn_comp(0.5)._ref_v, 'vpre', syn) ggn_vm_vec = h.Vector(ggn_vm[:, 1]) tvec = h.Vector(ggn_vm[:, 0]) model_dict[tvec] = None # vec.play(var_reference, t, continuous) for interpolating ret = ggn_vm_vec.play(ggn_comp(0.5)._ref_v, tvec, 1) print('####', ret) model_dict[ggn_vm_vec] = None ggn_g_vec = h.Vector() ggn_g_vec.record(syn._ref_g) model_dict[ggn_g_vec] = None kc_vm_vec = h.Vector() kc_vm_vec.record(kc.soma(0.5)._ref_v) model_dict[kc_vm_vec] = None print('Built model') return (kc_vm_vec, ggn_g_vec)
def create_mechs(param_dict): """Create mechanisms from parameter dictionary. It should be a dict of dict like: {name: {'gbar': gbar_quantity}} except for passive conductance `pas`, which must have {'g': gpas_quantity, 'e': epas_quantity} Returns: a dict of {name: ephys.Mechanism} """ ret = {} for name, params in param_dict.items(): if name == 'pas': gpas = Q_(params['g']) epas = Q_(params['e']) mech = Mechanism(name, g=gpas.to('S/cm**2').m, e=epas.to('mV').m) else: mech = Mechanism(name, gbar=Q_(params['gbar']).to('S/cm**2').m) ret[name] = mech return ret
def connect(self, sec, pos, synparams): self.synlist = [] self.netcons = [] count = synparams.get('count', 1) for ii in range(count): synapse = h.Exp2Syn(sec(pos)) synapse.e = Q_(synparams.get('e', '-80mV')).to('mV').m synapse.tau1 = Q_(synparams.get('tau1', '13.33ms')).to('ms').m synapse.tau2 = Q_(synparams.get('tau2', '13.33ms')).to('ms').m thresh = Q_(synparams.get('threshold', '-20mV')).to('mV').m delay = Q_(synparams.get('delay', '0ms')).to('ms').m gmax = Q_(synparams.get('gmax', '1nS')).to('uS').m netcon = h.NetCon(self.vecstim, synapse, thresh, delay, gmax) self.synlist.append(synapse) self.netcons.append(netcon) logger.info('Connected IG->{} at {}'.format(sec.name(), pos)) logger.info( 'threshold: {}, delay: {}, gmax: {}, e: {}, tau1: {}, tau2: {}' .format(netcon.threshold, netcon.delay, netcon.weight[0], synapse.e, synapse.tau1, synapse.tau2))
def create_pn_output(params): """This attempts to create inputs a KC receives from its presynaptic PNs References: Mazor and Laurent, 2005; Jortner, et al., 2007. Here the assumption is `odor_exc_frac` fraction of all PNs is activated by a specific odor, i.e. there firing rate goes up (exact formula in code). On the other hand, spont_exc_frac of the cells are spontaneously active. npn: total number of PNs odor_exc_frac: fraction of PNs that spike in response to odor spont_exc_frac: fraction of PNs that spike spontaneously stim_rate: baseline firing rate of a PN during odor stimulation spont_rate: baseline firing rate of a PN without odor osc_freq: frequency of LFP oscillation osc_amp_scale: The amplitude of oscillation as a fraction of baseline firing rate (default 0.3 from data from 50000 PNs). Returns a list of array quantitities containing spike times of each PN. """ onset = Q_(params['onset']).to('s').m duration = Q_(params['duration']).to('s').m tail = Q_(params['tail']).to('s').m delta = Q_(params['delta']).to('s').m npn = params['npn'] odor_exc_frac = params['odor_exc_frac'] spont_exc_frac = params['spont_exc_frac'] odor_inh_frac = params['odor_inh_frac'] stim_rate = Q_(params['stim_rate']).to('Hz').m spont_rate = Q_(params['spont_rate']).to('Hz').m osc_freq = Q_(params['osc_freq']).to('Hz').m osc_amp_scale = params['osc_amp_scale'] assert (odor_exc_frac + odor_inh_frac) <= 1 spont_amp = spont_rate * osc_amp_scale stim_amp = stim_rate * osc_amp_scale # Rate function for spontaneously active, excited by odor rate_fn_spont_odor = lambda t: \ stim_rate + stim_amp * np.sin(2 * np.pi * osc_freq * t) \ if (onset < t < (onset + duration)) \ else spont_rate + spont_amp * np.sin(2 * np.pi * osc_freq * t) # Odor inhibited, but spontaneously active rate_fn_odor_inh = lambda t: \ 0.0 if (onset < t < (onset + duration)) \ else spont_rate + spont_amp * np.sin(2 * np.pi * osc_freq * t) # Rate function for spontaneously active, unaffected by odor rate_fn_spont = lambda t: \ spont_rate + spont_amp * np.sin(2 * np.pi * osc_freq * t) # rate function for spontaneously silent, activated by odor rate_fn_odor = lambda t: \ stim_rate + stim_amp * np.sin(2 * np.pi * osc_freq * t) \ if (onset < t < (onset + duration)) \ else 0.0 n_spont_odor = int(npn * spont_exc_frac * odor_exc_frac + 0.5) # spontanously active, excited by odor n_odor = int(npn * (1 - spont_exc_frac) * odor_exc_frac + 0.5) # silent, activated by odor n_odor_inh = int(npn * spont_exc_frac * odor_inh_frac + 0.5) # spontaneously active inhibited by odor n_spont = int(npn * spont_exc_frac * (1 - odor_exc_frac - odor_inh_frac) + 0.5) # Ignore those that are neither spontaneously active nor activated # by a given odor: how many such PNs? pn_output = [] start = timer() for ii in range(n_spont_odor): spike_times = nhpp.nhpp_thinning(rate_fn_spont_odor, onset + duration + tail, delta) pn_output.append(Q_(np.array(spike_times), 's')) for ii in range(n_odor): spike_times = nhpp.nhpp_thinning(rate_fn_odor, onset + duration + tail, delta) pn_output.append(Q_(np.array(spike_times), 's')) for ii in range(n_spont): spike_times = nhpp.nhpp_thinning(rate_fn_spont, onset + duration + tail, delta) pn_output.append(Q_(np.array(spike_times), 's')) for ii in range(n_odor_inh): spike_times = nhpp.nhpp_thinning(rate_fn_odor_inh, onset + duration + tail, delta) pn_output.append(Q_(np.array(spike_times), 's')) end = timer() logger.info('Time to create {} PN spiketrains {} s'.format( len(pn_output), end - start)) silent = npn - len(pn_output) for ii in range(silent): pn_output.append(Q_(np.array([]), 's')) return pn_output
def create_shifting_response(params): """Create a response pattern with a shifting window into PN population over the LFP cycles. The step by step process to arrive at this design is in try_pn_output.ipynb. params should have the following key value pairs: onset: stimulus / activity onset duration: duration of stimulus offdur: duration of off response tail: simulation time after the stimulus is over trials(=1): number of trials (only `tail` interval between successive stimuli. delta: interval at which the Poisson rate is considered. npn(=830): total number of PNs odor_exc_shift(=0.1): fraction of excited PN changing state in each LFP cycle stim_rate(=20.0Hz): firing rate of excited PN upon odor, i.e. 1 spike in 50 ms spont_rate(=2.6Hz): spontaneous firing rate of a PN osc_freq(=20.0Hz): LFP oscillation frequency start_frac: fraction of cells in each group that start spiking simultaneously at the excitation phase of the group Returns: pn_spikes - list of lists: i-th entry is a list spike times of i-th PN. clusters - dict: list of PNs mapped to their stimulus response start time. The entries against off response time include bith EI and II PNs. response_types - dict: { 'EE': indices of PNs that spike throughout odor presentation, 'EI': indices of PNs that spike during first half of odor presentation, 'IE': indices of PNs that spike during last half of odor presentation, 'II': indices of PNs that do not spike during of odor presentation 'U': spntaneously active and unresponsive to odor stimulus } """ npn = params['npn'] lfp_bin = 50e-3 # second spont_rate = params['spont_rate'].to('Hz').m delta = params['delta'].to('s').m onset = params['onset'].to('s').m dur = params['duration'].to('s').m tail = params['tail'].to('s').m offdur = params['offdur'].to('s').m osc_freq = params['osc_freq'].to('Hz').m stim_rate = params['stim_rate'].to('Hz').m osc_amp_scale = params['osc_amp_scale'] start_frac = params['start_frac'] # Indices of different kinds of PNs. unresponsive = range(0, int(npn / 3.0)) EE = list( range(unresponsive[-1] + 1, int(unresponsive[-1] + 1 + npn / 6.0))) EI = list(range(EE[-1] + 1, int(EE[-1] + 1 + npn / 6.0))) IE = list(range(EI[-1] + 1, int(EI[-1] + 1 + npn / 6.0))) II = list(range(IE[-1] + 1, npn)) response_types = { 'EE': EE, 'EI': EI, 'IE': IE, 'II': II, 'U': unresponsive } clusters = defaultdict(list) pn_spikes = [] start = timer() # Spontaneous activity in all neurons spont_rate_fn = lambda t: spont_rate for ii in range(params['npn']): st1 = nhpp.nhpp_thinning(spont_rate_fn, onset, delta) st2 = nhpp.nhpp_thinning(spont_rate_fn, tail, delta) + onset + dur + offdur pn_spikes.append(list(st1) + list(st2)) # Unresponsive PNs - first 1/3rd for jj, index in enumerate(unresponsive): pn_spikes[index] += list( nhpp.nhpp_thinning(spont_rate_fn, dur + offdur, delta) + onset) # Excited throughout odor # The rate function: python has dynamic binding - so ee_rate_fn # sees the value of `start` at evaluation time. ee_rate_fn = lambda t: stim_rate + osc_fn((dur - t) * stim_rate * osc_amp_scale / dur, osc_freq, t) \ if t > start else 0 start = 0 # After every `pnclus` PNs, increase the start time. pnclus = round(params['shifting_frac'] * len(EE)) for jj, index in enumerate(EE): pn_spikes[index] += list( nhpp.nhpp_thinning(ee_rate_fn, dur, delta) + onset) clusters[onset + start].append(index) diff = jj - len(EE) * start_frac if (diff > 0) and (round(diff) % pnclus == 0): start += lfp_bin # PNs active only in first half of odour presentation ei_rate_fn = lambda t: stim_rate + osc_fn((dur - t) * stim_rate * osc_amp_scale / dur, osc_freq, t) \ if (t > start) and (t < dur/2.0) else 0 start = 0 pnclus = round( params['shifting_frac'] * len(EI)) # How many PNs should share the same starting time? for jj, index in enumerate(EI): pn_spikes[index] += list( nhpp.nhpp_thinning(ei_rate_fn, dur, delta) + onset) clusters[onset + start].append(index) diff = jj - len(EI) * start_frac if (diff > 0) and (round(diff) % pnclus == 0): start += lfp_bin # PNs active only in last half of odour presentation ie_rate_fn = lambda t: stim_rate + osc_fn((dur - t) * 2.0 * stim_rate * osc_amp_scale / dur, osc_freq, t) \ if (t > start) else 0 start = dur / 2.0 pnclus = round( params['shifting_frac'] * len(IE)) # How many PNs should share the same starting time? for jj, index in enumerate(IE): pn_spikes[index] += list( nhpp.nhpp_thinning(ie_rate_fn, dur, delta) + onset) clusters[onset + start].append(index) diff = jj - len(IE) * start_frac if (diff > 0) and (round(diff) % pnclus == 0): start += lfp_bin # off response off_rate_fn = lambda t: stim_rate + osc_fn((offdur - t) * stim_rate * osc_amp_scale / offdur, osc_freq, t) \ if (t > start) else 0 start = 0 pnclus = round( params['shifting_frac'] * len(EI)) # How many PNs should share the same starting time? for jj, index in enumerate(EI): pn_spikes[index] += list( nhpp.nhpp_thinning(off_rate_fn, offdur, delta) + onset + dur) clusters[onset + dur + start].append(index) diff = jj - len(EI) * start_frac if (diff > 0) and (round(diff) % pnclus == 0): start += lfp_bin start = 0 pnclus = round( params['shifting_frac'] * len(II)) # How many PNs should share the same starting time? for jj, index in enumerate(II): pn_spikes[index] += list( nhpp.nhpp_thinning(off_rate_fn, offdur, delta) + onset + dur) clusters[onset + dur + start].append(index) diff = jj - len(II) * start_frac if (diff > 0) and (round(diff) % pnclus == 0): start += lfp_bin qst = [] for ii, st in enumerate(pn_spikes): st = np.array(st) st.sort() qst.append(Q_(st, 's')) pn_spikes = qst end = timer() logger.info('Finished PN output creation in {} s'.format(end - start)) return pn_spikes, clusters, response_types
def create_cable(name, diameter, length, nseg, RA, CM=Q_('1.0uF/cm**2'), mechs=[], ek=Q_('-80.0mV'), ena=Q_('60.0mV'), eca=Q_('60.0mV'), verbose=False): """Create a cable with specified dimeter, length and number of segments and insert the mechanisms in mechs list. Parameters: diameter (um): cable diameter length (um): cable length nseg: number of segments to divide the cable into RA (ohm-cm): specific axial resistance CM (uF/cm2): specific membrane capacitance. mechs (Mechanism): list of mechanisms to be inserted into the section. You may want to update the properties later using `set_mech_param`. ek (mV): K+ reversal potential ena (mV): Na+ reversal potential eca (mV): Ca2+ reversal potential. Returns: cable: a new Section with all the properties and mechanisms. """ cable = h.Section(name=name) cable.diam = diameter.to(ur.um).m cable.L = length.to(ur.um).m cable.cm = CM.to('uF/cm**2').m cable.Ra = RA.to('ohm*cm').m cable.nseg = nseg for mech in mechs: mech.insert_into(cable) if hasattr(cable, 'ek'): cable.ek = ek.to('mV').m if hasattr(cable, 'ena'): cable.ena = ena.to('mV').m if hasattr(cable, 'eca'): cable.eca = eca.to('mV').m if verbose: print('Created cable', name, 'with', nseg, 'segments') for x in cable: print( 'Segment at', x.x, 'diam=', x.diam, 'area=', x.area(), 'area computed=', np.pi * x.diam * cable.L / cable.nseg, 'ri=', x.ri(), 'computed ri=', 0.01 * cable.Ra * (cable.L / 2 / cable.nseg) / (np.pi * (x.diam / 2)**2)) for mech in mechs: m = getattr(x, mech.name) print(' Has mechanism', m.name()) sys.stdout.flush() return cable
parser.add_argument( '--rec-by-region', action='store_true', help='record `reccount` sections in every region if possible', dest='recbyreg') return parser if __name__ == '__main__': parser = make_parser() args = parser.parse_args() mechs = { 'pas': { 'g': 1.0 / Q_(args.RM, 'ohm*cm**2'), 'e': Q_(args.Em, 'mV') } } h.xopen(args.celltemplate) ggn = nu.create_cell(args.cellname, mechparams=mechs, Ra=args.RA, filename=args.celltemplate) g0 = nu.nrngraph(ggn) # for node in g0: # print(node) # g, nmap = ng.renumber_nodes(g0, ggn.soma.name()) reccount = args.reccount good_nodes = [n for n in g0.nodes() if g0.node[n]['orig'] is not None] ggn.geom_nseg() # recompute the compartmentalization
parser.add_argument( '--amp', type=float, dest='amp', nargs='+', default=[1e-2, 1e-1, 5e-3], help='The amplitude (in nA) of injected current, or three numbers' ' specifying the start, stop and increment') return parser if __name__ == '__main__': args = make_parser().parse_args() logger.info('Command line: {}'.format(' '.join(sys.argv))) ggn_kc_syn_params = { 'vmid': Q_('-40mV').to('mV').m, 'vslope': Q_('5.0mV').to('mV').m, 'e': Q_('-80mV').to('mV').m, 'gbar': Q_('1e-3uS').to('uS').m, 'tau': Q_('4.0ms').to('ms').m } # Note: I increased threshold to avoid transmission during low frequency stimulus # where KC can have sustained depolarization. kc_ggn_syn_params = { 'threshold': Q_('-10.0mV').to('mV').m, 'delay': Q_('1.0ms').to('ms').m, 'e': Q_('0.0mV').to('mV').m, 'tau1': Q_('13.333ms').to('ms').m, 'tau2': Q_('13.333ms').to('ms').m, 'gmax': Q_('5pS').to('uS').m }
def main(): parser = make_parser() args = parser.parse_args() logger.info('Command line args: {}'.format(str(sys.argv))) print(args.ggn_vm_file) # KCs with GGN inhibition inhibited_vec = defaultdict(list) solo_vec_list = [] tstart = Q_(args.tstart).to('ms').m tend = Q_(args.tend).to('ms').m istart = Q_(args.istart).to('nA').m iend = Q_(args.iend).to('nA').m di = Q_(args.di).to('nA').m irange = np.arange(istart, iend + di / 2.0, di) logger.info('Starting current: {} nA'.format(istart)) logger.info('End current: {} nA'.format(iend)) logger.info('Increment: {} nA'.format(di)) logger.info('current range: {}'.format(irange)) ggn_vm = {} for input_file in args.ggn_vm_file: ggn_vm[input_file] = np.loadtxt(input_file) for inject in irange: for input_file, vm in ggn_vm.items(): kc_vvec, ggn_gvec = make_kc_with_dynaclamp(args.kc, args.kc_file, inject, tstart, tend, vm) inhibited_vec[input_file].append((kc_vvec, ggn_gvec)) # KC without any inhibition kc_vvec, ggn_gvec = make_kc_with_dynaclamp(args.kc, args.kc_file, inject, tstart, tend) solo_vec_list.append(kc_vvec) tvec = h.Vector() tvec.record(h._ref_t) h.tstop = tend print('Init') h.init() print('Run') h.run() print('Finished simulation') fig, ax = plt.subplots(nrows=len(irange) + 1, ncols=len(ggn_vm) + 1, sharex='all', sharey='all') t = np.array(tvec.x) solo_data = [] for ii, vvec in enumerate(solo_vec_list): ax[ii + 1, 0].plot(tvec, vvec, color='#e66101') solo_data.append(np.array(vvec.x)) combined = np.vstack(solo_data) prefix = 'UTC' + timestamp.strftime('%Y%m%d_%H%M%S') fname = '{}_solo_kc.npz'.format(prefix) np.savez(fname, t=t, vm=combined, inject=irange) logger.info('Saved solo KC data in {}'.format(fname)) for jj, input_file in enumerate(args.ggn_vm_file): fname = '{}_{}.npz'.format(prefix, os.path.basename(input_file)) data = [] kc_vm_list = inhibited_vec[input_file] for ii, (vvec, gvec) in enumerate(kc_vm_list): data.append(np.array(vvec.x)) ax[ii + 1, jj + 1].plot(tvec, vvec, color='#e66101') ax[ii + 1, 0].set_ylabel('{} pA'.format(irange[ii] * 1e3)) # ax[ii+1, 0].set_ylabel('{} pA'.format(int(np.round(irange[ii]*1e3)))) # to avoid decimal point when integer values # ax[0, jj+1].plot(tvec, gvec) # ax[0, jj+1].plot(ggn_vm[input_file][:,0], ggn_vm[input_file][:,1]) ax[0, jj + 1].set_title(input_file) combined = np.vstack(data) np.savez(fname, combined=combined, irange=irange, ggn_vm=ggn_vm[input_file]) logger.info( 'Saved data from dynamic clamp with input from {} in {}'.format( input_file, fname)) for axis in ax.flat: axis.set_xlim(250, 1750) fig.set_size_inches(210 / 25.4, 290 / 25.4) fig.tight_layout() fig.savefig('{}_KC_dynamic_range_with_ggn_vm.svg'.format(prefix)) plt.show() print('End')
import argparse import numpy as np from collections import defaultdict import matplotlib matplotlib.use('Qt5Agg') from matplotlib import pyplot as plt import h5py as h5 from config import Q_, h, logger, timestamp, mypid, myjobid, nrn_version from timeit import default_timer as timer import ephys import nrnutils as nu import neurograph as ng import nsdf GGN_KC_SYN_PARAMS = { 'vmid': Q_('-40mV').to('mV').m, 'vslope': Q_('5.0mV').to('mV').m, 'e': Q_('-80mV').to('mV').m, 'gbar': Q_('1e-3uS').to('uS').m, 'tau': Q_('4.0ms').to('ms').m } # keep global reference of created model components so that they are # not garbage collected when out of scope model_dict = {} def make_kc_with_dynaclamp(kc_name, kc_file, inject, tstart,