/*
    cuba_stdp.sli

    Copyright (C) 2004 The NEST Initiative                 
    This file is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
 
    This file is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330,
    Boston, MA  02111-1307, USA.
*/

/*
   The fifth simulator review benchmark is implemented as a variation of the
      * Brunel Network *

   (from the docu of brunel.sli)
   The SLI code in this file creates a sparsely coupled network of
   excitatory and inhibitory neurons.  Connections within and across
   both populations are created at random.  Both neuron populations
   receive Poissonian background input.  The spike output of 1000
   neurons are recorded.  Neurons are modeled as leaky 
   integrate-and-fire neurons with current-injecting synapses
   (exp functions). Excitatory-excitatory synapses implement 
   multiplicative STDP.  
   Marc-Oliver Gewaltig, Abigail Morrison, Wiebke Potjans, Tobias Potjans

   Usage:
   This file (cuba_stdp.sli) is used on its own, e.g.

     nest cuba_stdp.sli
   
   If virtual_processes is set to a value greater than one, the benchmarks can
   will be run with that number of virtual processes. If you do not use MPI, each
   virtual process is mapped to one POSIX thread. You should not use more POSIX
   threads per machine than there are processor cores. If you use MPI, you can
   run the benchmakr in a distributed fashion using mpirun, e.g.

     mpirun -np 2 nest cuba_stdp.sli

   In this case, the number of MPI processes must divide the number of virtual
   processes without remainder. You could, e.g. use 4 virtual processes on a
   mini-cluster of 2 machines with 2 CPUs each. 


*/
  
%%% PARAMETER SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% define all relevant parameters: changes should be made here
% all data is place in the userdict dictionary

modeldict using   % make neuron models visible
synapsedict using % make synapse models visible

% A dictionary is a list of name value pairs, enclosed in << and >>
% Here we use dictionaries to encapsulate the parameters for the different
% benchmarks
  
/brunel_params
<<

  /model  iaf_psc_exp  % the neuron model to use

  /model_params
   <<
   % Set variables for iaf_psc_exp
    /E_L     0.0  mV  % Resting membrane potential [mV]
    /V_th   20.0  mV  % Threshold [mV]
    /V_reset 0.0  mV  % Reset Potential [mV]
    /C_m   250.0  pF  % Capacity of the membrane [pF]
    /tau_m  10.0  ms  % Membrane time constant [ms]
    /tau_ex  0.33 ms  % time const. postsynaptic excitatory currents [ms]
    /tau_in  0.33 ms  % time const. postsynaptic inhibitory currents [ms]
    /tau_ref 0.5  ms  % duration of refractory period [ms]
                      % V randomly initialized see below
   >>

  /mean_potential 7.0 mV
  /sigma_potential 5.0 mV
 
  /plastic_synapses true

  /delay  1.5 ms         % synaptic delay, all connections [ms] 
   % synaptic strengths, here peak conductance

  /sigma_w 8.75      %standard dev. of E->E synapses [pA]
  /JE 175 pA
  /g  -17.0 	
    
  /stdp_params
  <<
    /delay 1.5 ms
    /alpha 1.05      %STDP asymmetry parameter
    /lambda 0.005    %STDP step size
    /mu_plus 1.0     %STDP weight dependence exponent (potentiation)
    /mu_minus 1.0    %STDP weight dependence exponent (depression)
  >>
  
  
  /stimulus poisson_generator 
  /stimulus_params
  <<
    /rate 900.0 30.0 mul Hz % rate of inital poisson stimulus
  >>

  /detector spike_detector
  /detector_params
  << 
   /withtime true 
   /withpath true
   /to_file true
   /label (cuba_stdp)
  >>

  % number of neurons per population to record from
  /Nrec 500 

  /simtime 2000.0 ms % simulated time 
  /dt         0.1 ms % simulation step   

  /NE 9000       % number of excitatory neurons
  /NI 2250       % number of inhibitory neurons
  /epsilon 0.1  % Connection probability 

  /virtual_processes 1  % number of virtual processes to use

>> def

% Here we resolve parameter dependencies, by making the independent
% values visible
brunel_params dup using
<<
 /E_synapse_params
  <<
     /weight JE     % excitatory synaptic conductance
  >>
 
  /I_synapse_params
  <<
     /weight JE g mul   % inhibitory synaptic current [pA]
  >>
>> SetStatus

stdp_params
<<
  /Wmax 2.0 JE mul %max strength of E->E synapses [pA]
>> SetStatus

% create one single random number generator    
rngdict /knuthlfg get 238 CreateRNG /myrng Set

% normal distribution to draw several quantities from
myrng rdevdict /normal get CreateRDV /normal_dv Set


%%% FUNCTION SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Take spike detector, find total number of spikes registered,
% return average rate per neuron in Hz.
% NOTE: If you are running with several MPI processes, this
%       function only gives an approximation to the true rate.
% 
% spike_det ComputeRate -> rate
/ComputeRate
{
  << >> begin  % anonymous dictionary for local variables
  /sdet Set    % spike detector address

  % sum spike numbers across local threads
  [0 0 GetStatus /local_num_threads get 1 sub]
  { 
    sdet GetAddress exch append 
    GetStatus /events get 
  } Table Plus
  /nspikes Set

  % We need to guess how many neurons we record from.
  % This assumes an even distribution of nodes across
  % processes, as well as homogeneous activity in the
  % network. So this is really a hack. NEST needs better
  % support for rate calculations, such as giving the
  % number of neurons recorded from by each spike detector.
  brunel_params /Nrec get cvd NumProcesses div
  /nnrn Set

  nspikes nnrn brunel_params /simtime get mul div
  1000 mul         % convert from mHz to Hz, leave on stack
  end
} bind             % optional, improves performance 
def

