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

/* 
    A library of SLI-routines operating on arrays

    Authors:
    R Kupper
    M-O Gewaltig
*/


/arr ($Revision: 9952 $) provide


/arr namespace


%----------------------------------------------------------------------------
/* BeginDocumentation
Name: arr::Sum - Return sum of the elements in array.

Synopsis: [array] Sum -> number

Description:
Returns the sum of all elements in the array. The array is expected
  to contain only numbers, and is flattened before the computation.

Parameters:
[array]: array of numbers

Examples:
[1 2 3 4]   Sum -> 10
[1 2 3 4.0] Sum -> 10.0

Author: R Kupper

FirstVersion: 17-sep-2007

Remarks:
The array must contain no other elements than numbers.
The return type (integer or double) depends on the type of elements.

Availability: library "arr"

SeeAlso: arr::Product, arr::Mean, arr::SDev, arr::Var
*/

/Sum[/arraytype]
{
  Flatten arrayload 1 sub {add} repeat
} bind def

%----------------------------------------------------------------------------
/* BeginDocumentation
Name: arr::Product - Return product of the elements in array.

Synopsis: [array] Product -> number

Description:
Returns the product of all elements in the array. The array is expected
  to contain only numbers, and is flattened before the computation.

Parameters:
[array]: array of numbers

Examples:
[1 2 3 4]   Product -> 24
[1 2 3 4.0] Product -> 24.0

Author: R Kupper

FirstVersion: 17-sep-2007

Remarks:
The array must contain no other elements than numbers.
The return type (integer or double) depends on the type of elements.

Availability: library "arr"

SeeAlso: arr::Sum, arr::Mean, arr::SDev, arr::Var
*/

/Product[/arraytype]
{
  Flatten arrayload 1 sub {mul} repeat
} bind def


%----------------------------------------------------------------------------
/* BeginDocumentation
Name: arr::Mean - Return mean of the elements in array.

Synopsis: [array] Mean -> number

Description:
Returns the sum of all elements in the array. The array is expected
  to contain only numbers, and is flattened before the computation.

Parameters:
[array]: array of numbers

Examples:
[1 2 3 4] Mean -> 2.5

Author: R Kupper

FirstVersion: 17-sep-2007

Remarks:
The array must contain no other elements than numbers.
The return type is always double.

Availability: library "arr"

SeeAlso: arr::Product, arr::Sum, arr::SDev, arr::Var
*/

/Mean[/arraytype]
{
  Flatten size exch
  arrayload 1 sub {add} repeat
  cvd
  exch div  
} bind def


%----------------------------------------------------------------------------
/* BeginDocumentation
Name: arr::Var - Return variance of the elements in array.

Synopsis: [array] Var -> number

Description:
Returns the variance of all elements in the array. The array is expected
  to contain only numbers, and is flattened before the computation.

Parameters:
[array]: array of numbers

Examples:
[0 1 2]   Var -> 1.0
[1 2 3 4] Var -> 1.666667

Author: R Kupper

FirstVersion: 17-sep-2007

Remarks:
The array must contain no other elements than numbers.
The return type is always double.

Availability: library "arr"

SeeAlso: arr::Product, arr::Sum, arr::Mean, arr::SDev
*/

/Var[/arraytype]
{
  Flatten
  size exch
  
  dup Mean sub
  {sqr} Map Sum

  exch 1 sub div
} bind def


%----------------------------------------------------------------------------
/* BeginDocumentation
Name: arr::SDev - Return standard deviation of the elements in array.

Synopsis: [array] SDev -> number

Description:
Returns the standard deviation of all elements in the array. The array is expected
  to contain only numbers, and is flattened before the computation.

Parameters:
[array]: array of numbers

Examples:
[1 2 3 4] SDev -> 

Author: R Kupper

FirstVersion: 17-sep-2007

Remarks:
The array must contain no other elements than numbers.
The return type is always double.

Availability: library "arr"

SeeAlso: arr::Product, arr::Sum, arr::Mean, arr::Var
*/

/SDev[/arraytype]
{
  Var sqrt
} bind def




