def test_bb_sed_luminosity(self, T_dt): """test that the luminosity of the DT BB SED is the same as xi_dt * L_disk, create DTs with different temperatrues (and radii)""" xi_dt = 0.5 L_dt = xi_dt * L_disk_test dt = RingDustTorus(L_disk_test, xi_dt, T_dt) # compute the SEDs, assume a random redshift z = 0.23 nu = np.logspace(10, 20, 100) * u.Hz sed = dt.sed_flux(nu, z) # compute back the luminosity d_L = Distance(z=z).to("cm") F_nu = sed / nu L = 4 * np.pi * np.power(d_L, 2) * np.trapz(F_nu, nu, axis=0) assert u.isclose(L, L_dt, atol=0 * u.Unit("erg s-1"), rtol=1e-2)
def test_abs_dt_vs_point_source(self): """check if in the limit of large distances the gamma-gamma optical depth on the DT tends to the one of a point-like source approximating it""" # dust torus L_disk = 2e46 * u.Unit("erg s-1") T_dt = 1e3 * u.K csi_dt = 0.1 dt = RingDustTorus(L_disk, csi_dt, T_dt) r = 1e22 * u.cm # point like source approximating the dt ps_dt = PointSourceBehindJet(dt.xi_dt * L_disk, dt.epsilon_dt) # absorption z = 0.859 theta_s = np.deg2rad(10) abs_dt = Absorption(dt, r, z, mu_s=np.cos(theta_s)) abs_ps_dt = Absorption(ps_dt, r, z, mu_s=np.cos(theta_s)) # taus E = np.logspace(2, 6) * u.GeV nu = E.to("Hz", equivalencies=u.spectral()) tau_dt = abs_dt.tau(nu) tau_ps_dt = abs_ps_dt.tau(nu) make_comparison_plot( nu, tau_ps_dt, tau_dt, "point source approximating the DT", "ring dust torus", "Absorption on Ring Dust Torus, " + r"$r = 10^{22}\,{\rm cm} \gg R_{\rm dt},\,\theta_s=10^{\circ}$", f"{figures_dir}/dt/tau_dt_point_source_comparison.png", "tau", ) # requires a 10% deviation from the two SED points assert check_deviation(nu, tau_dt, tau_ps_dt, 0.1)
def test_absorption_dt_reference_tau(self, r): """test agnpy gamma-gamma optical depth for DT against the one in Figure 14 of Finke 2016""" # reference tau E_ref, tau_ref = extract_columns_sample_file( f"{data_dir}/reference_taus/finke_2016/figure_14_left/tau_DT_r_{r}_R_Ly_alpha.txt", "GeV", ) nu_ref = E_ref.to("Hz", equivalencies=u.spectral()) # target L_disk = 2e46 * u.Unit("erg s-1") T_dt = 1e3 * u.K csi_dt = 0.1 dt = RingDustTorus(L_disk, csi_dt, T_dt) R_Ly_alpha = 1.1e17 * u.cm _r = float(r) * R_Ly_alpha # recompute the tau, use the full energy range of figure 14 z = 0.859 ec_dt = Absorption(dt, _r, z) tau_agnpy = ec_dt.tau(nu_ref) # comparison plot make_comparison_plot( nu_ref, tau_agnpy, 2 * tau_ref, "agnpy", "Figure 14, Finke (2016)", f"Absorption on Dust Torus, r = {r} R(Ly alpha)", f"{figures_dir}/dt/tau_dt_comprison_r_{r}_R_Ly_alpha_figure_14_finke_2016.png", "tau", y_range=[1e-5, 1e5], ) assert True
def test_ec_dt_reference_sed(self): """test agnpy SED for EC on DT against the one in Figure 11 of Finke 2016""" # reference SED sampled_ec_dt_table = np.loadtxt( f"{tests_dir}/sampled_seds/ec_dt_figure_11_finke_2016.txt", delimiter=",", comments="#", ) sampled_ec_dt_nu = sampled_ec_dt_table[:, 0] * u.Hz # multiply the reference SED for 2 as this is the missing factor # in the emissivity expression in Eq. 90 of Finke 2016 sampled_ec_dt_sed = 2 * sampled_ec_dt_table[:, 1] * u.Unit("erg cm-2 s-1") # agnpy SED L_disk = 2 * 1e46 * u.Unit("erg s-1") T_dt = 1e3 * u.K csi_dt = 0.1 dt = RingDustTorus(L_disk, csi_dt, T_dt) # recompute the SED at the same ordinates where the figure was sampled ec_dt = ExternalCompton(BPL_BLOB, dt, r=1e20 * u.cm) agnpy_ec_dt_sed = ec_dt.sed_flux(sampled_ec_dt_nu) # sed comparison plot make_sed_comparison_plot( sampled_ec_dt_nu, sampled_ec_dt_sed, agnpy_ec_dt_sed, "External Compton on Ring Dust Torus", "ec_dt_comparison_figure_11_finke_2016", ) # requires that the SED points deviate less than 30% from the figure assert u.allclose( agnpy_ec_dt_sed, sampled_ec_dt_sed, atol=0 * u.Unit("erg cm-2 s-1"), rtol=0.3, )
def test_tau_dt_mu_s_far(self, mu_s): """ comparing the DT absorption for mu_s !=1 with point source simplification """ L_disk = 2e46 * u.Unit("erg s-1") xi_DT = 0.1 temp = 1000 * u.K R_DT = 1.0e17 * u.cm # radius of DT dt = RingDustTorus(L_disk, xi_DT, temp, R_dt=R_DT) r = 10 * R_DT # distance at which the photon starts nu_ref = np.logspace(26, 32, 60) * u.Hz # absorption at mu_s abs_dt_mu_s = Absorption(dt, r, z=0, mu_s=mu_s) tau_dt_mu_s = abs_dt_mu_s.tau(nu_ref) uu = np.logspace(-5, 5, 100) * r _u, _nu_ref = axes_reshaper(uu, nu_ref) eps = (2.7 * temp * k_B).to("eV") # energy of soft photons # distance from the DT _x = np.sqrt(r * r + _u * _u + 2 * mu_s * _u * r) # soft photon density _nph = (L_disk * xi_DT / (4 * np.pi * _x**2) / c / eps).to("cm-3") _E = _nu_ref.to("eV", equivalencies=u.spectral()) _cospsi = (_u**2 + _x**2 - r**2) / (2 * _u * _x) _beta2 = 1 - 2 * mec2**2 / (_E * eps * (1 - _cospsi)) _beta2[_beta2 < 0] = 0 integrand = sigma_pp(np.sqrt(_beta2)) * _nph * (1 - _cospsi) tau_my = (np.trapz(integrand, uu, axis=0)).to("") print(tau_my / tau_dt_mu_s) max_agnpy = max(tau_dt_mu_s) max_my = max(tau_my) max_pos_agnpy = nu_ref[np.argmax(tau_dt_mu_s)] max_pos_my = nu_ref[np.argmax(tau_my)] # if r>>R_re this should be pretty precise, allowing for 10% accuracy assert np.isclose(max_agnpy, max_my, atol=0, rtol=0.1) assert np.isclose(max_pos_agnpy, max_pos_my, atol=0, rtol=0.1)
def test_tau_dt_mu_s_simple(self): """ order of magnitude test comparing with simplified calculations the case of a perpendicularly moving photon starting at r~0.5 * R_re """ r = 1.0e17 * u.cm # distance at which the photon starts mu_s = 0.0 # angle of propagation L_disk = 2e46 * u.Unit("erg s-1") xi_DT = 0.1 temp = 1000 * u.K R_DT = 2 * r # radius of DT: assume the same as the distance r dt = RingDustTorus(L_disk, xi_DT, temp, R_dt=R_DT) nu_ref = np.logspace(26, 32, 120) * u.Hz # absorption at mu_s abs_dt_mu_s = Absorption(dt, r, z=0, mu_s=mu_s) tau_dt_mu_s = abs_dt_mu_s.tau(nu_ref) eps = (2.7 * temp * k_B).to("eV") # energy of soft photons # soft photon density nph = (L_disk * xi_DT / (4 * np.pi * (r**2 + R_DT**2)) / c / eps).to("cm-3") E = nu_ref.to("eV", equivalencies=u.spectral()) cospsi = 0 # assume perpendicular scattering beta2 = 1 - 2 * mec2**2 / (E * eps * (1 - cospsi)) beta2[beta2 < 0] = 0 # below the threshold # for tau calculations we assume that gamma ray moves # roughtly the characteristic distance of ~R_DT tau_my = (sigma_pp(np.sqrt(beta2)) * nph * R_DT * (1 - cospsi)).to("") max_agnpy = max(tau_dt_mu_s) max_my = max(tau_my) max_pos_agnpy = nu_ref[np.argmax(tau_dt_mu_s)] max_pos_my = nu_ref[np.argmax(tau_my)] # very rough calculations so allowing for # 15% accuracy in peak maximum and 30% in peak position assert np.isclose(max_agnpy, max_my, atol=0, rtol=0.15) assert np.isclose(max_pos_agnpy, max_pos_my, atol=0, rtol=0.3)
def test_ec_dt_vs_point_source(self): """check if in the limit of large distances the EC on the DT tends to the one of a point-like source approximating it""" # dust torus L_disk = 2 * 1e46 * u.Unit("erg s-1") T_dt = 1e3 * u.K csi_dt = 0.1 dt = RingDustTorus(L_disk, csi_dt, T_dt) # point like source approximating the dt ps_dt = PointSourceBehindJet(dt.xi_dt * L_disk, dt.epsilon_dt) # external Compton ec_dt = ExternalCompton(BPL_BLOB, dt, r=1e22 * u.cm) ec_ps_dt = ExternalCompton(BPL_BLOB, ps_dt, r=1e22 * u.cm) # seds nu = np.logspace(15, 28) * u.Hz ec_dt_sed = ec_dt.sed_flux(nu) ec_ps_dt_sed = ec_ps_dt.sed_flux(nu) # requires a 20% deviation from the two SED points assert u.allclose( ec_dt_sed, ec_ps_dt_sed, atol=0 * u.Unit("erg cm-2 s-1"), rtol=0.2 )
def test_absorption_dt_reference_tau(self, r, nu_min): """test agnpy gamma-gamma optical depth for DT against the one in Figure 14 of Finke 2016""" # reference tau E_ref, tau_ref = extract_columns_sample_file( f"{data_dir}/reference_taus/finke_2016/figure_14_left/tau_DT_r_{r}_R_Ly_alpha.txt", "GeV", ) nu_ref = E_ref.to("Hz", equivalencies=u.spectral()) # target L_disk = 2 * 1e46 * u.Unit("erg s-1") T_dt = 1e3 * u.K csi_dt = 0.1 dt = RingDustTorus(L_disk, csi_dt, T_dt) R_Ly_alpha = 1.1 * 1e17 * u.cm # parse the string providing the distance in units of R_Ly_alpha # numbers as 1e1.5 cannot be directly converted to float num = r.split("e") Ly_alpha_units = (float(num[0]) * 10)**(float(num[-1])) _r = Ly_alpha_units * R_Ly_alpha # recompute the tau, use the full energy range of figure 14 z = 0.859 abs_dt = Absorption(dt, _r, z) tau_agnpy = abs_dt.tau(nu_ref) # check in a restricted energy range nu_range = [nu_min, 3e28] * u.Hz make_comparison_plot( nu_ref, tau_agnpy, 2 * tau_ref, "agnpy", "Figure 14, Finke (2016)", f"Absorption on Dust Torus, r = {Ly_alpha_units:.2e} R(Ly alpha)", f"{figures_dir}/dt/tau_dt_comprison_r_{r}_R_Ly_alpha_figure_14_finke_2016.png", "tau", comparison_range=nu_range.to_value("Hz"), y_range=[1e-6, 1e3], ) # requires that the SED points deviate less than 20 % from those of the reference figure assert check_deviation(nu_ref, tau_agnpy, 2 * tau_ref, 0.20, nu_range)
print("\ndisk definition:") print(disk) # blr definition epsilon_line = 2e-5 csi_line = 0.024 R_line = 1e17 * u.cm blr = SphericalShellBLR(disk, csi_line, epsilon_line, R_line) print("\nblr definition:") print(blr) # dust torus definition T_dt = 1e3 * u.K epsilon_dt = 2.7 * ((const.k_B * T_dt) / (const.m_e * const.c * const.c)).decompose() csi_dt = 0.1 dt = RingDustTorus(disk, csi_dt, epsilon_dt) print("\ntorus definition:") print(dt) # define the External Compton ec_disk = ExternalCompton(blob, disk, r=1e17 * u.cm) ec_blr = ExternalCompton(blob, blr, r=1e17 * u.cm) ec_dt = ExternalCompton(blob, dt, r=1e17 * u.cm) nu = np.logspace(15, 30) * u.Hz # commands to profile ec_disk_sed_command = "ec_disk.sed_flux(nu)" ec_blr_sed_command = "ec_blr.sed_flux(nu)" ec_dt_sed_command = "ec_dt.sed_flux(nu)" n = 100
import sys import numpy as np import astropy.units as u import matplotlib.pyplot as plt sys.path.append("../../") from agnpy.targets import SphericalShellBLR, RingDustTorus blr = SphericalShellBLR(1e46 * u.Unit("erg s-1"), 0.1, "Lyalpha", 1e17 * u.cm) dt = RingDustTorus(1e46 * u.Unit("erg s-1"), 0.6, 1000 * u.K) print(blr) print(dt) r = np.logspace(14, 21, 200) * u.cm plt.loglog(r, blr.u(r), label="BLR") plt.loglog(r, dt.u(r), label="Torus") plt.xlabel(r"$r\,/\,{\rm cm}$") plt.ylabel(r"$u\,/\,({\rm erg}\,{\rm cm}^{-3})$") plt.legend() plt.show()
M_BH = 1.2 * 1e9 * const.M_sun.cgs L_disk = 2 * 1e46 * u.Unit("erg s-1") eta = 1 / 12 R_in = 6 R_out = 200 disk = SSDisk(M_BH, L_disk, eta, R_in, R_out, R_g_units=True) # blr definition csi_line = 0.024 R_line = 1e17 * u.cm blr = SphericalShellBLR(L_disk, csi_line, "Lyalpha", R_line) # dust torus definition T_dt = 1e3 * u.K csi_dt = 0.1 dt = RingDustTorus(L_disk, csi_dt, T_dt) # consider a fixed distance of the blob from the target fields r = 1.1e16 * u.cm # let us consider 3C 454.3 as source z = 0.859 absorption_disk = Absorption(disk, r=r, z=z) absorption_blr = Absorption(blr, r=r, z=z) absorption_dt = Absorption(dt, r=r, z=z) # plot using the same energy range as in Finke 2016 E = np.logspace(0, 5) * u.GeV nu = E.to("Hz", equivalencies=u.spectral()) tau_disk = absorption_disk.tau(nu)
##################### # test one with emission region in the center of the DT L_disk = 0.91e45 * u.Unit("erg s-1") xi_dt = 0.6 T_dt = 100 * u.K R_dt = 1.0e18 * u.cm h = 0.01 * R_dt ## test with lower numbers, gives virtually the same # B0/=10 # T_dt/=10 # L_disk/=100 blob1 = Blob(r0, z, delta_D, Gamma, B0, norm, spectrum_dict, xi=xi) dt1 = RingDustTorus(L_disk, xi_dt, T_dt, R_dt=R_dt) # energy density of DT radiation field in the blob u_dt1 = dt1.u_ph(h, blob1) u_synch1 = blob1.u_ph_synch print( "energy density in the blob, DT radiation: ", u_dt1, "synchrotron photons: ", u_synch1, ) dt1_sed = dt1.sed_flux(nu, z) # energy density was set to be the same synch1 = Synchrotron(blob1, ssa=False) synch1_sed = synch1.sed_flux(nu)
def test_setting_radius(self): """check that, when passed manually, the radius is correctly set""" dt = RingDustTorus(L_DISK, 0.1, 1e3 * u.K, 1e19 * u.cm) assert u.allclose(dt.R_dt, 1e19 * u.cm, atol=0 * u.cm)
ETA = 1 / 12 R_G = 1.77 * 1e14 * u.cm R_IN_G_UNITS = 6 R_OUT_G_UNITS = 200 R_IN = R_IN_G_UNITS * R_G R_OUT = R_OUT_G_UNITS * R_G DISK = SSDisk(M_BH, L_DISK, ETA, R_IN, R_OUT) # useful for checks L_EDD = 15.12 * 1e46 * u.Unit("erg s-1") M_DOT = 2.019 * 1e26 * u.Unit("g s-1") # global SphericalShellBLR BLR = SphericalShellBLR(L_DISK, 0.1, "Lyalpha", 1e17 * u.cm) # dust torus definition DT = RingDustTorus(L_DISK, 0.1, 1000 * u.K) class TestCMB: """class grouping all the tests related to the CMB""" def test_u(self): """test u in the stationary reference frame""" assert u.isclose( 6.67945605e-12 * u.Unit("erg / cm3"), CMB_Z_1.u(), atol=0 * u.Unit("erg / cm3"), rtol=1e-3, ) def test_u_comoving(self): """test u in the reference frame comoving with the blob"""
eta_test = 1 / 12 R_g_test = 1.77 * 1e14 * u.cm R_in_tilde_test = 6 R_out_tilde_test = 200 R_in_test = R_in_tilde_test * R_g_test R_out_test = R_out_tilde_test * R_g_test disk_test = SSDisk(M_BH_test, L_disk_test, eta_test, R_in_test, R_out_test) # useful for checks L_Edd_test = 15.12 * 1e46 * u.Unit("erg s-1") m_dot_test = 2.019 * 1e26 * u.Unit("g s-1") # SphericalShellBLR blr_test = SphericalShellBLR(L_disk_test, 0.1, "Lyalpha", 1e17 * u.cm) # dust torus definition dt_test = RingDustTorus(L_disk_test, 0.1, 1000 * u.K) class TestCMB: """class grouping all the tests related to the CMB""" def test_u(self): """test u in the stationary reference frame""" assert u.isclose( 6.67945605e-12 * u.Unit("erg / cm3"), cmb_test.u(), atol=0 * u.Unit("erg / cm3"), rtol=1e-5, ) def test_u_comoving(self): """test u in the reference frame comoving with the blob"""