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

/*
    Implementation of the multi-layered local cortical network model by
    
    Potjans, Tobias C., and Markus Diesmann. "The cell-type specific 
    cortical microcircuit: relating structure and activity in a full-scale 
    spiking network model." Cerebral Cortex (2014): bhs358.

    Uses user_params.sli, sim_params.sli, and network_params.sli
    
    function definitions:
    - CheckParameters
    - PrepareSimulation
    - DerivedParameters
    - CreateNetworkNodes
    - WriteGIDstoFile
    - ConnectNetworkNodes

    Tobias Potjans 2008; adapted by Tom Tetzlaff, David Dahmen,
    Sacha van Albada 2013; Hannah Bos 2014
*/

/CheckParameters
{
    % non-exhaustive check of parameter dimensions and values
 
    neuron_model /iaf_psc_exp eq not
    {
        Rank 0 eq
        {      
            M_WARNING (CheckParameters)
            (Unexpected neuron type: script is tuned to /iaf_psc_exp neurons.)
            message  % this is written to the output file
        } if 
    } if
 
    % number of layers
    /n_layers full_scale_n_neurons length def
    % number of populations in each layer
    /n_pops_per_layer full_scale_n_neurons Dimensions 1 get def

    conn_probs Dimensions 0 get
    n_layers n_pops_per_layer mul 
    eq not
    conn_probs Dimensions 1 get
    n_layers n_pops_per_layer mul 
    eq not or
    {
        /CheckParameters /conn_probs_dimensions raiseerror
    } if 

    record_fraction_neurons_spikes
    {
        frac_rec_spikes 1 gt
        {
            /CheckParameters /frac_rec_spikes raiseerror
        } if
    }{
        n_rec_spikes full_scale_n_neurons { Min } Map Min area mul gt
        {
            /CheckParameters /n_rec_spikes raiseerror
        } if
    } ifelse

    record_fraction_neurons_voltage
    {
        frac_rec_voltage 1 gt
        {
            /CheckParameters /frac_rec_voltage raiseerror
        } if
    }{
        n_rec_voltage full_scale_n_neurons { Min } Map Min area mul gt
        {
            /CheckParameters /n_rec_voltage raiseerror
        } if
    } ifelse

} def



/PrepareSimulation
{
    ResetKernel
  
    % set global kernel parameters
    0
    <<
       /resolution dt
       /total_num_virtual_procs n_vp
       /overwrite_files overwrite_existing_files
       /rng_seeds master_seed [0 n_vp 1 sub] add Range % local RNG seeds
       /grng_seed master_seed n_vp add                 % global RNG seed
       run_mode (production) eq
       {
           /data_path output_path
       } if
    >> SetStatus

    /seed_offset master_seed n_vp add def
    /script_rngs [ n_vp ] 
    { seed_offset add rngdict /gsl_mt19937 get exch CreateRNG } Table def
    /normal_rdvs script_rngs { rdevdict /normal get CreateRDV } Map def

} def


/DerivedParameters
{
    % compute numbers of neurons for the given surface area
    /n_neurons
    full_scale_n_neurons { area mul cvi } [2] Map
    def
 
    % compute PSC amplitude from PSP amplitude
    
    model_params using
    % factor for transforming PSP amplitude to PSC amplitude
    
    /re tau_m tau_syn_ex div def
    /de tau_syn_ex tau_m sub def
    /ri tau_m tau_syn_in div def
    /di tau_syn_in tau_m sub def

    /PSC_e_over_PSP_e
    (((C_m)^(-1)*tau_m*tau_syn_ex/de*(re^(tau_m/de)-re^(tau_syn_ex/de)))^(-1))
    ExecMath def

    /PSC_i_over_PSP_i
    (((C_m)^(-1)*tau_m*tau_syn_in/di*(ri^(tau_m/di)-ri^(tau_syn_in/di)))^(-1))
    ExecMath def
    endusing

    /PSC_e PSC_e_over_PSP_e PSP_e mul def
    /PSC_e_23_4 PSC_e_over_PSP_e PSP_e_23_4 mul def
    /PSP_i PSP_e g mul def
    /PSC_i PSC_i_over_PSP_i PSP_i mul def
    
    % PSC amplitude for all external input
    /PSC_ext PSC_e_over_PSP_e PSP_ext mul def
        
    % array of synaptic current amplitudes
    [4 2 4] [PSC_e PSC_i] LayoutArray /PSC_array Set
    PSC_array [0 0 1 0]  PSC_e_23_4 put /PSC_array Set
        
    % standard deviations of synaptic current amplitudes
    /PSC_sd [PSC_e PSC_i] PSC_rel_sd mul def
    /PSC_th_sd PSC_ext PSC_rel_sd mul def
    
    % standard deviations of delays
    /delays_sd delays delay_rel_sd mul def
    /delay_th_sd delay_th delay_th_rel_sd mul def
    
    % numbers of neurons from which to record spikes and membrane potentials
    record_fraction_neurons_spikes
    {
        /n_neurons_rec_spikes
        n_neurons{ frac_rec_spikes mul cvi } [2] Map
        def
    }{
        /n_neurons_rec_spikes
        [n_layers n_pops_per_layer] n_rec_spikes LayoutArray
        def
    } ifelse

    record_fraction_neurons_voltage
    {
        /n_neurons_rec_voltage
        n_neurons{ frac_rec_voltage mul cvi } [2] Map
        def
    }{
        /n_neurons_rec_voltage
        [n_layers n_pops_per_layer] n_rec_voltage LayoutArray
        def
    } ifelse

} def



