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


/* BeginDocumentation
Name: testsuite::test_multimeter_accu - test support for multimeter accumulation mode

Synopsis: (test_multimeter_accu.sli) run -> dies if assertion fails

Description:
1. Test that all recording flags incompatible with /to_accumulator are set to false
2. Simulate iaf_cond_alpha net with poisson input and record detailed and accumulated
   potentials, compare.
3. Run network for different thread numbers to check accumulation across threads.

Author: Plesser
FirstVersion: 2011-02-11
*/

% The following test needs the model iaf_cond_alpha, so
% this test should only run if we have GSL
statusdict/have_gsl :: not {statusdict/exitcodes/success :: quit_i} if

(unittest) run
/unittest using

M_ERROR setverbosity


/clear_error
{ 
  counttomark npop % pop all but mark
  errordict begin /newerror false def end
} def

% integer_data --- return array with only unique entries in ascending order
%                  data must be intergers
%           
/unique_ascending
{
  /d << >> def
  { cvs cvlit d exch 0 put } forall
  d keys { cvs cvi } Map Sort
}
def

% times data --- times and data have corresponding entries; entries in 
%                data with equalt times are summed.
/sum_by_times
{
  /data Set
  dup { cvs cvlit } Map /times Set
  unique_ascending { cvs cvlit } Map /utimes Set
  /res << >> def
  utimes { res exch 0 put } forall
  [ data times ] { /key Set res key get add res exch key exch put  } ScanThread
  [ ] utimes { res exch get append } forall
}
def


% first test:
% make sure that to_accumulator forces all conflicting options to false
{
  << >> begin
    /mm /multimeter << /to_accumulator true >> Create def
    /mms mm GetStatus def
    true [ /to_file /to_screen /to_memory /withgid /withweight ]
  { mms exch get not and } Fold    
  end
} assert_or_die

% second test:
% connect normal and accumulating multimeter, simulate, compare
{
  ResetKernel
  << >> begin
    /recvars [ /g_in ] def
    /N 100 def

        /nnet /subnet Create def
        nnet ChangeSubnet
	/iaf_cond_alpha N Create ;
        0 ChangeSubnet
        /nrns nnet GetGlobalLeaves def

        /multimeter << /record_from recvars /withgid false /withtime true 
                       /interval 0.7 /start 4.8 /stop 233.1 /time_in_steps true >> SetDefaults
        /mm /multimeter Create def	
        /ac /multimeter << /to_memory false /to_accumulator true >> Create def	
        [ mm ] nrns /all_to_all Connect
        [ ac ] nrns /all_to_all Connect

        /pg /poisson_generator << /rate 1000. >> Create def
        [ pg ] nrns /all_to_all << /model /static_synapse /delay 1.0 /weight  1.0 >> Connect
        [ pg ] nrns /all_to_all << /model /static_synapse /delay 1.0 /weight -1.0 >> Connect

        250. Simulate

        /detevs mm /events get def
        /dettimes detevs /times get cva def          
        /accevs ac /events get def	
        /acctimes accevs /times get cva def          
        
        recvars 
        { 
          /var Set
          % get data from individual recordings
	  detevs var get cva
          % compute mean for error estimate
          dup Mean /varmean Set
          % sum results for equal times
          dettimes exch sum_by_times

          % data from accumulated recording
          accevs var get cva

          % test difference relative to mean	  
          sub true exch { varmean div abs 1e-15 lt and } Fold 
	} Map 

        true exch { and } Fold 

        % check that times are equal  
        dettimes unique_ascending acctimes eq and  

  end
} assert_or_die


% third test:
% as second test, but with three threads (NB: thread number should NOT divide number of neurons)
% since we know from the previous test that accumulation works on a single thread, we can
% test against single-thread accumulated results. This saves us a lot of work in matching
% gids to threads.
/run_mma
{
  << >> begin
    /n_threads Set

    /N 100 def  % should not be divisible by thread number
  
    ResetKernel
    0 /num_processes get 1 eq assert % distributed setting not covered
    0 << /local_num_threads n_threads >> SetStatus

    % actual neurons, placed in subnet
    /nnet /subnet Create def
    nnet ChangeSubnet
    /iaf_cond_alpha N << /I_e 40. >> Create ;
    0 ChangeSubnet
    /nrns nnet GetGlobalLeaves def
    nrns { dup /global_id get 1 sub 30. N div mul -90. add 
               /foo << >> def foo exch /V_m exch put foo SetStatus } forall

    % multimeter for accumulated recording
    /ac /multimeter << /record_from [ /V_m ] /withgid false 
                       /to_memory false /to_accumulator true >> Create def	
    [ac] nrns Connect

    250. Simulate

    % obtain data 
    ac /events get /V_m get cva
  end
}
def

{
  << >> begin
    1 run_mma dup  % run with single thread
    Mean /mn Set   % store mean
    3 run_mma      % run with three threads

    sub mn div true exch { abs 1e-14 lt and } Fold
  end
} assert_or_die

endusing
