/*
    brunel.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.
*/

/*
   Brunel Network

   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 500
   neurons from each population are recorded.  Neurons are modeled
   as leaky integrate-and-fire neurons with current-injecting synapses
   (alpha functions).  The model is based on 

      Nicolas Brunel
      Dynamics of sparsely connected networks of excitatory 
      and inhibitory spiking neurons
      Journal of Computational Neuroscience, 2000, vol 8, pp 183-208.

   There are two differences to Brunel's model: we use alpha
   functions instead of delta for synaptic currents, and our neurons
   reset to the resting potential (0 mv) instead of to half-way
   between resting potential and threshold. 

   This example shows how to

      - organize subpopulations in subnets
      - instrument a network with injection and recording devices
      - record data to files
      - define own functions
      - set parameters in a simple way
      - communicate with the user in a simple way
   
   Abigail Morrison, Marc-Oliver Gewaltig, Hans Ekkehard Plesser
*/

%%% PARAMETER SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

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

userdict begin        % open dictionary, all variables stored here
   
  /order 2500 def     % scales size of network (total 5*order neurons)

  % case C : slow oscillations 
  /g      5.0 def    % rel strength, inhibitory synapses
  /eta    2.0 def    % nu_ext / nu_thresh

  % case D : slow oscillations 
  %/g      4.5  def    % rel strength, inhibitory synapses
  %/eta    0.95 def    % nu_ext / nu_thresh

  /simtime 1000.0 def % simulation time [ms] 
  /dt         0.1 def % simulation step length [ms]   

  /connectseed 12345789 def   % seed for random generator for conns.
  /kernelseeds [43210987] def % seed(s) for kernel rng(s)
                              % array with one element per thread 

  /exfilename (spikes_ex.gdf) def  % output file for excit. population  
  /infilename (spikes_in.gdf) def  % output file for excit. population  

end  % close userdict

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%       NO USER-SERVICABLE PARTS BELOW
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

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

% Take spike detector, find total number of spikes registered,
% return average rate per neuron in Hz
% 
% spike_det ComputeRate -> rate
/ComputeRate
{
  userdict begin     % ensure we are in userdict  
    GetStatus        % get entire status dictionary
    /events get      % extract number of spike recorded
    Nrec simtime mul % normalization factor
    cvd div          % convert divisor to double before dividing              
    1000 mul         % convert from mHz to Hz, leave on stack
  end
} bind             % optional, improves performance 
def

%%% PREPARATION SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

userdict begin

  /NE 4 order mul cvi def  % number of excitatory neurons
  /NI 1 order mul cvi def  % number of inhibitory neurons
  /N  NI NE add def        % total number of neurons

  /epsilon 0.1 def            % connectivity 
  /CE epsilon NE mul cvi def  % number of excitatory synapses on neuron
  /CI epsilon NI mul cvi def  % number of inhibitory synapses on neuron
  /C  CE CI add def           % total number of internal synapses per n.   
  /Cext CE def                % number of external synapses on neuron  

  /tauMem 20.0 def    % neuron membrane time constant [ms]
  /tauSyn  0.5 def    % synaptic time constant [ms]
  /tauRef  2.0 def    % refractory time [ms]
  /U0      0.0 def    % resting potential [mV]
  /theta  20.0 def    % threshold 

  % synaptic weights, scaled for our alpha functions, such that
  % for constant membrane potential, charge J would be deposited
  /delay   1.5 def         % synaptic delay, all connections [ms]  
  /J       0.1 def         % synaptic weight [mV]
  /fudge 0.41363506632638 def   % ensures dV = J at V=0  
  /JE J tauSyn div fudge mul def
  /JI g JE mul neg def     % inhibitory  
  
  % threshold rate  
  /nu_thresh theta J CE mul tauMem mul div def
  /nu_ext eta nu_thresh mul def     % external rate per synapse
  /p_rate nu_ext Cext mul 1000. mul def % external input rate per neuron  
                                        % must be given in Hz

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

  % number of synapses---just so we know
  /Nsyn
    C          % internal synapses
    1 add      % synapse from PoissonGenerator
    N mul      
    Nrec 2 mul % "synapses" to spike detectors
    add   
  def

end

%%% CONSTRUCTION SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

ResetKernel      % clear all existing network elements

modeldict begin  % open model and user dictionaries
  userdict begin

    % set resolution and limits on delays
    % limits must be set BEFORE connecting any elements
    [0]
    <<
      /resolution dt
      /min_delay  dt
      /max_delay  delay
    >> SetStatus
    
    tic % start timer on construction    

    (Creating excitatory population.) =  % show message
    /E_net subnet Create def     % create subnet
    E_net ChangeSubnet           % enter subnet
    iaf_neuron NE CreateMany     % create neurons in subnet 
    ;                            % pop port numbers returned by CreateMany
    [0] ChangeSubnet             % return to full network

    (Creating inhibitory population.) =  % show message
    /I_net subnet Create def     % create subnet
    I_net ChangeSubnet           % enter subnet
    iaf_neuron NI CreateMany     % create neurons in subnet 
    ;                            % pop port numbers returned by CreateMany
    [0] ChangeSubnet             % return to full network

    (Creating excitatory Poisson generator.) =
    /expoisson poisson_generator Create def   
    expoisson        
    <<                % set firing rate
      /rate p_rate
    >> SetStatus

    (Creating inhibitory Poisson generator.) =
    /inpoisson poisson_generator Create def
    inpoisson
    <<
      /rate p_rate
    >> SetStatus

    % one detector would in principle be enough,
    % but by recording the populations separately,
    % we save us a lot of sorting work later    
    (Creating excitatory spike detector.) =
    /exsd spike_detector Create def
    exsd
    << 
      /withtime true   % record time of spikes
      /withpath true   % record which neuron spiked
    >> SetStatus

    (Creating inhibitory spike detector.) =
    /insd spike_detector Create def
    insd
    << 
      /withtime true 
      /withpath true 
    >> 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

    (Configuring neuron parameters.) =
    allNeurons
    {
      dup    % make copy of GID for ReserveConnections call
      <<
        /Tau    tauMem
        /TauSyn tauSyn
        /TauR   tauRef
        /U0     U0
        /Theta  theta
        /C      1.0     % capacitance is unity in Brunel model      
      >> SetStatus

      GetAddress  % ReserveConnections need address, not GID

      % reserving space for all connections originating from
      % a neuron before connecting saves a lot of time      
      % C is the expected number of outgoing connections from
      % each neuron      
      C ReserveConnections 
    } forall

        
    % Create random generator to use in randomized connection
    % functions    
    /rng    
      rngdict /knuthlfg get   % extract generator type from rngdict
      connectseed    
      CreateRNG
    def

    (Connecting excitatory population.) =  

    % space for neuron to neuron connections was reserved above    
    % reserve space for connections from Poisson generator to neurons    
    expoisson NE ReserveConnections

    E_neurons
    {
      /target Set

      % E -> E connections      
      % the following is a single call to RandomConvergentConnect      
      rng         % random number generator
      E_neurons   % source population [we pick from this]
      target      % target neuron
      CE          % number of source neurons to pick
      SpikeEvent  % event==connection type: send spikes!
      JE          % connection weight
      delay       % connection delay
      RandomConvergentConnect

      % I -> E connections 
      % as above, but on a single line       
      rng I_neurons target CI SpikeEvent JI delay RandomConvergentConnect

      % Connect Poisson generator to neuron      
      expoisson         % source: is in address form 
      target GetAddress % is GID, conversion necessary
      Connect           % defaults: SpikeEvent, weight 1, delay 1 step
                        % returns port number      
      expoisson exch    % stack: expoisson portnumber
      JE SetWeight      % set weight for port from Poisson gen to node
    } bind % bind improves efficiency
    forall

    (Connecting inhibitory population.) =

    % ... as above, just written more compact    

    inpoisson NI ReserveConnections

    I_neurons
    {
      /target Set
      rng E_neurons target CE SpikeEvent JE delay RandomConvergentConnect
      rng I_neurons target CI SpikeEvent JI delay RandomConvergentConnect
      inpoisson target GetAddress Connect
      inpoisson exch JE SetWeight
    } bind forall


    % 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    
    (Connecting spike detectors.) =    

    exsd Nrec ReserveConnections
    insd Nrec ReserveConnections

    E_neurons Nrec Take     % pick the first 500 neurons
    {
      exsd exch  % stack: SDaddress neuronGID
      GetAddress % stack: SDaddress neuronaddress
      Connect    % defaults: SpikeEvent, weight 1, delay 1 step
      ;          % pop port number
    } bind forall

    % same for inhibitory population    
    I_neurons Nrec Take
    {
      insd exch GetAddress Connect ;
    } bind forall

    % read out time used for building    
    toc /BuildCPUTime Set

  end  % userdict
end    % modeldict

%%% SIMULATION SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

userdict begin

  % open output files & register files with detectors
  /exspikefile exfilename (w) file def
  exsd << /output_stream exspikefile >> SetStatus
  
  /inspikefile infilename (w) file def
  insd << /output_stream inspikefile >> SetStatus

  % seed kernel RNG(s)
  [0] << /rng_seeds kernelseeds >> SetStatus

  % run, measure computer time with tic-toc
  tic
  simtime Simulate
  toc /SimCPUTime Set

  % close output files
  inspikefile close
  exspikefile close

  % write a little report
  (\nBrunel Network Simulation) =
  (Number of Neurons : ) =only N =
  (Number of Synapses: ) =only Nsyn =
  (Excitatory rate   : ) =only exsd ComputeRate =only ( Hz) =
  (Inhibitory rate   : ) =only insd ComputeRate =only ( Hz) =
  (Building time     : ) =only BuildCPUTime =only ( s) =    
  (Simulation time   : ) =only SimCPUTime   =only ( s\n) =    

end  % userdict