%----------------------------------------------------------------------------
/* BeginDocumentation

Name: arr::Reform - Reform the dimensions of a hyperrectengular array

Synopsis: [array] [dim1 dim2 ... dimn] Reform -> [reformedArray]

Description:
"Reform" changes the dimensionality i.e. the nesting of an array. The
result is a hyperrectangular array of the given dimensions. The order
of elements is not changed.

"Reform" relates to "Dimensions" in the following way:
Given a hyperrectangular array [a] of "Dimensions" [d], these
statements are true:

a           d Reform -> a
a Flatten   d Reform -> a
a dv Reform d Reform -> a, for any valid dimension vector [dv]

Diagnostics:
If the new dimensionality is not consistent with the numer of elements
in the array, /RangeCheck is raised.

Availability:
"Namespace"-dictionary "arr".

Author: Ruediger Kupper

FirstVersion: 10.3.2003

Remarks:
The new dimensionality must not change the number of elements in the
whole array, that is, dim1*...*dimn must equal the number of the
flattened source array.

References:
This routine is inspired by IDL's REFORM() routine.

SeeAlso: Dimensions, TensorRank, Flatten
*/


/Reform [/arraytype /arraytype]
{
  %stack:  [array] [dim1 dim2 ... dimn]
  
  % first do consistency check:
  dup Times
  %stack:  [array]  [dim1 dim2 ... dimn] product 
  2 pick Flatten size
  %stack:  [array]  [dim1 dim2 ... dimn] product [flatarray] size
  rolld neq
  {
    %stack:  [array]  [dim1 dim2 ... dimn] [flatarray]    
    M_ERROR (Reform) (New dimensions must not change the number of elements in the array.) message
    pop
    %stack:  [array]  [dim1 dim2 ... dimn]
    /Reform /RangeCheck raiseerror
  } if
  %stack:  [array]  [dim1 dim2 ... dimn] [flatarray]
  rolld pop exch  
  %stack:  [flatarray] [dim1 dim2 ... dimn]
  % having passed this check, we can discard the first (redundant) dimension:
  Rest  
  %stack:  [flatarray]  [dim2 ... dimn]  
  reverse {Partition} Fold
} bind def


%----------------------------------------------------------------------------
/* BeginDocumentation
Name: arr::getdeep - get an element from a nested container (array, dict, ...).

Synopsis:
(general form:)
nested_container index_array getdeep -> element

(specific forms:)
[nested_array]        [i1 i2 ... in]    getdeep -> element
{nested_procedure}    [i1 i2 ... in]    getdeep -> element
{nested_litprocedure} [i1 i2 ... in]    getdeep -> element
<<nested_dictionary>> [/n1 /n2 ... /nn] getdeep -> element

Description:
"Getdeep" resolves to a repeated call to "get". That is, it retrieves
a single element from a nested array. The call iterates as deep into
the nested structure as is indicated by the number of elements in the
index array.
In short: The call "a [i1 i2 ... in] getdeep" is identical to
"a i1 get i2 get ... in get".

Parameters:
"Getdeep" can be used on all containers that "get" can be used on.
Since strings cannot be nested, it does not make much sense for
strings, though.
The first argument is a (probably nested) container.
The second argument is a flat index array. For all array type containers,
this must be an array of integers. For nested dictionaries, this must
be an array of literals.
 The call iterates as deep into
the nested structure as is indicated by the number of elements in the
index array.

Examples:
[[1 2 3] [4 5 6] [7 8 9]] [0 2]   getdeep -> 3
[[1 2 3] [4 5 6] [7 8 9]] [1]     getdeep -> [4 5 6]
<< /a << /b 1  /c 2 >> >> [/a /c] getdeep -> 2

Diagnostics:
The number of elements in the index array must not exceed the number
of nested levels in the container. Otherwise, the nested call to get will
raise /ArgumentType.

The elements in the index array must be suited to index the container.
Otherwise, the nested call to get will raise /ArgumentType.

For array containers, the index at position i must be smaller than the
number of elements at level i in the container. Otherwise, the nested
call to get will raise /RangeCheck.

Author: R Kupper

FirstVersion: 19-jun-2006

Remarks:
There currently is no "putdeep" command, but this functionality is
provided by "put". See discussion thread on the nest developer list
from 19-jun-2006.

Availability: library "arr"

SeeAlso: put, get, Dimensions
*/