/BuildNetwork
{
  
  tic % start timer on construction    
  % set global kernel parameters
  [0]
  <<
     /resolution  dt
     /total_num_virtual_procs virtual_processes
  >> SetStatus


  model    model_params    SetModelStatus
  M_INFO (BuildNetwork)
  (Creating excitatory population.) message  % show message
  
  /E_net model [ NE ] LayoutNetwork def

  M_INFO (BuildNetwork)
  (Creating inhibitory population.) message  % show message
  /I_net model [ NI ] LayoutNetwork def

  M_INFO (BuildNetwork)
  (Creating excitatory stimulus generator.) message
  /E_stimulus stimulus Create def   
  E_stimulus stimulus_params SetStatus
  
  /I_stimulus stimulus Create def
   I_stimulus stimulus_params SetStatus

  % one detector would in principle be enough,
  % but by recording the populations separately,
  % we save ourselves a lot of sorting work later    

  M_INFO (BuildNetwork)
  (Creating excitatory spike detector.) message
  /E_detector detector Create def
  E_detector detector_params SetStatus

  M_INFO (BuildNetwork)
  (Creating inhibitory spike detector.) message
  /I_detector detector Create def
  I_detector detector_params SetStatus

  % some connecting functions need lists (arrays) over all
  % neurons they should work on, so we need to extract these
  % lists from the subnetworks    

  % obtain array with GIDs of all excitatory neurons    
  /E_neurons E_net GetNodes def

  % obtain array with GIDs of all inhibitory neurons    
  /I_neurons I_net GetNodes def

  % all neurons    
  /allNeurons E_neurons I_neurons join def
 
  /N allNeurons length def

  /CE NE epsilon mul iround def %number of incoming excitatory connections
  /CI NI epsilon mul iround def %number of incomining inhibitory connections	

  M_INFO (BuildNetwork)
  (Configuring neuron parameters.) message
  allNeurons
  {
   << /V_m  normal_dv Random sigma_potential mul mean_potential add >> SetStatus
  } forall
  
  % Create custom synapse types with appropriate values for
  % our excitatory and inhibitory connections
  << /delay delay >> SetSynapseDefaults
  (syn_std) CopySynapsetype ;
  E_synapse_params SetSynapseDefaults
  (syn_ex) CopySynapsetype ;
  I_synapse_params SetSynapseDefaults
  (syn_in) CopySynapsetype ;
      	
  stdp_synapse   SetSynapseContext
  stdp_params SetSynapseDefaults

  plastic_synapses
  {
    /synapse_model stdp_synapse def% plastic
  }
  {
    /synapse_model syn_ex def
  } ifelse
  
  % create a dictionary containing the initialization function
  % InitSynapse, which will be called by RandomConvergentConnect 
  % before each connection to be established.
  /init_dict
  <<
     % call: i InitSynapse    
     /InitSynapse
     {
       /i Set
	  
       synapse_model SetSynapseContext
       <<
         /weight weights i get  % weights will be added to the dict later
       >> SetSynapseDefaults
     }
  >> def
 
  % use predefined synapse type
  syn_ex SetSynapseContext

  M_INFO (BuildNetwork)
  (Connecting stimulus generators.) message
  
  % Connect Poisson generator to neuron      
  E_stimulus E_neurons DivergentConnect
  I_stimulus I_neurons DivergentConnect

  synapse_model SetSynapseContext     

  M_INFO (BuildNetwork)
  (Connecting excitatory -> excitatory population.) message
  E_neurons
  {
    /target Set
	
     % create an array of CE elements and store it in 'rnd_weights'; 
     % each element is chosen from a normal distribution with mean 
     % value JE and standard deviation sigma_w, add to init dictionary
     CE array { ; normal_dv Random sigma_w mul JE add } Map 
     /rnd_weights Set
    init_dict << /weights rnd_weights >> SetStatus 
    
    % E -> E connections      
    E_neurons   % source population [we pick from this]
    target      % target neuron
    CE          % number of source neurons to pick
    init_dict   % initialization dictionary
    RandomConvergentConnect
    
  } bind forall

  M_INFO (BuildNetwork)
  (Connecting excitatory -> inhibitory population.) message

  % change synapse type
  syn_ex SetSynapseContext
  I_neurons
  {
    /target Set
	E_neurons target CE RandomConvergentConnect
  } bind forall

  % change synapse type
  syn_in SetSynapseContext

  M_INFO (BuildNetwork)
  (Connecting inhibitory -> excitatory population.) message
  
  E_neurons
  {
    /target Set
	 
	% I -> E connections 
	% as above, but on a single line       
	I_neurons target CI RandomConvergentConnect
  } bind forall

  M_INFO (BuildNetwork)
  (Connecting inhibitory -> inhibitory population.) message
	
  I_neurons
  {
    /target Set
	I_neurons target CI RandomConvergentConnect
  } bind forall

  % reset synapse type to default
  syn_std SetSynapseContext

  % Spike detectors are connected to the first Nrec neurons in each
  % population.  Since neurons are equal and connectivity is homogeneously
  % randomized, this is equivalent to picking Nrec neurons at random
  % from each population    
  M_INFO (BuildNetwork)
  (Connecting spike detectors.) message
      
  E_neurons Nrec Take E_detector ConvergentConnect
  I_neurons Nrec Take I_detector ConvergentConnect

  % read out time used for building    
  toc /BuildCPUTime Set
 } def % end of buildnetwork
 
/RunSimulation
{
  ResetKernel
  
  BuildNetwork
  tic

  simtime Simulate

  toc /SimCPUTime Set
   % number of synapses---just so we know
  /Nsyn
    CE CI add  % internal synapses
    N mul      
    Nrec 2 mul % "synapses" to spike detectors
    add
    N add      % "synapses" from poisson generator   
  def

  (\nBrunel Network Simulation) =
  (Building time     : ) =only BuildCPUTime =only ( s) =
  (Simulation time   : ) =only SimCPUTime   =only ( s\n) =
  (Number of Neurons : ) =only N =
  (Number of Synapses: ) =only Nsyn =
  (Excitatory rate   : ) =only E_detector ComputeRate =only ( Hz) =
  (Inhibitory rate   : ) =only I_detector ComputeRate =only ( Hz) =

} def

RunSimulation