Olfactory bulb microcircuits model with dual-layer inhibition (Gilra & Bhalla 2015)

 Download zip file 
Help downloading and running models
Accession:153574
A detailed network model of the dual-layer dendro-dendritic inhibitory microcircuits in the rat olfactory bulb comprising compartmental mitral, granule and PG cells developed by Aditya Gilra, Upinder S. Bhalla (2015). All cell morphologies and network connections are in NeuroML v1.8.0. PG and granule cell channels and synapses are also in NeuroML v1.8.0. Mitral cell channels and synapses are in native python.
Reference:
1 . Gilra A, Bhalla US (2015) Bulbar microcircuit model predicts connectivity and roles of interneurons in odor coding. PLoS One 10:e0098045 [PubMed]
Citations  Citation Browser
Model Information (Click on a link to find other models with that property)
Model Type: Realistic Network;
Brain Region(s)/Organism: Olfactory bulb;
Cell Type(s): Olfactory bulb main mitral GLU cell; Olfactory bulb main interneuron periglomerular GABA cell; Olfactory bulb main interneuron granule MC GABA cell;
Channel(s): I A; I h; I K,Ca; I Sodium; I Calcium; I Potassium;
Gap Junctions:
Receptor(s): AMPA; NMDA; Gaba;
Gene(s):
Transmitter(s): Gaba; Glutamate;
Simulation Environment: Python; MOOSE/PyMOOSE;
Model Concept(s): Sensory processing; Sensory coding; Markov-type model; Olfaction;
Implementer(s): Bhalla, Upinder S [bhalla at ncbs.res.in]; Gilra, Aditya [aditya_gilra -at- yahoo -period- com];
Search NeuronDB for information about:  Olfactory bulb main mitral GLU cell; Olfactory bulb main interneuron periglomerular GABA cell; Olfactory bulb main interneuron granule MC GABA cell; AMPA; NMDA; Gaba; I A; I h; I K,Ca; I Sodium; I Calcium; I Potassium; Gaba; Glutamate;
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import math

sys.path.extend(["..","../networks","../generators","../simulations"])

from moose_utils import * # imports moose
from OBNetwork import *

from stimuliConstants import * # has SETTLETIME
from simset_inhibition import * # has REALRUNTIME
from sim_utils import * # has setup_tables(), plot_extras() and build_tweaks()
from moose_utils import *

RUNTIME = REALRUNTIME + SETTLETIME

# Whether to inject the current into tuft or soma, typically soma
TUFT_INJECT = False

from pylab import * # part of matplotlib that depends on numpy but not scipy

## Run: python2.6 inhibition.py <PGvsGranule|PGvsGranuleIPSC|lateral_granule>

#-----------------------------------------------------------

