• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LSSTDESC / NaMaster / 9452592154

10 Jun 2024 05:07PM UTC coverage: 98.112%. Remained the same
9452592154

Pull #194

github

web-flow
Merge fdd152fd8 into 08d981f31
Pull Request #194: Tests for mac

1195 of 1218 relevant lines covered (98.11%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/pymaster/workspaces.py
1
from pymaster import nmtlib as lib
1✔
2
import pymaster.utils as ut
1✔
3
import numpy as np
1✔
4
import healpy as hp
1✔
5

6

7
class NmtWorkspace(object):
1✔
8
    """ :obj:`NmtWorkspace` objects are used to compute and store the
9
    mode-coupling matrix associated with an incomplete sky coverage,
10
    and used in the MASTER algorithm. When initialized, this object
11
    is practically empty. The information describing the coupling
12
    matrix must be computed or read from a file afterwards.
13
    """
14
    def __init__(self):
1✔
15
        self.wsp = None
1✔
16
        self.has_unbinned = False
1✔
17

18
    def __del__(self):
1✔
19
        if self.wsp is not None:
1✔
20
            if lib.workspace_free is not None:
1✔
21
                lib.workspace_free(self.wsp)
1✔
22
            self.wsp = None
1✔
23

24
    def check_unbinned(self):
1✔
25
        """ Raises an error if this workspace does not contain the
26
        unbinned mode-coupling matrix.
27
        """
28
        if not self.has_unbinned:
1✔
29
            raise ValueError("This workspace does not store the unbinned "
1✔
30
                             "mode-coupling matrix.")
31

32
    def read_from(self, fname, read_unbinned_MCM=True):
1✔
33
        """ Reads the contents of an :obj:`NmtWorkspace` object from a
34
        FITS file.
35

36
        Args:
37
            fname (:obj:`str`): Input file name.
38
            read_unbinned_MCM (:obj:`bool`): If ``False``, the unbinned
39
                mode-coupling matrix will not be read. This can save
40
                significant IO time.
41
        """
42
        if self.wsp is not None:
1✔
43
            lib.workspace_free(self.wsp)
1✔
44
            self.wsp = None
1✔
45
        self.wsp = lib.read_workspace(fname, int(read_unbinned_MCM))
1✔
46
        self.has_unbinned = read_unbinned_MCM
1✔
47

48
    def update_beams(self, beam1, beam2):
1✔
49
        """ Update beams associated with this mode-coupling matrix.
50
        This is significantly faster than recomputing the matrix from
51
        scratch.
52

53
        Args:
54
            beam1 (`array`): First beam, in the form of a 1D array
55
                with the beam sampled at all integer multipoles up
56
                to the maximum :math:`\\ell` with which this
57
                workspace was initialised.
58
            beam2 (`array`): Second beam.
59
        """
60
        b1arr = isinstance(beam1, (list, tuple, np.ndarray))
1✔
61
        b2arr = isinstance(beam2, (list, tuple, np.ndarray))
1✔
62
        if ((not b1arr) or (not b2arr)):
1✔
63
            raise ValueError("The new beams must be provided as arrays")
1✔
64

65
        lmax = self.wsp.lmax_fields
1✔
66
        if (len(beam1) <= lmax) or (len(beam2) <= lmax):
1✔
67
            raise ValueError("The new beams must go up to ell = %d" % lmax)
1✔
68
        lib.wsp_update_beams(self.wsp, beam1, beam2)
1✔
69

70
    def update_bins(self, bins):
1✔
71
        """ Update binning associated with this mode-coupling matrix.
72
        This is significantly faster than recomputing the matrix from
73
        scratch.
74

75
        Args:
76
            bins (:class:`~pymaster.bins.NmtBin`): New binning scheme.
77
        """
78
        if self.wsp is None:
1✔
79
            raise ValueError("Can't update bins without first computing "
1✔
80
                             "the mode-coupling matrix")
81
        if bins.bin is None:
1✔
82
            raise ValueError("Can't replace with uninitialized bins")
1✔
83
        lib.wsp_update_bins(self.wsp, bins.bin)
1✔
84

85
    def compute_coupling_matrix(self, fl1, fl2, bins, is_teb=False,
1✔
86
                                l_toeplitz=-1, l_exact=-1, dl_band=-1):
87
        """ Computes the mode-coupling matrix associated with the
88
        cross-power spectrum of two :class:`~pymaster.field.NmtField` s
89
        and an :class:`~pymaster.bins.NmtBin` binning scheme. Note that
90
        the mode-coupling matrix will only contain :math:`\\ell` s up
91
        to the maximum multipole included in the bandpowers, which should
92
        match the :math:`\\ell_{\\rm max}` of the fields as well.
93

94
        Args:
95
            fl1 (:class:`~pymaster.field.NmtField`): First field to
96
                correlate.
97
            fl2 (:class:`~pymaster.field.NmtField`): Second field to
98
                correlate.
99
            bin (:class:`~pymaster.bins.NmtBin`): Binning scheme.
100
            is_teb (:obj:`bool`): If ``True``, all mode-coupling matrices
101
                (0-0,0-s,s-s) will be computed at the same time. In this
102
                case, ``fl1`` must be a spin-0 field and ``fl2`` must be
103
                spin-s.
104
            l_toeplitz (:obj:`int`): If a positive number, the Toeplitz
105
                approximation described in `Louis et al. 2020
106
                <https://arxiv.org/abs/2010.14344>`_ will be used.
107
                In that case, this quantity corresponds to
108
                :math:`\\ell_{\\rm toeplitz}` in Fig. 3 of that paper.
109
            l_exact (:obj:`int`): If ``l_toeplitz>0``, it corresponds to
110
                :math:`\\ell_{\\rm exact}` in Fig. 3 of the paper.
111
                Ignored if ``l_toeplitz<=0``.
112
            dl_band (:obj:`int`): If ``l_toeplitz>0``, this quantity
113
                corresponds to :math:`\\Delta \\ell_{\\rm band}` in Fig.
114
                3 of the paper. Ignored if ``l_toeplitz<=0``.
115
        """
116
        if not fl1.is_compatible(fl2):
1✔
117
            raise ValueError("Fields have incompatible pixelizations.")
1✔
118
        if fl1.ainfo.lmax != bins.lmax:
1✔
119
            raise ValueError(f"Maximum multipoles in bins ({bins.lmax}) "
1✔
120
                             f"and fields ({fl1.ainfo.lmax}) "
121
                             "are not the same.")
122
        if self.wsp is not None:
1✔
123
            lib.workspace_free(self.wsp)
1✔
124
            self.wsp = None
1✔
125

126
        ut._toeplitz_sanity(l_toeplitz, l_exact, dl_band,
1✔
127
                            bins.bin.ell_max, fl1, fl2)
128

129
        # Get mask PCL
130
        alm1 = fl1.get_mask_alms()
1✔
131
        if fl2 is fl1:
1✔
132
            alm2 = alm1
1✔
133
        else:
134
            alm2 = fl2.get_mask_alms()
1✔
135
        pcl_mask = hp.alm2cl(alm1, alm2, lmax=fl1.ainfo_mask.lmax)
1✔
136
        self.wsp = lib.comp_coupling_matrix(
1✔
137
            int(fl1.spin), int(fl2.spin),
138
            int(fl1.ainfo.lmax), int(fl1.ainfo_mask.lmax),
139
            int(fl1.pure_e), int(fl1.pure_b), int(fl2.pure_e), int(fl2.pure_b),
140
            fl1.beam, fl2.beam, pcl_mask,
141
            bins.bin, int(is_teb), l_toeplitz, l_exact, dl_band)
142
        self.has_unbinned = True
1✔
143

144
    def write_to(self, fname):
1✔
145
        """ Writes the contents of an :obj:`NmtWorkspace` object
146
        to a FITS file.
147

148
        Args:
149
            fname (:obj:`str`): Output file name
150
        """
151
        if self.wsp is None:
1✔
152
            raise RuntimeError("Must initialize workspace before writing")
1✔
153
        self.check_unbinned()
1✔
154
        lib.write_workspace(self.wsp, "!"+fname)
1✔
155

156
    def get_coupling_matrix(self):
1✔
157
        """ Returns the currently stored mode-coupling matrix.
158

159
        Returns:
160
            (`array`): Mode-coupling matrix. The matrix will have shape
161
            ``(nrows,nrows)``, with ``nrows = n_cls * n_ells``, where
162
            ``n_cls`` is the number of power spectra (1, 2 or 4 for
163
            spin 0-0, spin 0-2 and spin 2-2 correlations), and
164
            ``n_ells = lmax + 1``, and ``lmax`` is the maximum multipole
165
            associated with this workspace. The assumed ordering of power
166
            spectra is such that the ``L``-th element of the ``i``-th power
167
            spectrum be stored with index ``L * n_cls + i``.
168
        """
169
        if self.wsp is None:
1✔
170
            raise RuntimeError("Must initialize workspace before "
1✔
171
                               "getting a MCM")
172
        self.check_unbinned()
1✔
173
        nrows = (self.wsp.lmax + 1) * self.wsp.ncls
1✔
174
        return lib.get_mcm(self.wsp, nrows * nrows).reshape([nrows, nrows])
1✔
175

176
    def update_coupling_matrix(self, new_matrix):
1✔
177
        """
178
        Updates the stored mode-coupling matrix. The new matrix
179
        (``new_matrix``) must have shape ``(nrows,nrows)``.
180
        See docstring of :meth:`~NmtWorkspace.get_coupling_matrix` for an
181
        explanation of the size and ordering of this matrix.
182

183
        Args:
184
            new_matrix (`array`): Matrix that will replace the mode-coupling
185
                matrix.
186
        """
187
        if self.wsp is None:
1✔
188
            raise RuntimeError("Must initialize workspace before updating MCM")
1✔
189
        self.check_unbinned()
1✔
190
        if len(new_matrix) != (self.wsp.lmax + 1) * self.wsp.ncls:
1✔
191
            raise ValueError("Input matrix has an inconsistent size. "
1✔
192
                             f"Expected {(self.wsp.lmax+1)*self.wsp.ncls}, "
193
                             f"but got {len(new_matrix)}.")
194
        lib.update_mcm(self.wsp, len(new_matrix), new_matrix.flatten())
1✔
195

196
    def couple_cell(self, cl_in):
1✔
197
        """ Convolves a set of input power spectra with a coupling matrix
198
        (see Eq. 9 of the NaMaster paper).
199

200
        Args:
201
            cl_in (`array`): Set of input power spectra. The number of power
202
                spectra must correspond to the spins of the two fields that
203
                this :obj:`NmtWorkspace` object was initialized with (i.e. 1
204
                for two spin-0 fields, 2 for one spin-0 field and one spin-s
205
                field, and 4 for two spin-s fields).
206

207
        Returns:
208
            (`array`): Mode-coupled power spectra.
209
        """
210
        if (len(cl_in) != self.wsp.ncls) or \
1✔
211
           (len(cl_in[0]) < self.wsp.lmax + 1):
212
            raise ValueError("Input power spectrum has wrong shape. "
1✔
213
                             f"Expected ({self.wsp.ncls}, {self.wsp.lmax+1}), "
214
                             f"bu got {cl_in.shape}.")
215
        self.check_unbinned()
1✔
216

217
        # Shorten C_ells if they're too long
218
        cl_in = np.array(cl_in)[:, :self.wsp.lmax+1]
1✔
219
        cl1d = lib.couple_cell_py(self.wsp, cl_in,
1✔
220
                                  self.wsp.ncls * (self.wsp.lmax + 1))
221
        clout = np.reshape(cl1d, [self.wsp.ncls, self.wsp.lmax + 1])
1✔
222
        return clout
1✔
223

224
    def decouple_cell(self, cl_in, cl_bias=None, cl_noise=None):
1✔
225
        """ Decouples a set of pseudo-:math:`C_\\ell` power spectra into a
226
        set of bandpowers by inverting the binned coupling matrix (se Eq.
227
        16 of the NaMaster paper).
228

229
        Args:
230
            cl_in (`array`): Set of input power spectra. The number of power
231
                spectra must correspond to the spins of the two fields that
232
                this :obj:`NmtWorkspace` object was initialized with (i.e. 1
233
                for two spin-0 fields, 2 for one spin-0 field and one spin-s
234
                field, 4 for two spin-s fields, and 7 if this
235
                :obj:`NmtWorkspace` was created using ``is_teb=True``).
236
            cl_bias (`array`): Bias to the power spectrum associated with
237
                contaminant residuals (optional). This can be computed through
238
                :func:`deprojection_bias`.
239
            cl_noise (`array`): Noise bias (i.e. angular
240
                pseudo-:math:`C_\\ell` of masked noise realizations).
241

242
        Returns:
243
            (`array`): Set of decoupled bandpowers.
244
        """
245
        if (len(cl_in) != self.wsp.ncls) or \
1✔
246
           (len(cl_in[0]) < self.wsp.lmax + 1):
247
            raise ValueError("Input power spectrum has wrong shape. "
1✔
248
                             f"Expected ({self.wsp.ncls}, {self.wsp.lmax+1}), "
249
                             f"but got {cl_in.shape}")
250
        if cl_bias is not None:
1✔
251
            if (len(cl_bias) != self.wsp.ncls) or \
1✔
252
               (len(cl_bias[0]) < self.wsp.lmax + 1):
253
                raise ValueError(
1✔
254
                    "Input bias power spectrum has wrong shape. "
255
                    f"Expected ({self.wsp.ncls}, {self.wsp.lmax+1}), "
256
                    f"but got {cl_bias.shape}")
257
            clb = cl_bias.copy()
1✔
258
        else:
259
            clb = np.zeros_like(cl_in)
1✔
260
        if cl_noise is not None:
1✔
261
            if (len(cl_noise) != self.wsp.ncls) or \
1✔
262
               (len(cl_noise[0]) < self.wsp.lmax + 1):
263
                raise ValueError(
1✔
264
                    "Input noise power spectrum has wrong shape. "
265
                    f"Expected ({self.wsp.ncls}, {self.wsp.lmax+1}), "
266
                    f"but got {cl_noise.shape}")
267
            cln = cl_noise.copy()
1✔
268
        else:
269
            cln = np.zeros_like(cl_in)
1✔
270

271
        cl1d = lib.decouple_cell_py(
1✔
272
            self.wsp, cl_in, cln, clb, self.wsp.ncls * self.wsp.bin.n_bands
273
        )
274
        clout = np.reshape(cl1d, [self.wsp.ncls, self.wsp.bin.n_bands])
1✔
275

276
        return clout
1✔
277

278
    def get_bandpower_windows(self):
1✔
279
        """ Get bandpower window functions. Convolve the theory power spectra
280
        with these as an alternative to the combination of function calls \
281
        ``w.decouple_cell(w.couple_cell(cls_theory))``. See Eqs. 18 and
282
        19 of the NaMaster paper.
283

284
        Returns:
285
            (`array`): Bandpower windows with shape \
286
                ``(n_cls, n_bpws, n_cls, lmax+1)``.
287
        """
288
        self.check_unbinned()
1✔
289
        d = lib.get_bandpower_windows(self.wsp,
1✔
290
                                      self.wsp.ncls * self.wsp.bin.n_bands *
291
                                      self.wsp.ncls * (self.wsp.lmax+1))
292
        return np.transpose(d.reshape([self.wsp.bin.n_bands,
1✔
293
                                       self.wsp.ncls,
294
                                       self.wsp.lmax+1,
295
                                       self.wsp.ncls]),
296
                            axes=[1, 0, 3, 2])
297

298

299
class NmtWorkspaceFlat(object):
1✔
300
    """ :obj:`NmtWorkspaceFlat` objects are used to compute and store the
301
    mode-coupling matrix associated with an incomplete sky coverage, and
302
    used in the flat-sky version of the MASTER algorithm. When initialized,
303
    this object is practically empty. The information describing the
304
    coupling matrix must be computed or read from a file afterwards.
305
    """
306
    def __init__(self):
1✔
307
        self.wsp = None
1✔
308

309
    def __del__(self):
1✔
310
        if self.wsp is not None:
1✔
311
            if lib.workspace_flat_free is not None:
1✔
312
                lib.workspace_flat_free(self.wsp)
1✔
313
            self.wsp = None
1✔
314

315
    def read_from(self, fname):
1✔
316
        """ Reads the contents of an :obj:`NmtWorkspaceFlat` object from a
317
        FITS file.
318

319
        Args:
320
            fname (:obj:`str`): Input file name.
321
        """
322
        if self.wsp is not None:
1✔
323
            lib.workspace_flat_free(self.wsp)
1✔
324
            self.wsp = None
1✔
325
        self.wsp = lib.read_workspace_flat(fname)
1✔
326

327
    def compute_coupling_matrix(self, fl1, fl2, bins, ell_cut_x=[1., -1.],
1✔
328
                                ell_cut_y=[1., -1.], is_teb=False):
329
        """ Computes mode-coupling matrix associated with the cross-power
330
        spectrum of two :class:`~pymaster.field.NmtFieldFlat` s and an
331
        :class:`~pymaster.bins.NmtBinFlat` binning scheme.
332

333
        Args:
334
            fl1 (:class:`~pymaster.field.NmtFieldFlat`): First field to
335
                correlate.
336
            fl2 (:class:`~pymaster.field.NmtFieldFlat`): Second field to
337
                correlate.
338
            bin (:class:`~pymaster.bins.NmtBinFlat`): Binning scheme.
339
            ell_cut_x (`array`): Sequence of two elements determining the
340
                range of :math:`l_x` to remove from the calculation. No
341
                Fourier modes removed by default.
342
            ell_cut_y (`array`): Sequence of two elements determining the
343
                range of :math:`l_y` to remove from the calculation. No
344
                Fourier modes removed by default.
345
            is_teb (:obj:`bool`): If ``True``, all mode-coupling matrices
346
                (0-0,0-s,s-s) will be computed at the same time. In this
347
                case, ``fl1`` must be a spin-0 field and ``fl2`` must be
348
                spin-s.
349
        """
350
        if self.wsp is not None:
1✔
351
            lib.workspace_flat_free(self.wsp)
1✔
352
            self.wsp = None
1✔
353

354
        self.wsp = lib.comp_coupling_matrix_flat(
1✔
355
            fl1.fl,
356
            fl2.fl,
357
            bins.bin,
358
            ell_cut_x[0],
359
            ell_cut_x[1],
360
            ell_cut_y[0],
361
            ell_cut_y[1],
362
            int(is_teb),
363
        )
364

365
    def write_to(self, fname):
1✔
366
        """ Writes the contents of an :obj:`NmtWorkspaceFlat` object
367
        to a FITS file.
368

369
        Args:
370
            fname (:obj:`str`): Output file name.
371
        """
372
        if self.wsp is None:
1✔
373
            raise RuntimeError("Must initialize workspace before "
1✔
374
                               "writing")
375
        lib.write_workspace_flat(self.wsp, "!"+fname)
1✔
376

377
    def couple_cell(self, ells, cl_in):
1✔
378
        """ Convolves a set of input power spectra with a coupling
379
        matrix (see Eq. 42 of the NaMaster paper).
380

381

382
        Args:
383
            ells (`array`): List of multipoles on which the input power
384
                spectra are defined.
385
            cl_in (`array`): Set of input power spectra. The number of power
386
                spectra must correspond to the spins of the two fields that
387
                this :obj:`NmtWorkspace` object was initialized with (i.e. 1
388
                for two spin-0 fields, 2 for one spin-0 field and one spin-s
389
                field, and 4 for two spin-s fields).
390

391
        Returns:
392
            (`array`): Mode-coupled power spectra. The coupled power spectra \
393
                are returned at the multipoles returned by calling \
394
                :meth:`~pymaster.field.NmtFieldFlat.get_ell_sampling` for \
395
                any of the fields that were used to generate the workspace.
396
        """
397
        if (len(cl_in) != self.wsp.ncls) or (len(cl_in[0]) != len(ells)):
1✔
398
            raise ValueError("Input power spectrum has wrong shape. "
1✔
399
                             f"Expected ({self.wsp.ncls}, {len(ells)}, "
400
                             f"but got {cl_in.shape}.")
401
        cl1d = lib.couple_cell_py_flat(
1✔
402
            self.wsp, ells, cl_in, self.wsp.ncls * self.wsp.bin.n_bands
403
        )
404
        clout = np.reshape(cl1d, [self.wsp.ncls, self.wsp.bin.n_bands])
1✔
405
        return clout
1✔
406

407
    def decouple_cell(self, cl_in, cl_bias=None, cl_noise=None):
1✔
408
        """ Decouples a set of pseudo-:math:`C_\\ell` power spectra into a
409
        set of bandpowers by inverting the binned coupling matrix (see
410
        Eq. 47 of the NaMaster paper).
411

412
        Args:
413
            cl_in (`array`): Set of input power spectra. The number of power
414
                spectra must correspond to the spins of the two fields that
415
                this :obj:`NmtWorkspace` object was initialized with (i.e. 1
416
                for two spin-0 fields, 2 for one spin-0 field and one spin-s
417
                field, 4 for two spin-s fields, and 7 if this
418
                :obj:`NmtWorkspace` was created using ``is_teb=True``). These
419
                power spectra must be defined at the multipoles returned by
420
                :meth:`~pymaster.field.NmtFieldFlat.get_ell_sampling` for
421
                any of the fields used to create the workspace.
422
            cl_bias (`array`): Bias to the power spectrum associated with
423
                contaminant residuals (optional). This can be computed through
424
                :func:`deprojection_bias_flat`.
425
            cl_noise (`array`): Noise bias (i.e. angular
426
                pseudo-:math:`C_\\ell` of masked noise realisations).
427

428
        Returns:
429
            (`array`): Set of decoupled bandpowers.
430
        """
431
        if (len(cl_in) != self.wsp.ncls) or \
1✔
432
           (len(cl_in[0]) != self.wsp.bin.n_bands):
433
            raise ValueError(
1✔
434
                "Input power spectrum has wrong shape. "
435
                f"Expected ({self.wsp.ncls}, {self.wsp.bin.n_bands}), "
436
                f"but got {cl_in.shape}")
437
        if cl_bias is not None:
1✔
438
            if (len(cl_bias) != self.wsp.ncls) or \
1✔
439
               (len(cl_bias[0]) != self.wsp.bin.n_bands):
440
                raise ValueError(
1✔
441
                    "Input bias power spectrum has wrong shape. "
442
                    f"Expected ({self.wsp.ncls}, {self.wsp.bin.n_bands}), "
443
                    f"but got {cl_bias.shape}.")
444
            clb = cl_bias.copy()
1✔
445
        else:
446
            clb = np.zeros_like(cl_in)
1✔
447
        if cl_noise is not None:
1✔
448
            if (len(cl_noise) != self.wsp.ncls) or \
1✔
449
               (len(cl_noise[0]) != self.wsp.bin.n_bands):
450
                raise ValueError(
1✔
451
                    "Input noise power spectrum has wrong shape. "
452
                    f"Expected ({self.wsp.ncls}, {self.wsp.bin.n_bands}), "
453
                    f"but got {cl_noise.shape}.")
454
            cln = cl_noise.copy()
1✔
455
        else:
456
            cln = np.zeros_like(cl_in)
1✔
457

458
        cl1d = lib.decouple_cell_py_flat(
1✔
459
            self.wsp, cl_in, cln, clb, self.wsp.ncls * self.wsp.bin.n_bands
460
        )
461
        clout = np.reshape(cl1d, [self.wsp.ncls, self.wsp.bin.n_bands])
1✔
462

463
        return clout
1✔
464

465

466
def deprojection_bias(f1, f2, cl_guess, n_iter=None):
1✔
467
    """ Computes the bias associated to contaminant removal to the
468
    cross-pseudo-:math:`C_\\ell` of two fields. See Eq. 26 in the NaMaster
469
    paper.
470

471
    Args:
472
        f1 (:class:`~pymaster.field.NmtField`): First field to correlate.
473
        f2 (:class:`~pymaster.field.NmtField`): Second field to correlate.
474
        cl_guess (`array`): Array of power spectra corresponding to a
475
            best-guess of the true power spectra of ``f1`` and ``f2``.
476
        n_iter (:obj:`int`): Number of iterations when computing
477
            :math:`a_{\\ell m}` s. See docstring of
478
            :class:`~pymaster.field.NmtField`.
479

480
    Returns:
481
        (`array`): Deprojection bias pseudo-:math:`C_\\ell`.
482
    """
483
    if n_iter is None:
1✔
484
        n_iter = ut.nmt_params.n_iter_default
1✔
485

486
    if not f1.is_compatible(f2):
1✔
487
        raise ValueError("Fields have incompatible pixelizations.")
1✔
488

489
    def purify_if_needed(fld, mp):
1✔
490
        if fld.pure_e or fld.pure_b:
1✔
491
            # Compute mask alms if needed
492
            amask = fld.get_mask_alms()
1✔
493
            return fld._purify(fld.mask, amask, mp,
1✔
494
                               n_iter=n_iter, return_maps=False,
495
                               task=[fld.pure_e, fld.pure_b])
496
        else:
497
            return ut.map2alm(mp*fld.mask[None, :], fld.spin,
1✔
498
                              fld.minfo, fld.ainfo, n_iter=n_iter)
499

500
    pcl_shape = (f1.nmaps * f2.nmaps, f1.ainfo.lmax+1)
1✔
501
    if cl_guess.shape != pcl_shape:
1✔
502
        raise ValueError(
1✔
503
            f"Guess Cl should have shape {pcl_shape}")
504
    clg = cl_guess.reshape([f1.nmaps, f2.nmaps, f1.ainfo.lmax+1])
1✔
505

506
    if f1.lite or f2.lite:
1✔
507
        raise ValueError("Can't compute deprojection bias for "
1✔
508
                         "lightweight fields")
509

510
    clb = np.zeros((f1.nmaps, f2.nmaps, f1.ainfo.lmax+1))
1✔
511

512
    # Compute ff part
513
    if f1.n_temp > 0:
1✔
514
        pcl_ff = np.zeros((f1.n_temp, f1.n_temp,
1✔
515
                           f1.nmaps, f2.nmaps,
516
                           f1.ainfo.lmax+1))
517
        for ij, tj in enumerate(f1.temp):
1✔
518
            # SHT(v*fj)
519
            ftild_j = ut.map2alm(tj*f1.mask[None, :], f1.spin,
1✔
520
                                 f1.minfo, f1.ainfo, n_iter=n_iter)
521
            # C^ba*SHT[v*fj]
522
            ftild_j = np.array([
1✔
523
                np.sum([hp.almxfl(ftild_j[m], clg[m, n],
524
                                  mmax=f1.ainfo.mmax)
525
                        for m in range(f1.nmaps)], axis=0)
526
                for n in range(f2.nmaps)])
527
            # SHT^-1[C^ba*SHT[v*fj]]
528
            ftild_j = ut.alm2map(ftild_j, f2.spin, f2.minfo,
1✔
529
                                 f2.ainfo)
530
            # SHT[w*SHT^-1[C^ba*SHT[v*fj]]]
531
            ftild_j = purify_if_needed(f2, ftild_j)
1✔
532
            for ii, f_i in enumerate(f1.alm_temp):
1✔
533
                clij = np.array([[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
1✔
534
                                  for a2 in ftild_j]
535
                                 for a1 in f_i])
536
                pcl_ff[ii, ij, :, :, :] = clij
1✔
537
        clb -= np.einsum('ij,ijklm', f1.iM, pcl_ff)
1✔
538

539
    # Compute gg part and fg part
540
    if f2.n_temp > 0:
1✔
541
        pcl_gg = np.zeros((f2.n_temp, f2.n_temp,
1✔
542
                           f1.nmaps, f2.nmaps,
543
                           f1.ainfo.lmax+1))
544
        if f1.n_temp > 0:
1✔
545
            prod_fg = np.zeros((f1.n_temp, f2.n_temp))
1✔
546
            pcl_fg = np.zeros((f1.n_temp, f2.n_temp,
1✔
547
                               f1.nmaps, f2.nmaps,
548
                               f1.ainfo.lmax+1))
549

550
        for ij, tj in enumerate(f2.temp):
1✔
551
            # SHT(w*gj)
552
            gtild_j = ut.map2alm(tj*f2.mask[None, :], f2.spin,
1✔
553
                                 f2.minfo, f2.ainfo, n_iter=n_iter)
554
            # C^ab*SHT[w*gj]
555
            gtild_j = np.array([
1✔
556
                np.sum([hp.almxfl(gtild_j[n], clg[m, n],
557
                                  mmax=f2.ainfo.mmax)
558
                        for n in range(f2.nmaps)], axis=0)
559
                for m in range(f1.nmaps)])
560
            # SHT^-1[C^ab*SHT[w*gj]]
561
            gtild_j = ut.alm2map(gtild_j, f1.spin, f1.minfo,
1✔
562
                                 f1.ainfo)
563
            if f1.n_temp > 0:
1✔
564
                # Int[f^i*v*SHT^-1[C^ab*SHT[w*gj]]]
565
                for ii, ti in enumerate(f1.temp):
1✔
566
                    prod_fg[ii, ij] = f1.minfo.si.dot_map(
1✔
567
                        ti, gtild_j*f1.mask[None, :])
568

569
            # SHT[v*SHT^-1[C^ab*SHT[w*gj]]]
570
            gtild_j = purify_if_needed(f1, gtild_j)
1✔
571

572
            # PCL[g_i, gtild_j]
573
            for ii, g_i in enumerate(f2.alm_temp):
1✔
574
                clij = np.array([[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
1✔
575
                                  for a2 in g_i]
576
                                 for a1 in gtild_j])
577
                pcl_gg[ii, ij, :, :, :] = clij
1✔
578

579
        clb -= np.einsum('ij,ijklm', f2.iM, pcl_gg)
1✔
580
        if f1.n_temp > 0:
1✔
581
            # PCL[f_i, g_j]
582
            pcl_fg = np.array([[[[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
1✔
583
                                  for a2 in gj]
584
                                 for a1 in fi]
585
                                for gj in f2.alm_temp]
586
                               for fi in f1.alm_temp])
587
            clb += np.einsum('ij,rs,jr,isklm',
1✔
588
                             f1.iM, f2.iM, prod_fg, pcl_fg)
589
    return clb.reshape(pcl_shape)
1✔
590

591

592
def uncorr_noise_deprojection_bias(f1, map_var, n_iter=None):
1✔
593
    """ Computes the bias associated to contaminant removal in the
594
    presence of uncorrelated inhomogeneous noise to the
595
    auto-pseudo-:math:`C_\\ell` of a given field.
596

597
    Args:
598
        f1 (:class:`~pymaster.field.NmtField`): Field being correlated.
599
        map_var (`array`): Single map containing the local noise
600
            variance in one steradian. The map should have the same
601
            pixelization used by ``f1``.
602
        n_iter (:obj:`int`): Number of iterations when computing
603
            :math:`a_{\\ell m}` s. See docstring of
604
            :class:`~pymaster.field.NmtField`.
605

606
    Returns:
607
        (`array`): Deprojection bias pseudo-:math:`C_\\ell`.
608
    """
609
    if f1.lite:
1✔
610
        raise ValueError("Can't compute deprojection bias for "
1✔
611
                         "lightweight fields")
612
    if n_iter is None:
1✔
613
        n_iter = ut.nmt_params.n_iter_default
1✔
614

615
    # Flatten in case it's a 2D map
616
    sig2 = map_var.flatten()
1✔
617
    if len(sig2) != f1.minfo.npix:
1✔
618
        raise ValueError("Variance map doesn't match map resolution")
1✔
619

620
    pcl_shape = (f1.nmaps * f1.nmaps, f1.ainfo.lmax+1)
1✔
621

622
    # Return if no contamination
623
    if f1.n_temp == 0:
1✔
624
        return np.zeros(pcl_shape)
1✔
625

626
    clb = np.zeros((f1.nmaps, f1.nmaps, f1.ainfo.lmax+1))
1✔
627

628
    # First term in Eq. 39 of the NaMaster paper
629
    pcl_ff = np.zeros((f1.n_temp, f1.n_temp,
1✔
630
                       f1.nmaps, f1.nmaps,
631
                       f1.ainfo.lmax+1))
632
    for j, fj in enumerate(f1.temp):
1✔
633
        # SHT(v^2 sig^2 f_j)
634
        fj_v_s = ut.map2alm(fj*(f1.mask**2*sig2)[None, :], f1.spin,
1✔
635
                            f1.minfo, f1.ainfo, n_iter=n_iter)
636
        for i, fi in enumerate(f1.alm_temp):
1✔
637
            cl = np.array([[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
1✔
638
                            for a1 in fi]
639
                           for a2 in fj_v_s])
640
            pcl_ff[i, j, :, :, :] = cl
1✔
641
    clb -= 2*np.einsum('ij,ijklm', f1.iM, pcl_ff)
1✔
642

643
    # Second term in Eq. 39 of the namaster paper
644
    # PCL(fi, fs)
645
    pcl_ff = np.array([[[[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
1✔
646
                          for a2 in fs]
647
                         for a1 in fi]
648
                        for fs in f1.alm_temp]
649
                       for fi in f1.alm_temp])
650
    # Int[fj * fr * v^2 * sig^2]
651
    prod_ff = np.array([[
1✔
652
        f1.minfo.si.dot_map(fj, fr*(f1.mask**2*sig2)[None, :])
653
        for fr in f1.temp] for fj in f1.temp])
654
    clb += np.einsum('ij,rs,jr,isklm', f1.iM, f1.iM, prod_ff, pcl_ff)
1✔
655

656
    return clb.reshape(pcl_shape)
1✔
657

658

659
def deprojection_bias_flat(f1, f2, b, ells, cl_guess,
1✔
660
                           ell_cut_x=[1., -1.], ell_cut_y=[1., -1.]):
661
    """ Computes the bias associated to contaminant removal to the
662
    cross-pseudo-:math:`C_\\ell` of two flat-sky fields. See Eq. 50 in
663
    the NaMaster paper.
664

665
    Args:
666
        f1 (:class:`~pymaster.field.NmtFieldFlat`): First field to
667
            correlate.
668
        f2 (:class:`~pymaster.field.NmtFieldFlat`): Second field to
669
            correlate.
670
        b (:class:`~pymaster.bins.NmtBinFlat`): Binning scheme defining
671
            the output bandpowers.
672
        ells (`array`): List of multipoles on which the guess power
673
            spectra are defined.
674
        cl_guess (`array`): Array of power spectra corresponding to a
675
            best-guess of the true power spectra of ``f1`` and ``f2``.
676
        ell_cut_x (`array`): Sequence of two elements determining the
677
            range of :math:`l_x` to remove from the calculation. No
678
            Fourier modes removed by default.
679
        ell_cut_y (`array`): Sequence of two elements determining the
680
            range of :math:`l_y` to remove from the calculation. No
681
            Fourier modes removed by default.
682

683
    Returns:
684
        (`array`): Deprojection bias pseudo-:math:`C_\\ell`.
685
    """
686
    if len(cl_guess) != f1.fl.nmaps * f2.fl.nmaps:
1✔
687
        raise ValueError("Proposal Cell doesn't match number of maps")
1✔
688
    if len(cl_guess[0]) != len(ells):
1✔
689
        raise ValueError("cl_guess and ells must have the same length")
1✔
690
    cl1d = lib.comp_deproj_bias_flat(
1✔
691
        f1.fl,
692
        f2.fl,
693
        b.bin,
694
        ell_cut_x[0],
695
        ell_cut_x[1],
696
        ell_cut_y[0],
697
        ell_cut_y[1],
698
        ells,
699
        cl_guess,
700
        f1.fl.nmaps * f2.fl.nmaps * b.bin.n_bands,
701
    )
702
    cl2d = np.reshape(cl1d, [f1.fl.nmaps * f2.fl.nmaps, b.bin.n_bands])
1✔
703

704
    return cl2d
1✔
705

706

707
def compute_coupled_cell(f1, f2):
1✔
708
    """ Computes the full-sky pseudo-:math:`C_\\ell` of two masked
709
    fields (``f1`` and ``f2``) without aiming to deconvolve the
710
    mode-coupling matrix (Eq. 7 of the NaMaster paper). Effectively,
711
    this is equivalent to calling the usual HEALPix `anafast
712
    <https://healpy.readthedocs.io/en/latest/generated/healpy.sphtfunc.anafast.html>`_
713
    routine on the masked and contaminant-cleaned maps.
714

715
    Args:
716
        f1 (:class:`~pymaster.field.NmtField`): First field to
717
            correlate.
718
        f2 (:class:`~pymaster.field.NmtField`): Second field to
719
            correlate.
720

721
    Returns:
722
        (`array`): Array of coupled pseudo-:math:`C_\\ell` s.
723
    """  # noqa
724
    if not f1.is_compatible(f2):
1✔
725
        raise ValueError("You're trying to correlate incompatible fields")
1✔
726
    alm1 = f1.get_alms()
1✔
727
    alm2 = f2.get_alms()
1✔
728
    ncl = len(alm1) * len(alm2)
1✔
729
    lmax = min(f1.ainfo.lmax, f2.ainfo.lmax)
1✔
730
    cls = np.array([[hp.alm2cl(a1, a2, lmax=lmax)
1✔
731
                     for a2 in alm2] for a1 in alm1])
732
    cls = cls.reshape([ncl, lmax+1])
1✔
733
    return cls
1✔
734

735

736
def compute_coupled_cell_flat(f1, f2, b, ell_cut_x=[1., -1.],
1✔
737
                              ell_cut_y=[1., -1.]):
738
    """ Computes the flat-sky pseudo-:math:`C_\\ell` of two masked
739
    fields (``f1`` and ``f2``) without aiming to deconvolve the
740
    mode-coupling matrix (Eq. 42 of the NaMaster paper). Effectively,
741
    this is equivalent to computing the map FFTs and
742
    averaging over rings of wavenumber.  The returned power
743
    spectrum is defined at the multipoles returned by the
744
    method :meth:`~pytest.field.NmtFieldFlat.get_ell_sampling`
745
    of either ``f1`` or ``f2``.
746

747
    Args:
748
        f1 (:class:`~pymaster.field.NmtFieldFlat`): First field to
749
            correlate.
750
        f2 (:class:`~pymaster.field.NmtFieldFlat`): Second field to
751
            correlate.
752
        b (:class:`~pymaster.bins.NmtBinFlat`): Binning scheme defining
753
            the output bandpowers.
754
        ell_cut_x (`array`): Sequence of two elements determining the
755
            range of :math:`l_x` to remove from the calculation. No
756
            Fourier modes removed by default.
757
        ell_cut_y (`array`): Sequence of two elements determining the
758
            range of :math:`l_y` to remove from the calculation. No
759
            Fourier modes removed by default.
760

761
    Returns:
762
        (`array`): Array of coupled pseudo-:math:`C_\\ell` s.
763
    """
764
    if (f1.nx != f2.nx) or (f1.ny != f2.ny):
1✔
765
        raise ValueError("Fields must have same resolution")
1✔
766

767
    cl1d = lib.comp_pspec_coupled_flat(
1✔
768
        f1.fl,
769
        f2.fl,
770
        b.bin,
771
        f1.fl.nmaps * f2.fl.nmaps * b.bin.n_bands,
772
        ell_cut_x[0],
773
        ell_cut_x[1],
774
        ell_cut_y[0],
775
        ell_cut_y[1],
776
    )
777
    clout = np.reshape(cl1d, [f1.fl.nmaps * f2.fl.nmaps, b.bin.n_bands])
1✔
778

779
    return clout
1✔
780

781

782
def compute_full_master(f1, f2, b=None, cl_noise=None, cl_guess=None,
1✔
783
                        workspace=None, l_toeplitz=-1, l_exact=-1, dl_band=-1):
784
    """ Computes the full MASTER estimate of the power spectrum of two
785
    fields (``f1`` and ``f2``). This is equivalent to sequentially calling:
786

787
    - :meth:`NmtWorkspace.compute_coupling_matrix`
788
    - :meth:`deprojection_bias`
789
    - :meth:`compute_coupled_cell`
790
    - :meth:`NmtWorkspace.decouple_cell`
791

792

793
    Args:
794
        fl1 (:class:`~pymaster.field.NmtField`): First field to
795
            correlate.
796
        fl2 (:class:`~pymaster.field.NmtField`): Second field to
797
            correlate.
798
        b (:class:`~pymaster.bins.NmtBin`): Binning scheme.
799
        cl_noise (`array`): Noise bias (i.e. angular
800
            pseudo-:math:`C_\\ell` of masked noise realizations).
801
        cl_guess (`array`): Array of power spectra corresponding to a
802
            best-guess of the true power spectra of ``f1`` and ``f2``.
803
        workspace (:class:`~pymaster.workspaces.NmtWorkspace`):
804
            Object containing the mode-coupling matrix associated with
805
            an incomplete sky coverage. If provided, the function will
806
            skip the computation of the mode-coupling matrix and use
807
            the information encoded in this object.
808
        l_toeplitz (:obj:`int`): If a positive number, the Toeplitz
809
            approximation described in `Louis et al. 2020
810
            <https://arxiv.org/abs/2010.14344>`_ will be used.
811
            In that case, this quantity corresponds to
812
            :math:`\\ell_{\\rm toeplitz}` in Fig. 3 of that paper.
813
        l_exact (:obj:`int`): If ``l_toeplitz>0``, it corresponds to
814
            :math:`\\ell_{\\rm exact}` in Fig. 3 of the paper.
815
            Ignored if ``l_toeplitz<=0``.
816
        dl_band (:obj:`int`): If ``l_toeplitz>0``, this quantity
817
            corresponds to :math:`\\Delta \\ell_{\\rm band}` in Fig.
818
            3 of the paper. Ignored if ``l_toeplitz<=0``.
819

820
    Returns:
821
        (`array`): Set of decoupled bandpowers.
822
    """
823
    if (b is None) and (workspace is None):
1✔
824
        raise SyntaxError("Must supply either workspace or bins.")
1✔
825
    if not f1.is_compatible(f2):
1✔
826
        raise ValueError("Fields have incompatible pixelizations.")
1✔
827
    pcl_shape = (f1.nmaps * f2.nmaps, f1.ainfo.lmax+1)
1✔
828

829
    if cl_noise is not None:
1✔
830
        if cl_noise.shape != pcl_shape:
1✔
831
            raise ValueError(
1✔
832
                f"Noise Cl should have shape {pcl_shape}")
833
        pcln = cl_noise
1✔
834
    else:
835
        pcln = np.zeros(pcl_shape)
1✔
836
    if cl_guess is not None:
1✔
837
        if cl_guess.shape != pcl_shape:
1✔
838
            raise ValueError(
1✔
839
                f"Guess Cl should have shape {pcl_shape}")
840
        clg = cl_guess
1✔
841
    else:
842
        clg = np.zeros(pcl_shape)
1✔
843

844
    # Data power spectrum
845
    pcld = compute_coupled_cell(f1, f2)
1✔
846
    # Deprojection bias
847
    pclb = deprojection_bias(f1, f2, clg)
1✔
848

849
    if workspace is None:
1✔
850
        w = NmtWorkspace()
1✔
851
        w.compute_coupling_matrix(
1✔
852
            f1, f2, b, l_toeplitz=l_toeplitz,
853
            l_exact=l_exact, dl_band=dl_band)
854
    else:
855
        w = workspace
1✔
856

857
    return w.decouple_cell(pcld - pclb - pcln)
1✔
858

859

860
def compute_full_master_flat(f1, f2, b, cl_noise=None, cl_guess=None,
1✔
861
                             ells_guess=None, workspace=None,
862
                             ell_cut_x=[1., -1.], ell_cut_y=[1., -1.]):
863
    """
864
    Computes the full MASTER estimate of the power spectrum of two
865
    flat-sky fields (``f1`` and ``f2``). This is equivalent to
866
    sequentially calling:
867

868
    - :meth:`NmtWorkspaceFlat.compute_coupling_matrix`
869
    - :meth:`deprojection_bias_flat`
870
    - :meth:`compute_coupled_cell_flat`
871
    - :meth:`NmtWorkspaceFlat.decouple_cell`
872

873
    Args:
874
        f1 (:class:`~pymaster.field.NmtFieldFlat`): First field to
875
            correlate.
876
        f2 (:class:`~pymaster.field.NmtFieldFlat`): Second field to
877
            correlate.
878
        b (:class:`~pymaster.bins.NmtBinFlat`): Binning scheme defining
879
            the output bandpowers.
880
        cl_noise (`array`): Noise bias (i.e. angular
881
            pseudo-:math:`C_\\ell` of masked noise realisations).
882
        cl_guess (`array`): Array of power spectra corresponding to a
883
            best-guess of the true power spectra of ``f1`` and ``f2``.
884
        ells_guess (`array`): List of multipoles on which the guess power
885
            spectra are defined.
886
        workspace (:class:`~pymaster.workspaces.NmtWorkspaceFlat`):
887
            Object containing the mode-coupling matrix associated with
888
            an incomplete sky coverage. If provided, the function will
889
            skip the computation of the mode-coupling matrix and use
890
            the information encoded in this object.
891
        ell_cut_x (`array`): Sequence of two elements determining the
892
            range of :math:`l_x` to remove from the calculation. No
893
            Fourier modes removed by default.
894
        ell_cut_y (`array`): Sequence of two elements determining the
895
            range of :math:`l_y` to remove from the calculation. No
896
            Fourier modes removed by default.
897

898
    Returns:
899
        (`array`): Set of decoupled bandpowers.
900
    """
901
    if (f1.nx != f2.nx) or (f1.ny != f2.ny):
1✔
902
        raise ValueError("Fields must have same resolution")
1✔
903
    if cl_noise is not None:
1✔
904
        if (len(cl_noise) != f1.fl.nmaps * f2.fl.nmaps) or (
1✔
905
            len(cl_noise[0]) != b.bin.n_bands
906
        ):
907
            raise ValueError("Wrong length for noise power spectrum")
1✔
908
        cln = cl_noise.copy()
1✔
909
    else:
910
        cln = np.zeros([f1.fl.nmaps * f2.fl.nmaps, b.bin.n_bands])
1✔
911
    if cl_guess is not None:
1✔
912
        if ells_guess is None:
1✔
913
            raise ValueError("Must provide ell-values for cl_guess")
1✔
914
        if (len(cl_guess) != f1.fl.nmaps * f2.fl.nmaps) or (
1✔
915
            len(cl_guess[0]) != len(ells_guess)
916
        ):
917
            raise ValueError("Wrong length for guess power spectrum")
1✔
918
        lf = ells_guess.copy()
1✔
919
        clg = cl_guess.copy()
1✔
920
    else:
921
        lf = b.get_effective_ells()
1✔
922
        clg = np.zeros([f1.fl.nmaps * f2.fl.nmaps, b.bin.n_bands])
1✔
923

924
    if workspace is None:
1✔
925
        cl1d = lib.comp_pspec_flat(
1✔
926
            f1.fl,
927
            f2.fl,
928
            b.bin,
929
            None,
930
            cln,
931
            lf,
932
            clg,
933
            len(cln) * b.bin.n_bands,
934
            ell_cut_x[0],
935
            ell_cut_x[1],
936
            ell_cut_y[0],
937
            ell_cut_y[1],
938
        )
939
    else:
940
        cl1d = lib.comp_pspec_flat(
1✔
941
            f1.fl,
942
            f2.fl,
943
            b.bin,
944
            workspace.wsp,
945
            cln,
946
            lf,
947
            clg,
948
            len(cln) * b.bin.n_bands,
949
            ell_cut_x[0],
950
            ell_cut_x[1],
951
            ell_cut_y[0],
952
            ell_cut_y[1],
953
        )
954

955
    clout = np.reshape(cl1d, [len(cln), b.bin.n_bands])
1✔
956

957
    return clout
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc