lenspyx.lensing
CMB lensing operations: lensed map synthesis and related utilities.
This module provides functions for computing gravitationally lensed CMB maps from spherical harmonic coefficients. Gravitational lensing remaps the CMB temperature and polarization by deflecting photon paths according to the intervening mass distribution.
Overview
Gravitational lensing modifies the observed CMB by deflecting photon paths. The lensing operation remaps the CMB according to:
where \(\phi\) is the lensing potential and \(X\) represents T, Q, or U Stokes polarization modes.
For polarization, the Stokes parameters Q and U are additionally rotated by the lensing-induced angle \(\gamma\).
The implementation uses exact (non-perturbative) lensing via interpolation on the sphere,
with configurable accuracy through the epsilon parameter.
API Reference
Main Functions
- lenspyx.lensing.synfast(cls: dict, lmax=None, mmax=None, geometry=('healpix', {'nside': 2048}), epsilon=1e-07, nthreads=0, alm=False, seed=None, verbose=0)[source]
Generate lensed CMB realizations from power spectra.
Creates correlated Gaussian random fields on the sphere and applies gravitational lensing according to the input power spectra. This is the standard way to generate lensed CMB simulations.
- Parameters:
cls (dict) –
Dictionary of auto- and cross-power spectra with string keys. Recognized field labels (case-insensitive):
’T’ or ‘t’ : CMB temperature (spin-0 intensity)
’E’ or ‘e’ : E-mode polarization
’B’ or ‘b’ : B-mode polarization
’P’ or ‘p’ : Lensing potential \(\phi\)
’O’ or ‘o’ : Lensing curl potential \(\Omega\)
Keys are two-character strings like ‘TT’, ‘TE’, ‘EE’, ‘PP’, etc. Arrays must be \(C_\ell\) (not \(D_\ell = \ell(\ell+1)C_\ell/(2\pi)\)).
Important:
If auto-spectrum ‘AA’ is absent, field ‘A’ is assumed zero
If neither ‘PP’ nor ‘OO’ are present, output maps are unlensed
All relevant cross-spectra must be provided for correlated fields
lmax (int, optional) – Band-limit of unlensed alms. If None, inferred from length of input spectra.
mmax (int, optional) – Maximum azimuthal mode number. If None, defaults to lmax.
geometry (tuple of (str, dict), optional) – Pixelization: (geometry_name, parameters). Default: (‘healpix’, {‘nside’: 2048})
epsilon (float, optional) – Target numerical accuracy for lensing. Default: 1e-7. Execution time has weak dependence on this.
nthreads (int, optional) – Number of threads for SHTs. If 0, uses
os.cpu_count(). Default: 0.alm (bool, optional) – If True, also returns unlensed alms. Default: False.
seed (int, optional) – Random number generator seed for reproducible results. Default: None (random).
verbose (int, optional) – Print timing information if non-zero. Default: 0.
- Returns:
maps (dict) – Dictionary of lensed maps:
’T’ : Lensed temperature (if ‘TT’ was in cls and non-zero)
’QU’ : Lensed Q and U Stokes parameters, shape (2, npix) (if ‘EE’ or ‘BB’ were in cls and non-zero)
alms (tuple, optional (if alm=True)) – Tuple of (alm_arrays, field_labels) where:
alm_arrays : shape (nfields, nalm) with unlensed alms
field_labels : string indicating field ordering (e.g., ‘tebp’)
Examples
Generate lensed CMB with temperature and polarization:
>>> from lenspyx.lensing import synfast >>> from lenspyx.utils import camb_clfile >>> # Load power spectra >>> cls = camb_clfile('cosmo_params.ini') >>> # Generate lensed realization >>> maps = synfast(cls, lmax=3000, geometry=('healpix', {'nside': 2048}), ... nthreads=8, seed=42) >>> t_lensed = maps['T'] >>> q_lensed, u_lensed = maps['QU']
Generate and return unlensed alms:
>>> maps, (alms, labels) = synfast(cls, lmax=3000, alm=True, seed=42) >>> # labels might be 'tebp' for T, E, B, phi >>> if 't' in labels: ... alm_t = alms[labels.index('t')]
Generate unlensed maps (no lensing potential in cls):
>>> cls_unlensed = {'tt': cl_tt, 'ee': cl_ee, 'te': cl_te} >>> maps_unlensed = synfast(cls_unlensed, lmax=2000)
Notes
The function:
Generates correlated Gaussian random alms from the input \(C_\ell\)
If lensing potentials (P or O) are present, computes deflection field
Applies exact (non-perturbative) lensing via interpolation
Returns lensed maps on the specified geometry
The lensing deflection is computed as:
\[d_{\ell m} = \sqrt{\ell(\ell+1)} \left(\phi_{\ell m} + i \Omega_{\ell m}\right)\]See also
alm2lenmapLens pre-existing alms
lenspyx.utils.camb_clfileRead CAMB power spectrum files
- lenspyx.lensing.alm2lenmap(alm, dlms, geometry: tuple[str, dict] = ('healpix', {'nside': 2048}), epsilon=1e-07, verbose=0, nthreads: int = 0, pol=True)[source]
Compute lensed CMB maps from unlensed alms and deflection field.
This function performs exact (non-perturbative) gravitational lensing of CMB temperature and polarization maps using interpolation on the deflected sphere.
- Parameters:
alm (array_like or list of array_like) –
Unlensed CMB spherical harmonic coefficients. Can be:
Single array: Temperature only (spin-0)
List of 2 arrays: [T, E] if pol=True, otherwise two spin-0 fields
List of 3 arrays: [T, E, B] if pol=True, otherwise three spin-0 fields
dlms (array_like or list of array_like) –
Spin-1 deflection field in harmonic space:
Single array: Gradient-only deflection \(\sqrt{\ell(\ell+1)}\phi_{\ell m}\)
List of 2 arrays: [gradient, curl] deflections where curl is \(\sqrt{\ell(\ell+1)}\Omega_{\ell m}\)
The curl can be omitted if zero for slightly faster transforms.
geometry (tuple of (str, dict), optional) – Sphere pixelization: (geometry_name, parameters). Default: (‘healpix’, {‘nside’: 2048})
epsilon (float, optional) – Target numerical accuracy of the result. Default: 1e-7. Execution time has only weak dependence on this parameter.
verbose (int, optional) – If non-zero, prints timing and diagnostic information. Default: 0.
nthreads (int, optional) – Number of threads to use. If 0, uses
os.cpu_count(). Default: 0.pol (bool, optional) – If True, interprets input arrays as CMB fields (T, E, B) and returns lensed T, Q, U. If False, performs spin-0 transforms only. Default: True.
- Returns:
maps – Lensed maps, each an array of size npix from the input geometry:
If pol=True and 2-3 input alms: Returns (T, Q, U) tuple
If pol=False or single alm: Returns single map or list of maps
- Return type:
tuple or array_like
Examples
Lens temperature and polarization:
>>> from lenspyx.lensing import alm2lenmap >>> from lenspyx.utils_hp import Alm >>> import numpy as np >>> lmax = 3000 >>> nalm = Alm.getsize(lmax, lmax) >>> # Create unlensed alms >>> alm_t = np.random.randn(nalm) + 1j * np.random.randn(nalm) >>> alm_e = np.random.randn(nalm) + 1j * np.random.randn(nalm) >>> alm_b = np.zeros(nalm, dtype=complex) >>> # Create deflection from lensing potential >>> phi_lm = np.random.randn(nalm) + 1j * np.random.randn(nalm) >>> dlm = utils_hp.almxfl(phi_lm, np.sqrt(np.arange(lmax+1) * np.arange(1, lmax+2)), None, False) >>> # Compute lensed maps >>> t_lens, q_lens, u_lens = alm2lenmap([alm_t, alm_e, alm_b], dlm, ... geometry=('healpix', {'nside': 2048}), ... nthreads=8)
Lens temperature only:
>>> t_lens = alm2lenmap(alm_t, dlm, nthreads=8)
Notes
The lensing operation remaps the CMB according to:
\[X^{\text{lensed}}(\hat{n}) = X^{\text{unlensed}}(\hat{n} + \alpha(\hat{n}))\]where \(\alpha\) is the lensing deflection vector field and \(X\) is T, Q, or U, or another field.
For polarization, the Stokes parameters are rotated by the lensing-induced angle.
See also
alm2lenmap_spinLens arbitrary spin-weight fields
synfastGenerate lensed realizations from power spectra
dlm2anglesGet deflected angles from deflection alms
- lenspyx.lensing.alm2lenmap_spin(gclm: ndarray, dlms: ndarray, spin: int, geometry: tuple[str, dict] = ('healpix', {'nside': 2048}), epsilon: float = 1e-07, verbose=0, nthreads: int = 0)[source]
Compute lensed spin-weighted map from gradient/curl modes and deflection field.
This function remaps arbitrary spin-s fields.
- Parameters:
gclm (array_like or list of array_like) –
Unlensed gradient and curl mode alms (E and B modes for spin-2).
Single array: Gradient-only (e.g., E-mode only)
List of 2 arrays: [gradient, curl] (e.g., [E, B])
dlms (array_like or list of array_like) –
Spin-1 deflection field in harmonic space:
Single array: Gradient-only deflection \(\sqrt{\ell(\ell+1)}\phi_{\ell m}\)
List of 2 arrays: [gradient, curl] deflections
spin (int) –
Spin weight of the field to deflect (≥ 0). Examples:
spin=0 : Scalar field (temperature)
spin=2 : Polarization (most common)
spin≥3 : Higher-spin fields
geometry (tuple of (str, dict), optional) – Sphere pixelization. Default: (‘healpix’, {‘nside’: 2048})
epsilon (float, optional) – Target numerical accuracy. Default: 1e-7
verbose (int, optional) – Print timing information if non-zero. Default: 0
nthreads (int, optional) – Number of threads. If 0, uses
os.cpu_count(). Default: 0
- Returns:
maps – Lensed maps with shape (2, npix) containing the real and imaginary parts of the spin-s field (or shape (1, npix) for spin-0).
- Return type:
array_like
Notes
For a spin-s field, the lensing operation includes both deflection and rotation:
\[{}_s X^{\text{lensed}}(\hat{n}) = e^{is\gamma(\hat{n})} {}_s X^{\text{unlensed}}(\hat{n}')\]where \(\gamma\) is the rotation angle induced by lensing and \(\hat{n}' = \hat{n} + \alpha(\hat{n})\) is the deflected direction.
If curl modes are zero (for either the deflection or the field alms), they can be omitted, resulting in slightly faster transforms.
Examples
>>> from lenspyx.lensing import alm2lenmap_spin >>> import numpy as np >>> # Lens polarization (spin-2) >>> e_lm = np.random.randn(nalm) + 1j * np.random.randn(nalm) >>> b_lm = np.zeros_like(e_lm) >>> dlm =almxfl(phi_lm, np.sqrt(np.arange(lmax+1) * np.arange(1, lmax+2), None, False) >>> q_u_lensed = alm2lenmap_spin([e_lm, b_lm], dlm, spin=2, nthreads=8) >>> q_lensed = q_u_lensed[0] >>> u_lensed = q_u_lensed[1]
See also
alm2lenmapsame for spin-0 field
dlm2anglesGet deflected angles and rotation
- lenspyx.lensing.dlm2angles(dlms: ndarray, geometry: Geom, mmax=None, nthreads: int = 0, calc_rotation=False)[source]
Convert lensing deflection alms to deflected angles.
Computes the deflected pointing directions (and optionally rotation angles) from the deflection field spherical harmonic coefficients.
- Parameters:
dlms (array_like) –
Spin-1 deflection field in harmonic space. Can be:
Single array: gradient-only deflection \(\sqrt{\ell(\ell+1)}\phi_{\ell m}\) where \(\phi\) is the lensing potential
Two arrays: [gradient, curl] with gradient as above and curl \(\sqrt{\ell(\ell+1)}\Omega_{\ell m}\) where \(\Omega\) is the curl potential
The curl can be omitted if zero, resulting in slightly faster execution.
geometry (Geom) – Sphere pixelization geometry (iso-latitude ring structure).
mmax (int, optional) – Maximum m value of dlms. If None, assumes mmax = lmax.
nthreads (int, optional) – Number of threads to use. If 0, uses
os.cpu_count().calc_rotation (bool, optional) –
If True, also computes the rotation angle \(\gamma\) by which to rotate non-zero spin fields after deflection:
\[{}_s P(\hat{n}) \rightarrow e^{is\gamma(\hat{n})} {}_s P(\hat{n}')\]Default: False
- Returns:
angles – Array of shape (npix, 2) or (npix, 3) containing:
Column 0: Deflected colatitude \(\theta'\) (radians)
Column 1: Deflected longitude \(\phi'\) (radians)
Column 2: Rotation angle \(\gamma\) (radians, only if calc_rotation=True)
- Return type:
array_like
Examples
>>> from lenspyx.lensing import dlm2angles >>> from lenspyx.remapping.utils_geom import Geom >>> import numpy as np >>> # Create deflection from lensing potential >>> lmax = 2048 >>> phi_lm = np.random.randn(Alm.getsize(lmax, lmax)) + \ ... 1j * np.random.randn(Alm.getsize(lmax, lmax)) >>> utils_hp.almxfl(phi_lm, np.sqrt(np.arange(lmax+1) * np.arange(1, lmax+2)), None, False) >>> # Get deflected angles >>> geom = Geom.get_healpix_geometry(nside=1024) >>> angles = dlm2angles(dlm, geom, nthreads=8) >>> theta_deflected = angles[:, 0] >>> phi_deflected = angles[:, 1] >>> # With rotation angles for polarization >>> angles_rot = dlm2angles(dlm, geom, calc_rotation=True, nthreads=8) >>> gamma = angles_rot[:, 2]
Notes
The deflection field is derived from the lensing potentials via:
\[d_{\ell m} = \sqrt{\ell(\ell+1)} \left(\phi_{\ell m} + i \Omega_{\ell m}\right)\]See also
alm2lenmapCompute lensed maps directly
lenspyx.remapping.utils_geom.GeomGeometry class
Examples
Generate a lensed CMB realization
from lenspyx.lensing import synfast
from lenspyx.utils import camb_clfile
# Load CMB power spectra (including lensing potential)
cls = camb_clfile('cosmo2017_10K_acc3_lensedCls.dat')
# Must contain 'tt', 'ee', 'bb', 'te', 'pp' keys
# Generate lensed realization
maps = synfast(cls, lmax=3000,
geometry=('healpix', {'nside': 2048}),
nthreads=8, seed=42, verbose=1)
# Access lensed maps
t_lensed = maps['T'] # Temperature
q_lensed = maps['QU'][0] # Q Stokes parameter
u_lensed = maps['QU'][1] # U Stokes parameter
Lens existing unlensed alms
from lenspyx.lensing import alm2lenmap
from lenspyx.utils_hp import Alm, almxfl
import numpy as np
lmax = 3000
nalm = Alm.getsize(lmax, lmax)
# Create or load unlensed alms
alm_t = np.load('unlensed_alm_t.npy')
alm_e = np.load('unlensed_alm_e.npy')
alm_b = np.zeros(nalm, dtype=complex) # Unlensed B=0
# Create deflection from lensing potential
phi_lm = np.load('phi_lm.npy')
L = np.arange(lmax + 1, dtype=float)
deflection_factor = np.sqrt(L * (L + 1))
dlm = almxfl(phi_lm, deflection_factor, None, False)
# Compute lensed maps
t_lens, q_lens, u_lens = alm2lenmap([alm_t, alm_e, alm_b], dlm,
geometry=('healpix', {'nside': 2048}),
epsilon=1e-7, nthreads=8, verbose=1)
Compute deflected angles from lensing potential
from lenspyx.lensing import dlm2angles
from lenspyx.remapping.utils_geom import Geom
from lenspyx.utils_hp import Alm
import numpy as np
# Lensing potential alms
phi_lm = np.load('phi_lm.npy')
lmax = Alm.getlmax(phi_lm.size, mmax=None)
# Convert to deflection
dlm = phi_lm * np.sqrt(np.arange(lmax+1) * np.arange(1, lmax+2))
# Get geometry
geom = Geom.get_healpix_geometry(nside=1024)
# Compute deflected angles (with rotation for polarization)
angles = dlm2angles(dlm, geom, calc_rotation=True, nthreads=8)
theta_deflected = angles[:, 0] # Deflected colatitude
phi_deflected = angles[:, 1] # Deflected longitude
gamma_rotation = angles[:, 2] # Rotation angle for spin fields
Lens arbitrary spin-weighted fields
from lenspyx.lensing import alm2lenmap_spin
from lenspyx.utils_hp import almxfl
import numpy as np
# For spin-2 field (e.g., polarization)
e_lm = np.load('e_mode_alms.npy')
b_lm = np.load('b_mode_alms.npy')
# Deflection field
dlm = almxfl(phi_lm, np.sqrt(np.arange(lmax+1) * np.arange(1, lmax+2)), None, False)
# Lens the spin-2 field
qu_lensed = alm2lenmap_spin([e_lm, b_lm], dlm, spin=2,
geometry=('healpix', {'nside': 2048}),
nthreads=8)
q_lensed = qu_lensed[0]
u_lensed = qu_lensed[1]
Use different geometries
from lenspyx.lensing import synfast
# HEALPix geometry (most common)
maps_hp = synfast(cls, lmax=2000,
geometry=('healpix', {'nside': 1024}))
# Gauss-Legendre geometry (for high-accuracy applications)
maps_gl = synfast(cls, lmax=2000,
geometry=('gl', {'lmax': 2000}))
Notes
The lensing implementation is exact (non-perturbative), using interpolation
The
epsilonparameter controls numerical accuracyFor reproducible results, always set the
seedparameterThe deflection field is \(d_{\ell m} = \sqrt{\ell(\ell+1)}\left(\phi_{\ell m} + i \Omega_{\ell m}\right)\)
References
See Also
Jupyter notebook:
examples/demo_lenspyx.ipynbfor interactive exampleslenspyx.remapping.deflection_029: Low-level lensing implementationlenspyx.experimental: Optimized transforms for partial-sky data