Bulletin board code walkthroughs

initonerun.hoc

Description
Executes one simulation with a specified stimulus.
Displays response and reports spike frequency.
Usage
nrngui initonerun.hoc
A new simulation can be launched by entering the command
onerun(x)
at the oc> prompt, where x is a number that specifies the stimulus current amplitude in nA.
Example:
onerun(0.3)
Source
initonerun.hoc

Code walkthrough

initonerun.hoc is organized in a modular fashion. Only highlights are mentioned.

Simulation parameters
Firing frequency should be determined after the model has settled into a stable firing pattern. Tests show that the first few interspike intervals vary slightly, so the first NSETTLE=5 ISIs are ignored and frequency is computed from the last 10 ISIs in a simulation. The slowest sustained repetitive firing is > 40 Hz (longest ISI < 25 ms), so TSTOP = 375 ms would allow at least 15 ISIs. TSTOP has been set to 500 ms so that repetitive firing produces > 15 ISIs, and runs with < 15 are ignored.
Model specification
loads the cell's source code
Instrumentation
stimulus--attaches an IClamp to soma(0.5)
data recording and analysis--uses a NetCon to record the times at which spikes reach dend(1)
proc postproc() verifies that enough spikes have occurred, then calculates freq from the last NINVL=10 recorded ISIs.
Simulation control and reporting of results
proc onerun() expects a single numerical argument that specifies the stimulus amplitude. It creates a graph that will show dend.v(1) vs. time, runs a simulation, analyzes the results, and prints out the stimulus amplitude and firing frequency.

initbatser.hoc

Description
Executes a batch of simulations, one at a time, in which stimulus amplitude increases from run to run. Then saves results, reports performance, and optionally plots an f-i graph.
Usage
nrngui initbatser.hoc
Source
initbatser.hoc
Code walkthrough

initbatser.hoc is based on initonerun.hoc. Only significant differences are mentioned.

Simulation parameters
If PLOTRESULTS = 1, an f-i curve will be generated at the end of program execution; if not, the program simply exits when done.
AMP0, D_AMP, and NRUNS specify the stimulus current in the first run, the increment from one run to the next, and the number of simulations that are executed, respectively.
Instrumentation
setparams() assigns values to the parameters that differ from run to run. In this example, it sets stimulus amplitude to a value that depends on its argument. Its argument is the "run index", a whole number that ranges from 0 to NRUNS-1 (see proc batchrun() in the following discussion of "Simulation control").
Simulation control
This has been separated from reporting of results.
trun = startsw() records system time at the beginning of the code whose run time will be evaluated.
proc batchrun() contains a for loop that iterates the run counter ii from 0 to NRUNS-1. Each pass through this loop results in a new simulation with a new stimulus amplitude, finds the spike frequency, and saves the stimulus amplitude and frequency to a pair of vectors. It also prints a "." to the terminal to indicate progress.
Reporting of results
proc saveresults() writes the stimulus and frequency vectors to a text file in the format used by "NEURON Main Menu / Vector / Save to File" and "Retrieve from File".
After this is done, the program reports run time.
Then it plots an f-i curve or quits, depending on PLOTRESULTS.

initbatpar.hoc

Description
Performs the same task as initbatser.hoc, i.e. executes a batch of simulations, but does it serially or in parallel, depending on how the program is launched. Parallel execution uses NEURON's bulletin board.
Usage
Serial execution: nrngui initbatpar.hoc
runs simulations one after another on a single processor, i.e. serially. Parallel execution: mpiexec -n N nrniv -mpi initbatpar.hoc
launches N processes that carry out the simulations. On a multicore PC or Mac, parallel execution with N equal to the number of cores can reduce total run time to about 1/N of the run time required by initbatser.hoc, serial execution of initbatpar.hoc, or parallel execution of initbatpar.hoc with N = 1.
Source
initbatpar.hoc
Code walkthrough

initbatpar.hoc is based on initbatser.hoc. Only key differences are mentioned below. Note that many statements have been wrapped in paried curly brackets { } to suppress printing of undesired return values (0s, 1s, etc.).

ParallelContext
An instance of the ParallelContext class is created near the start of the program. printf statements inserted after this point to monitor program execution can report not only what code is being executed in the course of which simulation, but also the identity (pc.id) of the host that is executing the code.
Simulation control
This is where most of the changes have been made.
The speedup of bulletin board style parallelization depends on keeping the workers as busy as possible, while minimizing communication (data exchange via the bulletin board) as much as possible. To this end, the master should post as little data as necessary to the bulletin board. The workers should do as much work as possible, and then return as little data as necessary to the bulletin board.

The serial program initbatser.hoc has a proc batchrun() that uses this for loop to execute a series of simulations, one at a time, on a single processor:

  for ii=0,$1-1 {
    setparams(ii) // set parameters for this run
    run()
    postproc() // analyze the data
    svec.append(stim.amp)
    fvec.append(freq)
    printf(".") // indicate progress
  }
In initbatpar.hoc, everything that can be offloaded to the workers has been taken out of batchrun() and inserted into a new func fi() that is defined prior to batchrun().
func fi() { // set params, execute a simulation, analyze and return results
  setparams($1) // set parameters for this run
  run()
  postproc() // analyze the data
  return freq
}
Notice that fi() contains the procedures that involve the most computational overhead. Also notice that fi() expects a single numerical argument, and returns a single numerical result. This is how the implementation of fi() tries to satisfy the aim of keeping the workers busy, while minimizing communication overhead.

Here is the heart of initbatpar.hoc's batchrun() procedure:

  for ii = 0, $1-1 pc.submit("fi", ii) // post all jobs to bulletin board
  // retrieve results from bulletin board
  while (pc.working) { // is a result ready?
    fvec.append(pc.retval()) // get frequency
    pc.unpack(&tmp) // get job number
    svec.append(tmp)
    printf(".") // indicate progress
  }
There still is a for loop, but it uses pc.submit() to post jobs to the bulletin board. Communication is minimized by passing only the name of a function ("fi" of course) and the simulation index ii for each run that is to be carried out.

Next comes a while loop in which the master checks the bulletin board for returned results. If nothing is found, the master picks a task from the bulletin board and executes it. If a result is present, the master retrieves it from the bulletin board: pc.retval() gets the value returned by fi(), and pc.unpack(&tmp) gets the job number into tmp. The job number starts at 0 and increments by 1 each time another job is posted, so it is identical to the simulation index.

After the last job has been completed, the master exits the while loop, and batchrun() is finished. Then pc.done() releases the workers.

But the master still has some work to do.

Reporting of results
Although the jobs were posted in order of increasing stimulus intensity, there is no guarantee that simulation results will be returned in the same sequence. Also, the values in svec are job numbers, not stimulus currents. So results have to be sorted, and job numbers must be converted to stimulus currents. This is done by
fvec = fvec.index(svec.sortindex()) // rearrange fvec according to svec sortindex
{
svec.sort()
// but svec contains job numbers, not actual stimulus currents
svec.apply("fstimamp")
}


NEURON hands-on course
Copyright © 2011 by N.T. Carnevale and M.L. Hines, all rights reserved.