class lateral_granule_inhibition:
    
    def __init__(self):
        pass

    def setup_stim(self,network,args):
        if SPIKEBLOCK:
            print "Blocking Na in mitral cells only"\
                " (don't have granules, else inconsistent) ..."
            for cell in network.mitralTable.values():
                #blockChannels(cell, ['Na','K2']) # only K2 i.e. Kfast, not all K
                blockChannels(cell, ['Na']) # only TTX, only Na blocked not K2
        iAinject = offInject
        iBinject = onInject
        ipulse_duration = 400e-3#150e-3#400e-3 # seconds
        ## 1-1200pA for 400ms was used by Arevian et al to generate FvsI curves.
        ## I seem to be using much larger currents - increase the inhibition
        if TUFT_INJECT:
            tuftsegname = 'Seg0_prim_dend_5_20'
            #tuftsegname = 'Seg0_glom_81_102'
            print "injecting into tuft/tuft-base segment",tuftsegname
            self.compA = moose.Compartment(network.mitralTable[mitralidx].path+'/'+tuftsegname)
            self.compB = moose.Compartment(network.mitralTable[mitralsidekickidx].path+'/'+tuftsegname)
            self.mitAtuftbaseTable = setupTable('mittuftbaseTable',self.compA,'Vm')
        else:
            self.compA = network.mitralTable[mitralidx].soma
            self.compB = network.mitralTable[mitralsidekickidx].soma
        iA = setup_iclamp(self.compA, '_mitralA', SETTLETIME, ipulse_duration, iAinject)
        ## 1-1200pA for 400ms was used by Arevian et al to generate FvsI curves.
        ## slightly stagger the start of current pulses in the two cells
        ## so that the mitrals do not continuously co-fire.
        iB = setup_iclamp(self.compB,'_mitralB', SETTLETIME-50e-3, ipulse_duration+50e-3, iBinject)
        ## To check backpropagating AP, check at various distances from the soma on the lateral dendrites.
        dend_seg = moose.Compartment(network.mitralTable[mitralidx].path+\
            #'/Seg0_sec_dendp4_0_254') # at 43.86 microns from soma
            #'/Seg0_sec_dendd3_0_204') # at 190.56 microns from soma
            '/Seg0_sec_dendd4_2_269') # at 1004.47 microns from soma
        self.mitAdendTable = setupTable('mitdendTable',dend_seg,'Vm')
        dend_seg = moose.Compartment(network.mitralTable[mitralsidekickidx].path+\
            #'/Seg0_sec_dendp4_0_254') # at 43.86 microns from soma
            #'/Seg0_sec_dendd3_0_204') # at 190.56 microns from soma
            '/Seg0_sec_dendd4_2_269') # at 1004.47 microns from soma
        self.mitBdendTable = setupTable('mitdendTable',dend_seg,'Vm')
        print "Glomerulus segment of mitral A = ", network.mitralTable[mitralidx].glom.path
        print "Glomerulus segment of mitral B = ", network.mitralTable[mitralsidekickidx].glom.path
        print 'Injecting mitral A with '+str(iAinject)+' and B with '+str(iBinject)

    def run_inhibition(self,network,args):
        resetSim(network.context, SIMDT, PLOTDT) # from moose_utils.py sets clocks and resets
        network.context.step(RUNTIME)
        
        timevec = arange(0.0,RUNTIME+1e-10,SIMDT)*1000
        fig = figure(facecolor='w')
        ax = fig.add_subplot(111)
        #ax.set_title('mitral A',size='large')
        #plot(plotBins(network.mitralTable[str(mitralidx)]._vmTableSoma),'r-,')
        #plot(plotSpikes(network.mitralTable[str(mitralidx)]._vmTableSoma),'r-,')
        plot(timevec,array(network.mitralTable[mitralidx]._vmTableSoma)*1000,\
            'r',linestyle='-',linewidth=2.0,label='soma')
        if TUFT_INJECT:
            plot(timevec,array(self.mitAtuftbaseTable)*1000,\
                'b',linestyle='--',linewidth=2.0,label='tuft base')
        else:
            plot(timevec,array(network.mitralTable[mitralidx]._vmTableGlom)*1000,\
                'b',linestyle='--',linewidth=2.0,label='tuft')
        plot(timevec,array(self.mitAdendTable)*1000,\
            'g',linestyle='-.',linewidth=2.0,label='lat dend')
        biglegend()
        axes_labels(ax,"time (ms)","Vm (mV)")
        
        figure(facecolor='w')
        title('mitral B')
        #plot(plotBins(network.mitralTable[str(mitralsidekickidx)]._vmTableSoma),'g-,')
        #plot(plotSpikes(network.mitralTable[str(mitralsidekickidx)]._vmTableSoma),'g-,')
        plot(timevec,network.mitralTable[mitralsidekickidx]._vmTableSoma,'r-,',label='Soma')
        plot(timevec,network.mitralTable[mitralsidekickidx]._vmTableGlom,'b-,',label='Glom')
        plot(timevec,self.mitBdendTable,'g-,',label='Dend')
        legend()

#----------------------------------------------------------------

