source: trunk/SRC/Matrix/cmapply.pro @ 163

Last change on this file since 163 was 163, checked in by navarro, 18 years ago

header improvements : type of parameters and keywords, default values, spell checking + idldoc assistant (IDL online_help)

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