/************************************************************
'superdeep' model code repository
Written by Marianne Bezaire, marianne.bezaire@gmail.com, www.mariannebezaire.com
Last updated on May 14, 2014
Latest versions of this code are available online at:
ModelDB: http://senselab.med.yale.edu/ModelDB/ShowModel.asp?model=153280
Open Source Brain: http://www.opensourcebrain.org/projects/nc_superdeep

This code is associated with the publication:
Lee S, Marchionni I, Bezaire MJ, Varga C, Danielson N, Lovett-Barron M, Losonczy A, Soltesz I.
GABAergic Basket Cells Differentiate Among Hippocampal Pyramidal Cells.
Neuron, 2014. In press.
http://www.cell.com/neuron/abstract/S0896-6273%2814%2900336-5

This code can be used with the SimTracker tool:
http://senselab.med.yale.edu/SimToolDB/ShowTool.asp?Tool=153281

If you do not want to run this code, but want to analyze the results
obtained by running this code, the results are available upon request by
emailing casem@uci.edu

If you want to view a graphical description of the model network built
by this code, please see: 
http://www.ivansolteszlab.org/models/superdeep/graphicalmodel.html

For more information about this code, see:
- ModelDB Quick Start Guide.pdf (included with model code)
- Lab website: http://www.ivansolteszlab.org/models/superdeep.html

To run this model from the command line, enter:
nrniv superdeep.hoc
************************************************************/

loadstart = startsw()					// record the start time of the set up portion of the code
/***********************************************************************************************
I.  LOAD LIBRARIES
***********************************************************************************************/
{load_file("nrngui.hoc")}				// Standard definitions - NEURON library file

{load_file("netparmpi.hoc")}			// Contains the template that defines the properties of
										//  the ParallelNetManager class, which is used to set
										//   up a network that runs on parallel processors
										
{load_file("./setupfiles/ranstream.hoc")}	// Contains the template that defines a RandomStream
											//  class used to produce random numbers
											
{load_file("./setupfiles/CellCategoryInfo.hoc")}	// Contains the template that defines a 
													//  CellCategoryInfo class used to store
													// 	celltype-specific parameters
													
{load_file("./setupfiles/defaultvar.hoc")}	// Contains the proc definition for the default_var proc

{load_file("./setupfiles/parameters.hoc")}	// Loads in default values for operational and model
											// parameters whose values were not specified ahead of
											// time, ie by passing them in at the command line or
											// in a wrapper hoc file						
			
{load_file("./setupfiles/set_other_parameters.hoc")}// Loads in operational and model parameters
													//  that we would not want to define ahead of
													// time (either because they depend on ones that
													// *are* specified ahead of time or because they
													// don't usually change

/***********************************************************************************************
II. SET MODEL SIZE, CELL DEFINITIONS
***********************************************************************************************/
celsius=34	// set the temperature of the model to a reasonable value

{load_file("./setupfiles/load_cell_category_info.hoc")}	// Loads cell-specific information into one
														// 'CellCategoryInfo' object for each cell
														// type. Info includes number of cells,
														// gid ranges, type of cell, name of cell 
														// Also, defines 'numCellTypes' used below

{load_file("./setupfiles/load_cell_conns.hoc")}	// Load in the network connectivity info

objref fAll, fSyn						// These objects are used by the cell templates

strdef tempFileStr						// Define a string reference to store the name of the
										//  current cell template file 

proc loadCellTemplates(){local i		// Proc to load the template that defines (each) cell class

	for i=0, numCellTypes-1 {			// Iterate over each cell type
	
		sprint(tempFileStr,"./cells/class_%s.hoc",cellType[i].technicalType)	// Concatenate the
																				//  path and file
																				
		load_file(tempFileStr)			// Load the file with the template that defines the class
										//  for each cell type
	}
}	
loadCellTemplates()