class PGvsGranule:
    
    def __init__(self):
        pass

    def setup_stim(self,network,args):
        mitstr = "mitrals_"+str(mitralidx) # mitralidx from simset_inhibition.py
        #### Connect a short current injector into a granule_single to generate an AP.
        #### This granule_single must connect to our mitral number mitralidx
        found_granmitsyn = False
        # the post_segment on the mitral must be ~= required_distance from the soma.
        self.required_distance = args[0] # meters
        dist_list = []

        for proj in network.projectionDict.values(): # see NetworkML_reader.py for projectionDict format
            # is 'granules_singles' part of the source population name and 'mitral' part of target:
            if 'granules_singles' in proj[0] and 'mitrals' in proj[1]:
                #### The same granule_mitral_GABA SynChan is used for multiple connections
                #### (each SynChan has an array of inputs)
                #### I don't want to change the Gbar for a SynChan multiple times, so maintain a unique list.
                conn_changed = {}
                for conn in proj[2]: # loop through the connections that are part of this projection
                    if mitstr in conn[2]: # if mitral_<mitralidx> is in the post_segment_path
                        post_segment = moose.Compartment(conn[2])
                        ### I correct ALL the granule-|mitral Gbar for different clubbing of PGs vs granules
                        ### I only activate the synapses I am interested in so it doesn't matter what I do to others!       
                        granmitsyn = moose.SynChan(get_matching_children(post_segment, ['granule_mitral_GABA'])[0])
                        if granmitsyn.path in conn_changed: continue
                        conn_changed[granmitsyn.path] = True
                        print 'original', granmitsyn.path, granmitsyn.Gbar
                        mitsoma = network.mitralTable[mitralidx].soma
                        distance = sqrt( (post_segment.x-mitsoma.x)**2 \
                            + (post_segment.y-mitsoma.y)**2 + (post_segment.z-mitsoma.z)**2 )
                        dist_list.append( (abs(distance-self.required_distance), conn, distance) )
                        # linear scaling by factor 20 - suspect!!!
                        granmitsyn.Gbar = granmitsyn.Gbar*PG_CLUB/GRANS_CLUB_SINGLES
                dist_list.sort()
                distdiff,conn,self.dist = dist_list[0]
                print "Lateral compartment is at",self.dist,"meters."
                self.dendsegpath = conn[2]
                post_seg = moose.Compartment(self.dendsegpath) # wrap post_segment_path conn[2]
                #printRecursiveTree(post_seg.id,2)
                ## take the cellname part [-2] of pre_segment_path conn[1],
                ## then take the last [-1] id part of cellname
                granidx = string.split( string.split(conn[1],'/')[-2], '_' )[-1]
                gran = network.populationDict['granules_singles'][1][int(granidx)]
                 ## assumes at least one soma and takes the first!
                gran.soma = moose.Compartment(get_matching_children(gran, ['Soma','soma'])[0])
                gran_inject = 0.25e-9 # Amperes
                ipulse_duration = 5e-3 # seconds
                igran = setup_iclamp(gran.soma, '_gran', SETTLETIME+100e-3, ipulse_duration, gran_inject)
                self.granTable = setupTable('granTable',gran.soma,'Vm')
                self.granDendTable = setupTable('granDendTable',moose.Compartment(conn[1]),'Vm') # pre_segment
                ### measure the Vm in the post_segment of the mitral
                self.mitdendTable = setupTable('mitdendTable',post_seg,'Vm')
                break # only one segment needed.

        if args[1] == 'IPSC': # voltage clamp mitral soma
            #### Since Dong et al use a Ca chelator and TEA (K blocker) in the Vclamp pipette,
            #### I block Ca and K channels in the mitral cell
            blockChannels(network.mitralTable[mitralidx], ['K','Ca']) # in moose_utils.py
            ## PID gain: I think for Davison 4-comp-mitral/granule: 0.5e-5 # optimal gain
            ## too high 0.5e-4 drives it to oscillate at high frequency,
            ## too low 0.5e-6 makes it have an initial overshoot (due to Na channels?)
            ## But for BBmit1993, gain of 1e-6 is optimal
            self.PIDITable = setup_vclamp(mitsoma, '_somavclamp', 0, RUNTIME+150e-3, 0.0e-3, gain=1e-6)
            self.mitITable = setupTable('mitITable',mitsoma,'Im')

        #### Find any one PG that connects to our mitral number mitralidx
        #### Connect a short current injector into this PG to generate an AP.
        #### Find the number of synapses between this PG and this mitral mitralidx
        ####  and normalize by it to compare with granule.
        found_PGmitsyn = False
        mitSegsList = []
        for proj in network.projectionDict.values(): # see NetworkML_reader.py for projectionDict format
            # is 'PG' part of the source population name and 'mitral' part of target:
            if 'PG' in proj[0] and 'mitrals' in proj[1]:
                for conn in proj[2]: # loop through the connections that are part of this projection
                    if mitstr in conn[2]: # if mitral_<mitralidx> is in the post_segment_path
                        ### select the first PG that connects to this mitral mitralidx
                        ###  and connect a current clamp to it. also set up tables for the PG dend and mitral tuft.
                        if not found_PGmitsyn:
                            PG_path = conn[1]
                            # take the cellname part [-2] of PG_path, then take the last [-1] id part of cellname
                            PGidx = string.split( string.split(PG_path,'/')[-2], '_' )[-1]
                            PG = network.populationDict['PGs'][1][int(PGidx)]
                            PG.soma = moose.Compartment(get_matching_children(PG, ['Soma','soma'])[0]) # assumes at least one soma and takes the first!
                            self.PGTable = setupTable('PGTable',PG.soma,'Vm')
                            self.PGDendTable = setupTable('PGDendTable',moose.Compartment(conn[1]),'Vm') # pre_segment
                            PG_inject = 0.25e-9 # Amperes
                            ipulse_duration = 5e-3 # seconds
                            iPG = setup_iclamp(PG.soma, '_PG', SETTLETIME+RUNTIME/2.0, ipulse_duration, PG_inject)
                            self.tuftsegpath = conn[2]
                            post_seg = moose.Compartment(self.tuftsegpath)
                            ### measure the Vm in the post_segment of the mitral by wrapping post_segment_path conn[2]
                            self.mittuftTable = setupTable('mittuftTable',post_seg,'Vm')
                            found_PGmitsyn = True
                            numsyns_thisPG_to_thismitral = 1
                            mitSegsList.append(self.tuftsegpath)
                        ### find out how many times this selected PG connects to this mitral mitralidx
                        else:
                            comparedPG_path = conn[1]
                            # take the cellname part [-2] of PG_path, then take the last [-1] id part of cellname
                            comparedPGidx = string.split( string.split(comparedPG_path,'/')[-2], '_' )[-1]
                            if PGidx == comparedPGidx:
                                numsyns_thisPG_to_thismitral += 1
                                mitSegsList.append(conn[2])
            if found_PGmitsyn: break

        ### I correct ALL the PG-|mitral Gbar for multiple PG-|mit synapses of selected PG to desired mitral
        ### I only activate the synapses I am interested in so it doesn't matter what I do to others!       
        ### make a set (keep unique elements only) of the post-segments, so as not to correct any segment twice.
        for segpath in set(mitSegsList):
            post_seg = moose.Compartment(segpath)
            PGmitsyn = moose.SynChan(get_matching_children(post_seg, ['PG_mitral'])[0])
            PGmitsyn.Gbar = PGmitsyn.Gbar/numsyns_thisPG_to_thismitral
        print "Corrected PG Gbar for selected PG",PGidx,"making",\
            numsyns_thisPG_to_thismitral,"synapses to mitral",mitralidx
        print "Also corrected granule Gbar for",GRANS_CLUB_SINGLES,\
            "single grans clubbed and",PG_CLUB,"PGs clubbed."
        print "Finally PG-|mit and gran-|mit synapses are each PG_CLUB =",\
            PG_CLUB,"times the single synapse."

        print 'Injecting granule_single',granidx,'with',gran_inject,\
            'at',SETTLETIME,'s and PG',PGidx,'with',PG_inject,'at',SETTLETIME+RUNTIME/2.0,'s.'        

    def run_inhibition(self,network,args):
        resetSim(network.context, SIMDT, PLOTDT) # from moose_utils.py sets clocks and resets
        network.context.step(RUNTIME)
        timevec = arange(0.0,RUNTIME+1e-10,SIMDT)
        figure(facecolor='w')
        plot(timevec,network.mitralTable[mitralidx]._vmTableSoma,'r-,',label='mitral soma Vm')
        plot(timevec,self.mitdendTable,',-g',label=self.dendsegpath+' Vm')
        plot(timevec,self.mittuftTable,',-b',label=self.tuftsegpath+' Vm')
        legend(loc='lower right')
        ylabel('SecDend is at %0.2f microns'%(self.dist*1e6,),fontsize='large')
        xlabel('time (s)', fontsize='large')
        figure()
        plot(timevec,self.granTable,',-g',label='granule soma Vm')
        plot(timevec,self.granDendTable,',-y',label='granule dend Vm')
        plot(timevec,self.PGTable,',-b',label='PG soma Vm')
        plot(timevec,self.PGDendTable,',-c',label='PG dend Vm')
        legend(loc='upper right')
        if args[1] == 'IPSC':
            figure(facecolor='w')
            #plot(timevec,self.mitITable,',-r',label='mitral soma Im')
            plot(timevec,self.PIDITable,',-g',label='PID I to mitral')
            legend(loc='upper right')
            ylabel('I (A); SecDend is at %0.2f microns'%(self.dist*1e6,),fontsize='large')
            xlabel('time (s)', fontsize='large')