/getdeep[/anytype /arraytype]
{
  {get} forall
} bind def


%----------------------------------------------------------------------------
/* BeginDocumentation

Name: arr::EdgeTruncate - Truncate 2-d array indices at array edges

Synopsis: [2d-indices] height width EdgeTruncate -> [truncated-2d-indices]

Description:
This function iterates through the given array indices and checks if
they lie inside the bounds [0,height) and [0,width), respectively.

Indices are modified according to the following rules:

1. If both indices lie inside [0,height) and [0,width), respectively,
   they are left untouched.
2. If either the row index lies outside [0,height), or the column
   index lies outside [0,width), the respective index is replaced by "false".

Note that by NEST convention, for index pairs, the first index denotes
the row, and the second index denotes the column.

Dimensions of the index array are preserved.

Diagnostics:
The index array is expected to be a (nested) array of integer values
only. Code will break otherwise.

Availability:
"Namespace"-dictionary "arr".

Author: Ruediger Kupper

FirstVersion: 17.3.2003

Remarks:
The index array is expected to be a (nested) array of integer values only.

SeeAlso: arr::EdgeWrap, arr::EdgeClip, area2

*/

/EdgeTruncate [/arraytype /integertype /integertype]
{
  %stack:    [2d-indices] height width
  2 arraystore
  %stack:    [2d-indices] [height width]

  % we store array dimensions before operation and restore it afterwards:  
  exch dup Dimensions rollu
  
  %stack: [Dimensions] [height width] [2d-indices] 

  Flatten 2 Partition
  %stack: [Dimensions] [height width] [[y1 x1] [y2 x2] ... [yn xn]]


  { 
    %entry stack: [height width] [yi xi] 
    1 pick  2 arraystore
    %stack: [height width] [ [yi xi] [height width] ]

    {
      %entry stack: index maxvalue
      1 pick rollu
      %stack: index index maxvalue 
      geq_ii
      {
        pop
        false        
      }
      {
        %stack: index
        dup 0 lt_ii
        {
          pop
          false
        } if
      } ifelse
      %exit stack:  truncated_index      
    } MapThread

    %exit stack :  [height width] [yi_truncated xi_truncated]
  } Map
    
  %stack: [Dimensions] [height width] [[y1_truncated x1_truncated] [y2_truncated x2_truncated] ... [yn_truncated xn_truncated]]
  rolld Reform 
  %stack: [height width] [truncated-2d-indices-in-right-dimensions]
  exch pop
} bind def


%----------------------------------------------------------------------------
/* BeginDocumentation

Name: arr::EdgeClip - Clip 2-d array indices at array edges

Synopsis: [2d-indices] height width EdgeClip -> [clipped-2d-indices]

Description: 
This function iterates through the given array indices and checks if
they lie inside the specified range [0,height) and [0,width),
respectively.

Indices are modified according to the following rules:

1. If both indices lie inside [0,height) and [0,width), respectively,
   they are left untouched.
2. If the row    index lies below 0, this index is replaced by 0.
3. If the row    index lies above "height", this index is replaced by "height".
4. If the column index lies below 0, this index is replaced by 0.
5. If the column index lies above "width",  this index is replaced by "width".

Thus, the indices are effectively hard clipped to the array bounds;
that is, to the range [0,height), [0,width) respectively.

Note that by NEST convention, for index pairs, the first index denotes
the row, and the second index denotes the column.

Dimensions of the index array are preserved.

Diagnostics:
The index array is expected to be a (nested) array of integer values
only. Code will break otherwise.

Availability:
"Namespace"-dictionary "arr".

Author: Ruediger Kupper

FirstVersion: 17.3.2003

Remarks:
The index array is expected to be a (nested) array of integer values only.

SeeAlso: arr::EdgeWrap, arr::EdgeTruncate, area2

*/