proc calcNetSize(){local i				// Calculate the final network size (after any cell death,
										// which does not occur in the superdeep model)

	totalCells = 0						// Initialize totalCells (which counts the number of 'real'
										//  cells) so we can add to it iteratively in the 'for' loop
										
	ncell = 0							// Initialize ncell (which counts all 'real' and 'artificial'
										//  cells) so we can add to it iteratively in the 'for' loop
										
	for i=0,numCellTypes-1 {			// For each cell type
	
		if (cellType[i].is_art==0) {
			totalCells = totalCells + cellType[i].numCells		// Update the total number of cells
																//   after cell death, not including
																//   artificial cells
		}
		
		ncell = ncell + cellType[i].numCells 					// Update the total number of cells
																//   after cell death, including
																//   artificial cells
	}
}
calcNetSize()

proc calcBinSize(){

	for i=0, numCellTypes-1 {		// Using the specified dimensions of the network (in um) and
									//  the total number of cells of each type, set the number
									//  of bins in X, Y, Z dimensions such that the cells will be
									//  evenly distributed throughout the allotted volume
	
		cellType[i].setBins(LongitudinalLength,TransverseLength,LayerVector.x[cellType[i].layerflag])  
									// For the z length, use the height of the layer in which the
									// cell somata are found for this cell type
	}
}
calcBinSize()

/***********************************************************************************************
III.SET UP PARALLEL CAPABILITY AND MEMORY USAGE RECORDER, AND WRITE OUT RUN RECEIPT
***********************************************************************************************/
objref pnm, pc, nc, nil
proc parallelizer() {
	pnm = new ParallelNetManager(ncell)	// Set up a parallel net manager for all the cells
	pc = pnm.pc
	pnm.round_robin()					// Incorporate all processors - cells 0 through ncell-1
										//	are distributed throughout the hosts
										//	(cell 0 goes to host 0, cell 1 to host 1, etc, like
										//	dealing a deck of cards)
}
parallelizer()

iterator pcitr() {local i1, i2, gid, startgid	// Create an iterator for use instead of standard 'for' 
												//  loop. This provides an easy way for each processor 
												//  to do a 'for' loop only over cells that it owns. 
												//  This particular iterator code is slightly more 
												//  complex than Michael Hines' usual example, as it 
												//  allows for getting the index of any cell on a 
												//  processor relative to all cells on a processor or 
												//  just that cell type.
												//
												//  usage:
												//  for pcitr(&i1, &i2, &gid, it_start, it_end) {do stuff}
												//   the first three arguments are pointers that pcitr will
												//	  fill for you:
												//    - i1: index of the cell on the cell list for that host
												//    - i2: index of the cell for that cell type on that host
												//    - gid: index of the cell in the whole network
												//   it_start and it_end let you define the gid range over
												//     which to iterate, usually one of the following:
												//		- the gid range for a cell type
												//		- the gid range for the whole network
										
	numcycles = int($4/pc.nhost)
	extra = $4%pc.nhost
	addcycle=0
	if (extra>pc.id) {addcycle=1}
	startgid=(numcycles+addcycle)*pc.nhost+pc.id
	i1 = numcycles+addcycle // the index into the cell # on this host.
	i2 = 0 // the index of the cell in that cell type's list on that host
	if (startgid<=$5) {
		for (gid=startgid; gid <= $5; gid += pc.nhost) {	// Just iterate through the cells on
														//  this host(this simple statement
														//  iterates through all the cells on
														//  this host and only these cells because 
														//  the roundrobin call made earlier dealt
														//  the cells among the processors in an
														//  orderly manner (like a deck of cards)
				$&1 = i1
				$&2 = i2
				$&3 = gid
				iterator_statement						// this is the code that you put inside  
														//  your pcitr loop when you call the 
														//  pcitr iterator
				i1 += 1
				i2 += 1
		}
	}
}