#----------------------------------------------------

if __name__ == "__main__":
    err_string = "You must tell me the type of simulation\
 to run as a command line parameter:\n\
 'lateral_granule' or 'PGvsGranule' or 'PGvsGranuleIPSC'"
    if len(sys.argv) < 2:
        print err_string
        sys.exit(1)
    sim_option = sys.argv[1]
    if sim_option == 'lateral_granule':
        sim =  lateral_granule_inhibition()
        includeProjections = ['granule_baseline']
        args = []
    elif sim_option == 'PGvsGranule':
        sim =  PGvsGranule()
        includeProjections = []
        # the post_segment on the mitral must be ~ this distance from the soma
        args = [100e-6,'IPSP'] # meters
    elif sim_option == 'PGvsGranuleIPSC':
        sim =  PGvsGranule()
        includeProjections = []
        # the post_segment on the mitral must be ~ this distance from the soma
        args = [100e-6,'IPSC'] # meters
    else:
        print "Sorry wrong option.", err_string
        sys.exit(1)

    tweaks = build_tweaks( CLUB_MITRALS, NO_SPINE_INH, NO_SINGLES, NO_JOINTS,\
        NO_MULTIS, NO_PGS, ONLY_TWO_MITS, includeProjections, (mitralidx,mitralsidekickidx) )
    network = OBNetwork(OBNet_file, synchan_activation_correction,\
        tweaks, mpirank, granfilebase, spiketable=False)
    #printNetTree() # from moose_utils.py

    sim.setup_stim(network, args)
    spikes = True
    tables = setupTables(network, NO_PGS, NO_SINGLES, NO_JOINTS, spikes=spikes)
    sim.run_inhibition(network, args) # tests simset_inhibition i.e. inhibition between two mitrals
    if spikes:
        bins = 20
        timevec = arange(0.0,REALRUNTIME,REALRUNTIME/float(bins))
        plot_extras_spikes(timevec, tables, NO_PGS, NO_SINGLES, NO_JOINTS, bins, RUNTIME, SETTLETIME)
    else:
        timevec = arange(0.0,RUNTIME+1e-12,PLOTDT)
        plot_extras(timevec, tables, NO_PGS, NO_SINGLES, NO_JOINTS, '')
    show()