def ilc(components, instrument, data, patch_ids=None): """ Internal Linear Combination Parameters ---------- components: list or tuple of lists `Components` of the mixing matrix. They must have no free parameter. instrument: Object that provides the following as a key or an attribute. - **frequency** They can be anything that is convertible to a float numpy array. data: ndarray or MaskedArray Data vector to be separated. Shape ``(n_freq, ..., n_pix)``. ``...`` can be also absent. Values equal to hp.UNSEEN or, if MaskedArray, masked values are neglected during the component separation process. patch_ids: array It stores the id of the region over which the ILC weights are computed independently. It must be broadcast-compatible with data. Returns ------- result : dict It includes - **W**: *(ndarray)* - ILC weights for each component and possibly each patch. - **freq_cov**: *(ndarray)* - Empirical covariance for each patch - **s**: *(ndarray)* - Component maps Note ---- * During the component separation, a pixel is masked if at least one of its frequencies is masked. """ # Checks instrument = standardize_instrument(instrument) np.broadcast(data, patch_ids) n_freq = data.shape[0] assert len(instrument.frequency) == n_freq,\ "The number of frequencies does not match the number of maps provided" n_comp = len(components) # Prepare mask and set to zero all the frequencies in the masked pixels: # NOTE: mask are good pixels mask = ~_intersect_mask(data) mm = MixingMatrix(*components) A = mm.eval(instrument.frequency) data = data.T res = OptimizeResult() res.s = np.full(data.shape[:-1] + (n_comp, ), hp.UNSEEN) def ilc_patch(ids_i, i_patch): if not np.any(ids_i): return data_patch = data[ids_i] # data_patch is a copy (advanced indexing) cov = np.cov(data_patch.reshape(-1, n_freq).T) # Perform the inversion of the correlation instead of the covariance. # This allows to meaninfully invert covariances that have very noisy # channels. assert cov.ndim == 2 cov_regularizer = np.diag(cov)**0.5 * np.diag(cov)[:, np.newaxis]**0.5 correlation = cov / cov_regularizer try: inv_freq_cov = np.linalg.inv(correlation) / cov_regularizer except np.linalg.LinAlgError: np.set_printoptions(precision=2) logging.error( f"Empirical covariance matrix cannot be reliably inverted.\n" f"The domain that failed is {i_patch}.\n" f"Covariance matrix diagonal {np.diag(cov)}\n" f"Correlation matrix\n{correlation}") raise res.freq_cov[i_patch] = cov res.W[i_patch] = alg.W(A, inv_freq_cov) res.s[ids_i] = alg._mv(res.W[i_patch], data_patch) if patch_ids is None: res.freq_cov = np.full((n_freq, n_freq), hp.UNSEEN) res.W = np.full((n_comp, n_freq), hp.UNSEEN) ilc_patch(mask, np.s_[:]) else: n_id = patch_ids.max() + 1 res.freq_cov = np.full((n_id, n_freq, n_freq), hp.UNSEEN) res.W = np.full((n_id, n_comp, n_freq), hp.UNSEEN) patch_ids_bak = patch_ids.copy().T patch_ids_bak[~mask] = -1 for i in range(n_id): ids_i = np.where(patch_ids_bak == i) ilc_patch(ids_i, i) res.s = res.s.T res.components = mm.components return res
def harmonic_ilc_alm(components, instrument, alms, lbins=None, fsky=None): """ Internal Linear Combination of alms Parameters ---------- components: list or tuple of lists `Components` of the mixing matrix. They must have no free parameter. instrument: Object that provides the following as a key or an attribute. - **frequency** It can be anything that is convertible to a float numpy array. alms: ndarray Data vector to be separated. Shape ``(n_freq, ..., lm)``. ``...`` can be 1, 3 or absent. The ILC weights are computed independently for each of its indices. lbins: array It stores the edges of the bins that will have the same ILC weights. If a multipole is not in a bin but is the alms, an independent bin will be assigned to it fsky: array If provided the output power spectra are corrected for this factor Returns ------- result : dict It includes - **W**: *(ndarray)* - ILC weights for each component and possibly each index of the `...` dimension in the alms. - **s**: *(ndarray)* - Alms of the cleaned components - **cl_in**: *(ndarray)* - Spectra of the input alm - **cl_out**: *(ndarray)* - Spectra of the output alm - **fsky**: *(ndarray)* - The input fsky used to correct the cls """ cl_in = np.array([hp.alm2cl(alm) for alm in alms]) mm = MixingMatrix(*components) A = mm.eval(instrument.frequency) cov = _empirical_harmonic_covariance(alms) if lbins is not None: for lmin, lmax in zip(lbins[:-1], lbins[1:]): # Average the covariances in the bin lmax = min(lmax, cov.shape[-1]) dof = 2 * np.arange(lmin, lmax) + 1 cov[..., lmin:lmax] = ((dof / dof.sum() * cov[..., lmin:lmax]).sum(-1))[..., np.newaxis] cov = _regularized_inverse(cov.swapaxes(-1, -3)) ilc_filter = np.linalg.inv(A.T @ cov @ A) @ A.T @ cov del cov, dof res = OptimizeResult() res.s = _apply_harmonic_W(ilc_filter, alms) # Craft output cl_out = np.array([hp.alm2cl(alm) for alm in res.s]) res.cl_in = cl_in res.cl_out = cl_out if fsky: res.cl_in /= fsky res.cl_out /= fsky res.fsky = fsky res.W = ilc_filter return res