Activating a synapse at recorded or precomputed times

Moderator: wwlytton

Post Reply
Rivaldo
Posts: 6
Joined: Mon Jan 12, 2015 8:58 pm

Activating a synapse at recorded or precomputed times

Post by Rivaldo » Wed Jan 21, 2015 12:59 am

Hi Ted,

I have a question related to this post
Driving a synapse with recorded or precomputed spike events
viewtopic.php?f=28&t=2117
I have 1000 vectors of event time and I want to apply these synapses to 1000 selected points on a dendritic tree. In a for loop I select a section in random and now I don't know how to apply spikes on the selected section. Do I need to use Vector.play()? Your help is really appreciated.

Thanks
-Mahmood

ted
Site Admin
Posts: 5510
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: Activating a synapse at recorded or precomputed times

Post by ted » Wed Jan 21, 2015 1:37 pm

Rivaldo wrote:I don't know how to apply spikes on the selected section.
One doesn't apply spikes to a section. Spike events are to be delivered to a mechanism that knows what to do when it receives an event, e.g. an artificial spiking cell, or a synaptic mechanism described by NMODL code that has a NET_RECEIVE block (like an ExpSyn or Exp2Syn).

The simplest algorithm for doing this is this loop, written in pseudocode for the sake of clarity:

Code: Select all

REPEAT
  pick a location on a model cell
  attach a synaptic mechanism to this location
  specify that this mechanism will be activated at times contained in a Vector
UNTIL done
The first step seems easy enough, but you need to define what you mean by "select . . . at random." Do you want to approximate a roughly uniform distribution over the cell, or do you want synaptic density to be a function of path distance from the soma or depth from the cortical surface? And what is your definition of synaptic density--is it # synapses/(micron of dendritic length) or # synapses/(square micron of dendritic area)? The difference is important, because point processes are attached to segments, and all the segments are likely to have different lengths and surface areas. Presumably a segment that is long or has a large surface area should be more likely to have a synapse than a segment that is short or has a small surface area. Note that this means that synapses will only be attached to nodes that lie between 0 and 1, because the 0 and 1 locations of a section have no associated length or area. Finally, what will be your policy about multiple synaptic attachments? Will you allow more than one synapse to attach to any segment? If you don't, you're biasing your distribution of synapses toward short segments.

The second step is also easy, once you know which segment on which section is to get the new synapse.

The third step is just to create a new instance of the VecStim class, make a NetCon that will deliver the events it generates to the newly created synapse, and finally to tell the VecStim which Vector contains the times at which it should generate events.

Assuming that your spike time vectors were created with code that is equivalent to these statements

Code: Select all

NSTVECS = 1000
objref stvec[NSTVECS]
for i = 0,NSTVECS-1 fill stvec[i] with spike times
then stvec contains the spike times that should drive the ith synapse.
How to make all those NetCons? Prior to entering the REPEAT . . . UNTIL loop, declare

Code: Select all

objref vslist, nclist
vslist = new List()
nclist = new List()
Then the third step inside the loop is, in pseudocode,

Code: Select all

make a new instance of the VecStim class and append it to vslist
tell this new VecStim that it is to generate events at the times contained in stvec[i]
make a new instance of the NetCon class that connects the new VecStim to the new synaptic mechanism, and append this NetCon to nclist
specify the weight of the new NetCon
As always, writing the actual code is best done in an iterative manner, making one change at a time and testing after every change. Use of procs, funcs, and obfuncs will help keep the code organized and easier to read and debug.

Rivaldo
Posts: 6
Joined: Mon Jan 12, 2015 8:58 pm

Re: Activating a synapse at recorded or precomputed times

Post by Rivaldo » Thu Jan 22, 2015 2:03 pm

Thank you so much for the thorough answer. Here I pasted a part of the code in the case someone need it and/or for you to check it.
However, I got this error saying " if arg 1 is an object it must be a point process or NULLObject". Can you help me on this?
Thanks,

Code: Select all

BEGINSECTION= 0
ENDSECTION= TOTALDEND - 1
printf ("*********** Total number of dendrites: %g ***********", ENDSECTION+1)

