Figs 1,4,5 from Matthews+ 2022

This notebook contains routines for reproducing some of the figures from J. H. Matthews et al. 2022, “How do magnetic field models affect astrophysical limits on light axion-like particles? An X-ray case study with NGC 1275”.

At the moment, this page is incomplete and minimally documented, but I will gradually add to it. All the plots are missing results from the Gaussian random field models at the moment because I haven’t incorporated those models into alpro directly. The docstrings and commenting in these scripts is fairly rudimentary.

For limits plots, see the NGC1275 limits page.

Figure 1: Pressure Profile

[1]:
%matplotlib inline
import numpy as np
import alpro
import matplotlib.pyplot as plt
from alpro.models import unit
plt.rcParams["figure.dpi"] = 200
pressure_conversion = 1.0 / (1000.0 * unit.ev)
beta = 100.0

def B_from_P_ylim(Ptuple):
    '''
    function that calculates the relevant limits for B based on pressure limits
    '''
    B1 = 1e6 * np.sqrt((Ptuple[0] / pressure_conversion / beta)  * 8.0 * np.pi)
    B2 = 1e6 * np.sqrt((Ptuple[1] / pressure_conversion / beta)  * 8.0 * np.pi)
    return (B1, B2)

def beta_func(r, alpha=0.5, beta0=100, r0 = 25.0):
    '''
    a power-law variable plasma beta with radius
    '''
    beta = beta0 * (r/r0) ** alpha
    return (beta)

def make_fig1():
    '''
    make the actual figure
    '''

    fig, ax2 = plt.subplots()
    ax = ax2.twinx()

    models = ["1275a", "1275b"]
    label = ["A", "B"]
    color = ["C3", "C0"]
    for i, mod_name in enumerate(models):
        s = alpro.Survival(mod_name)
        s.init_model()

        r = np.logspace(-1,np.log10(2000), 1000)

        B = s.cluster.get_B(r)
        PB = (B ** 2) / 8.0 / np.pi
        ax.plot(r, pressure_conversion * PB * beta, label="Reynolds$+$20 Model {}".format(label[i]), c=color[i], ls="-.", lw=3)

        if mod_name == "1275b":
            s.cluster.plasma_beta = beta_func
            s.init_model()
            ax2.plot(r, 1e6*s.cluster.get_B(r), ls=":", c="C0", label=r"$B$, $\beta_{\rm pl} = 100 (z/25 {\rm kpc})^{1/2}")
            ax.plot(r, s.cluster.get_B(r), ls=":", c="C0", label=r"$B,\,\beta_{\rm pl} = 100 (z/25\,{\rm kpc})^{1/2}$")


    ax.set_ylim(5e-4, 2)
    ax2.set_ylim(B_from_P_ylim(ax.get_ylim()))
    ax2.set_xlabel("$z$~(kpc)", fontsize=18, labelpad=-2)
    ax.set_ylabel(r"$P_{\rm th}~{\rm for}~\beta_{\rm pl} = 100~({\rm keV~cm}^{-3})$", fontsize=16, labelpad=-2)
    ax.legend(fontsize=14, frameon=False, loc=3)
    ax2.set_xlim(1,1800)
    ax.get_shared_y_axes().join(ax,ax2)

    ax2.set_ylabel(r"$B~(\mu {\rm G})$", fontsize=18, labelpad=-5)
    plt.loglog()

alpro.util.set_default_plot_params(tex=True)
make_fig1()
findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.
../_images/examples_Matthews2022_2_1.png

Figure 4: Rotation Measures

[2]:
def get_rm_models(model_kwargs, N = 200):
    '''
    calculate the RM from N models (default N = 200)
    '''
    N = 200
    rm = np.zeros(N)
    for i in range(N):
        s = setup_model(i, 1e-13 * 1e-9, 1e-13, **model_kwargs)
        rm[i] = s.domain.get_rm(cell_centered=False)
    return (np.fabs(rm), np.median(np.fabs(rm)))

