/*
 *  brunel.sli
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST 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.
 *
 *  NEST 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 NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
   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 50
   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

  /order 2500 def     % scales size of network (total 5*order neurons)

  % case C : asynchronous irregular
  /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  100.0 def % simulation time [ms]
  /dt          0.1 def % simulation step length [ms]

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

%%% 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

  % 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.

  userdict /Nrec get cvd NumProcesses div /nnrn Set
  sdet /n_events get nnrn userdict /simtime get mul div
  1000 mul         % convert from mHz to Hz, leave on stack
  end
} bind             % optional, improves performance
def

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

  /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 50 def

 [0]
  <<
    /resolution  dt
    /overwrite_files true
    /print_time true
  >> SetStatus


/sli_neuron GetDefaults begin
   /h             0.1 def
   /C_m           1.0 def
   /E_L           0.0 def
   /I_e           0.0 def
   /tau_minus    20.0 def
   /tau_m       tauMem def
   /tau_syn     tauSyn def
   /t_ref       tauRef def
   /E_L         U0 def
   /V_th        theta def
   /C_m         1.0  def % Yes, capacitance is 1 in the Brunel model
   /t_spike      -1.0 def
   /V_m           U0 def
   /V_reset       U0 def
   /V_th         U0 theta add def
   /theta        theta def
   /currents      0.0 def

   /y0 0.0 def
   /y1 0.0 def
   /y2 0.0 def
   /y3 0.0 def
   /r  0 def

/update_template
{
   r 0 eq
   {
        update_y3 /y3 Set
   }
   {
        r 1 sub /r Set
   } ifelse

   update_y2 /y2 Set
   y1 P11 mul /y1 Set
   y1 PSCInitialValue ex_spikes in_spikes add mul add /y1 Set

   y3 theta geq                           % voltage larger threshold?
   dup /spike Set                         % the return value
   {
      t_ref_steps /r Set
      V_reset   /y3 Set                    % Reset the potential
   } if
   y3 E_L add /V_m Set
   currents /y0 Set

} def

/calibrate
{
  GetResolution /h Set
}

/compile
{
  (V_th - E_L) ExecMath /theta Set
  << >> /consts Set
  consts begin
    (exp(-h/tau_syn)) ExecMath /P11 Set
    (exp(-h/tau_m))   ExecMath /P33 Set
    (P11 * h)         ExecMath /P21 Set
    ((1.0 - P33)*tau_m/C_m)   ExecMath /P30 Set
    (1/ C_m * ((P11-P33)/(-1/tau_syn + 1/tau_m)- h*P11)/(-1/tau_m+1/tau_syn)) ExecMath /P31 Set
    (1/C_m*(P33-P11)/(-1/tau_m + 1/tau_syn)) ExecMath /P32 Set
    (exp(1.0)/tau_syn) ExecMath /PSCInitialValue Set
     t_ref h div 0.5 add floor cvi /t_ref_steps Set
    (P30*(y0 + I_e) + P31 * y1 + P32 * y2 + P33 *y3) CompileMath consts Inline /update_y3 Set
    (P21* y1 + P11 * y2) CompileMath consts Inline /update_y2 Set
  end

  /update_template load consts Inline
  /update Set
} def

end
/sli_neuron GetDefaults /compile call

{/sli_neuron GetDefaults /y3 known} assert

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

% set resolution and total/local number of threads
      tic % start timer on construction

      % Setting neuron default parameters

      (Configuring neuron parameters.) =

      (Creating the network.) =  % show message

      /sli_neuron [NE] LayoutNetwork /E_net Set
      /sli_neuron [NI] LayoutNetwork /I_net Set

      % 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/inhibitory neurons
      /E_neurons E_net GetNodes def
      /I_neurons I_net GetNodes def

      % list of all neurons
      /allNeurons E_neurons I_neurons join def

      (Creating Poisson generators.) =
      /poisson_generator
      <<                % set firing rate
        /rate p_rate
      >> SetDefaults

      /expoisson /poisson_generator Create def
      /inpoisson /poisson_generator Create def


      % 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
      <<
        /label (brunel-1-ex)
        /withtime true   % record time of spikes
        /withgid  true   % record which neuron spiked
        /to_file true    % write results to a file
        /to_memory false
      >> SetStatus

      (Creating inhibitory spike detector.) =
      /insd /spike_detector Create def
      insd
      <<
        /label (brunel-1-in)
        /withtime true
        /withgid  true
        /to_file  true
        /to_memory false
      >> SetStatus

      % Create custom synapse types with appropriate values for
      % our excitatory and inhibitory connections
      /static_synapse << /delay delay >> SetDefaults
      /static_synapse /syn_ex << /weight JE >> CopyModel
      /static_synapse /syn_in << /weight JI >> CopyModel

      (Connecting excitatory population.) =


      % Connect Poisson generator to neurons
      expoisson E_neurons /syn_ex DivergentConnect

      E_neurons
      {

        % E -> E connections
        % the following is a single call to RandomConvergentConnect
        E_neurons   % source population [we pick from this]
        exch        % target neuron
        CE          % number of source neurons to pick
        /syn_ex     % the synapse model
        RandomConvergentConnect
      } bind % bind improves efficiency
      forall

      E_neurons
      {
        I_neurons exch CI /syn_in RandomConvergentConnect

      } bind forall

      (Connecting inhibitory population.) =

      % ... as above, just written more compact

      inpoisson I_neurons /syn_ex DivergentConnect

      I_neurons
      {
        E_neurons exch CE /syn_ex RandomConvergentConnect
      } bind forall

      I_neurons
      {
        I_neurons exch CI /syn_in RandomConvergentConnect
      } 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.) =

      E_neurons Nrec Take exsd ConvergentConnect  % pick the first 500 neurons
      I_neurons Nrec Take insd ConvergentConnect

    toc /BuildCPUTime Set
     /syn_ex GetDefaults /num_connections get /n_ex_syn Set
     /syn_in GetDefaults /num_connections get /n_in_syn Set

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

  % run, measure computer time with tic-toc
  tic

  simtime Simulate
  toc /SimCPUTime Set

  % write a little report
  (\nBrunel Network Simulation) =
  (Number of Neurons : ) =only N =
  (Number of Synapses: ) =only 0 GetStatus /num_connections get =
  (       Excitatory : ) =only n_ex_syn =
  (       Inhibitory : ) =only n_in_syn =
  (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) =