/EdgeClip [/arraytype /integertype /integertype]
{
  %stack:    [2d-indices] height width
  2 arraystore
  %stack:    [2d-indices] [height width]
  {1 sub_ii} Map  
  %stack:    [2d-indices] [height-1 width-1]

  % we store array dimensions before operation and restore it afterwards:  
  exch dup Dimensions rollu
  
  %stack: [Dimensions] [height-1 width-1] [2d-indices] 

  Flatten 2 Partition
  %stack: [Dimensions] [height-1 width-1] [[y1 x1] [y2 x2] ... [yn xn]]


  { 
    %entry stack: [height-1 width-1] [yi xi] 
    1 pick  2 arraystore
    %stack: [height-1 width-1] [ [yi xi] [height-1 width-1] ]

    {
      %entry stack: index maxvalue
      min_i_i
      0 max_i_i
      %exit stack:  clipped_index      
    } MapThread

    %exit stack :  [height-1 width-1] [yi_clipped xi_clipped]
  } Map
    
  %stack: [Dimensions] [height-1 width-1] [[y1_clipped x1_clipped] [y2_clipped x2_clipped] ... [yn_clipped xn_clipped]]
  rolld Reform 
  %stack: [height-1 width-1] [clipped-2d-indices-in-right-dimensions]
  exch pop
} bind def


%----------------------------------------------------------------------------
/* BeginDocumentation

Name: arr::EdgeWrap - Wrap 2-d array indices around edges (toriodal)

Synopsis: [2d-indices] height width EdgeWrap -> [wrapped-2d-indices]

Description: 
This function iterates through the given array indices and checks if
they lie inside the specified range [0,height) and [0,width),
respectively.

Indices are modified according to the following rules:

1. If both indices lie inside [0,height) and [0,width), respectively,
   they are left untouched.
2. If the row    index lies outside [0,height) it is cyclicly wrapped
   around. That is, a suitable multiple of "height" is added or
   substracted, that makes the index fall inside [0,height).
3. If the column index lies outside [0,width) it is cyclicly wrapped
   around. That is, a suitable multiple of "width" is added or
   substracted, that makes the index fall inside [0,width).

Thus, the indices are effectively wrapped around the array edges;
that is, they are mapped onto a torus of dimensions height,width.

Note that by NEST convention, for index pairs, the first index denotes
the row, and the second index denotes the column.

Dimensions of the index array are preserved.

Diagnostics:
The index array is expected to be a (nested) array of integer values
only. Code will break otherwise.

Availability:
"Namespace"-dictionary "arr".

Author: Ruediger Kupper

FirstVersion: 14.3.2003 (Einstein's birthday)

Remarks:
The index array is expected to be a (nested) array of integer values only.

SeeAlso: arr::IndexWrap, arr::EdgeTruncate, arr::EdgeClip, area2

*/

/EdgeWrap [/arraytype /integertype /integertype]
{
  %stack:    [2d-indices] height width
  2 arraystore
  %stack:    [2d-indices] [height width]
    
  % we store array dimensions before operation and restore it afterwards:  
  exch dup Dimensions rollu
  
  %stack: [Dimensions] [height width] [2d-indices] 

  Flatten 2 Partition
  %stack: [Dimensions] [height width] [[y1 x1] [y2 x2] ... [yn xn]]


  { 
    %entry stack: [height width] [yi xi] 
    1 pick  2 arraystore
    %stack: [height width] [ [yi xi] [height width] ]

    {
      %entry stack: index maxvalue
      IndexWrap_i_i
      %exit stack:  wrapped_index      
    } MapThread

    %exit stack :  [height width] [yi_wrapped xi_wrapped]
  } Map
    
  %stack: [Dimensions] [height width] [[y1_wrapped x1_wrapped] [y2_wrapped x2_wrapped] ... [yn_wrapped xn_wrapped]]
  rolld Reform 
  %stack: [height width] [wrapped-2d-indices-in-right-dimensions]
  exch pop
} bind def


%----------------------------------------------------------------------------
/* BeginDocumentation

Name: arr::IndexWrap - project a cyclic index value onto interval [0,N).

Synopsis:  index N CyclicValue -> normindex

Description: 
  "IndexWrap" projects a cyclic integer index in the range (-oo,oo),
  of periodicy N, onto its norm interval [0,N).
    
  This function can be used to "wrap around" array indices in order to
  index an array

  Alternatives: Function IndexWrap_i_i (undocumented) -> behaviour and
  synopsis are the same, except that no warnings or error messages are
  thrown.

Parameters: 
  In : index: integer value in (-oo,oo).

       N: Peroidicity of the cyclic index.
          "index" is projected on the half-open interval [0,N).
          N must be positive (and different from 0).

  Out: The cyclic equivalent of the given index, regarding period N.

Diagnostics:
  N must be positive (and different from 0). If N <= 0, /RangeCheck is raised.
  Note that the variant IndexWrap_i_i does not do this check for
  efficiency, and will break or yield invalid results in this case.

Examples:
  -6 3 IndexWrap   -> 0        
  -5 3 IndexWrap   -> 1         
  -4 3 IndexWrap   -> 2        
  -3 3 IndexWrap   -> 0         
  -2 3 IndexWrap   -> 1         
  -1 3 IndexWrap   -> 2         
   0 3 IndexWrap   -> 0         
   1 3 IndexWrap   -> 1        
   2 3 IndexWrap   -> 2         
   3 3 IndexWrap   -> 0         
   4 3 IndexWrap   -> 1         
   5 3 IndexWrap   -> 2         
   6 3 IndexWrap   -> 0         

Availability:
"Namespace"-dictionary "arr".

Author: Ruediger Kupper

FirstVersion: 14.2.2003 (Einstein's birthday)

Remarks: 
  This function behaves like Mathematica's Mod function (which is
  different from the mathematical definition of MOD).

SeeAlso: arr::EdgeWrap, mod, CyclicValue
*/

/IndexWrap_i_i
{
  %stack:  index N
  % index might be negative, so we add a suitable multiple of N:
  % posindex = index + (abs(index)/N + 1)*N  with integer div.  

  exch 1 pick 1 pick abs_i 1 pick
  %stack:  N index N abs(index) N
  div_ii 1 add_ii mul_ii add_ii
  %stack:  N posindex
  % now that we know everything is positive, we can use mod:
  exch_ mod
} bind def
            
/IndexWrap [/integertype /integertype]
{
  dup_ 0 gt_ii 
  {
    IndexWrap_i_i
  }
  {
    M_ERROR (IndexWrap) (Period N must be positive (and different from 0).) message
    /IndexWrap /RangeCheck raiseerror
  }ifelse
} bind def  


/* BeginDocumentation
Name: arr::GaborPatch - Return a two-dimensional array with Gabor function.
Synopsis:
nrows ncols GaborPatch -> [[] .. []]
Parameters: 
nrows: number of rows of the result matrix.
ncols: number of columns of the result matrix.

Description:
This function returns a matrix of nrows by ncols, with the amplitudes
of a Gabor function, computed over the range of [x_min,x_max] by [y_min,y_max].
These and other parameters can be set by changing the function's options.

An orientation of 0 RAD results in a vertically oriented Gabor.
An orientation of Pi/2 results in an horizontally oriented Gabor.
Angles are measured counter clockwise from the positive x-axis.

The implementation follows the description given by 
N. Petkov and R. Kruizinga Biol.Cybern. 76, 83-97 (1997).

Note that GaborPatch automatically performs a coordinate transformation
from the mathematical x-y plane to the matrix row-column system. 

Options:
Options which determine the argument range of the Gabor patch.

x_min     doubletype  - smallest x coordinate value  [-2Pi]
x_max     doubletype  - largest  x coordinate value. [ 2Pi]
y_min     doubletype  - smallest y coordinate value. [-2Pi]
y_max     doubletype  - largest  y coordinate value. [ 2Pi]

Options which determine the Gabor function:

lambda      doubletype  - Wavelength of the Gabor in RAD. [ 2Pi]  
phase       doubletype  - Phase of the Gabor in RAD.      [ 0.0]
orientation doubletype  - Rotation of the Gabor in RAD    [ 0.0]
sigma       doubletype  - Width of the Gaussian envelope. [ Pi]
gamma       doubletype  - Spatial aspect ratio.           [ 1.0]

The options correspond to the status dictionary entries of the
gabor_device. Thus, it is possible to use the option dictionary
of the GaborPatch to set the properties of a gabor device.

Author: Marc-Oliver Gewaltig
References: Petkov N and Kruizinga P: Biol. Cybern. 76, 83-96 (1997)
SeeAlso: gabor_
*/
/GaborPatch
[/integertype /integertype]
{
  /arr::GaborPatch GetOptions
  begin
   x_min x_max
   y_min y_max 
   lambda orientation phase sigma gamma
  end
  gabor_ 
} def


