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

LSSTDESC / NaMaster / 23483492181

24 Mar 2026 09:56AM UTC coverage: 90.781% (-8.3%) from 99.049%
23483492181

Pull #244

github

web-flow
Merge c9c97e22c into 24365fa59
Pull Request #244: Catalog field unification

19 of 20 new or added lines in 1 file covered. (95.0%)

140 existing lines in 2 files now uncovered.

1546 of 1703 relevant lines covered (90.78%)

1.82 hits per line

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

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

7

8
class NmtWorkspace(object):
2✔
9
    """ :obj:`NmtWorkspace` objects are used to compute and store the
10
    mode-coupling matrix associated with an incomplete sky coverage,
11
    and used in the MASTER algorithm. :obj:`NmtWorkspace` objects can be
12
    initialised from a pair of :class:`~pymaster.field.NmtField` objects
13
    and an :class:`~pymaster.bins.NmtBin` object, containing information
14
    about the masks involved and the :math:`\\ell` binning scheme, or
15
    read from a file where the mode-coupling matrix was stored.
16

17
    We recommend using the class methods :meth:`from_fields` and
18
    :meth:`from_file` to create new :obj:`NmtWorkspace` objects,
19
    rather than using the main constructor.
20

21
    Args:
22
        fl1 (:class:`~pymaster.field.NmtField`): First field being
23
            correlated.
24
        fl2 (:class:`~pymaster.field.NmtField`): Second field being
25
            correlated.
26
        bins (:class:`~pymaster.bins.NmtBin`): Binning scheme.
27
        is_teb (:obj:`bool`): If ``True``, all mode-coupling matrices
28
            (0-0,0-s,s-s) will be computed at the same time. In this
29
            case, ``fl1`` must be a spin-0 field and ``fl2`` must be
30
            spin-s.
31
        l_toeplitz (:obj:`int`): If a positive number, the Toeplitz
32
            approximation described in `Louis et al. 2020
33
            <https://arxiv.org/abs/2010.14344>`_ will be used.
34
            In that case, this quantity corresponds to
35
            :math:`\\ell_{\\rm toeplitz}` in Fig. 3 of that paper.
36
        l_exact (:obj:`int`): If ``l_toeplitz>0``, it corresponds to
37
            :math:`\\ell_{\\rm exact}` in Fig. 3 of the paper.
38
            Ignored if ``l_toeplitz<=0``.
39
        dl_band (:obj:`int`): If ``l_toeplitz>0``, this quantity
40
            corresponds to :math:`\\Delta \\ell_{\\rm band}` in Fig.
41
            3 of the paper. Ignored if ``l_toeplitz<=0``.
42
        fname (:obj:`str`): Input file name. If not `None`, this
43
            workspace will be initialised from file, and the values
44
            of ``fl1``, ``fl2``, and ``bin`` will be ignored.
45
        read_unbinned_MCM (:obj:`bool`): If ``False``, the unbinned
46
            mode-coupling matrix will not be read. This can save
47
            significant IO time.
48
        normalization (:obj:`str`): Normalization convention to use for
49
            the bandpower window functions. Two options supported:
50
            `'MASTER'` (default) corresponds to the standard inversion
51
            of the binned mode-coupling matrix. `'FKP'` simply divides
52
            by the mean of the mask product, forcing a unit response
53
            to an input white spectrum.
54
    """
55
    def __init__(self, fl1=None, fl2=None, bins=None, is_teb=False,
2✔
56
                 l_toeplitz=-1, l_exact=-1, dl_band=-1, fname=None,
57
                 read_unbinned_MCM=True, normalization='MASTER'):
58
        self.wsp = None
2✔
59
        self.has_unbinned = False
2✔
60

61
        if ((fl1 is None) and (fl2 is None) and (bins is None) and
2✔
62
                (fname is None)):
63
            warnings.warn("The bare constructor for `NmtWorkspace` "
2✔
64
                          "objects is deprecated and will be removed "
65
                          "in future versions of NaMaster. Consider "
66
                          "using the class methods "
67
                          "`from_fields` and `from_file`, or pass "
68
                          "the necessary arguments to the constructor.",
69
                          category=DeprecationWarning)
70
            return
2✔
71

72
        if (fname is not None):
2✔
73
            self.read_from(fname, read_unbinned_MCM=read_unbinned_MCM)
2✔
74
            return
2✔
75

76
        self.compute_coupling_matrix(
2✔
77
            fl1, fl2, bins, is_teb=is_teb,
78
            l_toeplitz=l_toeplitz, l_exact=l_exact, dl_band=dl_band,
79
            normalization=normalization)
80

81
    @classmethod
2✔
82
    def from_fields(cls, fl1, fl2, bins, is_teb=False,
2✔
83
                    l_toeplitz=-1, l_exact=-1, dl_band=-1,
84
                    normalization='MASTER'):
85
        """ Creates an :obj:`NmtWorkspace` object containing the
86
        mode-coupling matrix associated with the cross-power spectrum of
87
        two :class:`~pymaster.field.NmtField` s
88
        and an :class:`~pymaster.bins.NmtBin` binning scheme. Note that
89
        the mode-coupling matrix will only contain :math:`\\ell` s up
90
        to the maximum multipole included in the bandpowers, which should
91
        match the :math:`\\ell_{\\rm max}` of the fields as well.
92

93
        Args:
94
            fl1 (:class:`~pymaster.field.NmtField`): First field to correlate.
95
            fl2 (:class:`~pymaster.field.NmtField`): Second field to correlate.
96
            bins (:class:`~pymaster.bins.NmtBin`): Binning scheme.
97
            is_teb (:obj:`bool`): If ``True``, all mode-coupling matrices
98
                (0-0,0-s,s-s) will be computed at the same time. In this
99
                case, ``fl1`` must be a spin-0 field and ``fl2`` must be
100
                spin-s.
101
            l_toeplitz (:obj:`int`): If a positive number, the Toeplitz
102
                approximation described in `Louis et al. 2020
103
                <https://arxiv.org/abs/2010.14344>`_ will be used.
104
                In that case, this quantity corresponds to
105
                :math:`\\ell_{\\rm toeplitz}` in Fig. 3 of that paper.
106
            l_exact (:obj:`int`): If ``l_toeplitz>0``, it corresponds to
107
                :math:`\\ell_{\\rm exact}` in Fig. 3 of the paper.
108
                Ignored if ``l_toeplitz<=0``.
109
            dl_band (:obj:`int`): If ``l_toeplitz>0``, this quantity
110
                corresponds to :math:`\\Delta \\ell_{\\rm band}` in Fig.
111
                3 of the paper. Ignored if ``l_toeplitz<=0``.
112
            normalization (:obj:`str`): Normalization convention to use
113
                for the bandpower window functions. Two options
114
                supported: `'MASTER'` (default) corresponds to the
115
                standard inversion of the binned mode-coupling matrix.
116
                `'FKP'` simply divides by the mean of the mask product,
117
                forcing a unit response to an input white spectrum.
118
        """
119
        return cls(fl1=fl1, fl2=fl2, bins=bins, is_teb=is_teb,
2✔
120
                   l_toeplitz=l_toeplitz, l_exact=l_exact,
121
                   dl_band=dl_band, normalization=normalization)
122

123
    @classmethod
2✔
124
    def from_file(cls, fname, read_unbinned_MCM=True):
2✔
125
        """ Creates an :obj:`NmtWorkspace` object from a mode-coupling
126
        matrix stored in a FITS file. See :meth:`write_to`.
127

128
        Args:
129
            fname (:obj:`str`): Input file name.
130
            read_unbinned_MCM (:obj:`bool`): If ``False``, the unbinned
131
                mode-coupling matrix will not be read. This can save
132
                significant IO time.
133
        """
134
        return cls(fname=fname, read_unbinned_MCM=read_unbinned_MCM)
2✔
135

136
    def __del__(self):
2✔
137
        if self.wsp is not None:
2✔
138
            if lib.workspace_free is not None:
2✔
139
                lib.workspace_free(self.wsp)
2✔
140
            self.wsp = None
2✔
141

142
    def check_unbinned(self):
2✔
143
        """ Raises an error if this workspace does not contain the
144
        unbinned mode-coupling matrix.
145
        """
146
        if not self.has_unbinned:
2✔
147
            raise ValueError("This workspace does not store the unbinned "
2✔
148
                             "mode-coupling matrix.")
149

150
    def read_from(self, fname, read_unbinned_MCM=True):
2✔
151
        """ Reads the contents of an :obj:`NmtWorkspace` object from a
152
        FITS file.
153

154
        Args:
155
            fname (:obj:`str`): Input file name.
156
            read_unbinned_MCM (:obj:`bool`): If ``False``, the unbinned
157
                mode-coupling matrix will not be read. This can save
158
                significant IO time.
159
        """
160
        if self.wsp is not None:
2✔
161
            lib.workspace_free(self.wsp)
2✔
162
            self.wsp = None
2✔
163
        self.wsp = lib.read_workspace(fname, int(read_unbinned_MCM))
2✔
164
        self.has_unbinned = read_unbinned_MCM
2✔
165

166
    def update_beams(self, beam1, beam2):
2✔
167
        """ Update beams associated with this mode-coupling matrix.
168
        This is significantly faster than recomputing the matrix from
169
        scratch.
170

171
        Args:
172
            beam1 (`array`): First beam, in the form of a 1D array
173
                with the beam sampled at all integer multipoles up
174
                to the maximum :math:`\\ell` with which this
175
                workspace was initialised.
176
            beam2 (`array`): Second beam.
177
        """
178
        b1arr = isinstance(beam1, (list, tuple, np.ndarray))
2✔
179
        b2arr = isinstance(beam2, (list, tuple, np.ndarray))
2✔
180
        if ((not b1arr) or (not b2arr)):
2✔
181
            raise ValueError("The new beams must be provided as arrays")
2✔
182

183
        lmax = self.wsp.lmax_fields
2✔
184
        if (len(beam1) <= lmax) or (len(beam2) <= lmax):
2✔
185
            raise ValueError("The new beams must go up to ell = %d" % lmax)
2✔
186
        lib.wsp_update_beams(self.wsp, beam1, beam2)
2✔
187

188
    def update_bins(self, bins):
2✔
189
        """ Update binning associated with this mode-coupling matrix.
190
        This is significantly faster than recomputing the matrix from
191
        scratch.
192

193
        Args:
194
            bins (:class:`~pymaster.bins.NmtBin`): New binning scheme.
195
        """
196
        if self.wsp is None:
2✔
197
            raise ValueError("Can't update bins without first computing "
2✔
198
                             "the mode-coupling matrix")
199
        if bins.bin is None:
2✔
200
            raise ValueError("Can't replace with uninitialized bins")
2✔
201
        lib.wsp_update_bins(self.wsp, bins.bin)
2✔
202

203
    def compute_coupling_matrix(self, fl1, fl2, bins, is_teb=False,
2✔
204
                                l_toeplitz=-1, l_exact=-1, dl_band=-1,
205
                                normalization='MASTER'):
206
        """ Computes the mode-coupling matrix associated with the
207
        cross-power spectrum of two :class:`~pymaster.field.NmtField` s
208
        and an :class:`~pymaster.bins.NmtBin` binning scheme. Note that
209
        the mode-coupling matrix will only contain :math:`\\ell` s up
210
        to the maximum multipole included in the bandpowers, which should
211
        match the :math:`\\ell_{\\rm max}` of the fields as well.
212

213
        Args:
214
            fl1 (:class:`~pymaster.field.NmtField`): First field to correlate.
215
            fl2 (:class:`~pymaster.field.NmtField`): Second field to correlate.
216
            bins (:class:`~pymaster.bins.NmtBin`): Binning scheme.
217
            is_teb (:obj:`bool`): If ``True``, all mode-coupling matrices
218
                (0-0,0-s,s-s) will be computed at the same time. In this
219
                case, ``fl1`` must be a spin-0 field and ``fl2`` must be
220
                spin-s.
221
            l_toeplitz (:obj:`int`): If a positive number, the Toeplitz
222
                approximation described in `Louis et al. 2020
223
                <https://arxiv.org/abs/2010.14344>`_ will be used.
224
                In that case, this quantity corresponds to
225
                :math:`\\ell_{\\rm toeplitz}` in Fig. 3 of that paper.
226
            l_exact (:obj:`int`): If ``l_toeplitz>0``, it corresponds to
227
                :math:`\\ell_{\\rm exact}` in Fig. 3 of the paper.
228
                Ignored if ``l_toeplitz<=0``.
229
            dl_band (:obj:`int`): If ``l_toeplitz>0``, this quantity
230
                corresponds to :math:`\\Delta \\ell_{\\rm band}` in Fig.
231
                3 of the paper. Ignored if ``l_toeplitz<=0``.
232
            normalization (:obj:`str`): Normalization convention to use for
233
                the bandpower window functions. Two options supported:
234
                `'MASTER'` (default) corresponds to the standard inversion
235
                of the binned mode-coupling matrix. `'FKP'` simply divides
236
                by the mean of the mask product, forcing a unit response to
237
                an input white spectrum.
238
        """
239
        if not fl1.is_compatible(fl2, strict=False):
2✔
240
            raise ValueError("Fields have incompatible pixelizations.")
2✔
241
        if fl1.ainfo.lmax != bins.lmax:
2✔
242
            raise ValueError(f"Maximum multipoles in bins ({bins.lmax}) "
2✔
243
                             f"and fields ({fl1.ainfo.lmax}) "
244
                             "are not the same.")
245
        if self.wsp is not None:
2✔
246
            lib.workspace_free(self.wsp)
2✔
247
            self.wsp = None
2✔
248

249
        anisotropic_mask_any = fl1.anisotropic_mask or fl2.anisotropic_mask
2✔
250
        if anisotropic_mask_any and (l_toeplitz >= 0):
2✔
251
            raise NotImplementedError("Toeplitz approximation not "
2✔
252
                                      "implemented for anisotropic masks.")
253
        ut._toeplitz_sanity(l_toeplitz, l_exact, dl_band,
2✔
254
                            bins.bin.ell_max, fl1, fl2)
255

256
        # Get mask PCL
257
        alm1 = fl1.get_mask_alms()
2✔
258
        Nw = 0
2✔
259
        if fl2 is fl1:
2✔
260
            alm2 = alm1
2✔
261
            Nw = fl1.Nw
2✔
262
        else:
263
            alm2 = fl2.get_mask_alms()
2✔
264
        pcl_mask = hp.alm2cl(alm1, alm2, lmax=fl1.ainfo_mask.lmax)
2✔
265
        if anisotropic_mask_any:
2✔
266
            pcl0 = pcl_mask * 0
2✔
267
            pclm_00 = pcl_mask
2✔
268
            pclm_0e = pclm_0b = pclm_e0 = pclm_b0 = pcl0
2✔
269
            pclm_ee = pclm_eb = pclm_be = pclm_bb = pcl0
2✔
270
            if fl1.anisotropic_mask:
2✔
271
                alm1a = fl1.get_anisotropic_mask_alms()
2✔
272
            if fl2.anisotropic_mask:
2✔
273
                alm2a = fl2.get_anisotropic_mask_alms()
2✔
274
            if fl2.anisotropic_mask:
2✔
275
                pclm_0e = hp.alm2cl(alm1, alm2a[0], lmax=fl1.ainfo_mask.lmax)
2✔
276
                pclm_0b = hp.alm2cl(alm1, alm2a[1], lmax=fl1.ainfo_mask.lmax)
2✔
277
            if fl1.anisotropic_mask:
2✔
278
                pclm_e0 = hp.alm2cl(alm1a[0], alm2, lmax=fl1.ainfo_mask.lmax)
2✔
279
                pclm_b0 = hp.alm2cl(alm1a[1], alm2, lmax=fl1.ainfo_mask.lmax)
2✔
280
                if fl2.anisotropic_mask:
2✔
281
                    pclm_ee = hp.alm2cl(alm1a[0], alm2a[0],
2✔
282
                                        lmax=fl1.ainfo_mask.lmax)
283
                    pclm_eb = hp.alm2cl(alm1a[0], alm2a[1],
2✔
284
                                        lmax=fl1.ainfo_mask.lmax)
285
                    pclm_be = hp.alm2cl(alm1a[1], alm2a[0],
2✔
286
                                        lmax=fl1.ainfo_mask.lmax)
287
                    pclm_bb = hp.alm2cl(alm1a[1], alm2a[1],
2✔
288
                                        lmax=fl1.ainfo_mask.lmax)
289

290
        if normalization == 'MASTER':
2✔
291
            norm_type = 0
2✔
292
        elif normalization == 'FKP':
2✔
293
            norm_type = 1
2✔
294
        else:
295
            raise ValueError(f"Unknown normalization type {normalization}. "
2✔
296
                             "Allowed options are 'MASTER' and 'FKP'.")
297

298
        wawb = 0
2✔
299
        if norm_type == 1:
2✔
300
            if fl1.is_catalog or fl2.is_catalog:
2✔
301
                if fl2 is fl1:
2✔
302
                    wawb = fl1.Nw
2✔
303
                else:
304
                    raise ValueError("Cannot use FKP normalisation for "
2✔
305
                                     "catalog fields unless they are the "
306
                                     "same field.")
307
            else:
308
                msk1 = fl1.get_mask()
2✔
309
                msk2 = fl2.get_mask()
2✔
310
                wawb = fl1.minfo.si.dot_map(msk1, msk2)/(4*np.pi)
2✔
311

312
        if anisotropic_mask_any:
2✔
313
            self.wsp = lib.comp_coupling_matrix_anisotropic(
2✔
314
                int(fl1.spin), int(fl2.spin),
315
                int(fl1.anisotropic_mask), int(fl2.anisotropic_mask),
316
                int(fl1.ainfo.lmax), int(fl1.ainfo_mask.lmax),
317
                pclm_00, pclm_0e, pclm_0b, pclm_e0, pclm_b0,
318
                pclm_ee, pclm_eb, pclm_be, pclm_bb,
319
                fl1.beam, fl2.beam, bins.bin,
320
                int(norm_type), wawb)
321
        else:
322
            self.wsp = lib.comp_coupling_matrix(
2✔
323
                int(fl1.spin), int(fl2.spin),
324
                int(fl1.ainfo.lmax), int(fl1.ainfo_mask.lmax),
325
                int(fl1.pure_e), int(fl1.pure_b),
326
                int(fl2.pure_e), int(fl2.pure_b),
327
                int(norm_type), wawb,
328
                fl1.beam, fl2.beam, pcl_mask.flatten()-Nw,
329
                bins.bin, int(is_teb), l_toeplitz, l_exact, dl_band)
330
        self.has_unbinned = True
2✔
331

332
    def write_to(self, fname):
2✔
333
        """ Writes the contents of an :obj:`NmtWorkspace` object
334
        to a FITS file.
335

336
        Args:
337
            fname (:obj:`str`): Output file name
338
        """
339
        if self.wsp is None:
2✔
340
            raise RuntimeError("Must initialize workspace before writing")
2✔
341
        self.check_unbinned()
2✔
342
        lib.write_workspace(self.wsp, "!"+fname)
2✔
343

344
    def get_coupling_matrix(self):
2✔
345
        """ Returns the currently stored mode-coupling matrix.
346

347
        Returns:
348
            (`array`): Mode-coupling matrix. The matrix will have shape
349
            ``(nrows,nrows)``, with ``nrows = n_cls * n_ells``, where
350
            ``n_cls`` is the number of power spectra (1, 2 or 4 for
351
            spin 0-0, spin 0-2 and spin 2-2 correlations), and
352
            ``n_ells = lmax + 1``, and ``lmax`` is the maximum multipole
353
            associated with this workspace. The assumed ordering of power
354
            spectra is such that the ``L``-th element of the ``i``-th power
355
            spectrum be stored with index ``L * n_cls + i``.
356
        """
357
        if self.wsp is None:
2✔
358
            raise RuntimeError("Must initialize workspace before "
2✔
359
                               "getting a MCM")
360
        self.check_unbinned()
2✔
361
        nrows = (self.wsp.lmax + 1) * self.wsp.ncls
2✔
362
        return lib.get_mcm(self.wsp, nrows * nrows).reshape([nrows, nrows])
2✔
363

364
    def update_coupling_matrix(self, new_matrix):
2✔
365
        """
366
        Updates the stored mode-coupling matrix. The new matrix
367
        (``new_matrix``) must have shape ``(nrows,nrows)``.
368
        See docstring of :meth:`~NmtWorkspace.get_coupling_matrix` for an
369
        explanation of the size and ordering of this matrix.
370

371
        Args:
372
            new_matrix (`array`): Matrix that will replace the mode-coupling
373
                matrix.
374
        """
375
        if self.wsp is None:
2✔
376
            raise RuntimeError("Must initialize workspace before updating MCM")
2✔
377
        self.check_unbinned()
2✔
378
        if len(new_matrix) != (self.wsp.lmax + 1) * self.wsp.ncls:
2✔
379
            raise ValueError("Input matrix has an inconsistent size. "
2✔
380
                             f"Expected {(self.wsp.lmax+1)*self.wsp.ncls}, "
381
                             f"but got {len(new_matrix)}.")
382
        lib.update_mcm(self.wsp, len(new_matrix), new_matrix.flatten())
2✔
383

384
    def couple_cell(self, cl_in):
2✔
385
        """ Convolves a set of input power spectra with a coupling matrix
386
        (see Eq. 9 of the NaMaster paper).
387

388
        Args:
389
            cl_in (`array`): Set of input power spectra. The number of power
390
                spectra must correspond to the spins of the two fields that
391
                this :obj:`NmtWorkspace` object was initialized with (i.e. 1
392
                for two spin-0 fields, 2 for one spin-0 field and one spin-s
393
                field, and 4 for two spin-s fields).
394

395
        Returns:
396
            (`array`): Mode-coupled power spectra.
397
        """
398
        if (len(cl_in) != self.wsp.ncls) or \
2✔
399
           (len(cl_in[0]) < self.wsp.lmax + 1):
400
            raise ValueError("Input power spectrum has wrong shape. "
2✔
401
                             f"Expected ({self.wsp.ncls}, {self.wsp.lmax+1}), "
402
                             f"bu got {cl_in.shape}.")
403
        self.check_unbinned()
2✔
404

405
        # Shorten C_ells if they're too long
406
        cl_in = np.array(cl_in)[:, :self.wsp.lmax+1]
2✔
407
        cl1d = lib.couple_cell_py(self.wsp, cl_in,
2✔
408
                                  self.wsp.ncls * (self.wsp.lmax + 1))
409
        clout = np.reshape(cl1d, [self.wsp.ncls, self.wsp.lmax + 1])
2✔
410
        return clout
2✔
411

412
    def decouple_cell(self, cl_in, cl_bias=None, cl_noise=None):
