1; -----------------------------------------------------------------------------
2;
3;  Copyright (C) 1997-2013  Krzysztof M. Gorski, Eric Hivon, Anthony J. Banday
4;
5;
6;
7;
8;
9;  This file is part of HEALPix.
10;
11;  HEALPix is free software; you can redistribute it and/or modify
12;  it under the terms of the GNU General Public License as published by
13;  the Free Software Foundation; either version 2 of the License, or
14;  (at your option) any later version.
15;
16;  HEALPix is distributed in the hope that it will be useful,
17;  but WITHOUT ANY WARRANTY; without even the implied warranty of
18;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19;  GNU General Public License for more details.
20;
21;  You should have received a copy of the GNU General Public License
22;  along with HEALPix; if not, write to the Free Software
23;  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
24;
25;  For more information about HEALPix see http://healpix.sourceforge.net
26;
27; -----------------------------------------------------------------------------
28pro remove_dipole, map, weight, $
29                   bad_data = bad_data, $
30                   gal_cut  = gal_cut, $
31                   coord_in = u_coord_in, $
32                   coord_out= u_coord_out, $
33                   covariance_matrix = covariance, $
34                   dipole   = dipole, $
35                   monopole = monopole, $
36                   noremove = noremove, $
37                   nside    = nside_usr, $
38                   onlymonopole=onlymonopole, $
39                   ordering= ordering, $
40                   pixel   = pixel, $
41                   units   = units, $
42                   help    = help, $
43                   silent  = silent
44;+
45; NAME:
46;   remove_dipole
47;
48; PURPOSE:
49;   remove best fit monopole and dipole (simultaneous fit)
50;
51; CATEGORY:
52;   Healpix map processing
53;
54; CALLING SEQUENCE:
55;  remove_dipole, map, [weight, BAD_DATA=, GAL_CUT=, COORD_IN=, COORD_OUT=, $
56;             COVARIANCE_MATRIX=, DIPOLE=, MONOPOLE=, $
57;             NOREMOVE=, NSIDE=, ONLYMONOPOLE=, ORDERING=, PIXEL=, UNITS=, $
58;             HELP=, SILENT=]
59;
60; INPUTS:
61;   map : array on which monopole and dipole are to be removed
62;      (also used for output)
63;      assumed to be a full sky data set, unless pixel is set and has the same
64;      size as map
65;
66; OPTIONAL INPUTS:
67;   weight : array of same size as map,
68;     describe weighting to apply to each pixel for the fit
69;
70; KEYWORD PARAMETERS:
71;   BAD_DATA : scalar float, value given on input to bad pixels
72;         default = !healpix.bad_value = -1.63750000e+30
73;   GAL_CUT : the pixels with galactic latitude |b|<gal_cut are not considered in the
74;        fit. In Degrees, in [0, 90]
75;   COORD_IN : coordinate system (either 'Q' or 'C' or 'q' or 'c' equatorial,
76;        'G'/'g' galactic or 'E'/'e' ecliptic) of the input map
77;         default = 'G' (galactic)
78;   COORD_OUT : coordinate system in which to output dipole vector in Dipole
79;         default = same as coord_in
80;   NOREMOVE : if set, the dipole and monopole are computed but not removed
81;   NSIDE : scalar integer, healpix resolution parameter
82;   ONLYMONOPOLE : fit and remove only the monopole
83;   ORDERING : string, describe pixelisation (either 'RING' or 'NESTED')
84;   PIXEL : vector, gives the actual list of pixels whose temperature is given in map
85;         useful in case of very limited sky coverage
86;   UNITS : units of the input map
87;   HELP:   displays this information header
88;   SILENT: works silently (except for error messages)
89;
90; OUTPUTS:
91;   map : contains the map minus monopole and dipole
92;    unless NOREMOVE is set
93;
94; OPTIONAL OUTPUTS:
95;   COVARIANCE_MATRIX: scalar (or symmetric 4x4 matrix) containing the covariance
96;     of the statistical errors made on monopole (and dipole) determination
97;   DIPOLE : 3-vector : amplitude and direction of dipole
98;   MONOPOLE : amplitude of monopole, same units as map
99;
100; SIDE EFFECTS:
101;
102;
103;
104; PROCEDURE:
105;   least square fit
106;   directly hacked from A. J. Banday's Dipole
107;   'optimized' to reduce memory requirement
108;
109; EXAMPLE:
110;
111;
112;
113; MODIFICATION HISTORY:
114;      2000-02-16, EH, Caltech, 1.0
115;      2002-08-16, EH, Caltech, 1.1 cosmetic editions
116;      2006-06-23, EH, IAP: total() -> total(,/double) for improved accuracy
117;      2008-08-21, EH, IAP: accept Nside>8192; added /HELP keyword; slight speed
118;      increase
119;      2009-10-30, EH, IAP: replaced obsolete SVD with SVDC (+ SVSOL)
120;                           monopole and dipole now output in double precision
121;      2009-12-11, EH, IAP: COORD_IN and COORD_OUT can be upper or lower case
122;                           add SILENT keyword
123;      2010-03-31, EH, IAP: compute covariance matrix;
124;                           issue error message if map is not 1-dimensional.
125;-
126
127defsysv, '!healpix', exists = exists
128if (exists ne 1) then init_healpix
129
130syntax = 'remove_dipole, map, [weight, bad_data=, gal_cut=, covariance_matrix=, coord_in=, coord_out=, dipole=, monopole=, noremove=, nside=, onlymonopole=, ordering=, pixel=, units=, help=, silent=]'
131
132code = 'remove_dipole'
133if keyword_set(help) then begin
134    doc_library,code
135    return
136endif
137
138if n_params() lt 1 or n_params() gt 2 then begin
139    print,syntax
140    if n_params() gt 2 then message,'Abort' else return
141endif
142be_verbose = ~keyword_set(silent)
143loadsky
144
145obs_npix = n_elements(map)
146obs_npix2 = n_elements(weight)
147obs_npix3 = n_elements(pixel)
148if undefined(bad_data) then bad_data = !healpix.bad_value ;-1.63750000e+30
149sz = size(map)
150
151if (sz[0] gt 1) then begin
152    message,syntax,/info
153    message,/info,'Current input map has '+strtrim(sz[0],2)+' dimensions'
154    message,'Only 1 D maps are accepted.'
155endif
156
157dow8 = 0
158if (obs_npix2 gt 1) then begin
159    if (obs_npix2 ne obs_npix) then begin
160        message,syntax,/info
161        message,'inconsistent map and weight'
162    endif else begin
163        dow8 = 1
164    endelse
165endif
166
167partial = 0
168if (obs_npix3 gt 1) then begin
169    if (obs_npix3 ne obs_npix) then begin
170        message,syntax,/info
171        message,'inconsistent map and pixel'
172    endif else begin
173        partial = 1
174    endelse
175endif
176
177galcut = 0
178scut = ' '
179if defined(gal_cut) then begin
180    if (gal_cut) gt 0. and gal_cut lt 90 then begin
181        galcut = 1
182        zcut = sin(gal_cut*!dtor)
183        sgal_cut = '|b| > '+string(gal_cut,form='(f7.3)')+' Deg'
184    endif
185endif
186coord_in = defined(u_coord_in) ? strmid(strtrim(strupcase(u_coord_in),2),0,1) : 'G'
187if coord_in  EQ 'C' then coord_in =  'Q'
188fullcoord = decode_coord(coord_in,error=error_coord1)
189
190coord_out = defined(u_coord_out) ? strmid(strtrim(strupcase(u_coord_out),2),0,1) : coord_in
191if coord_out  EQ 'C' then coord_out =  'Q'
192fullcoord_out = decode_coord(coord_out,error=error_coord2)
193
194if (error_coord1+error_coord2) ne 0 then begin
195    message,syntax,/info
196    message,'invalid choice of coordinate'
197endif
198
199if undefined(units) then units = ' '
200
201dodipole = 1
202Vector = dblarr(4)
203Matrix = dblarr(4,4)
204if keyword_set(onlymonopole) then begin
205    dipole_out = 0
206    dodipole = 0
207    Vector = [0.d0]
208    Matrix = [0.d0]
209endif
210if (dodipole or galcut) then begin
211    if undefined(nside_usr) or undefined(ordering) then begin
212        print,code+': Nside and Ordering have to be known when '+$
213          'removing dipole '
214        return
215    endif
216endif
217if defined(ordering)  then pix_type = STRUPCASE(STRMID(ordering,0,1))
218healpix_data = (pix_type eq 'R' || pix_type eq 'N')
219if defined(nside_usr) then begin
220    pix_param = nside_usr
221    if (healpix_data && obs_npix gt nside2npix(nside_usr)) then begin
222        message,syntax,/info
223        message,'Inconsistent inputs:',/info
224        message,' Map size: '+strtrim(obs_npix,2),/info
225        message,' Nside= '+strtrim(nside_usr,2)+' -> Npix= '+strtrim(nside2npix(nside_usr),2),/info
226        message,'Map is too large.'
227    endif
228endif
229
230npiece = 24
231if (~dodipole && ~galcut) then npiece = 12
232stride = obs_npix/npiece > 1
233
234
235;-------------- mean square equation ------------------------------
236;-------------- construct matrix and vector ------------------------
237; do a loop to minimize arrays size
238for is = 0,npiece do begin
239    imin = is*stride
240    imax = (imin+stride-1) < (obs_npix-1)
241    if (imin gt imax) then goto, enough
242    map_tmp = map[imin:imax]
243    ; replace NaN by bad_data to allow calculations
244    nan = where(finite(map_tmp,/nan), nnan)
245    if (nnan gt 0) then map_tmp[nan] = bad_data
246    nan = 0
247
248    ; pixel weight
249    flag = (abs(map_tmp/bad_data-1.) gt .01) ; byte array, either 1 for good pixels or 0 for bad ones
250    if dow8 then flag = flag*weight[imin:imax]
251
252    if (dodipole or galcut) then begin
253        ; compute pixel position from pixel number
254        if partial then begin
255            id_pix = pixel[imin:imax]
256        endif else begin
257            id_pix = (pix_param gt 8192) ? lindgen64(imax-imin+1)+imin : lindgen(imax-imin+1)+imin
258        endelse
259        case pix_type of
260            'R' : PIX2VEC_RING, pix_param, id_pix, vec ; Healpix ring
261            'N' : PIX2VEC_NEST, pix_param, id_pix, vec ; Healpix nest
262            'Q' : vec = PIX2UV(id_pix, pix_param) ; QuadCube (COBE cgis software)
263            else : print,code+': error on pix_type'
264        endcase
265        if (galcut) then begin
266            if coord_in ne 'G' then vecg = SKYCONV(vec, inco = coord_in, outco =  'G') else vecg=vec
267            flagg = abs(vecg[*,2]) gt zcut ; 1 for pixels outside galactic cut, 0 otherwise
268            flag = flag*flagg
269            vecg = 0
270        endif
271    endif
272
273    if dodipole then begin
274        temp = map_tmp*flag
275        Vector[0] += total(temp, /double)
276        Vector[1] += total(temp*vec[*,0], /double)
277        Vector[2] += total(temp*vec[*,1], /double)
278        Vector[3] += total(temp*vec[*,2], /double)
279    endif else begin
280        Vector[0] += total(map_tmp*flag, /double)
281    endelse
282    Matrix[0,0] += TOTAL(flag, /double)
283    if dodipole then begin
284        for i = 1,3 do begin
285            Matrix[i,0] += TOTAL(vec[*,i-1]*flag, /double)
286            for j = 1,i do begin ; only fill one half of symmetric matrix
287                Matrix[i,j] += TOTAL(vec[*,i-1]*vec[*,j-1]*flag, /double)
288            endfor
289        endfor
290    endif
291endfor
292enough:
293
294do_cov = arg_present(covariance)
295
296if (be_verbose) then print,'Best fit: '+scut
297if ~dodipole then begin
298    monopole = Vector[0]/Matrix[0]
299    if (be_verbose) then print,'Monopole ['+units+']: ',monopole
300    covariance = 1.d0/Matrix[0]
301endif else begin
302    ; prepare identity matrix for computation of covariance matrix
303    if (do_cov) then begin
304        covariance = dblarr(4,4)
305        if (is_gdl()) then begin
306            identity = dblarr(4,4)
307            for i=0,3 do identity[i,i] = 1.d0
308        endif else begin
309            identity = diag_matrix(replicate(1.d0,4))
310        endelse
311    endif
312
313    ; fill other half of matrix by symmetry
314    for i=1,3 do Matrix[0:i-1,i] = Matrix[i,0:i-1]
315
316    ; check matrix diagonal
317    err_on_mat = abs((Matrix[1,1]+Matrix[2,2]+Matrix[3,3])/Matrix[0,0]-1.d0)
318    if (err_on_mat gt 1.d-10) then begin
319        print, err_on_mat
320        message,'Error in matrix construction'
321    endif
322
323   ; use SVD to invert matrix M and solve, A is inverse of Single Value
324   ; Decomposition of M
325   ; 2015-06-30: corrected substitute for SVSOL in GDL case (only makes a difference for non-symmetric matrix)
326    ndim = 4
327    SVDC, Matrix, w, U, V, /double ; w contains (positive) eigenvalues
328    w_threshold = max(abs(w)) * 1.0d-06
329    if (is_gdl()) then begin ; GDL: no SVSOL, no DIAG_MATRIX
330        nw = n_elements(w)
331        Wp = dblarr(nw,nw)
332        large = where(abs(w) GT w_threshold, count) ; find large eigenvalues
333        if (count gt 0) then Wp[large, large] = 1.d0/w[large] ; only invert those
334        ;;;;;;;;C = transpose(U) # ( Wp # (V # Vector))
335        C = transpose(V) # ( Wp # (U # Vector)) ; should be faster than expected (U ## Wp ## Transpose(V)) # Vector
336        if (do_cov) then begin
337            ;;;;;;for i=0,3 do covariance[*,i] = transpose(U) # ( Wp # (V # identity[*,i]))
338            for i=0,ndim-1 do covariance[*,i] = transpose(V) # ( Wp # (U # identity[*,i]))
339        endif
340    endif else begin
341        w = w * ( abs(w) gt w_threshold) ; set low eigenvalues to zero
342        C = SVSOL( U, w, V, Vector, /double)
343        if (do_cov) then begin
344            for i=0,ndim-1 do covariance[*,i] = SVSOL( U, w, V, identity[*,i], /double)
345        endif
346    endelse
347    ; force symmetry of covariance matrix
348    if (do_cov) then begin
349        covariance = (covariance + transpose(covariance))*0.5d0
350    endif
351
352    ;;;C = float(C)
353    Monopole = C[0]
354    Dipole = C[1:3]
355
356    if (be_verbose) then print,'Monopole ['+units+']: ',monopole
357    dip_ampli = sqrt(total(dipole^2, /double))
358    if (be_verbose) then print,'Dipole : amplitude: ',dip_ampli
359    vec2ang,dipole,lat,long,/astro
360    if (be_verbose) then print,fullcoord+' coordinates [Deg]: ',long, lat,form='(a,f7.2,f7.2)'
361    dipole_out = dipole
362    if (coord_in ne coord_out) then begin
363        dipole_out = SKYCONV(dipole, inco = coord_in, outco =  coord_out)
364        vec2ang,dipole_out,lat,long,/astro
365        if (be_verbose) then print,fullcoord_out+' coordinates [Deg]: ',long, lat,form='(a,f7.2,f7.2)'
366    endif
367endelse
368
369; correct map for monopole and dipole
370
371if ~keyword_set(noremove) then begin
372
373; do a loop to minimize arrays size
374    for is=0,npiece do begin
375        imin = is*stride
376        imax = (imin+stride-1) < (obs_npix-1)
377        if (imin gt imax) then goto, done
378        map_tmp = map[imin:imax]
379
380        ; replace NaN by bad_data to allow calculations
381        nan = where(finite(map_tmp,/nan), nnan)
382        if (nnan gt 0) then map_tmp[nan] = bad_data
383        nan = 0
384
385        good = imin + where( abs(map_tmp/bad_data-1.) gt .01, ngood )
386
387        if (ngood gt 0) then begin
388            if (dodipole) then begin
389           ; compute pixel position from pixel number
390                if partial then id_pix = pixel[good] $
391                           else id_pix = good
392                case pix_type of
393                    'R' : PIX2VEC_RING, pix_param, id_pix, vec ; Healpix ring
394                    'N' : PIX2VEC_NEST, pix_param, id_pix, vec ; Healpix nest
395                    'Q' : vec = PIX2UV(id_pix, pix_param) ; QuadCube (COBE cgis software)
396                    else : print,code+': error on pix_type'
397                endcase
398                map[good] = map[good] - (monopole + dipole ## vec )
399            endif else begin
400                map[good] = map[good] - monopole
401            endelse
402        endif
403
404    endfor
405done:
406endif
407
408dipole = dipole_out
409
410return
411end
412
413