objref  strobj
strobj = new StringFunctions()
strdef direx
if (strcmp(UID,"0")==0 && pc.id==0) {					// if a unique ID (UID) was not already 
														//  specified for this run, then generate 
														//  it now. Only one per run is needed, so
														//  only host 0 should generate it. A unique 
														//  ID could have been generated outside of 
														//  NEURON (say, by using the SimTracker) and 
														//  then passed in to a wrapper hoc code file
														//  that calls this file, or passed in at the 
														//  command line. If one was not already passed 
														//  in, then the UID defaults to 0 and the code
														//  below runs
														
	type = unix_mac_pc() 		// get the operating system type:
								//  1 if unix, 2 if mac, 3 if mswin, or 4 if mac osx darwin
	
	if (type<3 || type==4) { // unix or mac
		{system("uuidgen", direx)} // use a built in system command to generate the UID
		strobj.left(direx, strobj.len(direx)-1) // remove a newline character from the end
	} else { // pc
		{system("cscript //NoLogo setupfiles/uuid.vbs", direx)} // use a custom VBS (Visual Basic script)
																//  to generate the UID. This VBS file is
																//  included in the code repository
	}
	UID = direx
}

if (OK2executeSysCmds==1) {
	{load_file("./setupfiles/save_run_info.hoc")}		// This file will ensure a unique RunName (if 
														//  the specified RunName was already used, it 
														//  will append numbers to it like "_00", "_01" 
														//  to create a unique RunName). Then it will 
														//  create a RunName directory within the results 
														//  folder, where all the results will be stored. 
														//  It will also write out a receipt of the run 
														//  parameters and other administrative sorts of 
														//  run information.
	// NOTE: If you don't like the NEURON code to make new directories, concatenate files, or delete files, 
	//  set the OK2executeSysCmds parameter to 0 in "set_other_parameters.hoc" file. Watch out though, if 
	//  OK2executeSysCmds is set to 0, the code will use whatever RunName you supply or 'none' if you do not
	//  supply a RunName. This means:
	//  - the program will expect a directory named 'RunName' to be in the results directory
	//  - the program will overwrite any existing files in the ./results/RunName directory
}

objref  memfile, topfile

{zzz=0} // zzz contains the last memory usage from the previous call to mallinfo

if (pc.id==0) {
	{sprint(cmd,"./results/%s/memory.dat", RunName)} 	// Host 0 will make repeated calls to the 
	{memfile = new File(cmd)}							//  mallinfo function defined below and 
	{memfile.wopen()}									//  record the nrn_mallinfo output here

	{sprint(cmd,"./results/%s/topoutput.dat", RunName)}	// Host 0 will make repeated calls to the 
	{topfile = new File(cmd)}							//  mallinfo function defined below and  
	{topfile.wopen()}									//  record the top command output here
}

strdef direx1, TopCommand

func mallinfo() {local m 			// Arguments to this function:
									//  $1 is the previous memory usage (from previous call to mallinfo)
									//  $s2 is a string comment describing what stage the code is at
									
        m = nrn_mallinfo(0) // this function is a wrapper for the system function mallinfo
        if (pc.id == 0) {
			if (PrintTerminal>1) {printf("Memory (host 0) - %s: %ld.  Since last call: %ld\n", $s2, m, m - $1)}
			memfile.printf("%s\t%f\t%ld\t%ld\n", $s2, startsw()-loadstart, m, m - $1)	// Print the code stage,
																						//  current memory usage,
																						//  and difference in 
																						//  memory usage since 
																						//  last call
																						
			// Create a system command that calls the top command and extracts the NEURON process's memory info from the process list
			sprint(TopCommand,"top -p `pgrep %s | head -n20 | tr \"\\n\" \",\" | sed 's/,$//'` -n1 -b | tail -n2 | head -n1", TopProc)

			if (strcmp(TopProc,"")!=0) { system(TopCommand, direx1)}	// As long as the NEURON 
																		//  process name parameter 
																		//  was specified (usually 
																		//  nrniv or special), 
																		//  execute the system command 
																		//  and save the output into
																		//  the direx1 string
																		
			topfile.printf("%s\t%s\n", $s2, direx1)	// Print the extracted top output into a file
		}
        return m // Return the current memory usage (to be passed into this function at the next call)
}

{zzz = mallinfo(zzz, "Prior to creating cells")}	// Memory usage prior to creating the cells

loadtime = startsw() - loadstart		// Calculate the time used for the set up phase (now - recorded start time) in seconds
if (pc.id == 0) {printf("\nTIME HOST 0: %g seconds (set up)\n************\n", loadtime)}
createstart = startsw()					// Record the start time of the cell creation phase