% the following variables are created here: 
% neuron_subnet_GIDs
% poisson_GIDs
% dc_GIDs
% spike_detector_GIDs
% voltmeter_GIDs
% th_neuron_subnet_GID
% th_poisson_GID
% th_spike_detector_GID
/CreateNetworkNodes
{
    % create and configure neurons
    neuron_model model_params SetDefaults
    % arrays of GIDs:
    % neuron subnets
    /neuron_subnet_GIDs [n_layers n_pops_per_layer] 0 LayoutArray def
    % spike detectors
    /spike_detector_GIDs [n_layers n_pops_per_layer] 0 LayoutArray def
    % voltmeters
    /voltmeter_GIDs [n_layers n_pops_per_layer] 0 LayoutArray def
    % Poisson generators
    /poisson_GIDs [n_layers n_pops_per_layer] 0 LayoutArray def
    % DC generators 
    /dc_GIDs  [n_layers n_pops_per_layer] 0 LayoutArray def
    0 1 n_layers 1 sub
    {
        /layer_index Set
        0 ChangeSubnet   % change to the root node
        /subnet Create /layer_subnet Set
        0 1 n_pops_per_layer 1 sub
        {
            /population_index Set
	    layer_subnet ChangeSubnet
            /subnet Create /population_subnet Set
            population_subnet ChangeSubnet
	    % create neurons
	    /subnet Create /neuron_subnet Set
	    neuron_subnet ChangeSubnet
	    neuron_subnet_GIDs [layer_index population_index] neuron_subnet put
            /neuron_subnet_GIDs Set
            neuron_model n_neurons layer_index get population_index get
            Create ;

            % initialize membrane potentials
            neuron_subnet GetLocalNodes
            {
               dup /vp get /node_vp Set
	        << /V_m normal_rdvs node_vp get Random Vm0_std mul Vm0_mean add
                >> SetStatus
	    } forall
	    
	    population_subnet ChangeSubnet
	    % create and configure stimulus and recording devices
	    /subnet Create /device_subnet Set
	    device_subnet ChangeSubnet
            /spike_detector Create /this_spike_detector Set
	    this_spike_detector
	    <<
	       % Set spike detector label for filenames. The GID of the spike
               % detector and the process number are appended automatically.
	       /label spike_detector_label layer_index cvs join (_) join
               population_index cvs join
	       /to_file save_cortical_spikes
	    >> SetStatus 
            spike_detector_GIDs [layer_index population_index]
            this_spike_detector put
	    /spike_detector_GIDs Set
	    /voltmeter Create /this_voltmeter Set
	    this_voltmeter
	    <<
	       /label voltmeter_label layer_index cvs join (_) join
               population_index cvs join
	       /to_file save_voltages
	    >> SetStatus
            voltmeter_GIDs [layer_index population_index] this_voltmeter put
	    /voltmeter_GIDs Set
            /poisson_generator Create /this_poisson_generator Set
            K_bg layer_index get population_index get /this_K_bg Set
	    this_poisson_generator
	    <<
	       /rate this_K_bg bg_rate mul
            >> SetStatus
            
            poisson_GIDs [layer_index population_index] this_poisson_generator
            put
	    /poisson_GIDs Set	    
            /dc_generator Create /this_dc_generator Set
	    this_dc_generator
	    <<
	       /amplitude this_K_bg dc_amplitude mul
	    >> SetStatus
            dc_GIDs [layer_index population_index] this_dc_generator put
	    /dc_GIDs Set
        } for % loop over e and i populations
    } for % loop over layers

    % create and configure thalamic neurons (parrots) and their Poisson inputs
    0 ChangeSubnet
    n_thal 0 gt
    {
        /subnet Create /th_subnet Set 
        th_subnet ChangeSubnet
	/subnet Create /th_neuron_subnet_GID Set
	th_neuron_subnet_GID ChangeSubnet
        /parrot_neuron n_thal Create ;
        th_subnet ChangeSubnet
	/subnet Create /th_device_subnet Set
	th_device_subnet ChangeSubnet
        /poisson_generator Create /th_poisson_GID Set
        th_poisson_GID 
        << 
           /rate th_rate 
           /start th_start 
           /stop th_start th_duration add 
        >> SetStatus
        record_thalamic_spikes
        {
            /spike_detector Create /th_spike_detector_GID Set
            th_spike_detector_GID
	    <<
	       % Set thalamic spike detector label. The GID of the spike
               % detector and the process number are appended automatically.
	       /label th_spike_detector_label
	       /to_file save_thalamic_spikes
	    >> SetStatus
        } if
    } if
} def


/WriteGIDstoFile
{
    run_mode (test) eq
    {
        GID_filename
    } if

    run_mode (production) eq
    {
        output_path (/) join GID_filename join
    } if

    (w) ofsopen
    ; /outputstream Set
    Rank 0 eq
    {
        outputstream 
    } if   
    neuron_subnet_GIDs Flatten
    {
        GetGlobalNodes /GIDs Set    
        Rank 0 eq
        {
            GIDs Min <- (\t) <- GIDs Max <- (\n) <-
	} if
    } forall
    Rank 0 eq
    {
        close
    } if
} def


/ConnectNetworkNodes
{
    tic

    0 1 n_layers 1 sub % target layer
    {
        /target_layer Set
        0 1 n_pops_per_layer 1 sub % target population
        {
	    /target_pop Set
            % get neuron IDs

            neuron_subnet_GIDs target_layer get target_pop get 
	    dup GetGlobalNodes /target_nodes Set
            GetLocalNodes /local_target_nodes Set	  

            /n_targets n_neurons target_layer get target_pop get def
            /full_scale_n_targets full_scale_n_neurons target_layer get
            target_pop get def
	  
	    0 1 n_layers 1 sub % source layer
	    {
	        /source_layer Set
	        0 1 n_pops_per_layer 1 sub % source population
	        {
		    /source_pop Set
		   
		    %%% local connections
		   
		    % get neuron IDs
                    neuron_subnet_GIDs source_layer get source_pop get
                    GetGlobalNodes 
                    /source_nodes Set
		   
                    /n_sources n_neurons source_layer get source_pop get def
                    /full_scale_n_sources full_scale_n_neurons
                    source_layer get source_pop get def

                    % get connection probability
		    conn_probs
		    % pick row (target) in conn_probs
		    target_layer n_pops_per_layer mul target_pop add get
		    % pick column (source) in conn_probs
		    source_layer n_pops_per_layer mul source_pop add get
		    /this_conn Set % probability for this connection

                    % Compute numbers of synapses assuming binomial degree
                    % distributions and allowing for multapses (see Potjans
                    % and Diesmann 2012 Cereb Cortex Eq. 1)
                    preserve_K
                    {
                        /prod full_scale_n_sources full_scale_n_targets mul def
                        ( log(1.-this_conn)/log((prod-1.)/prod) ) ExecMath 
                        /n_syn_temp Set
                        n_syn_temp n_targets mul full_scale_n_targets div
                    }{
                        /prod n_sources n_targets mul def
                        ( log(1.-this_conn)/log((prod-1.)/prod) ) ExecMath
                    } ifelse
                    cvi /this_n_synapses Set
                       
                    this_n_synapses 0 gt
                    {
                        PSC_array target_layer get target_pop get source_layer
                        get source_pop get /mean_weight Set

                        % Create label for target and source populations
                        /conn_label layers target_layer get (_) join
                                    populations target_pop get join (-) join
                                    layers source_layer get join (_) join
                                    populations source_pop get join
                                    def

                        % fill the weight dictionary for Connect and insert it into the synapse dictionary
                        mean_weight 0 gt
                        {
                          weight_dict_exc /mu mean_weight put
                          weight_dict_exc /sigma PSC_sd source_pop get abs put
                          syn_dict /weight weight_dict_exc put
                        }{
                          weight_dict_inh /mu mean_weight put
                          weight_dict_inh /sigma PSC_sd source_pop get abs put
                          syn_dict /weight weight_dict_inh put
                        } ifelse

			% fill the delay dictionary for Connect and insert it into the synapse dictionary
                        delay_dict /mu delays source_pop get put
                        delay_dict /sigma delays_sd source_pop get abs put
                        syn_dict /delay delay_dict put        

			% fill the connectivity dictionary with the number of synapses to be used
                        conn_dict /N this_n_synapses put
                        conn_dict /rule /fixed_total_number put

                        % Connect the populations
                        source_nodes
                        target_nodes
                        conn_dict
                        syn_dict
                        Connect

                    } if % this_n_synapses > 0
              
	        } for % source population
	    } for % source layer
	  
            n_thal 0 gt
            {
                % connections from thalamic neurons
            
                th_neuron_subnet_GID GetGlobalNodes 
                /source_nodes Set
            
                C_th target_layer get target_pop get /this_conn Set
                 
                % Compute numbers of synapses assuming binomial degree
                % distributions and allowing for multapses (see Potjans and
                % Diesmann 2012 Cereb Cortex Eq. 1)
                preserve_K
                {
                    /prod n_thal full_scale_n_targets mul def
                    ( log(1.-this_conn)/log((prod-1.)/prod) ) ExecMath 
                    /n_syn_temp Set
                    n_syn_temp n_targets mul full_scale_n_targets div
                }{
                    /prod n_thal n_targets mul def
                    ( log(1.-this_conn)/log((prod-1.)/prod) ) ExecMath
                } ifelse
                cvi /this_n_synapses Set
       
                this_n_synapses 0 gt
                {                    

                    % create label for current target population
                    /th_conn_label layers target_layer get (_) join
                                   populations target_pop get join
                                   def

                    % fill the weight dictionary for Connect
                    weight_dict_exc /mu PSC_ext put
                    weight_dict_exc /sigma PSC_th_sd abs put

                    % insert the weight dictionary into the synapse dictionary
                    syn_dict /weight weight_dict_exc put

                    % fill the delay dictionary for Connect
                    delay_dict /mu delay_th put
                    delay_dict /sigma delay_th_sd abs put

                    % insert the delay dictionary into the synapse dictionary
                    syn_dict /delay delay_dict put 

		    % fill the connectivity dictionary with the number of synapses to be used       
                    conn_dict /N this_n_synapses put
                    conn_dict /rule /fixed_total_number put


                    % Connect the thalamus
                    source_nodes
                    target_nodes
                    conn_dict
                    syn_dict
                    Connect                        

                } if % this_n_synapses > 0
            } if % n_thal > 0        

            % Connect devices

            % Connect to the spike detector
            target_nodes
            % record from a continuous range of IDs
            % (appropriate for networks without topology)
            n_neurons_rec_spikes target_layer get target_pop get
            Take
            spike_detector_GIDs target_layer get target_pop get
            1 arraystore
            /all_to_all
            Connect

            % Connect to the voltmeter
            voltmeter_GIDs target_layer get target_pop get
	        1 arraystore
            target_nodes
            % record from a continuous range of IDs
            n_neurons_rec_voltage target_layer get target_pop get
            Take
            /all_to_all
            Connect

            % Connect to the Poisson generators
            poisson_GIDs target_layer get target_pop get
	        1 arraystore
            target_nodes
            /all_to_all 
            << /weight PSC_ext /delay delays 0 get >>
            Connect            

            % Connect to the DC generators
            dc_GIDs target_layer get target_pop get
   	        1 arraystore
            target_nodes
            /all_to_all
            Connect

        } for % target population
    } for % target layer
    
    n_thal 0 gt
    {
        % Connect thalamic poisson_generator to thalamic neurons (parrots)
        th_poisson_GID
        1 arraystore
        th_neuron_subnet_GID GetGlobalNodes
        Connect  
    } if    

    record_thalamic_spikes n_thal 0 gt and
    { 
        % Connect thalamic neurons to spike detector
        th_neuron_subnet_GID GetGlobalNodes
        th_spike_detector_GID 
        1 arraystore
        Connect
    } if
    
    toc /ConnectTime Set
} def


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%     	Executive section		%%%        
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% load parameters
(user_params) run
(network_params) run

CheckParameters

PrepareSimulation

DerivedParameters

CreateNetworkNodes

WriteGIDstoFile

ConnectNetworkNodes

tic
t_sim Simulate
toc /SimTime Set

Rank 0 eq
{ 
    (Connecting took ) ConnectTime cvs join ( s\n) join
    (Simulating took ) join SimTime cvs join ( s) join
    M_INFO
    message  
} if