def setup_model(i, g, mass, mod = "1275b", lc_scale = True, var_beta = False):
    '''
    setup the cell model using alpro with the relevant coherence length
    and plasma beta properties
    '''
    s = alpro.Survival(mod)
    s.init_model()
    if var_beta:
        s.cluster.plasma_beta = beta_func
    if lc_scale == False:
        s.set_coherence_r0(None)
    s.domain.create_box_array(1800.0, i, s.coherence_func, r0=0.0)
    s.set_params(g = g, mass = mass)
    return (s)

# define plotting properties and labels
ls = ["-", "-", "-", ":"]
colors = ["C0", "C1", "C3", "k"]
labels = ["1: Cell-based", r"2: Cell-based, no $\Lambda_c$ scaling", r"3: Cell-based, variable $\beta(z)$", "R20 Model A"]

# define characteristics of the model
model = ["1275b", "1275b", "1275b", "1275a"]
var_betas = [False, False, True, False]
lc_scales = [True, False, True, True]
imods = np.arange(len(ls))
bins = np.linspace(0,4e4,100)         # RM bins
params = zip(imods, model, var_betas, lc_scales, labels, colors)
fig = plt.figure()

# loop over the models and calculate RMs in each case, then plot them
for imod, mod, var_beta, lc_scale, label, color in params:
    model_kwargs = {"mod": mod, "var_beta": var_beta, "lc_scale": lc_scale}

    iseed = 0

    rms, median = get_rm_models(model_kwargs)
    hist, bin_edges = np.histogram(rms, bins=bins)
    cdf = np.cumsum(hist) / np.sum(hist)

    # plot the results and mark the median RM
    plt.plot(bin_edges[:-1], cdf, color=color, lw=3, label=labels[imod], ls = ls[imod])
    plt.vlines([median], 0, 0.1, color=color, lw=2)

plt.ylabel("Cumulative Distribution Function")
plt.xlabel(r"$|{\rm RM| (rad~m^{-2}})$")
plt.fill_between([6500,7500], 0, 1, color="k", alpha=0.3, label= "RM from Taylor$+$ 2006")
plt.ylim(0,1)
plt.xlim(0,19000)
plt.legend(loc=4, frameon=False)
plt.subplots_adjust(hspace=0.05, wspace=0.35, right=0.98)
findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.
../_images/examples_Matthews2022_4_1.png

Figure 5: Magnetic Field Models and survival probabilities

This code will reproduce figure 5 from the paper. To avoid a fairly lengthy calculation of the mean survival probability, the code uses precalculated values stored in this repository.

Model

\(N\)

\(B\)-Field

\(\Lambda_c\) scaling?

\(\beta_{\rm pl}(z)\)

Range of scales (kpc)

Median $

{\rm RM}

1

200

Cell-based

Yes

constant, 100

\(3.5-10\)

1915

Blue

2

200

Cell-based

No

constant, 100

\(3.5-10\)

1603

Orange

3

200

Cell-based

Yes

\(100~\sqrt{z/25\,{\rm kpc}}\)

\(3.5-10\)

2045

Red

[3]:
g = 1e-12 * 1e-9
mass = 1e-13
from scipy.interpolate import interp1d

def set_pgg_axes(ax, xtick, ymin=0.855):
    ax.set_xscale("log")
    ax.set_xlim(1,10)
    ax.set_ylim(ymin,1)
    xticks_to_use = [1,2,3,4,6,10]
    ax.set_xticks(xticks_to_use)
    if xtick:
        ax.set_xlabel("$E$ (keV)", fontsize=18)
        ax.set_xticklabels([str(i) for i in xticks_to_use])
    else:
        ax.set_xticklabels([])