/***********************************************************************************************
IV. CREATE, UNIQUELY ID, AND POSITION CELLS
***********************************************************************************************/
objref cells, ransynlist, ranstimlist
cells = new List()			// a list that will contain an entry for each cell in the network						
ransynlist = new List()		// a list that will contain an entry for each random number generator
							//  used by the synapse chooser (during the connectivity part of the 
							//  code) each cell will have its own random number generator for 
							//  synapse choosing
							
ranstimlist = new List()	// a list that will contain an entry for each random number generator
							//  used by the stimulation generator (during the actual simulation)
							//  each cell will have its own random number generator for stimulation
							//  specification. But only artificial cells will actually use their 
							//  generators

{load_file("./setupfiles/position_functions.hoc")}	// Defines the algorithm used to set the
													//  positions of the cells
													
{load_file("./setupfiles/create_cells_pos.hoc")}	// Creates each cell on its assigned host
													//  and sets its position

strdef cmd
{load_file("./setupfiles/write_positions.hoc")}		// Defines the proc to write the position
													//  of each cell to file
													
if (PrintCellPositions>0) {posout()}	// Calls the proc that writes the position of each cell 
										//  to the file "position.dat" in the format: gid, x, y, z

createtime = startsw() - createstart	// Calculate time taken to create the cells
if (pc.id == 0) {printf("\nTIME HOST 0: %g seconds (created cells)\n************\n", createtime)}
connectstart = startsw()				// Grab start time of cell connection

oldtimeout = pc.timeout(0) 

zzz = mallinfo(zzz, "After creating cells") // Record the memory usage after cells are created

/***********************************************************************************************
V.	CONNECT THE MODEL CELLS AND CONNECT THE STIMULATION CELLS TO SOME MODEL CELLS
***********************************************************************************************/

celsius=34								// The temperature at which to run the simulation (affects
										//  some ion channel calculations)

{load_file("./setupfiles/nc_append_functions.hoc")}	// Defines nc_append and nc_appendo, which 
													//  are procs used to create the netcon
													//  object associated with each synapse
													//  between cells


{sprint(cmd,"./connectivity/%s_connections.hoc", Connectivity)}
{load_file(cmd)}			// Loads in a particular algorithm to connect the cells in the network.
							//  Each algorithm is specified by a different file in the connectivity
							//  folder, and the file names are all of the format:
							//  [Connectivity]_connections.hoc, where [Connectivity] is the value in
							//  the Connectivity parameter that was set in the parameters.hoc file
							//  loaded above, or passed to this code file via the command line or a
							//  wrapper hoc file. The program only loads one connectivity file for
							//  each simulation run.
							// Some connectivity algorithms connect all network cells, including the
							//  artificial cells, while others only make the connections between
							//  real cells. Which connectivity algorithm you use will depend on the
							//  stimulation algorithms you use (as some stimulation algorithms specify
							//  the connectivity of the artificial cells onto the real cells).							

zzz = mallinfo(zzz, "After connecting cells")	// Record the memory usage after creating the connections

{sprint(cmd,"./stimulation/%s_stimulation.hoc", Stimulation)}
{load_file(cmd)}			// Loads in a particular algorithm to stimulation the network. The algorithm
							//  will always specify the spike properties (or actual spike trains) of the
							//  artificial cells in the model, and for some cases will also specify the
							//  connectivity from the artificial (stimulating) cells to the real cells of
							//  the model.

zzz = mallinfo(zzz, "After defining stimulation")	// Record the memory usage after defining stimulation

{load_file("./setupfiles/write_conns_functions.hoc")}	// Defines procedures that can be used to write out
														//  connectivity information for the model network.
														//  The procedures are not called by this file, but
														//  may be called later on in this code.
														
if (PrintConnSummary==1) {printNumConFile()}	// Write "numcons.dat": a QUICK, SMALL summary file of the # of
												//  connections by pre and post cell types
										
if (PrintConnDetails>0) {tracenetfast()}	// Write "connections.dat": a VERY SLOW, LARGE file of all cell
											//	connections (pre- and post-synaptic cell gids, synapse type,
											//  host on which the postsynaptic cell exists). Do NOT do this
											//  for large networks. Just print out a few of the connections
											//  instead, using tracecellsfast