objref rc, rd
rc = new Random()
rc.MCellRan4(highindex+250)
rc.uniform(BEGINSECTION, ENDSECTION) 
rd = new Random()
totSyn = 1000

objref Ens[totSyn], syn[totSyn], nc[totSyn]
double  synWt[totSyn]

access soma
soma distance()

for (i=0; i <= totSyn-1; i +=1) {
    count = 1
    /*---------generate a syn at random section and random segment--*/
	flag=0
	while (flag==0) {
		comp=int(rc.repick()+0.5)
		/* NOTE1: Here I approximated a roughly uniform distribution over the cell, meaning
		   that synapses are generated regardless of the length of each segment. So long and
		   short dendrites have equal probability of receiving synapses. If you want to take
		   into account this factor, add an if statement here to accept "comp" with the
		   probability of (section length/max section length).
		  
		   NOTE2: Multiple synapses are allowed to attach to a single segment.
		*/
		dend[comp].sec { 
		    rd.MCellRan4(highindex+i*15)
		    rd.uniform(1, nseg+1)
		    count = 0
		    while (count <= nseg && flag==0) {
			tmpnseg = int( rd.repick())
			count += 1
			tmp = (2*tmpnseg - 1)/(2*nseg)
			flag=1
		    }
		}
	}//now right section, right segment has been found
    
    dend[comp].sec {
	syn[i] = new Exp2Syn(tmp)
        syn[i].e=0
        syn[i].tau1 = TAU1
        syn[i].tau2 = TAU2

        nc[i] = new NetCon(PreInputs[i], syn[i]) //PreInputs[i] contains ith event times
	nc[i].weight =(A*dist*dist+B)/1e+06  // A and B are some constants.

    } 
}

ted
Site Admin
Posts: 5510
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: Activating a synapse at recorded or precomputed times

Post by ted » Thu Jan 22, 2015 5:21 pm

Rivaldo wrote:I got this error saying " if arg 1 is an object it must be a point process or NULLObject".
In debugging, the first task is to locate the cause of the problem. The hoc parser is particularly good at pinpointing syntax errors: it quotes the statement in which it found the error occurred. A run time error can be more difficult to pinpoint, because the error message is often generated many execution steps after the programming mistake that caused it.

The error message you report looks to me like a syntax error caused it. Did I guess correctly, and do you see why it occurred?

ted
Site Admin
Posts: 5510
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: Activating a synapse at recorded or precomputed times

Post by ted » Thu Jan 22, 2015 6:07 pm

Code: Select all

		/* NOTE1: Here I approximated a roughly uniform distribution over the cell, meaning
		   that synapses are generated regardless of the length of each segment. So long and
		   short dendrites have equal probability of receiving synapses.
Here is a simple, efficient, and mathematically sound algorithm for attaching a predetermined number of synapses.

Code: Select all

to_be_done = nsyn
func onepass() { local p, num
  for each section to which a new synapse might be attached {
    for (x,0) {
      if (to_be_done>0) {
        determine the likelihood p that this segment will get a new synapse
        pick a number num from the uniform distribution over the interval 0...1, excluding 0 and 1
        if num <= p {
          attach a new synapse to this location
          decrease to_be_done by 1
        }
      }
    }
  }
}
// there is no guarantee that all synapses will be placed 
// in a single pass
while (to_be_done>0) onepass()
Implementation comments

1. "for each section to which a new synapse might be attached"
In advance, create a SectionList called innervated.
Append to it all sections that might be innervated.
Then
forsec innervated
is the statement that iterates over these sections.

2. "pick a number num from the uniform distribution over the interval 0...1, excluding 0 and 1"
Locations 0 and 1 are not associated with length or surface area, so the probability of attaching a synapse to either one of them is 0.
So instead of just picking a value for num, do this

Code: Select all

repeat
  num = value from the interval [0,1]
until ((num>0) && (num<1))
3. "the likelihood p that a segment will get a new synapse"
If you want to specify synaptic density in terms of synapses per micron length, in advance calculate the total length of all sections in innervated.
total_length = 0
forsec innervated total_length+=L
Then synaptic density is nsyn/total_length, and the probability that any particular segment will be innervated is
p = (L/nseg)*(nsyn/total_length)

If you prefer to specify synaptic density in terms of synapses per square micron surface area, in advance calculate the total area of all sections in innervated
total_area = 0
forsec innervated for (x,0) total_area+=area(x)
Then synaptic density is nsyn/total_area, and the probability that any particular segment will be innervated is
p = area(x)*nsyn/total_area

ted
Site Admin
Posts: 5510
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: Activating a synapse at recorded or precomputed times

Post by ted » Thu Jan 22, 2015 11:25 pm

Not so mathematically sound after all, because it has a source of bias: what I'll call the "first in line" effect. That is, the sections that appear earlier in the SectionList are more likely to have synapses attached to them than those that are closer to the end of the SectionList. This will happen because, once the final synapse has been attached, none of the remaining sections in the SectionList will have a chance to get a new synapse. This is guaranteed to happen. The severity of the resulting bias depends inversely on how many passes are made through the SectionList before the last synapse is placed.

One could abandon the idea of attaching a fixed number nsyn of synapses to the model, and simply make a single pass through the SectionList, realizing that the actual number of synapses attached may not be exactly the number that one wanted, but that if one created several models using the same morphology each time, but seeding the random number generator differently on each run, the number of synapses averaged over all the models would be nsyn.

If one insists on attaching exactly nsyn synapses, an alternative strategy is required. An approach that would work would be:
1. Map all sections to adjacent intervals on the real number line over the range 0...total_length. That is, the first section in the SectionList would correspond to the interval 0...L0 where L0 is the length of this section, the second section would correspond to the interval L0...L0+L1, the third to L0+L1...L0+L1+L2 etc.
2. Execute this loop

Code: Select all

for i=0,nsyn-1 {
  draw a number num from the uniform distribution over the range 0..total_length
  discover the section foo that corresponds to num (i.e. the section that corresponds to the interval on the number line that contains num)
  discover x which is defined as the the relative location of num in the interval that contains num
  attach a new synapse to foo at location x e.g. foo syn = new Exp2Syn(x)
}
This will attach exactly nsyn synapses to the model. Of course, "draw the number num" should test to make sure that the value of num does not lie exactly on one of the boundaries between section intervals on the number line.

ted
Site Admin
Posts: 5510
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: Activating a synapse at recorded or precomputed times

Post by ted » Fri Jan 23, 2015 12:49 pm

If one insists on attaching exactly nsyn synapses
here's one way to do it, assuming that synaptic density is specified as # synapses/micron length; if density is in # synapses/square micron, the approach would be similar in broad outline but different in some significant details.

Append all the sections that are to be innervated to a SectionList called innervated.
Calculate total_length, which is the total length of all of these sections.
Draw nsyn numbers > 0 from the uniform distribution over 0...total_length and append them to a Vector called sites.
Sort sites in ascending order.
Then

Code: Select all

forsec innervated {
  if any synapses are to be attached to the currently accessed section
    attach them to the appropriate positions on this section
}
Implementational details.
Assumes that sites has nsyn elements that are > 0, and has been sorted in ascending order.

Code: Select all

min = 0
max = 0
objref loci
loci = new Vector()
objref synlist
synlist = new List() // to hold the new synapse instances
forsec innervated {
  min = max
  max += L
  // synaptic locations that lie in the currently accessed section
  // will be elements of sites whose values are > min and <= max
  // i.e. those that lie in the half open interval (min,max]
  loci.indvwhere(sites, "(]", min, max)
  if (loci.size()>0) {
    loci.sub(min).div(L) // elements should now lie in (0,1]
    // make sure they do
    // if largest locus is >= 1 force it to lie in last segment
    if (loci.max()>=1) loci.x[loci.size()-1] = 1 - 0.1/nseg
    // if first element is 0, discard it (will have been mapped to previous section in innervated) 
    if (loci.x[0]<=0) loci.remove(0) // discard the first element if it is 0
    for i=0,loci.size()-1 {
      synlist.append(new Exp2Syn(loci.x[i]))
    }
  }
}
print "created ", synlist.count(), " synapses"

Rivaldo
Posts: 6
Joined: Mon Jan 12, 2015 8:58 pm

Re: Activating a synapse at recorded or precomputed times

Post by Rivaldo » Thu Jan 29, 2015 10:03 am

Hello,

Thanks for your posts. I got it to work and at least I don't get any syntax error now. But it doesn't spike at all no matter what the input firings are. Here is the pesudocode :

Code: Select all

SPECIFY ALL THE PARAMETERS AND OBJECTS
totSyn = 1000

objref syn[totSyn], nc[totSyn]
double  synWt[totSyn]

objref vslist, nclist, StimVector[totSyn]
vslist = new List()
nclist = new List()

access soma
soma distance()

for (i=0; i <= totSyn-1; i +=1) {
	StimVector[i] = new VecStim()
	StimVector[i].play(PreInputs[i]) // PreInputs[i] holds event times for ith syn mechanism
	vslist.append(StimVector[i])
    	
    	GENERATE A SYN AT RANDOM SECTION AND SEGMENT WITH ALL THE CONSIDERATIONS
    
    SELECTED SECTION {
	dist = distance(tmp)
        syn[i] = new Exp2Syn(tmp)
        syn[i].e=0
        syn[i].tau1 = TAU1
        syn[i].tau2 = TAU2
        nc[i] = new NetCon(StimVector[i], syn[i])
	nclist.apend(nc[i])
	nc[i].weight=SOME FUNCTION OF DISTANCE

    } 
}
Am I dong something horribly wrong here?

Thanks a lot.

ted
Site Admin
Posts: 5510
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: Activating a synapse at recorded or precomputed times

Post by ted » Thu Jan 29, 2015 11:00 am

Pseudocode is important, but so is incremental development and testing.

Suggest you take a big step back from your current code and do this:
1. Make a single compartment passive model cell, attach a single synapse to it, and drive that synapse with events at times that are specified in a single Vector. To make this toy model as easy to work with as possible, use a short run time (5 or 10 ms at most) and only two or three synaptic activation times.
When that works properly,
2. Using the same model cell, attach two synapses, and drive each with its own unique event stream.
3. Switch to a ball and stick model cell (soma and dend) where soma.nseg is 1 and dend.nseg is 3. Attach a synapse to each compartment, and drive each synapse with its own event stream.

It will be easiest to write scalable code if you organize your code into procedures and use Lists to manage collections of objects. Eventually you'll end up with code that looks something like the following examples:

Code: Select all

objref synlist
synlist = new List()
forall for (x,0) synlist.append(makesyn(x)) // attach a new synapse to each internal node in your model
where your first version of makesyn() would be

Code: Select all

// attaches a new synapse to location $1 on currently accessed section
// and returns an objref that points to it
obfunc makesyn() { localobj tobj
  tobj = new ExpSyn($1) // or whatever other synaptic mechanism you want
  . . . statements that set params of tobj . . .
  return tobj
}
You could set up the activation time vectors, the VecStims, and the NetCons that attach the VecStims to the synapses in a similar way.

Code: Select all

objref tvecs
tvecs = new List()
for ii=0,synlist.count()-1 tvecs.append(maketvec(ii))
where maketvec() is an obfunc that returns a Vector of activation times, and it expects an argument $1 that it uses to determine what file to read or that governs an algorithm that creates the numerical values that are contained in that Vector. Then you could
for ii=0,tvecs.count()-1 connect_stream(tvecs.o(ii))
where connect_stream() is a proc that looks like this

Code: Select all

objref vslist, nclist
vslist = new List()
nclist = new List()
// $o1 is a Vector of activation times
// $o2 is an objref that points to an event-driven synaptic mechanism
// creates a new VecStim, and appends to vslist
// associates the new VecStim with $o1
// creates a new NetCon that delivers the VecStim's events to $o2
// appends the new NetCon to nclist
proc connect_stream() {
  . . . fill in the blanks . . .
}

Post Reply