def get_mean_sd_models(model_kwargs, rm_reject=False):
    N = 200
    rinterp = np.linspace(0,1800,1801)
    Bfine = np.zeros((N,len(rinterp)))
    moments = np.zeros((2,len(rinterp)))
    for i in range(N):
        s = setup_model(i, g, mass, **model_kwargs)
        rinterp = np.linspace(0,1800,1801)
        interp_func = interp1d(s.domain.rcen, 1e6*s.domain.B, bounds_error=False, fill_value="extrapolate")
        Bfine[i,:] = interp_func(rinterp)

    moments[0,:] = np.mean(Bfine, axis=0)
    moments[1,:] = np.std(Bfine, axis=0)
    return moments

def plot_model(ax, s, label, color, xtick, moments):
    rinterp = np.linspace(0,1800,1801)
    ax.step(s.domain.r, 1e6*s.domain.B, label=label, where="post", c=color)
    ax.fill_between(rinterp, y1=moments[0,:]-moments[1,:], y2=moments[0,:]+moments[1,:], color=color, alpha=0.3, step="mid")
    ax.set_yscale("log")
    ax.set_xscale("log")
    if xtick:
        ax.set_xlabel("$z$~(kpc)", fontsize=18)
    ax.legend(fontsize=14, frameon=False, loc=3, handlelength=0.5, handletextpad=0.2)
    ax.set_ylabel(r"$B_\perp~(\mu {\rm G})$", fontsize=18, labelpad=-2)
    ax.set_xlim(10,1800)
    ax.set_ylim(2e-2,30)
    yticks = [0.1,1,10]
    ax.set_yticks(yticks)
    ax.set_yticklabels([str(i) for i in yticks])
    if xtick == False:
        ax.set_xticklabels([])

def plot_pgg(ax, s, label, color, xtick):
    energies = np.logspace(3,4,1000)
    P, _ = s.propagate_with_pruning(s.domain, energies, threshold=0.1, refine=10, required_res=3)
    ax.plot(energies/1e3, 1.0 - P, c=color)
    set_pgg_axes(ax, xtick, ymin=0.855)
    ax.set_ylabel(r"$P_{\gamma\gamma}$", fontsize=18,  labelpad=-2)

def plot_mean(ax, fname, color, xtick):
    energies, P, sd = np.genfromtxt("../data/{}_13.0_12.0.dat".format(fname), unpack=True)
    ax.plot(energies, P, c=color)
    ax.fill_between(energies, y1=P-sd, y2=P+sd, color=color, alpha=0.3)
    set_pgg_axes(ax, xtick, ymin=0.855)
    ax.set_ylabel(r"$\bar{P}_{\gamma\gamma,200}$", fontsize=18, labelpad=-1)


var_betas = [False, False, True]
lc_scales = [True, False, True]
labels = ["Cell-based", r"Cell-based, no $\Lambda_c$ scaling", r"Cell-based, variable $\beta(z)$"]
iplots = [1, 4, 7]
colors = ["C0", "C1", "C3"]
xticks = [False, False, True]
model = np.arange(len(labels))
fnames = ["cell", "cell_noLscaling", "cell_varbeta"]
params = zip(model, fnames, var_betas, lc_scales, labels, iplots, colors, xticks)

i = 0
fig = plt.figure(figsize=(12,12))

for imod, fname, var_beta, lc_scale, label, iplot, color, xtick in params:

    # get mean and SD for resampled magnetic field
    model_kwargs = {"mod": "1275b", "var_beta": var_beta, "lc_scale": lc_scale}
    moments = get_mean_sd_models(model_kwargs)

    # initialise individual model
    iseed = 0
    s = setup_model(iseed, g, mass, **model_kwargs)

    # make axes to plot
    ax1 = fig.add_subplot(5,3,iplot)
    ax2 = fig.add_subplot(5,3,iplot+1)
    ax3 = fig.add_subplot(5,3,iplot+2)

    # plot the magnetic field, pgg, and mean pgg
    plot_model(ax1, s, label, color, xtick, moments)
    plot_pgg(ax2, s, label, color, xtick)
    plot_mean(ax3, fname, color, xtick)
plt.subplots_adjust(hspace=0.25)
../_images/examples_Matthews2022_6_0.png
[ ]: