3.2 — Abell 360 cluster¶

Authors: Xiangchong Li, Prakruth Adari, Anja von der Linden LSST Science Pipelines: v29_0_0_rc5 Kernel: Python-dp1 (uses clmm/pyccl).

Purpose¶

Condensed end-to-end pipeline for Abell 360 at (ra, dec) = (37.865017, 6.982205), z_cl = 0.22: build the source catalog from the pre-extracted AnaCal table, derive the reduced tangential / cross shear profile, fit M_500c with NFW, and build the Schirmer aperture-mass S/N map.

Steps¶

  1. Set up clmm cosmology and pre-compute β_s, β_s² for the SRD source distribution (used only as a reference for the shear-profile overlay; the mass fit later uses the photo-z stacked source n(z)).
  2. Read the pre-extracted catalog catalogs/anacal_catalog_a360.fits, keep is_primary only; load the cached photo-z point estimates + per-source PDFs from rail_data/data_a360_redshift{,_pdfs}.{hdf5, fits}; derive {g,r,i,z}_mag from *_flux_gauss2.
  3. Apply selection mask (wsel > 1e-5, griz mag cuts, FPFS trace cut, and zmode > 0.38 as a background-source photo-z cut; no selection-response correction for the photo-z cut).
  4. Combine 4-band FPFS moments → (e1, e2, R) via utils.calibrate_shapes; rotate to tangential / cross.
  5. Bin tangential / cross shear with fixed Mpc edges [0.3, 0.7, 1.06, 1.62, 2.46, 3.75, 5, 6] using utils.anacal_get_tang_cross.
  6. Overlay an NFW M_500c = 6×10¹⁴ M_⊙, c=4 reference model and report √χ² + χ² p-values for both components.
  7. Stack response-weighted per-source photo-z PDFs → source n(z); integrate against β_s and β_s² to get the cluster-specific cluster_beta_s, cluster_beta_s_sqr.
  8. Fit M_500c with fixed concentration (NFW, c=4) via curve_fit
    • small emcee chain, using the photo-z-stacked β values.
  9. Build a flat-tangent WCS centred on the BCG and evaluate the Schirmer aperture-mass filter on a 121×121 grid (half-extent 0.5°, aperture_size=0.4, a=15, b=150); plot E/B-mode S/N histograms and 2D maps.

Inputs¶

  • catalogs/anacal_catalog_a360.fits
  • rail_data/data_a360_redshift.hdf5
  • rail_data/data_a360_redshift_pdfs.fits

Outputs¶

Inline figures only (RA/Dec scatter, reduced tangential/cross shear vs NFW, source-vs-full n(z), mass posterior histogram, and E/B-mode aperture-mass S/N maps).

In [3]:
%matplotlib inline
%config InlineBackend.figure_format='retina'
import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integrate
import scipy.stats as stats
from matplotlib import cm
from astropy.table import Table
import emcee

import clmm
from clmm import GCData, GalaxyCluster
from clmm import Cosmology, utils as clmm_utils
from clmm.support.sampler import fitters
cosmo = clmm.Cosmology(H0=70.0, Omega_dm0=0.3 - 0.045, Omega_b0=0.045, Omega_k0=0.0)

from utils import (
    anacal_get_tang_cross, calibrate_shapes,
    build_flat_wcs_grid, compute_mass_map, schirmer_filter,
)

z_cl = 0.22

# Assume sources follow the DESC SRD redshift distribution.
z_distrib_func = clmm_utils.redshift_distributions.desc_srd
beta_kwargs = {
    "z_cl": z_cl,
    "z_inf": 10.0,
    "cosmo": cosmo,
    "z_distrib_func": z_distrib_func,
}
beta_s_mean = clmm_utils.compute_beta_s_mean_from_distribution(**beta_kwargs)
beta_s_square_mean = clmm_utils.compute_beta_s_square_mean_from_distribution(**beta_kwargs)
rproj = np.logspace(np.log10(0.3), np.log10(7.), 100)
In [4]:
def Da(z):
    return cosmo.eval_da(z)
In [5]:
import os
import tables_io
from astropy.io import fits

cluster_coords = (37.865017, 6.982205)  # BCG of Abell 360
ra_bcg, dec_bcg = cluster_coords

Dir = "/gpfs/mnt/gpfs02/astro/workarea/xli6/work/DP1"
anacal_table = Table.read(os.path.join(Dir, "catalogs/anacal_catalog_a360.fits"))
anacal_table = anacal_table[anacal_table["is_primary"]]