if ((PrintConnDetails==0) && (PrintVoltage>0)) {tracecellsfast()}	// If detailed connections are not being
																	//  written out, at least write them out 
																	//  for cells that are being traced

connecttime = startsw() - connectstart			// Calculate time taken to connect the cells
if (pc.id == 0) {printf("\nTIME HOST 0: %g seconds (connected cells)\n************\n", connecttime)}
simstart = startsw()

/***********************************************************************************************
VI.	INITIALIZE AND RUN NETWORK, OUTPUT RESULT FILES
***********************************************************************************************/
if (PrintVoltage>0) {load_file("./setupfiles/recordvoltageauto.hoc")}	// This file contains a function to
																		//  record intracellular somatic
																		//  voltage traces of a few cells
																		//  of each type in your model.
																		//  Alternatively, you can save only
																		//  the connectivity information for
																		//  a few cells and combine that
																		//  information with the spike raster
																		//  to regenerate the voltage trace of
																		//  any cell for which you saved the 
																		//  connectivity information.

{load_file("./setupfiles/custom_init.hoc")} 	// Redefines the init proc that initializes the
												//  simulation so that it includes a "pre-run" of the
												//  simulation to reach steady state prior to the start
												//  of the simulation at time t = 0 ms.
												// This function will be called later, just before the
												//  simulation starts.
												//
												// WARNING: Any time you redefine the init function, you
												//  risk not completely initializing everything that needs
												//  to be initialized. Read the Initialization section of
												//  the NEURON Book and see this NEURON forum thread to make
												//  sure that you are initializing everything correctly:
												//  http://...

{load_file("./setupfiles/write_results_functions.hoc")}		// Defines procedures to write out various
															//  simulation results, but does not call
															//  the procedures yet. They will be called
															//  later on.

{load_file("./setupfiles/simulation_optimization.hoc")}		// Sets various simulation conditions to
															//  make the simulation more efficient. Note
															//  that the settings in here will need to be
															//  tested and possibly altered for each
															//  computer that the code is run on, and
															//  the ideal value for some settings may
															//  also change if your network model changes
															//  significantly (in number of cells, activity
															//  level of cells, number of processors you
															//  usually use, etc).

if (get_spike_hist==1) {		// If you use spike compression to speed up the spike exchange part of the
								//  simulation (see the simulation_optimization file), this setting is
								//  relevant. It allows you to obtain a histogram of how many spikes are 
								//  transferred at each exchange. That knowledge can then be used to set the 
								//  spike compression settings appropriately.
								// When running a significantly changed network or running it using a
								//  different supercomputer or configuration, you may want to generate a new
								//  histogram to get a sense of the new spike exchange structure so you can
								//  again find the most efficient setting for the spike compression
	setupSpikeHistogram()
}
															
{load_file("./setupfiles/sim_execution_functions.hoc")}		// Defines a procedure to run the simulation and another one
															//  to periodically check how long the simulation is taking
															//  so as to end it early enough to write out results if
															//  there is limited time on the supercomputer

zzz = mallinfo(zzz, "Before running simulation")	// Get memory usage before running simulation

rrun()	// Run the network simulation and write out results

zzz = mallinfo(zzz, "After running simulation")		// Get memory usage after running simulation

if (pc.id==0) {			// Close the files used to record the memory usage as no more data points will be written
	{memfile.close()}
	{topfile.close()}
}


if (pc.id==0 && get_spike_hist==1) {	// If a spike histogram was generated, print it out.
	printSpikeHistogram()
}

// New quit commands, for use with NEURON development version ... or higher.
//  Use these commands if the original quit commands don't stop the code
//  when you run the program on a supercomputer
/*
{pc.barrier}
{quit()} */

// Original quit commands, which usually work:
{pc.runworker()} 	// Everything after this line is executed only by the host node
					//  The NEURON website describes this as "The master host returns immediately. Worker hosts
					//  start an infinite loop of requesting tasks for execution." 
					
{pc.done()}			// Sends the quit message to the worker processors, which then quit NEURON

quit()	// Sends the quit message to the host processor, which then quits NEURON