2✔
413
        """ Decouples a set of pseudo-:math:`C_\\ell` power spectra into a
414
        set of bandpowers by inverting the binned coupling matrix (se Eq.
415
        16 of the NaMaster paper).
416

417
        Args:
418
            cl_in (`array`): Set of input power spectra. The number of power
419
                spectra must correspond to the spins of the two fields that
420
                this :obj:`NmtWorkspace` object was initialized with (i.e. 1
421
                for two spin-0 fields, 2 for one spin-0 field and one spin-s
422
                field, 4 for two spin-s fields, and 7 if this
423
                :obj:`NmtWorkspace` was created using ``is_teb=True``).
424
            cl_bias (`array`): Bias to the power spectrum associated with
425
                contaminant residuals (optional). This can be computed through
426
                :func:`deprojection_bias`.
427
            cl_noise (`array`): Noise bias (i.e. angular
428
                pseudo-:math:`C_\\ell` of masked noise realizations).
429

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

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

464
        return clout
2✔
465

466
    def get_bandpower_windows(self):
2✔
467
        """ Get bandpower window functions. Convolve the theory power spectra
468
        with these as an alternative to the combination of function calls \
469
        ``w.decouple_cell(w.couple_cell(cls_theory))``. See Eqs. 18 and
470
        19 of the NaMaster paper.
471

472
        As an example consider the power spectrum of two spin-2 fields. In
473
        this case, the estimated bandpowers would have shape ``[4, n_bpw]``,
474
        where ``n_bpw`` is the number of bandpowers. The unbinned power
475
        spectra would have shape ``[4, lmax+1]``, where ``lmax`` is the
476
        maximum multipole under study. The bandpower window functions would
477
        then have shape ``[4, n_bpw, 4, lmax+1]`` and, for example, the
478
        window function at indices ``[0, b1, 3, ell2]`` quantifies the
479
        amount of :math:`BB` power at :math:`\\ell=` ``ell2`` that is leaked
480
        into the ``b1``-th :math:`EE` bandpower.
481

482
        Returns:
483
            (`array`): Bandpower windows with shape \
484
                ``(n_cls, n_bpws, n_cls, lmax+1)``.
485
        """
486
        self.check_unbinned()
2✔
487
        d = lib.get_bandpower_windows(self.wsp,
2✔
488
                                      self.wsp.ncls * self.wsp.bin.n_bands *
489
                                      self.wsp.ncls * (self.wsp.lmax+1))
490
        return np.transpose(d.reshape([self.wsp.bin.n_bands,
2✔
491
                                       self.wsp.ncls,
492
                                       self.wsp.lmax+1,
493
                                       self.wsp.ncls]),
494
                            axes=[1, 0, 3, 2])
495

496

497
class NmtWorkspaceFlat(object):
2✔
498
    """ :obj:`NmtWorkspaceFlat` objects are used to compute and store the
499
    mode-coupling matrix associated with an incomplete sky coverage, and
500
    used in the flat-sky version of the MASTER algorithm. When initialized,
501
    this object is practically empty. The information describing the
502
    coupling matrix must be computed or read from a file afterwards.
503
    """
504
    def __init__(self):
2✔
505
        self.wsp = None
2✔
506

507
    def __del__(self):
2✔
508
        if self.wsp is not None:
2✔
509
            if lib.workspace_flat_free is not None:
2✔
510
                lib.workspace_flat_free(self.wsp)
2✔
511
            self.wsp = None
2✔
512

513
    def read_from(self, fname):
2✔
514
        """ Reads the contents of an :obj:`NmtWorkspaceFlat` object from a
515
        FITS file.
516

517
        Args:
518
            fname (:obj:`str`): Input file name.
519
        """
520
        if self.wsp is not None:
2✔
521
            lib.workspace_flat_free(self.wsp)
2✔
522
            self.wsp = None
2✔
523
        self.wsp = lib.read_workspace_flat(fname)
2✔
524

525
    def compute_coupling_matrix(self, fl1, fl2, bins, ell_cut_x=[1., -1.],
2✔
526
                                ell_cut_y=[1., -1.], is_teb=False):
527
        """ Computes mode-coupling matrix associated with the cross-power
528
        spectrum of two :class:`~pymaster.field.NmtFieldFlat` s and an
529
        :class:`~pymaster.bins.NmtBinFlat` binning scheme.
530

531
        Args:
532
            fl1 (:class:`~pymaster.field.NmtFieldFlat`): First field to
533
                correlate.
534
            fl2 (:class:`~pymaster.field.NmtFieldFlat`): Second field to
535
                correlate.
536
            bin (:class:`~pymaster.bins.NmtBinFlat`): Binning scheme.
537
            ell_cut_x (`array`): Sequence of two elements determining the
538
                range of :math:`l_x` to remove from the calculation. No
539
                Fourier modes removed by default.
540
            ell_cut_y (`array`): Sequence of two elements determining the
541
                range of :math:`l_y` to remove from the calculation. No
542
                Fourier modes removed by default.
543
            is_teb (:obj:`bool`): If ``True``, all mode-coupling matrices
544
                (0-0,0-s,s-s) will be computed at the same time. In this
545
                case, ``fl1`` must be a spin-0 field and ``fl2`` must be
546
                spin-s.
547
        """
548
        if self.wsp is not None:
2✔
549
            lib.workspace_flat_free(self.wsp)
2✔
550
            self.wsp = None
2✔
551

552
        self.wsp = lib.comp_coupling_matrix_flat(
2✔
553
            fl1.fl,
554
            fl2.fl,
555
            bins.bin,
556
            ell_cut_x[0],
557
            ell_cut_x[1],
558
            ell_cut_y[0],
559
            ell_cut_y[1],
560
            int(is_teb),
561
        )
562

563
    def write_to(self, fname):
2✔
564
        """ Writes the contents of an :obj:`NmtWorkspaceFlat` object
565
        to a FITS file.
566

567
        Args:
568
            fname (:obj:`str`): Output file name.
569
        """
570
        if self.wsp is None:
2✔
571
            raise RuntimeError("Must initialize workspace before "
2✔
572
                               "writing")
573
        lib.write_workspace_flat(self.wsp, "!"+fname)
2✔
574

575
    def couple_cell(self, ells, cl_in):
2✔
576
        """ Convolves a set of input power spectra with a coupling
577
        matrix (see Eq. 42 of the NaMaster paper).
578

579

580
        Args:
581
            ells (`array`): List of multipoles on which the input power
582
                spectra are defined.
583
            cl_in (`array`): Set of input power spectra. The number of power
584
                spectra must correspond to the spins of the two fields that
585
                this :obj:`NmtWorkspace` object was initialized with (i.e. 1
586
                for two spin-0 fields, 2 for one spin-0 field and one spin-s
587
                field, and 4 for two spin-s fields).
588

589
        Returns:
590
            (`array`): Mode-coupled power spectra. The coupled power spectra \
591
                are returned at the multipoles returned by calling \
592
                :meth:`~pymaster.field.NmtFieldFlat.get_ell_sampling` for \
593
                any of the fields that were used to generate the workspace.
594
        """
595
        if (len(cl_in) != self.wsp.ncls) or (len(cl_in[0]) != len(ells)):
2✔
596
            raise ValueError("Input power spectrum has wrong shape. "
2✔
597
                             f"Expected ({self.wsp.ncls}, {len(ells)}, "
598
                             f"but got {cl_in.shape}.")
599
        cl1d = lib.couple_cell_py_flat(
2✔
600
            self.wsp, ells, cl_in, self.wsp.ncls * self.wsp.bin.n_bands
601
        )
602
        clout = np.reshape(cl1d, [self.wsp.ncls, self.wsp.bin.n_bands])
2✔
603
        return clout
2✔
604

605
    def decouple_cell(self, cl_in, cl_bias=None, cl_noise=None):
2✔
606
        """ Decouples a set of pseudo-:math:`C_\\ell` power spectra into a
607
        set of bandpowers by inverting the binned coupling matrix (see
608
        Eq. 47 of the NaMaster paper).
609

610
        Args:
611
            cl_in (`array`): Set of input power spectra. The number of power
612
                spectra must correspond to the spins of the two fields that
613
                this :obj:`NmtWorkspace` object was initialized with (i.e. 1
614
                for two spin-0 fields, 2 for one spin-0 field and one spin-s
615
                field, 4 for two spin-s fields, and 7 if this
616
                :obj:`NmtWorkspace` was created using ``is_teb=True``). These
617
                power spectra must be defined at the multipoles returned by
618
                :meth:`~pymaster.field.NmtFieldFlat.get_ell_sampling` for
619
                any of the fields used to create the workspace.
620
            cl_bias (`array`): Bias to the power spectrum associated with
621
                contaminant residuals (optional). This can be computed through
622
                :func:`deprojection_bias_flat`.
623
            cl_noise (`array`): Noise bias (i.e. angular
624
                pseudo-:math:`C_\\ell` of masked noise realisations).
625

626
        Returns:
627
            (`array`): Set of decoupled bandpowers.
628
        """
629
        if (len(cl_in) != self.wsp.ncls) or \
2✔
630
           (len(cl_in[0]) != self.wsp.bin.n_bands):
631
            raise ValueError(
2✔
632
                "Input power spectrum has wrong shape. "
633
                f"Expected ({self.wsp.ncls}, {self.wsp.bin.n_bands}), "
634
                f"but got {cl_in.shape}")
635
        if cl_bias is not None:
2✔
636
            if (len(cl_bias) != self.wsp.ncls) or \
2✔
637
               (len(cl_bias[0]) != self.wsp.bin.n_bands):
638
                raise ValueError(
2✔
639
                    "Input bias power spectrum has wrong shape. "
640
                    f"Expected ({self.wsp.ncls}, {self.wsp.bin.n_bands}), "
641
                    f"but got {cl_bias.shape}.")
642
            clb = cl_bias.copy()
2✔
643
        else:
644
            clb = np.zeros_like(cl_in)
2✔
645
        if cl_noise is not None:
2✔
646
            if (len(cl_noise) != self.wsp.ncls) or \
2✔
647
               (len(cl_noise[0]) != self.wsp.bin.n_bands):
648
                raise ValueError(
2✔
649
                    "Input noise power spectrum has wrong shape. "
650
                    f"Expected ({self.wsp.ncls}, {self.wsp.bin.n_bands}), "
651
                    f"but got {cl_noise.shape}.")
652
            cln = cl_noise.copy()
2✔
653
        else:
654
            cln = np.zeros_like(cl_in)
2✔
655

656
        cl1d = lib.decouple_cell_py_flat(
2✔
657
            self.wsp, cl_in, cln, clb, self.wsp.ncls * self.wsp.bin.n_bands
658
        )
659
        clout = np.reshape(cl1d, [self.wsp.ncls, self.wsp.bin.n_bands])
2✔
660

661
        return clout
2✔
662

663

664
def deprojection_bias(f1, f2, cl_guess, n_iter=None):
2✔
665
    """ Computes the bias associated to contaminant removal to the
666
    cross-pseudo-:math:`C_\\ell` of two fields. See Eq. 26 in the NaMaster
667
    paper.
668

669
    Args:
670
        f1 (:class:`~pymaster.field.NmtField`): First field to correlate.
671
        f2 (:class:`~pymaster.field.NmtField`): Second field to correlate.
672
        cl_guess (`array`): Array of power spectra corresponding to a
673
            best-guess of the true power spectra of ``f1`` and ``f2``.
674
        n_iter (:obj:`int`): Number of iterations when computing
675
            :math:`a_{\\ell m}` s. See docstring of
676
            :class:`~pymaster.field.NmtField`.
677

678
    Returns:
679
        (`array`): Deprojection bias pseudo-:math:`C_\\ell`.
680
    """
681
    if n_iter is None:
2✔
682
        n_iter = ut.nmt_params.n_iter_default
2✔
683

684
    if not f1.is_compatible(f2):
2✔
685
        raise ValueError("Fields have incompatible pixelizations.")
2✔
686

687
    def purify_if_needed(fld, mp):
2✔
688
        if fld.pure_e or fld.pure_b:
2✔
689
            # Compute mask alms if needed
690
            amask = fld.get_mask_alms()
2✔
691
            return fld._purify(fld.mask, amask, mp,
2✔
692
                               n_iter=n_iter, return_maps=False,
693
                               task=[fld.pure_e, fld.pure_b])
694
        else:
695
            return ut.map2alm(mp*fld.mask[None, :], fld.spin,
2✔
696
                              fld.minfo, fld.ainfo, n_iter=n_iter)
697

698
    pcl_shape = (f1.nmaps * f2.nmaps, f1.ainfo.lmax+1)
2✔
699
    if cl_guess.shape != pcl_shape:
2✔
700
        raise ValueError(
2✔
701
            f"Guess Cl should have shape {pcl_shape}")
702
    clg = cl_guess.reshape([f1.nmaps, f2.nmaps, f1.ainfo.lmax+1])
2✔
703

704
    if f1.lite or f2.lite:
2✔
705
        raise ValueError("Can't compute deprojection bias for "
2✔
706
                         "lightweight fields")
707

708
    clb = np.zeros((f1.nmaps, f2.nmaps, f1.ainfo.lmax+1))
2✔
709

710
    # Compute ff part
711
    if f1.n_temp > 0:
2✔
712
        pcl_ff = np.zeros((f1.n_temp, f1.n_temp,
2✔
713
                           f1.nmaps, f2.nmaps,
714
                           f1.ainfo.lmax+1))
715
        for ij, tj in enumerate(f1.temp):
2✔
716
            # SHT(v*fj)
717
            ftild_j = ut.map2alm(tj*f1.mask[None, :], f1.spin,
2✔
718
                                 f1.minfo, f1.ainfo, n_iter=n_iter)
719
            # C^ba*SHT[v*fj]
720
            ftild_j = np.array([
2✔
721
                np.sum([hp.almxfl(ftild_j[m], clg[m, n],
722
                                  mmax=f1.ainfo.mmax)
723
                        for m in range(f1.nmaps)], axis=0)
724
                for n in range(f2.nmaps)])
725
            # SHT^-1[C^ba*SHT[v*fj]]
726
            ftild_j = ut.alm2map(ftild_j, f2.spin, f2.minfo,
2✔
727
                                 f2.ainfo)
728
            # SHT[w*SHT^-1[C^ba*SHT[v*fj]]]
729
            ftild_j = purify_if_needed(f2, ftild_j)
2✔
730
            for ii, f_i in enumerate(f1.alm_temp):
2✔
731
                clij = np.array([[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
2✔
732
                                  for a2 in ftild_j]
733
                                 for a1 in f_i])
734
                pcl_ff[ii, ij, :, :, :] = clij
2✔
735
        clb -= np.einsum('ij,ijklm', f1.iM, pcl_ff)
2✔
736

737
    # Compute gg part and fg part
738
    if f2.n_temp > 0:
2✔
739
        pcl_gg = np.zeros((f2.n_temp, f2.n_temp,
2✔
740
                           f1.nmaps, f2.nmaps,
741
                           f1.ainfo.lmax+1))
742
        if f1.n_temp > 0:
2✔
743
            prod_fg = np.zeros((f1.n_temp, f2.n_temp))
2✔
744
            pcl_fg = np.zeros((f1.n_temp, f2.n_temp,
2✔
745
                               f1.nmaps, f2.nmaps,
746
                               f1.ainfo.lmax+1))
747

748
        for ij, tj in enumerate(f2.temp):
2✔
749
            # SHT(w*gj)
750
            gtild_j = ut.map2alm(tj*f2.mask[None, :], f2.spin,
2✔
751
                                 f2.minfo, f2.ainfo, n_iter=n_iter)
752
            # C^ab*SHT[w*gj]
753
            gtild_j = np.array([
2✔
754
                np.sum([hp.almxfl(gtild_j[n], clg[m, n],
755
                                  mmax=f2.ainfo.mmax)
756
                        for n in range(f2.nmaps)], axis=0)
757
                for m in range(f1.nmaps)])
758
            # SHT^-1[C^ab*SHT[w*gj]]
759
            gtild_j = ut.alm2map(gtild_j, f1.spin, f1.minfo,
2✔
760
                                 f1.ainfo)
761
            if f1.n_temp > 0:
2✔
762
                # Int[f^i*v*SHT^-1[C^ab*SHT[w*gj]]]
763
                for ii, ti in enumerate(f1.temp):
2✔
764
                    prod_fg[ii, ij] = f1.minfo.si.dot_map(
2✔
765
                        ti, gtild_j*f1.mask[None, :])
766

767
            # SHT[v*SHT^-1[C^ab*SHT[w*gj]]]
768
            gtild_j = purify_if_needed(f1, gtild_j)
2✔
769

770
            # PCL[g_i, gtild_j]
771
            for ii, g_i in enumerate(f2.alm_temp):
2✔
772
                clij = np.array([[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
2✔
773
                                  for a2 in g_i]
774
                                 for a1 in gtild_j])
775
                pcl_gg[ii, ij, :, :, :] = clij
2✔
776

777
        clb -= np.einsum('ij,ijklm', f2.iM, pcl_gg)
2✔
778
        if f1.n_temp > 0:
2✔
779
            # PCL[f_i, g_j]
780
            pcl_fg = np.array([[[[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
2✔
781
                                  for a2 in gj]
782
                                 for a1 in fi]
783
                                for gj in f2.alm_temp]
784
                               for fi in f1.alm_temp])
785
            clb += np.einsum('ij,rs,jr,isklm',
2✔
786
                             f1.iM, f2.iM, prod_fg, pcl_fg)
787
    return clb.reshape(pcl_shape)
2✔
788

789

790
def uncorr_noise_deprojection_bias(f1, map_var, n_iter=None):
2✔
791
    """ Computes the bias associated to contaminant removal in the
792
    presence of uncorrelated inhomogeneous noise to the
793
    auto-pseudo-:math:`C_\\ell` of a given field.
794

795
    Args:
796
        f1 (:class:`~pymaster.field.NmtField`): Field being correlated.
797
        map_var (`array`): Single map containing the local noise
798
            variance in one steradian. The map should have the same
799
            pixelization used by ``f1``.
800
        n_iter (:obj:`int`): Number of iterations when computing
801
            :math:`a_{\\ell m}` s. See docstring of
802
            :class:`~pymaster.field.NmtField`.
803

804
    Returns:
805
        (`array`): Deprojection bias pseudo-:math:`C_\\ell`.
806
    """
807
    if f1.lite:
2✔
808
        raise ValueError("Can't compute deprojection bias for "
2✔
809
                         "lightweight fields")
810
    if n_iter is None:
2✔
811
        n_iter = ut.nmt_params.n_iter_default
2✔
812

813
    # Flatten in case it's a 2D map
814
    sig2 = map_var.flatten()
2✔
815
    if len(sig2) != f1.minfo.npix:
2✔
816
        raise ValueError("Variance map doesn't match map resolution")
2✔
817

818
    pcl_shape = (f1.nmaps * f1.nmaps, f1.ainfo.lmax+1)
2✔
819

820
    # Return if no contamination
821
    if f1.n_temp == 0:
2✔
822
        return np.zeros(pcl_shape)
2✔
823

824
    clb = np.zeros((f1.nmaps, f1.nmaps, f1.ainfo.lmax+1))
2✔
825

826
    # First term in Eq. 39 of the NaMaster paper
827
    pcl_ff = np.zeros((f1.n_temp, f1.n_temp,
2✔
828
                       f1.nmaps, f1.nmaps,
829
                       f1.ainfo.lmax+1))
830
    for j, fj in enumerate(f1.temp):
2✔
831
        # SHT(v^2 sig^2 f_j)
832
        fj_v_s = ut.map2alm(fj*(f1.mask**2*sig2)[None, :], f1.spin,
2✔
833
                            f1.minfo, f1.ainfo, n_iter=n_iter)
834
        for i, fi in enumerate(f1.alm_temp):
2✔
835
            cl = np.array([[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
2✔
836
                            for a1 in fi]
837
                           for a2 in fj_v_s])
838
            pcl_ff[i, j, :, :, :] = cl
2✔
839
    clb -= 2*np.einsum('ij,ijklm', f1.iM, pcl_ff)
2✔
840

841
    # Second term in Eq. 39 of the namaster paper
842
    # PCL(fi, fs)
843
    pcl_ff = np.array([[[[hp.alm2cl(a1, a2, lmax=f1.ainfo.lmax)
2✔
844
                          for a2 in fs]
845
                         for a1 in fi]
846
                        for fs in f1.alm_temp]
847
                       for fi in f1.alm_temp])
848
    # Int[fj * fr * v^2 * sig^2]
849
    prod_ff = np.array([[
2✔
850
        f1.minfo.si.dot_map(fj, fr*(f1.mask**2*sig2)[None, :])
851
        for fr in f1.temp] for fj in f1.temp])
852
    clb += np.einsum('ij,rs,jr,isklm', f1.iM, f1.iM, prod_ff, pcl_ff)
2✔
853

854
    return clb.reshape(pcl_shape)
2✔
855

856

857
def deprojection_bias_flat(f1, f2, b, ells, cl_guess,
2✔
858
                           ell_cut_x=[1., -1.], ell_cut_y=[1., -1.]):
859
    """ Computes the bias associated to contaminant removal to the
860
    cross-pseudo-:math:`C_\\ell` of two flat-sky fields. See Eq. 50 in
861
    the NaMaster paper.
862

863
    Args:
864
        f1 (:class:`~pymaster.field.NmtFieldFlat`): First field to
865
            correlate.
866
        f2 (:class:`~pymaster.field.NmtFieldFlat`): Second field to
867
            correlate.
868
        b (:class:`~pymaster.bins.NmtBinFlat`): Binning scheme defining
869
            the output bandpowers.
870
        ells (`array`): List of multipoles on which the guess power
871
            spectra are defined.
872
        cl_guess (`array`): Array of power spectra corresponding to a
873
            best-guess of the true power spectra of ``f1`` and ``f2``.
874
        ell_cut_x (`array`): Sequence of two elements determining the
875
            range of :math:`l_x` to remove from the calculation. No
876
            Fourier modes removed by default.
877
        ell_cut_y (`array`): Sequence of two elements determining the
878
            range of :math:`l_y` to remove from the calculation. No
879
            Fourier modes removed by default.
880

881
    Returns:
882
        (`array`): Deprojection bias pseudo-:math:`C_\\ell`.
883
    """
884
    if len(cl_guess) != f1.fl.nmaps * f2.fl.nmaps:
2✔
885
        raise ValueError("Proposal Cell doesn't match number of maps")
2✔
886
    if len(cl_guess[0]) != len(ells):
2✔
887
        raise ValueError("cl_guess and ells must have the same length")
2✔
888
    cl1d = lib.comp_deproj_bias_flat(
2✔
889
        f1.fl,
890
        f2.fl,
891
        b.bin,
892
        ell_cut_x[0],
893
        ell_cut_x[1],
894
        ell_cut_y[0],
895
        ell_cut_y[1],
896
        ells,
897
        cl_guess,
898
        f1.fl.nmaps * f2.fl.nmaps * b.bin.n_bands,
899
    )
900
    cl2d = np.reshape(cl1d, [f1.fl.nmaps * f2.fl.nmaps, b.bin.n_bands])
2✔
901

902
    return cl2d
2✔
903

904

905
def compute_coupled_cell(f1, f2):
2✔
906
    """ Computes the full-sky pseudo-:math:`C_\\ell` of two masked
907
    fields (``f1`` and ``f2``) without aiming to deconvolve the
908
    mode-coupling matrix (Eq. 7 of the NaMaster paper). Effectively,
909
    this is equivalent to calling the usual HEALPix `anafast
910
    <https://healpy.readthedocs.io/en/latest/generated/healpy.sphtfunc.anafast.html>`_
911
    routine on the masked and contaminant-cleaned maps.
912

913
    Args:
914
        f1 (:class:`~pymaster.field.NmtField`): First field to
915
            correlate.
916
        f2 (:class:`~pymaster.field.NmtField`): Second field to
917
            correlate.
918

919
    Returns:
920
        (`array`): Array of coupled pseudo-:math:`C_\\ell` s.
921
    """  # noqa
922
    if not f1.is_compatible(f2, strict=False):
2✔
923
        raise ValueError("You're trying to correlate incompatible fields")
2✔
924
    alm1 = f1.get_alms()
2✔
925
    alm2 = f2.get_alms()
2✔
926
    ncl = len(alm1) * len(alm2)
2✔
927
    lmax = min(f1.ainfo.lmax, f2.ainfo.lmax)
2✔
928

929
    Nf = 0
2✔
930
    if f2 is f1:
2✔
931
        Nf = f1.Nf
2✔
932

933
    cls = np.array([[hp.alm2cl(a1, a2, lmax=lmax)
2✔
934
                     for a2 in alm2] for a1 in alm1])
935
    if Nf != 0:
2✔
UNCOV
936
        for i in range(len(alm1)):
×
UNCOV
937
            cls[i, i, :] -= Nf
×
938
    cls = cls.reshape([ncl, lmax+1])
2✔
939
    return cls
2✔
940

941

942
def compute_coupled_cell_flat(f1, f2, b, ell_cut_x=[1., -1.],
2✔
943
                              ell_cut_y=[1., -1.]):
944
    """ Computes the flat-sky pseudo-:math:`C_\\ell` of two masked
945
    fields (``f1`` and ``f2``) without aiming to deconvolve the
946
    mode-coupling matrix (Eq. 42 of the NaMaster paper). Effectively,
947
    this is equivalent to computing the map FFTs and
948
    averaging over rings of wavenumber.  The returned power
949
    spectrum is defined at the multipoles returned by the
950
    method :meth:`~pytest.field.NmtFieldFlat.get_ell_sampling`
951
    of either ``f1`` or ``f2``.
952

953
    Args:
954
        f1 (:class:`~pymaster.field.NmtFieldFlat`): First field to
955
            correlate.
956
        f2 (:class:`~pymaster.field.NmtFieldFlat`): Second field to
957
            correlate.
958
        b (:class:`~pymaster.bins.NmtBinFlat`): Binning scheme defining
959
            the output bandpowers.
960
        ell_cut_x (`array`): Sequence of two elements determining the
961
            range of :math:`l_x` to remove from the calculation. No
962
            Fourier modes removed by default.
963
        ell_cut_y (`array`): Sequence of two elements determining the
964
            range of :math:`l_y` to remove from the calculation. No
965
            Fourier modes removed by default.
966

967
    Returns:
968
        (`array`): Array of coupled pseudo-:math:`C_\\ell` s.
969
    """
970
    if (f1.nx != f2.nx) or (f1.ny != f2.ny):
2✔
971
        raise ValueError("Fields must have same resolution")
2✔
972

973
    cl1d = lib.comp_pspec_coupled_flat(
2✔
974
        f1.fl,
975
        f2.fl,
976
        b.bin,
977
        f1.fl.nmaps * f2.fl.nmaps * b.bin.n_bands,
978
        ell_cut_x[0],
979
        ell_cut_x[1],
980
        ell_cut_y[0],
981
        ell_cut_y[1],
982
    )
983
    clout = np.reshape(cl1d, [f1.fl.nmaps * f2.fl.nmaps, b.bin.n_bands])
2✔
984

985
    return clout
2✔
986

987

988
def compute_full_master(f1, f2, b=None, cl_noise=None, cl_guess=None,
2✔
989
                        workspace=None, l_toeplitz=-1, l_exact=-1, dl_band=-1,
990
                        normalization='MASTER'):
991
    """ Computes the full MASTER estimate of the power spectrum of two
992
    fields (``f1`` and ``f2``). This is equivalent to sequentially calling:
993

994
    - :meth:`NmtWorkspace.compute_coupling_matrix`
995
    - :meth:`deprojection_bias`
996
    - :meth:`compute_coupled_cell`
997
    - :meth:`NmtWorkspace.decouple_cell`
998

999

1000
    Args:
1001
        fl1 (:class:`~pymaster.field.NmtField`): First field to
1002
            correlate.
1003
        fl2 (:class:`~pymaster.field.NmtField`): Second field to
1004
            correlate.
1005
        b (:class:`~pymaster.bins.NmtBin`): Binning scheme.
1006
        cl_noise (`array`): Noise bias (i.e. angular
1007
            pseudo-:math:`C_\\ell` of masked noise realizations).
1008
        cl_guess (`array`): Array of power spectra corresponding to a
1009
            best-guess of the true power spectra of ``f1`` and ``f2``.
1010
        workspace (:class:`~pymaster.workspaces.NmtWorkspace`):
1011
            Object containing the mode-coupling matrix associated with
1012
            an incomplete sky coverage. If provided, the function will
1013
            skip the computation of the mode-coupling matrix and use
1014
            the information encoded in this object.
1015
        l_toeplitz (:obj:`int`): If a positive number, the Toeplitz
1016
            approximation described in `Louis et al. 2020
1017
            <https://arxiv.org/abs/2010.14344>`_ will be used.
1018
            In that case, this quantity corresponds to
1019
            :math:`\\ell_{\\rm toeplitz}` in Fig. 3 of that paper.
1020
        l_exact (:obj:`int`): If ``l_toeplitz>0``, it corresponds to
1021
            :math:`\\ell_{\\rm exact}` in Fig. 3 of the paper.
1022
            Ignored if ``l_toeplitz<=0``.
1023
        dl_band (:obj:`int`): If ``l_toeplitz>0``, this quantity
1024
            corresponds to :math:`\\Delta \\ell_{\\rm band}` in Fig.
1025
            3 of the paper. Ignored if ``l_toeplitz<=0``.
1026
        normalization (:obj:`str`): Normalization convention to use for
1027
            the bandpower window functions. Two options supported:
1028
            `'MASTER'` (default) corresponds to the standard inversion
1029
            of the binned mode-coupling matrix. `'FKP'` simply divides
1030
            by the mean of the mask product, forcing a unit response
1031
            to an input white spectrum.
1032

1033
    Returns:
1034
        (`array`): Set of decoupled bandpowers.
1035
    """
1036
    if (b is None) and (workspace is None):
2✔
1037
        raise SyntaxError("Must supply either workspace or bins.")
2✔
1038
    if not f1.is_compatible(f2, strict=False):
2✔
1039
        raise ValueError("Fields have incompatible pixelizations.")
2✔
1040
    pcl_shape = (f1.nmaps * f2.nmaps, f1.ainfo.lmax+1)
2✔
1041

1042
    if cl_noise is not None:
2✔
1043
        if cl_noise.shape != pcl_shape:
2✔
1044
            raise ValueError(
2✔
1045
                f"Noise Cl should have shape {pcl_shape}")
1046
        pcln = cl_noise
2✔
1047
    else:
1048
        pcln = np.zeros(pcl_shape)
2✔
1049
    if cl_guess is not None:
2✔
1050
        if cl_guess.shape != pcl_shape:
2✔
1051
            raise ValueError(
2✔
1052
                f"Guess Cl should have shape {pcl_shape}")
1053
        clg = cl_guess
2✔
1054
    else:
1055
        clg = np.zeros(pcl_shape)
2✔
1056

1057
    # Data power spectrum
1058
    pcld = compute_coupled_cell(f1, f2)
2✔
1059
    # Deprojection bias
1060
    pclb = deprojection_bias(f1, f2, clg)
2✔
1061

1062
    if workspace is None:
2✔
1063
        w = NmtWorkspace.from_fields(
2✔
1064
            fl1=f1, fl2=f2, bins=b,
1065
            l_toeplitz=l_toeplitz,
1066
            l_exact=l_exact, dl_band=dl_band,
1067
            normalization=normalization)
1068
    else:
1069
        w = workspace
2✔
1070

1071
    return w.decouple_cell(pcld - pclb - pcln)
2✔
1072

1073

1074
def compute_full_master_flat(f1, f2, b, cl_noise=None, cl_guess=None,
2✔
1075
                             ells_guess=None, workspace=None,
1076
                             ell_cut_x=[1., -1.], ell_cut_y=[1., -1.]):
1077
    """
1078
    Computes the full MASTER estimate of the power spectrum of two
1079
    flat-sky fields (``f1`` and ``f2``). This is equivalent to
1080
    sequentially calling:
1081

1082
    - :meth:`NmtWorkspaceFlat.compute_coupling_matrix`
1083
    - :meth:`deprojection_bias_flat`
1084
    - :meth:`compute_coupled_cell_flat`
1085
    - :meth:`NmtWorkspaceFlat.decouple_cell`
1086

1087
    Args:
1088
        f1 (:class:`~pymaster.field.NmtFieldFlat`): First field to
1089
            correlate.
1090
        f2 (:class:`~pymaster.field.NmtFieldFlat`): Second field to
1091
            correlate.
1092
        b (:class:`~pymaster.bins.NmtBinFlat`): Binning scheme defining
1093
            the output bandpowers.
1094
        cl_noise (`array`): Noise bias (i.e. angular
1095
            pseudo-:math:`C_\\ell` of masked noise realisations).
1096
        cl_guess (`array`): Array of power spectra corresponding to a
1097
            best-guess of the true power spectra of ``f1`` and ``f2``.
1098
        ells_guess (`array`): List of multipoles on which the guess power
1099
            spectra are defined.
1100
        workspace (:class:`~pymaster.workspaces.NmtWorkspaceFlat`):
1101
            Object containing the mode-coupling matrix associated with
1102
            an incomplete sky coverage. If provided, the function will
1103
            skip the computation of the mode-coupling matrix and use
1104
            the information encoded in this object.
1105
        ell_cut_x (`array`): Sequence of two elements determining the
1106
            range of :math:`l_x` to remove from the calculation. No
1107
            Fourier modes removed by default.
1108
        ell_cut_y (`array`): Sequence of two elements determining the
1109
            range of :math:`l_y` to remove from the calculation. No
1110
            Fourier modes removed by default.
1111

1112
    Returns:
1113
        (`array`): Set of decoupled bandpowers.
1114
    """
1115
    if (f1.nx != f2.nx) or (f1.ny != f2.ny):
2✔
1116
        raise ValueError("Fields must have same resolution")
2✔
1117
    if cl_noise is not None:
2✔
1118
        if (len(cl_noise) != f1.fl.nmaps * f2.fl.nmaps) or (
2✔
1119
            len(cl_noise[0]) != b.bin.n_bands
1120
        ):
1121
            raise ValueError("Wrong length for noise power spectrum")
2✔
1122
        cln = cl_noise.copy()
2✔
1123
    else:
1124
        cln = np.zeros([f1.fl.nmaps * f2.fl.nmaps, b.bin.n_bands])
2✔
1125
    if cl_guess is not None:
2✔
1126
        if ells_guess is None:
2✔
1127
            raise ValueError("Must provide ell-values for cl_guess")
2✔
1128
        if (len(cl_guess) != f1.fl.nmaps * f2.fl.nmaps) or (
2✔
1129
            len(cl_guess[0]) != len(ells_guess)
1130
        ):
1131
            raise ValueError("Wrong length for guess power spectrum")
2✔
1132
        lf = ells_guess.copy()
2✔
1133
        clg = cl_guess.copy()
2✔
1134
    else:
1135
        lf = b.get_effective_ells()
2✔
1136
        clg = np.zeros([f1.fl.nmaps * f2.fl.nmaps, b.bin.n_bands])
2✔
1137

1138
    if workspace is None:
2✔
1139
        cl1d = lib.comp_pspec_flat(
2✔
1140
            f1.fl,
1141
            f2.fl,
1142
            b.bin,
1143
            None,
1144
            cln,
1145
            lf,
1146
            clg,
1147
            len(cln) * b.bin.n_bands,
1148
            ell_cut_x[0],
1149
            ell_cut_x[1],
1150
            ell_cut_y[0],
1151
            ell_cut_y[1],
1152
        )
1153
    else:
1154
        cl1d = lib.comp_pspec_flat(
2✔
1155
            f1.fl,
1156
            f2.fl,
1157
            b.bin,
1158
            workspace.wsp,
1159
            cln,
1160
            lf,
1161
            clg,
1162
            len(cln) * b.bin.n_bands,
1163
            ell_cut_x[0],
1164
            ell_cut_x[1],
1165
            ell_cut_y[0],
1166
            ell_cut_y[1],
1167
        )
1168

1169
    clout = np.reshape(cl1d, [len(cln), b.bin.n_bands])
2✔
1170

1171
    return clout
2✔
1172

1173

1174
def get_general_coupling_matrix(pcl_mask, s1, s2, n1, n2):
2✔
1175
    """ Returns a general mode-coupling matrix of the form
1176

1177
    .. math::
1178
      M_{\\ell \\ell'}=\\sum_{\\ell''}
1179
      \\frac{(2\\ell'+1)(2\\ell''+1)}{4\\pi}
1180
      \\tilde{C}^{uv}_\\ell
1181
      \\left(\\begin{array}{ccc}
1182
      \\ell & \\ell' & \\ell'' \\\\
1183
      n_1 & -s_1 & s_1-n_1
1184
      \\end{array}\\right)
1185
      \\left(\\begin{array}{ccc}
1186
      \\ell & \\ell' & \\ell'' \\\\
1187
      n_2 & -s_2 & s_2-n_2
1188
      \\end{array}\\right)
1189

1190
    Args:
1191
        pcl_mask (`array`): 1D array containing the power spectrum
1192
          of the masks :math:`\\tilde{C}_\\ell^{uw}`.
1193
        s1 (:obj:`int`): spin index :math:`s_1` above.
1194
        s2 (:obj:`int`): spin index :math:`s_2` above.
1195
        n1 (:obj:`int`): spin index :math:`n_1` above.
1196
        n2 (:obj:`int`): spin index :math:`n_2` above.
1197

1198
    Returns:
1199
        (`array`): 2D array of shape ``[nl, nl]``, where ``nl`` is
1200
        the size of ``pcl_mask``, containing the mode-coupling
1201
        matrix for multipoles from 0 to ``nl-1``.
1202
    """
1203

1204
    lmax = len(pcl_mask)-1
2✔
1205
    xi = lib.comp_general_coupling_matrix(
2✔
1206
        int(s1), int(s2), int(n1),
1207
        int(n2), int(lmax),
1208
        pcl_mask, int((lmax+1)**2))
1209
    xi = xi.reshape([lmax+1, lmax+1])
2✔
1210
    return xi
2✔
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