# Cached photo-z point estimates + per-source PDFs (row-aligned with the
# is_primary anacal table).
a360_redshifts = tables_io.read(
    os.path.join(Dir, "rail_data/data_a360_redshift.hdf5"),
    tType="astropyTable",
)
with fits.open(os.path.join(Dir, "rail_data/data_a360_redshift_pdfs.fits")) as hdul:
    a360_pdfs = hdul[0].data           # shape (N, 501); z-grid = arange(0, 5.01, 0.01)
anacal_table["zbest"] = a360_redshifts["zmode"]
anacal_table["index"] = np.arange(len(anacal_table))

# Derive AB-zero=31.4 mags from the gauss2 fluxes.
mag_zero = 31.4
for b in ("g", "r", "i", "z"):
    anacal_table[f"{b}_mag"] = mag_zero - 2.5 * np.log10(
        anacal_table[f"{b}_flux_gauss2"]
    )

anacal_table0 = anacal_table
/tmp/ipykernel_147858/699198058.py:26: RuntimeWarning: invalid value encountered in log10
  anacal_table[f"{b}_mag"] = mag_zero - 2.5 * np.log10(
In [6]:
plt.scatter(anacal_table["ra"], anacal_table["dec"], s=0.1)
Out[6]:
<matplotlib.collections.PathCollection at 0x14eb4cce54f0>
No description has been provided for this image
In [7]:
print(len(anacal_table0))
113945
In [124]:
plt.hist(anacal_table0["z_mag"], bins=50)
Out[124]:
(array([2.0000e+00, 2.8000e+01, 5.4000e+01, 1.1700e+02, 1.8800e+02,
        2.1200e+02, 2.5900e+02, 3.5400e+02, 4.4800e+02, 6.2700e+02,
        7.8700e+02, 1.1260e+03, 1.5450e+03, 2.0920e+03, 2.9340e+03,
        4.1020e+03, 5.5210e+03, 7.6640e+03, 1.0901e+04, 1.4654e+04,
        1.7363e+04, 1.5074e+04, 1.0107e+04, 5.3420e+03, 3.1340e+03,
        1.7630e+03, 1.0820e+03, 6.7700e+02, 4.4800e+02, 2.9300e+02,
        1.9100e+02, 9.7000e+01, 9.4000e+01, 6.3000e+01, 4.2000e+01,
        3.9000e+01, 1.9000e+01, 1.1000e+01, 7.0000e+00, 8.0000e+00,
        2.0000e+00, 2.0000e+00, 0.0000e+00, 2.0000e+00, 0.0000e+00,
        0.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00]),
 array([15.12294085, 15.53348562, 15.94403039, 16.35457516, 16.76511993,
        17.1756647 , 17.58620946, 17.99675423, 18.407299  , 18.81784377,
        19.22838854, 19.63893331, 20.04947808, 20.46002285, 20.87056762,
        21.28111239, 21.69165716, 22.10220193, 22.5127467 , 22.92329147,
        23.33383623, 23.744381  , 24.15492577, 24.56547054, 24.97601531,
        25.38656008, 25.79710485, 26.20764962, 26.61819439, 27.02873916,
        27.43928393, 27.8498287 , 28.26037347, 28.67091823, 29.081463  ,
        29.49200777, 29.90255254, 30.31309731, 30.72364208, 31.13418685,
        31.54473162, 31.95527639, 32.36582116, 32.77636593, 33.1869107 ,
        33.59745547, 34.00800024, 34.418545  , 34.82908977, 35.23963454,
        35.65017931]),
 <BarContainer object of 50 artists>)
No description has been provided for this image
In [139]:
msk = np.logical_and.reduce((
    (anacal_table0["zbest"] > 0.40),
    (anacal_table0["zbest"] < 2.0),
    (anacal_table0["wsel"] > 1e-5),
    (anacal_table0["g_mag"] < 25.5),
    (anacal_table0["r_mag"] < 25.0),
    (anacal_table0["i_mag"] < 23.5),
    (anacal_table0["z_mag"] < 24.5),
    (((anacal_table0["i_fpfs1_m20"] + anacal_table0["i_fpfs1_m00"]) / anacal_table0["i_fpfs1_m00"]) > 0.1),
))
print(np.sum(msk))
anacal_table = anacal_table0[msk]
26591
In [140]:
e1, e2, res = calibrate_shapes(anacal_table)
anacal_table["response"] = res
In [141]:
source_phi = np.arctan2(
    anacal_table['dec'] - cluster_coords[1],
    (cluster_coords[0] - anacal_table['ra']) * np.cos(np.deg2rad(cluster_coords[1])),
)
ang_dist = np.sqrt(
    ((anacal_table['ra'] - cluster_coords[0]) * np.cos(np.deg2rad(cluster_coords[1]))) ** 2
    + (anacal_table['dec'] - cluster_coords[1]) ** 2
)
sky_distance = Da(z_cl) * ang_dist * (np.pi / 180)

trial_shear = e1 + 1j * e2
cl_shear = trial_shear * -np.exp(-2j * source_phi)

bins_mpc = np.array([0.3, 0.7, 1.06495522, 1.62018517, 2.46489237, 3.75, 5, 6])
bin_mids = 0.5 * (bins_mpc[1:] + bins_mpc[:-1])
shear_cl = anacal_get_tang_cross(
    cl_shear, sky_distance, bins_mpc, res, ci_level=.68,
)

moo = clmm.Modeling(massdef="critical", delta_mdef=500, halo_profile_model="nfw")
moo.set_cosmo(cosmo)
moo.set_concentration(4)
moo.set_mass(6e14)
gt_z = moo.eval_reduced_tangential_shear(
    rproj, z_cl, [beta_s_mean, beta_s_square_mean],
    z_src_info="beta", approx="order2",
)
In [142]:
plt.close()
cmap = cm.coolwarm

plt.plot(rproj, gt_z, label=r'NFW, $M_{500c}$=$6 \times 10^{14} M_{\odot}$, c=4, n(z)=SRD', ls=':')
plt.plot(bin_mids, shear_cl[0], '.', label='tangential', color=cmap(.05))
plt.vlines(bin_mids, shear_cl[2][:,0], shear_cl[2][:,1], color=cmap(0.05))

plt.plot(1.05*bin_mids, shear_cl[1], '.', label='cross', color=cmap(.95))
plt.vlines(1.05*bin_mids, shear_cl[3][:,0], shear_cl[3][:,1], color=cmap(0.95))
chi2_t = np.sum(shear_cl[0] ** 2.0 / ((shear_cl[2][:,0] - shear_cl[2][:,1]) / 2.0) ** 2.0)
chi2_x = np.sum(shear_cl[1] ** 2.0 / ((shear_cl[3][:,0] - shear_cl[3][:,1]) / 2.0) ** 2.0)

plt.semilogx()
plt.axhline(0, ls='--', color='k', alpha=.5)

plt.ylim([-0.05,0.10])
plt.xlim([0.45,7.5])
plt.ylabel("Reduced shear")
plt.xlabel("R (Mpc)")
plt.legend(frameon=False, loc='upper right', fontsize=15)
Out[142]:
<matplotlib.legend.Legend at 0x14eb2a74f710>
No description has been provided for this image
In [143]:
print(np.sqrt(chi2_t))
print(np.sqrt(chi2_x))
4.581021216262394
1.76505803184541

Source-sample n(z) and β_s¶

Stack the per-source photo-z PDFs of the selected (post-selection) sources, weighted by their shear response, to get the effective source n(z). Compute β_s and β_s² by integrating against that n(z) — these replace the SRD approximation in the mass fit below.

In [144]:
test_zs = np.arange(0, 5.01, 0.01)

nz_ndxs = anacal_table["index"]
response_stacked_nz = np.dot(anacal_table["response"], a360_pdfs[nz_ndxs])
nz_normalization = integrate.trapezoid(response_stacked_nz, test_zs)
normalized_nz = response_stacked_nz / nz_normalization

full_nz = np.sum(a360_pdfs, axis=0)
full_norm_nz = full_nz / integrate.trapezoid(full_nz, test_zs)

cmap = cm.coolwarm
plt.plot(test_zs, normalized_nz, '-', label='Source Sample', color=cmap(0.95))
plt.plot(test_zs, full_norm_nz, '-', label='Full Sample', color=cmap(0.05))
plt.xlabel("Redshift")
plt.ylabel("Normalized $N(z)$")
plt.title("Abell 360 Redshift Distribution")
plt.axvline(z_cl, ls='--', color='k', alpha=0.3)
plt.legend()
Out[144]:
<matplotlib.legend.Legend at 0x14eb2a3c4920>
No description has been provided for this image
In [145]:
def Daz1z2(z1, z2):
    return cosmo.eval_da_z1z2(z1, z2)

def beta_s_of_zs(zl, zs):
    if zs <= zl:
        return 0.0
    return Daz1z2(zl, zs) / cosmo.eval_da(zs) * cosmo.eval_da(1e3) / Daz1z2(zl, 1e3)


def get_beta_betasqr_pdf(nz, beta_s, beta_sqr, zs):
    norm = integrate.trapezoid(nz, zs)
    return (
        integrate.trapezoid(beta_s * nz, zs) / norm,
        integrate.trapezoid(beta_sqr * nz, zs) / norm,
    )


beta_s_vals = np.array([beta_s_of_zs(z_cl, zz) for zz in test_zs])
beta_sqr_vals = beta_s_vals ** 2
cluster_beta_s, cluster_beta_s_sqr = get_beta_betasqr_pdf(
    normalized_nz, beta_s_vals, beta_sqr_vals, test_zs,
)
print(f"cluster_beta_s     = {cluster_beta_s:.4f}")
print(f"cluster_beta_s_sqr = {cluster_beta_s_sqr:.4f}")
cluster_beta_s     = 0.6577
cluster_beta_s_sqr = 0.4609

CLMM mass fit¶

Wrap the selected sources in a clmm.GalaxyCluster, recompute the radial profile in CLMM, fit M_500c with fixed concentration (NFW, c=4) via curve_fit, then refine with a small emcee chain.

In [146]:
def fit_mass(predict_function, fit_data, **kwargs):
    popt, pcov = fitters["curve_fit"](
        predict_function,
        fit_data["radius"],
        fit_data["gt"],
        fit_data["gt_err"],
        **kwargs,
    )
    return {"vals": popt, "err": np.sqrt(np.diag(pcov)), "cov": pcov}


def fixed_concentration_fit(profile, beta, betasqr, cdelta, halo_profile="nfw"):
    fn = lambda radius, logm: clmm.compute_reduced_tangential_shear(
        r_proj=radius,
        mdelta=10 ** logm,
        cdelta=cdelta,
        z_cluster=z_cl,
        z_src=(beta, betasqr),
        z_src_info="beta",
        approx="order2",
        cosmo=cosmo,
        delta_mdef=500,
        massdef="critical",
        halo_profile_model=halo_profile,
    )
    mass_fit = fit_mass(fn, profile.profile, bounds=[10.0, 17.0])
    return fn, mass_fit


def mcmc_fit_1d(profile, fn, init, nwalkers=32, nsteps=100):
    fit_data = profile.profile

    def log_prob(pars):
        if pars[0] < 10 or pars[0] > 16:
            return -np.inf
        model = fn(fit_data["radius"], *pars)
        diff = model - fit_data["gt"]
        sig = fit_data["gt_err"]
        return float(-0.5 * np.sum(diff ** 2 / sig ** 2))

    sampler = emcee.EnsembleSampler(nwalkers, 1, log_prob)
    p0 = np.random.normal(init["vals"], init["err"] * 0.3, (nwalkers, 1))
    p0[p0 < 0] *= -1
    sampler.run_mcmc(p0, nsteps, progress=True)
    return {
        "mcmc_chain": sampler.get_chain().flatten(),
        "mcmc_loglike": sampler.get_log_prob().flatten(),
    }
In [147]:
global_R_fit = float(np.mean(res))
print(f"global R (mass fit) = {global_R_fit:.4f}")

galcat = GCData()
galcat['ra'] = anacal_table['ra']
galcat['dec'] = anacal_table['dec']
galcat['e1'] = e1 / global_R_fit   # AnaCal response correction
galcat['e2'] = e2 / global_R_fit
galcat['id'] = np.arange(len(anacal_table))
galcat['z'] = anacal_table["zbest"]

gc_object = GalaxyCluster("A360", ra_bcg, dec_bcg, z_cl, galcat)
gc_object.compute_tangential_and_cross_components(add=True);
gc_object.make_radial_profile(
    bins=bins_mpc, bin_units='Mpc', add=True,
    cosmo=cosmo, overwrite=True,
    use_weights=False, gal_ids_in_bins=False,
);
profile = gc_object.profile
global R (mass fit) = 0.3460
/gpfs/mnt/gpfs02/astro/workarea/xli6/superonionSDCC/miniconda3/envs/analysis/lib/python3.12/site-packages/clmm/gcdata.py:30: UserWarning: coordinate_system not set, defaulting to 'euclidean'.
  warnings.warn("coordinate_system not set, defaulting to 'euclidean'.")
In [148]:
fn, init = fixed_concentration_fit(
    gc_object, cluster_beta_s, cluster_beta_s_sqr, 4, "nfw",
)
print(f"curve_fit: logM = {init['vals'][0]:.3f} ± {init['err'][0]:.3f}")

mcmc_results = mcmc_fit_1d(gc_object, fn, init, nwalkers=32, nsteps=100)
chain = mcmc_results['mcmc_chain']
ll = mcmc_results['mcmc_loglike']
fin = np.isfinite(ll)
chain = chain[fin]
ll = ll[fin]
curve_fit: logM = 14.630 ± 0.145
100%|██████████| 100/100 [00:08<00:00, 12.19it/s]
  2%|▏         | 2/100 [00:00<00:08, 12.13it/s]
  4%|▍         | 4/100 [00:00<00:09, 10.66it/s]
  6%|▌         | 6/100 [00:00<00:08, 11.52it/s]
  8%|▊         | 8/100 [00:00<00:07, 11.70it/s]
 10%|█         | 10/100 [00:00<00:07, 11.99it/s]
 12%|█▏        | 12/100 [00:01<00:07, 12.14it/s]
 14%|█▍        | 14/100 [00:01<00:06, 12.39it/s]
 16%|█▌        | 16/100 [00:01<00:06, 12.54it/s]
 18%|█▊        | 18/100 [00:01<00:06, 12.47it/s]
 20%|██        | 20/100 [00:01<00:06, 12.53it/s]
 22%|██▏       | 22/100 [00:01<00:06, 12.39it/s]
 24%|██▍       | 24/100 [00:01<00:06, 12.44it/s]
 26%|██▌       | 26/100 [00:02<00:05, 12.42it/s]
 28%|██▊       | 28/100 [00:02<00:05, 12.43it/s]
 30%|███       | 30/100 [00:02<00:05, 12.47it/s]
 32%|███▏      | 32/100 [00:02<00:05, 12.46it/s]
 34%|███▍      | 34/100 [00:02<00:05, 12.45it/s]
 36%|███▌      | 36/100 [00:02<00:05, 12.52it/s]
 38%|███▊      | 38/100 [00:03<00:04, 12.51it/s]
 40%|████      | 40/100 [00:03<00:04, 12.65it/s]
 42%|████▏     | 42/100 [00:03<00:04, 12.52it/s]
 44%|████▍     | 44/100 [00:03<00:04, 12.49it/s]
 46%|████▌     | 46/100 [00:03<00:04, 12.25it/s]
 48%|████▊     | 48/100 [00:03<00:04, 12.29it/s]
 50%|█████     | 50/100 [00:04<00:03, 12.51it/s]
 52%|█████▏    | 52/100 [00:04<00:03, 12.58it/s]
 54%|█████▍    | 54/100 [00:04<00:03, 12.68it/s]
 56%|█████▌    | 56/100 [00:04<00:03, 12.57it/s]
 58%|█████▊    | 58/100 [00:04<00:03, 12.39it/s]
 60%|██████    | 60/100 [00:04<00:03, 12.56it/s]
 62%|██████▏   | 62/100 [00:05<00:03, 12.63it/s]
 64%|██████▍   | 64/100 [00:05<00:02, 12.76it/s]
 66%|██████▌   | 66/100 [00:05<00:02, 13.08it/s]
 68%|██████▊   | 68/100 [00:05<00:02, 12.95it/s]
 70%|███████   | 70/100 [00:05<00:02, 12.92it/s]
 72%|███████▏  | 72/100 [00:05<00:02, 12.75it/s]
 74%|███████▍  | 74/100 [00:05<00:02, 12.04it/s]
 76%|███████▌  | 76/100 [00:06<00:01, 12.33it/s]
 78%|███████▊  | 78/100 [00:06<00:01, 12.53it/s]
 80%|████████  | 80/100 [00:06<00:01, 12.67it/s]
 82%|████████▏ | 82/100 [00:06<00:01, 12.83it/s]
 84%|████████▍ | 84/100 [00:06<00:01, 13.16it/s]
 86%|████████▌ | 86/100 [00:06<00:01, 12.74it/s]
 88%|████████▊ | 88/100 [00:07<00:00, 12.63it/s]
 90%|█████████ | 90/100 [00:07<00:00, 12.25it/s]
 92%|█████████▏| 92/100 [00:07<00:00, 11.38it/s]
 94%|█████████▍| 94/100 [00:07<00:00, 11.44it/s]
 96%|█████████▌| 96/100 [00:07<00:00, 11.76it/s]
 98%|█████████▊| 98/100 [00:07<00:00, 11.93it/s]
100%|██████████| 100/100 [00:08<00:00, 12.08it/s]
100%|██████████| 100/100 [00:08<00:00, 12.37it/s]

In [149]:
fig = plt.figure(figsize=(7, 3))
cmap = cm.coolwarm
mass_bins = np.arange(13.1, 15.5, 0.05)
plt.hist(chain, bins=mass_bins, weights=np.exp(ll), density=True,
         linewidth=2, histtype='step', color=cmap(0.05))
plt.hist(chain, bins=mass_bins, weights=np.exp(ll), density=True,
         linewidth=2, histtype='stepfilled', color=cmap(0.05), alpha=0.2)
plt.xlim(13, 16)
plt.xlabel(r"$\log{M_{500c}/M_\odot}$")
plt.ylabel("Normalized counts")
plt.title("Abell 360 — NFW mass posterior (fixed c=4)")
Out[149]:
Text(0.5, 1.0, 'Abell 360 — NFW mass posterior (fixed c=4)')
No description has been provided for this image

Schirmer aperture-mass S/N map¶

Project sources to flat-sky degrees around the BCG (+x = west, +y = north, matching the AnaCal e1/e2 convention) and evaluate the Schirmer aperture-mass filter on a 121×121 grid covering ±0.5°. The S/N is taken w.r.t. the variance map. Filter shape parameters a=15, b=150, aperture_size=0.4.

In [150]:
N, rr = 121, 0.5
x, y, x_grid, y_grid = build_flat_wcs_grid(
    ra_bcg, dec_bcg, anacal_table['ra'], anacal_table['dec'],
    n=N, half_extent_deg=rr,
)
global_R = float(np.mean(res))
print(f"global R = {global_R:.4f}")
g1 = e1 / global_R
g2 = e2 / global_R
weights = np.ones(len(x))

e_ap, b_ap, v_ap = compute_mass_map(
    x_grid, y_grid, x, y, g1, g2, weights,
    schirmer_filter,
    filter_kwargs={'aperture_size': 0.4, 'a': 15, 'b': 150},
)
global R = 0.3460
In [151]:
test_xs = np.linspace(-5, 5, num=1001)
normal_dist = stats.norm(0, 1).pdf(test_xs)
plt.hist((b_ap / np.sqrt(v_ap)).flatten(), bins=51, label='B Mode',
         histtype='step', density=True)
plt.hist((e_ap / np.sqrt(v_ap)).flatten(), bins=51, label='E Mode',
         histtype='step', density=True)
plt.plot(test_xs, normal_dist, '--')
plt.yscale('log')
plt.ylim(1e-4, 5e-1)
plt.legend()
Out[151]:
<matplotlib.legend.Legend at 0x14eb2a2f9790>
No description has been provided for this image
In [152]:
fig, axs = plt.subplots(ncols=2, figsize=(10, 6))
extent = (rr, -rr, -rr, rr)
sn_v = np.sqrt(np.mean(v_ap))

axs[0].set_title('E-Mode SN')
MapE = axs[0].imshow(e_ap / sn_v, vmin=-2, vmax=5, origin='lower', extent=extent)
y_p, x_p = np.where(e_ap / sn_v == np.max(e_ap / sn_v))
print((e_ap / sn_v)[y_p, x_p])
x_vals = np.linspace(extent[0], extent[1], N)
y_vals = np.linspace(extent[2], extent[3], N)
axs[0].scatter(x_vals[x_p], y_vals[y_p], s=10.0, color='red')

axs[1].set_title('B-Mode SN')
axs[1].imshow(b_ap / sn_v, vmin=-2, vmax=5, origin='lower', extent=extent)

fig.colorbar(MapE, ax=axs, fraction=0.025)
fig.supxlabel('RA',  y=0.14)
fig.supylabel('DEC', x=0.05)
[5.35517838]
Out[152]:
Text(0.05, 0.5, 'DEC')
No description has been provided for this image