%% initialization values correspond to those of the NEST
%% gabor_generator device (see file synod2/nest/gabor.cpp).
/arr::GaborPatch
<<
  /x_min -2.0 Pi mul
  /x_max  2.0 Pi mul
  /y_min -2.0 Pi mul
  /y_max  2.0 Pi mul
  /lambda 2.0 Pi mul
  /orientation 0.0
  /phase 0.0
  /sigma Pi
  /gamma 1.0
>> Options

/* BeginDocumentation
Name: arr::GaussPatch - Return a two-dimensional array with Gauss function.
Synopsis:
nrows ncols GaussPatch -> [[] .. []]
Parameters: 
nrows: number of rows of the result matrix.
ncols: number of columns of the result matrix.

Description:
This function returns a matrix of nrows by ncols, with the amplitudes
of a two dimensional Gauss function, computed over the range of 
[x_min,x_max] by [y_min,y_max].
These and other parameters can be set by changing the function's options.

If the aspect ratio gamma < 1, an orientation of 0 RAD results in 
a vertically oriented eliptic Gauss .
Angles are measured counter clockwise from the positive x-axis.

Note that GaussPatch automatically performs a coordinate transformation
from the mathematical x-y plane to the matrix row-column system. 

Options:
Options which determine the argument range of the Gauss patch.

x_min     doubletype  - smallest x coordinate value  [-3.0]
x_max     doubletype  - largest  x coordinate value. [ 3.0]
y_min     doubletype  - smallest y coordinate value. [-3.0]
y_max     doubletype  - largest  y coordinate value. [ 3.0]

Options which determine the Gaussian function:

orientation doubletype  - Rotation of the Gaussian in RAD [ 0.0]
sigma       doubletype  - Width of the Gaussian.          [ 1.0]
gamma       doubletype  - Spatial aspect ratio.           [ 1.0]

Normalization options:

Normalize   booltype    - Normalize the Gaussian to    [false]
                          integral 1.0 (before sampling
                          to output array)
NormalizeSum booltype   - After sampling the Gaussian, [false]
                          normalize the output array
                          to have total sum of 1.0

Author: Marc-Oliver Gewaltig

SeeAlso: gauss2d_
*/
/GaussPatch
[/integertype /integertype]
{
  /arr::GaussPatch GetOptions
  begin
   x_min x_max
   y_min y_max 
   orientation sigma gamma

   gauss2d_

   Normalize
    {%normalize the integral by a factor (sigma_x*sqrt(2Pi))*(sigma_y*sqrt(2Pi))
      % = sigma_x*sigma_y*2Pi
      % where sigma_x=sigma, sigma_y=sigma/gamma
      2 Pi mul  sigma mul  sigma gamma div mul  div  
    } if
   NormalizeSum
    {%normalize to total sum of array elements
     dup Flatten Plus div    
    } if

   end   
} def


/arr::GaussPatch
<<
  /x_min -3.0
  /x_max  3.0
  /y_min -3.0
  /y_max  3.0
  /orientation 0.0
  /sigma 1.0
  /gamma 1.0

  /Normalize    false
  /NormalizeSum false
>> Options


end % namespace arr


%% NOTE: There must be a carriage return after the last line, i.e., here: