A Menagerie of Lenses#

Here we have a quick visual demo of every type of lens in caustics. This is a good way to pick out what you need and quickly copy paste. For all of these lenses we have placed a Sersic source with the same parameters as the background, that way you can visualize the differences between them.

%load_ext autoreload
%autoreload 2

import torch
from torch.nn.functional import avg_pool2d
import matplotlib.pyplot as plt
import numpy as np

import caustics
cosmology = caustics.FlatLambdaCDM()
cosmology.to(dtype=torch.float32)
z_s = torch.tensor(1.0)
z_l = torch.tensor(0.5, dtype=torch.float32)
base_sersic = caustics.Sersic(
    x0=0.1,
    y0=0.1,
    q=0.6,
    phi=np.pi / 3,
    n=2.0,
    Re=1.0,
    Ie=1.0,
)
n_pix = 100
res = 0.05
upsample_factor = 2
fov = res * n_pix
thx, thy = caustics.utils.meshgrid(
    res / upsample_factor,
    upsample_factor * n_pix,
    dtype=torch.float32,
)

plt.imshow(np.log10(base_sersic.brightness(thx, thy).numpy()), origin="lower")
plt.gca().axis("off")
plt.title("Base Sersic")
plt.show()
../_images/9f71b0457969af0b01646065447cb6354cc6378fbf23c4ee395900251b786239.png

Point (Point)#

The simplest lens, an infinitely small point of mass (did someone say black holes?).

lens = caustics.Point(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    Rein=1.0,
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)

Hide code cell source

fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
# axarr[0].imshow(np.log10(convergence.numpy()), origin = "lower")
axarr[0].axis("off")
axarr[0].set_title("Point Convergence not defined")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/181f86ee0d3725fe4e78fd48b8027ceda60cb4701e6ae841cc95f4e70e991b27.png

Singular Isothermal Sphere (SIS)#

An SIS is a mass distribution represented by a constant temperature velocity dispersion of masses. Alternatively, a constant temperature gas in a spherical gravitational potential.

lens = caustics.SIS(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    Rein=1.0,
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)

Hide code cell source

fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
convergence = avg_pool2d(
    lens.convergence(thx, thy).squeeze()[None, None], upsample_factor
).squeeze()
axarr[0].imshow(np.log10(convergence.numpy()), origin="lower")
axarr[0].axis("off")
axarr[0].set_title("SIS Convergence")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/7fbc7060b5f7b39904a6d5ecd0174dea065440d1ea3eb47ebf82810427961b7f.png

Singular Isothermal Ellipsoid (SIE)#

The SIE is just like the SIS except it has been compressed along one axis.

lens = caustics.SIE(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    q=0.6,
    phi=np.pi / 2,
    Rein=1.0,
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)

Hide code cell source

fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
convergence = avg_pool2d(
    lens.convergence(thx, thy).squeeze()[None, None], upsample_factor
).squeeze()
axarr[0].imshow(np.log10(convergence.numpy()), origin="lower")
axarr[0].axis("off")
axarr[0].set_title("SIE Convergence")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/4fdd8ea5c0374e3c53b9412a902f90c7c81869973abf9e8954aaac89ecbe5bc3.png

Elliptical Power Law (EPL)#

This is a power law mass distribution with an elliptical isodensity contour.

lens = caustics.EPL(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    q=0.6,
    phi=np.pi / 2,
    Rein=1.0,
    t=0.5,
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)

Hide code cell source

fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
convergence = avg_pool2d(
    lens.convergence(thx, thy).squeeze()[None, None], upsample_factor
).squeeze()
axarr[0].imshow(np.log10(convergence.numpy()), origin="lower")
axarr[0].axis("off")
axarr[0].set_title("EPL Convergence")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/9f326edcdecc0050e123a0dd6a8be6863663053c12bc214337b7ae101dc3b112.png

Truncated NFW profile (TNFW)#

The TNFW profile is a slight modification to the classic NFW mass profile that approximates the mass distribution of halos in large dark matter simulations. The new density profile is defined as:

\[\rho_{\rm tnfw}(r) = \rho_{\rm nfw}(r)\frac{\tau^2}{\tau^2 + \frac{r^2}{r_s^2}}\]

where \(\tau = \frac{r_t}{r_s}\) is the ratio of the truncation radius to the scale radius. Note that the truncation happens smoothly so there are no sharp boundaries. In the TNFW profile, the mass quantity now actually corresponds the to the total mass since it is no longer divergent. This often means the mass values are larger.

lens = caustics.TNFW(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    mass=1e12,
    Rs=1.0,
    tau=3.0,
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)

Hide code cell source

fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
convergence = avg_pool2d(
    lens.convergence(thx, thy).squeeze()[None, None], upsample_factor
).squeeze()
axarr[0].imshow(np.log10(convergence.numpy()), origin="lower")
axarr[0].axis("off")
axarr[0].set_title("Truncated NFW Convergence")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/ce188a18243681ffe532a219e7d217547847b8ef61f98ec480c8589790ae3011.png

Pseudo Jaffe (PseudoJaffe)#

The Pseudo Jaffe closely approximates an isothermal mass distribution except that it is easier to compute and has finite mass.

\[ \rho(r) = \frac{\rho_0}{\left(1 + \frac{r^2}{r_c^2}\right)\left(1 + \frac{r^2}{r_s^2}\right)} \]

where \(\rho_0\) is the central density limit, \(r_c\) is the core radius, \(r_s\) is the scale radius.

lens = caustics.PseudoJaffe(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    mass=1e13,
    Rc=5e-1,
    Rs=15.0,
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)

Hide code cell source

fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
convergence = avg_pool2d(
    lens.convergence(thx, thy).squeeze()[None, None], upsample_factor
).squeeze()
axarr[0].imshow(np.log10(convergence.numpy()), origin="lower")
axarr[0].axis("off")
axarr[0].set_title("Pseudo Jaffe Convergence")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/7031bcdfd3745ff9b863ed39eab9d9c6e4f826340f3b7ddf813dfc0efa50bd4c.png

External Shear (ExternalShear)#

It is often necessary to embed a lens in an external shear field to account for the fact that the lensing mass is not the only mass in the universe.

lens = caustics.ExternalShear(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    gamma_1=1.0,
    gamma_2=-1.0,
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)

Hide code cell source

fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
# convergence = avg_pool2d(lens.convergence(thx, thy).squeeze()[None, None], upsample_factor).squeeze()
# axarr[0].imshow(np.log10(convergence.numpy()), origin = "lower")
axarr[0].axis("off")
axarr[0].set_title("External Shear Convergence not defined")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/b2719bafec11f7b6e5570d812645d7f57041ab0bdf709f12f3a1db432c09dbf6.png

Mass Sheet (MassSheet)#

This is a simple case of an external shear field which represents an infinite constant surface density sheet.

lens = caustics.MassSheet(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    kappa=1.5,
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)

Hide code cell source

fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
convergence = avg_pool2d(
    lens.convergence(thx, thy).squeeze()[None, None], upsample_factor
).squeeze()
axarr[0].imshow(np.log10(convergence.numpy()), origin="lower")
axarr[0].axis("off")
axarr[0].set_title("Mass Sheet Convergence")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/4392d5439830da76aee3e0f86038af53c921065a7af5e3eabe76d3803b8d35ad.png

Pixelated Convergence (PixelatedConvergence)#

This is an image which can be used to represent the convergence field of a mass distribution. Typically this is useful for taking n-body simulations and using them for lensing, or when using machine learning.

kappa_map = np.load("assets/kappa_maps.npz", allow_pickle=True)["kappa_maps"][1]

lens = caustics.PixelatedConvergence(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    convergence_map=kappa_map,
    pixelscale=res,
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)
fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
convergence = avg_pool2d(
    lens.convergence(thx, thy).squeeze()[None, None], upsample_factor
).squeeze()
axarr[0].imshow(np.log10(convergence.numpy()), origin="lower")
axarr[0].axis("off")
axarr[0].set_title("Pixelated Convergence")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/875e37aa032e2e0020ce472c26c25b89a64933a65a14ad662c30456794a73a77.png

Multipole (Multipole)#

This is a lens type typically used to add a perturbation to another lens. Higher order multipoles allow for more complex perturbations.

lens = caustics.Multipole(
    cosmology=cosmology,
    x0=0.0,
    y0=0.0,
    m=(3, 4),
    a_m=(0.5, 0.25),
    phi_m=(0.25, -0.125),
    z_l=z_l,
    z_s=z_s,
)
sim = caustics.LensSource(
    lens=lens,
    source=base_sersic,
    pixelscale=res,
    pixels_x=n_pix,
    upsample_factor=2,
)
fig, axarr = plt.subplots(1, 2, figsize=(14, 7))
convergence = avg_pool2d(
    lens.convergence(thx, thy).squeeze()[None, None], upsample_factor
).squeeze()
axarr[0].imshow(np.arctan(convergence.numpy()), origin="lower")
axarr[0].axis("off")
axarr[0].set_title("Multipole Convergence")
axarr[1].imshow(np.log10(sim().numpy()), origin="lower")
axarr[1].axis("off")
axarr[1].set_title("Lensed Sersic")
plt.show()
../_images/cc1d4efd5db15081ee63d8cde5194b8ff928e761f6080c2795256dcf1d0624c4.png