source: trunk/SRC/ToBeReviewed/MATRICE/cmapply.pro @ 114

Last change on this file since 114 was 114, checked in by smasson, 18 years ago

new compilation options (compile_opt idl2, strictarrsubs) in each routine

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 13.3 KB
Line 
1;+
2; NAME:
3;   CMAPPLY
4;
5; AUTHOR:
6;   Craig B. Markwardt, NASA/GSFC Code 662, Greenbelt, MD 20770
7;   craigm@lheamail.gsfc.nasa.gov
8;
9; PURPOSE:
10;   Applies a function to specified dimensions of an array
11;
12; MAJOR TOPICS:
13;   Arrays
14;
15; CALLING SEQUENCE:
16;   XX = CMAPPLY(OP, ARRAY, DIMS, [/DOUBLE], [TYPE=TYPE])
17;
18; DESCRIPTION:
19;   CMAPPLY will apply one of a few select functions to specified
20;   dimensions of an array.  Unlike some IDL functions, you *do* have
21;   a choice of which dimensions that are to be "collapsed" by this
22;   function.  Iterative loops are avoided where possible, for
23;   performance reasons.
24;
25;   The possible functions are:             (and number of loop iterations:)
26;     +     - Performs a sum (as in TOTAL)       number of collapsed dimensions
27;     AND   - Finds LOGICAL "AND" (not bitwise)  same
28;     OR    - Finds LOGICAL "OR"  (not bitwise)  same
29;     *     - Performs a product                 LOG_2[no. of collapsed elts.]
30;
31;     MIN   - Finds the minimum value            smaller of no. of collapsed
32;     MAX   - Finds the maximum value            or output elements
33;
34;     USER  - Applies user-defined function      no. of output elements
35;
36;
37;   It is possible to perform user-defined operations arrays using
38;   CMAPPLY.  The OP parameter is set to 'USER:FUNCTNAME', where
39;   FUNCTNAME is the name of a user-defined function.  The user
40;   defined function should be defined such that it accepts a single
41;   parameter, a vector, and returns a single scalar value.  Here is a
42;   prototype for the function definition:
43;
44;      FUNCTION FUNCTNAME, x, KEYWORD1=key1, ...
45;         scalar = ... function of x or keywords ...
46;         RETURN, scalar
47;      END
48;
49;   The function may accept keywords.  Keyword values are passed in to
50;   CMAPPLY through the FUNCTARGS keywords parameter, and passed to
51;   the user function via the _EXTRA mechanism.  Thus, while the
52;   definition of the user function is highly constrained in the
53;   number of positional parameters, there is absolute freedom in
54;   passing keyword parameters.
55;
56;   It's worth noting however, that the implementation of user-defined
57;   functions is not particularly optimized for speed.  Users are
58;   encouraged to implement their own array if the number of output
59;   elements is large.
60;
61;
62; INPUTS:
63;
64;   OP - The operation to perform, as a string.  May be upper or lower
65;        case.
66;
67;        If a user-defined operation is to be passed, then OP is of
68;        the form, 'USER:FUNCTNAME', where FUNCTNAME is the name of
69;        the user-defined function.
70;
71;   ARRAY - An array of values to be operated on.  Must not be of type
72;           STRING (7) or STRUCTURE (8).
73;
74; OPTIONAL INPUTS:
75;
76;   DIMS - An array of dimensions that are to be "collapsed", where
77;          the the first dimension starts with 1 (ie, same convention
78;          as IDL function TOTAL).  Whereas TOTAL only allows one
79;          dimension to be added, you can specify multiple dimensions
80;          to CMAPPLY.  Order does not matter, since all operations
81;          are associative and transitive.  NOTE: the dimensions refer
82;          to the *input* array, not the output array.  IDL allows a
83;          maximum of 8 dimensions.
84;          DEFAULT: 1 (ie, first dimension)
85;
86; KEYWORDS:
87;
88;   DOUBLE - Set this if you wish the internal computations to be done
89;            in double precision if necessary.  If ARRAY is double
90;            precision (real or complex) then DOUBLE=1 is implied.
91;            DEFAULT: not set
92;
93;   TYPE - Set this to the IDL code of the desired output type (refer
94;          to documentation of SIZE()).  Internal results will be
95;          rounded to the nearest integer if the output type is an
96;          integer type.
97;          DEFAULT: same is input type
98;
99;   FUNCTARGS - If OP is 'USER:...', then the contents of this keyword
100;               are passed to the user function using the _EXTRA
101;               mechanism.  This way you can pass additional data to
102;               your user-supplied function, via keywords, without
103;               using common blocks.
104;               DEFAULT: undefined (i.e., no keywords passed by _EXTRA)
105;
106; RETURN VALUE:
107;
108;   An array of the required TYPE, whose elements are the result of
109;   the requested operation.  Depending on the operation and number of
110;   elements in the input array, the result may be vulnerable to
111;   overflow or underflow.
112;
113; EXAMPLES:
114;   Shows how CMAPPLY can be used to total the second dimension of the
115;   array called IN.  This is equivalent to OUT = TOTAL(IN, 2)
116;
117;   IDL> IN  = INDGEN(5,5)
118;   IDL> OUT = CMAPPLY('+', IN, [2])
119;   IDL> HELP, OUT
120;   OUT             INT       = Array[5]
121;
122;   Second example.  Input is assumed to be an 5x100 array of 1's and
123;   0's indicating the status of 5 detectors at 100 points in time.
124;   The desired output is an array of 100 values, indicating whether
125;   all 5 detectors are on (=1) at one time.  Use the logical AND
126;   operation.
127;
128;   IDL> IN = detector_status             ; 5x100 array
129;   IDL> OUT = CMAPPLY('AND', IN, [1])    ; collapses 1st dimension
130;   IDL> HELP, OUT
131;   OUT             BYTE      = Array[100]
132;
133;   (note that MIN could also have been used in this particular case,
134;   although there would have been more loop iterations).
135;
136;   Third example.  Shows sum over first and third dimensions in an
137;   array with dimensions 4x4x4:
138;
139;   IDL> IN = INDGEN(4,4,4)
140;   IDL> OUT = CMAPPLY('+', IN, [1,3])
141;   IDL> PRINT, OUT
142;        408     472     536     600
143;
144;   Fourth example. A user-function (MEDIAN) is used:
145;
146;   IDL> IN = RANDOMN(SEED,10,10,5)
147;   IDL> OUT = CMAPPLY('USER:MEDIAN', IN, 3)
148;   IDL> HELP, OUT
149;   OUT             FLOAT     = Array[10, 10]
150;
151;   (OUT[i,j] is the median value of IN[i,j,*])
152;
153; MODIFICATION HISTORY:
154;   Mar 1998, Written, CM
155;   Changed usage message to not bomb, 24 Mar 2000, CM
156;   Signficant rewrite for *, MIN and MAX (inspired by Todd Clements
157;     <Todd_Clements@alumni.hmc.edu>); FOR loop indices are now type
158;     LONG; copying terms are liberalized, CM, 22, Aug 2000
159;   More efficient MAX/MIN (inspired by Alex Schuster), CM, 25 Jan
160;     2002
161;   Make new MAX/MIN actually work with 3d arrays, CM, 08 Feb 2002
162;   Add user-defined functions, ON_ERROR, CM, 09 Feb 2002
163;   Correct bug in MAX/MIN initialization of RESULT, CM, 05 Dec 2002
164;
165;  $Id$
166;
167;-
168; Copyright (C) 1998, 2000, 2002, Craig Markwardt
169; This software is provided as is without any warranty whatsoever.
170; Permission to use, copy, modify, and distribute modified or
171; unmodified copies is granted, provided this copyright and disclaimer
172; are included unchanged.
173;-
174
175;; Utility function, adapted from CMPRODUCT
176function cmapply_product, x
177;
178  compile_opt idl2, strictarrsubs
179;
180  sz = size(x)
181  n = sz[1]
182
183  while n GT 1 do begin
184      if (n mod 2) EQ 1 then x[0,*] = x[0,*] * x[n-1,*]
185      n2 = floor(n/2)
186      x = x[0:n2-1,*] * x[n2:*,*]
187      n = n2
188  endwhile
189  return, reform(x[0,*], /overwrite)
190end
191
192;; Utility function, used to collect collaped dimensions
193pro cmapply_redim, newarr, dimapply, dimkeep, nkeep, totcol, totkeep
194;
195  compile_opt idl2, strictarrsubs
196;
197  sz = size(newarr)
198  ;; First task: rearrange dimensions so that the dimensions
199  ;; that are "kept" (ie, uncollapsed) are at the back
200  dimkeep = where(histogram(dimapply,min=1,max=sz[0]) ne 1, nkeep)
201  if nkeep EQ 0 then return
202
203  newarr = transpose(temporary(newarr), [dimapply-1, dimkeep])
204  ;; totcol is the total number of collapsed elements
205  totcol = sz[dimapply[0]]
206  for i = 1, n_elements(dimapply)-1 do totcol = totcol * sz[dimapply[i]]
207  totkeep = sz[dimkeep[0]+1]
208  for i = 1, n_elements(dimkeep)-1 do totkeep = totkeep * sz[dimkeep[i]+1]
209
210  ;; this new array has two dimensions:
211  ;;   * the first, all elements that will be collapsed
212  ;;   * the second, all dimensions that will be preserved
213  ;; (the ordering is so that all elements to be collapsed are
214  ;;  adjacent in memory)
215  newarr = reform(newarr, [totcol, totkeep], /overwrite)
216end
217
218;; Main function
219function cmapply, op, array, dimapply, double=dbl, type=type, $
220                  functargs=functargs, nocatch=nocatch
221;
222  compile_opt idl2, strictarrsubs
223;
224
225  if n_params() LT 2 then begin
226      message, "USAGE: XX = CMAPPLY('OP',ARRAY,2)", /info
227      message, '       where OP is +, *, AND, OR, MIN, MAX', /info
228      return, -1L
229  endif
230  if NOT keyword_set(nocatch) then $
231    on_error, 2 $
232  else $
233    on_error, 0
234
235  ;; Parameter checking
236  ;; 1) the dimensions of the array
237  sz = size(array)
238  if sz[0] EQ 0 then $
239    message, 'ERROR: ARRAY must be an array!'
240
241  ;; 2) The type of the array
242  if sz[sz[0]+1] EQ 0 OR sz[sz[0]+1] EQ 7 OR sz[sz[0]+1] EQ 8 then $
243    message, 'ERROR: Cannot apply to UNDEFINED, STRING, or STRUCTURE'
244  if n_elements(type) EQ 0 then type = sz[sz[0]+1]
245
246  ;; 3) The type of the operation
247  szop = size(op)
248  if szop[szop[0]+1] NE 7 then $
249    message, 'ERROR: operation OP was not a string'
250
251  ;; 4) The dimensions to apply (default is to apply to first dim)
252  if n_params() EQ 2 then dimapply = 1
253  dimapply = [ dimapply ]
254  dimapply = dimapply[sort(dimapply)]   ; Sort in ascending order
255  napply = n_elements(dimapply)
256
257  ;; 5) Use double precision if requested or if needed
258  if n_elements(dbl) EQ 0 then begin
259      dbl=0
260      if type EQ 5 OR type EQ 9 then dbl=1
261  endif
262
263  newop = strupcase(op)
264  newarr = array
265  newarr = reform(newarr, sz[1:sz[0]], /overwrite)
266  case 1 of
267
268      ;; *** Addition
269      (newop EQ '+'): begin
270          for i = 0L, napply-1 do begin
271              newarr = total(temporary(newarr), dimapply[i]-i, double=dbl)
272          endfor
273      end
274
275      ;; *** Multiplication
276      (newop EQ '*'): begin ;; Multiplication (by summation of logarithms)
277          cmapply_redim, newarr, dimapply, dimkeep, nkeep, totcol, totkeep
278          if nkeep EQ 0 then begin
279              newarr = reform(newarr, n_elements(newarr), 1, /overwrite)
280              return, (cmapply_product(newarr))[0]
281          endif
282
283          result = cmapply_product(newarr)
284          result = reform(result, sz[dimkeep+1], /overwrite)
285          return, result
286      end
287
288      ;; *** LOGICAL AND or OR
289      ((newop EQ 'AND') OR (newop EQ 'OR')): begin
290          newarr = temporary(newarr) NE 0
291          totelt = 1L
292          for i = 0L, napply-1 do begin
293              newarr = total(temporary(newarr), dimapply[i]-i)
294              totelt = totelt * sz[dimapply[i]]
295          endfor
296          if newop EQ 'AND' then return, (round(newarr) EQ totelt)
297          if newop EQ 'OR'  then return, (round(newarr) NE 0)
298      end
299
300      ;; Operations requiring a little more attention over how to
301      ;; iterate
302      ((newop EQ 'MAX') OR (newop EQ 'MIN')): begin
303          cmapply_redim, newarr, dimapply, dimkeep, nkeep, totcol, totkeep
304          if nkeep EQ 0 then begin
305              if newop EQ 'MAX' then return, max(newarr)
306              if newop EQ 'MIN' then return, min(newarr)
307          endif
308         
309          ;; Next task: create result array
310          result = make_array(totkeep, type=type)
311         
312          ;; Now either iterate over the number of output elements, or
313          ;; the number of collapsed elements, whichever is smaller.
314          if totcol LT totkeep then begin
315              ;; Iterate over the number of collapsed elements
316              result[0] = reform(newarr[0,*],totkeep,/overwrite)
317              case newop of
318                  'MAX': for i = 1L, totcol-1 do $
319                    result[0] = result > newarr[i,*]
320                  'MIN': for i = 1L, totcol-1 do $
321                    result[0] = result < newarr[i,*]
322              endcase
323          endif else begin
324              ;; Iterate over the number of output elements
325              case newop of
326                  'MAX': for i = 0L, totkeep-1 do result[i] = max(newarr[*,i])
327                  'MIN': for i = 0L, totkeep-1 do result[i] = min(newarr[*,i])
328              endcase
329          endelse
330
331          result = reform(result, sz[dimkeep+1], /overwrite)
332          return, result
333      end
334
335      ;; User function
336      (strmid(newop,0,4) EQ 'USER'): begin
337          functname = strmid(newop,5)
338          if functname EQ '' then $
339            message, 'ERROR: '+newop+' is not a valid operation'
340
341          cmapply_redim, newarr, dimapply, dimkeep, nkeep, totcol, totkeep
342          if nkeep EQ 0 then begin
343              if n_elements(functargs) GT 0 then $
344                return, call_function(functname, newarr, _EXTRA=functargs)
345              return, call_function(functname, newarr)
346          endif
347         
348          ;; Next task: create result array
349          result = make_array(totkeep, type=type)
350         
351          ;; Iterate over the number of output elements
352          if n_elements(functargs) GT 0 then begin
353              for i = 0L, totkeep-1 do $
354                result[i] = call_function(functname, newarr[*,i], _EXTRA=functargs)
355          endif else begin
356              for i = 0L, totkeep-1 do $
357                result[i] = call_function(functname, newarr[*,i])
358          endelse
359
360          result = reform(result, sz[dimkeep+1], /overwrite)
361          return, result
362      end
363
364             
365  endcase
366
367  newsz = size(newarr)
368  if type EQ newsz[newsz[0]+1] then return, newarr
369
370  ;; Cast the result into the desired type, if necessary
371  castfns = ['UNDEF', 'BYTE', 'FIX', 'LONG', 'FLOAT', $
372             'DOUBLE', 'COMPLEX', 'UNDEF', 'UNDEF', 'DCOMPLEX' ]
373  if type GE 1 AND type LE 3 then $
374    return, call_function(castfns[type], round(newarr)) $
375  else $
376    return, call_function(castfns[type], newarr)
377end
378 
Note: See TracBrowser for help on using the repository browser.