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

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

change *.pro file properties (del eof-style, del executable, set keywords Id

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