def test_cstat_rsppha(): """What does CSTAT calculate when there is an RSP+PHA instrument model. This includes the AREASCAL when evaluating the model. See Also -------- test_cstat_nophamodel, test_cstat_arfpha, test_cstat_rmfpha """ dset, mdl, expected = setup_likelihood(scale=True) # use the full channel grid; the energy grid has to be # "the same" as the channel values since the model # has a dependency on the independent axis # egrid = 1.0 * np.concatenate((dset.channel, [dset.channel.max() + 1])) arf = make_arf(energ_lo=egrid[:-1], energ_hi=egrid[1:]) rmf = make_ideal_rmf(e_min=egrid[:-1], e_max=egrid[1:]) mdl_ascal = RSPModelPHA(arf, rmf, dset, mdl) stat = CStat() sval_ascal = stat.calc_stat(dset, mdl_ascal) assert_allclose(sval_ascal[0], expected)
def test_rspmodelpha_delta_call(ignore): """What happens calling a rsp with a pha (RMF is a delta fn)? The ignore value gives the channel to ignore (counting from 0). """ exposure = 200.1 estep = 0.025 egrid = np.arange(0.1, 0.8, estep) elo = egrid[:-1] ehi = egrid[1:] specresp = 2.4 * np.ones(elo.size, dtype=np.float32) specresp[2:5] = 0.0 specresp[16:19] = 3.2 adata = create_arf(elo, ehi, specresp, exposure=exposure) rdata = create_delta_rmf(elo, ehi, e_min=elo, e_max=ehi) nchans = elo.size constant = 2.3 mdl = Const1D('flat') mdl.c0 = constant channels = np.arange(1, nchans + 1, dtype=np.int16) counts = np.ones(nchans, dtype=np.int16) pha = DataPHA('test-pha', channel=channels, counts=counts, exposure=exposure) pha.set_rmf(rdata) # force energy units (only needed if ignore is set) pha.set_analysis('energy') if ignore is not None: de = estep * 0.9 e0 = egrid[ignore] pha.notice(lo=e0, hi=e0 + de, ignore=True) # The assert are intended to help people reading this # code rather than being a useful check that the code # is working. mask = [True] * nchans mask[ignore] = False assert (pha.mask == mask).all() wrapped = RSPModelPHA(adata, rdata, pha, mdl) # The model is evaluated on the RMF grid, not whatever # is sent in. It is also integrated across the bins, # which is why there is a multiplication by the # grid width (for this constant model). # # Note that the filter doesn't change the grid. # de = egrid[1:] - egrid[:-1] expected = constant * specresp * de out = wrapped([4, 5]) assert_allclose(out, expected)
def test_rspmodelpha_matrix_call(ignore): """What happens calling a rsp with a pha (RMF is a matrix)? The ignore value gives the channel to ignore (counting from 0). """ exposure = 200.1 rdata = create_non_delta_rmf() specresp = create_non_delta_specresp() elo = rdata.energ_lo ehi = rdata.energ_hi adata = create_arf(elo, ehi, specresp, exposure=exposure) nchans = rdata.e_min.size constant = 22.3 slope = -1.2 mdl = Polynom1D('sloped') mdl.c0 = constant mdl.c1 = slope channels = np.arange(1, nchans + 1, dtype=np.int16) counts = np.ones(nchans, dtype=np.int16) pha = DataPHA('test-pha', channel=channels, counts=counts, exposure=exposure) pha.set_rmf(rdata) # force energy units (only needed if ignore is set) pha.set_analysis('energy') if ignore is not None: e0 = rdata.e_min[ignore] e1 = rdata.e_max[ignore] de = 0.9 * (e1 - e0) pha.notice(lo=e0, hi=e0 + de, ignore=True) # The assert are intended to help people reading this # code rather than being a useful check that the code # is working. mask = [True] * nchans mask[ignore] = False assert (pha.mask == mask).all() wrapped = RSPModelPHA(adata, rdata, pha, mdl) # The filter does not change the grid modvals = specresp * mdl(rdata.energ_lo, rdata.energ_hi) matrix = get_non_delta_matrix() expected = np.matmul(modvals, matrix) out = wrapped([4, 5]) assert_allclose(out, expected)
def test_rsp1d_delta_pha_zero_energy_bin(): "What happens when the first bin starts at 0, with replacement" ethresh = 2.0e-7 # PHA and ARF have different exposure ties exposure1 = 0.1 exposure2 = 2.4 egrid = np.asarray([0.0, 0.1, 0.2, 0.4, 0.5, 0.7, 0.8]) elo = egrid[:-1] ehi = egrid[1:] specresp = np.asarray([10.2, 9.8, 10.0, 12.0, 8.0, 10.0]) with warnings.catch_warnings(record=True) as ws: warnings.simplefilter("always") adata = create_arf(elo, ehi, specresp, exposure=exposure1, ethresh=ethresh) validate_zero_replacement(ws, 'ARF', 'user-arf', ethresh) with warnings.catch_warnings(record=True) as ws: warnings.simplefilter("always") rdata = create_delta_rmf(elo, ehi, ethresh=ethresh) validate_zero_replacement(ws, 'RMF', 'delta-rmf', ethresh) channels = np.arange(1, 7, dtype=np.int16) counts = np.ones(6, dtype=np.int16) pha = DataPHA('test-pha', channel=channels, counts=counts, exposure=exposure2) pha.set_rmf(rdata) pha.set_arf(adata) pha.set_analysis('energy') mdl = MyPowLaw1D() tmdl = PowLaw1D() wrapped = RSPModelPHA(adata, rdata, pha, mdl) out = wrapped([0.1, 0.2]) elo[0] = ethresh expected = specresp * tmdl(elo, ehi) assert_allclose(out, expected) assert not np.isnan(out[0])
def test_rspmodelpha_matrix_call_xspec(): """Check XSPEC constant is invariant to wavelength/energy setting. As XSPEC models internally convert from Angstrom to keV, do a simple check here. """ exposure = 200.1 rdata = create_non_delta_rmf() specresp = create_non_delta_specresp() adata = create_arf(rdata.energ_lo, rdata.energ_hi, specresp, exposure=exposure) constant = 2.3 mdl = XSconstant('flat') mdl.factor = constant nchans = rdata.e_min.size channels = np.arange(1, nchans + 1, dtype=np.int16) counts = np.ones(nchans, dtype=np.int16) pha = DataPHA('test-pha', channel=channels, counts=counts, exposure=exposure) # The set_arf call isn't necessary, but leave in pha.set_arf(adata) pha.set_rmf(rdata) # The XSPEC models are evaluated on an energy grid, even when # the analysis setting is wavelength. Also, unlike the Sherpa # Constant model, the XSPEC XSconstant model is defined # over the integrated bin, so no correction is needed for the # bin width. # modvals = constant * specresp matrix = get_non_delta_matrix() expected = np.matmul(modvals, matrix) wrapped = RSPModelPHA(adata, rdata, pha, mdl) pha.set_analysis('wave') out_wl = wrapped([4, 5]) assert_allclose(out_wl, expected) pha.set_analysis('energy') out_en = wrapped([4, 5]) assert_allclose(out_en, expected)
def test_rspmodelpha_delta_call_wave(): """What happens calling a rsp with a pha (RMF is a delta fn)? Wavelength. Unlike the energy case no bins are ignored, as this code path has already been tested. """ exposure = 200.1 estep = 0.025 egrid = np.arange(0.1, 0.8, estep) elo = egrid[:-1] ehi = egrid[1:] specresp = 2.4 * np.ones(elo.size, dtype=np.float32) specresp[2:5] = 0.0 specresp[16:19] = 3.2 adata = create_arf(elo, ehi, specresp, exposure=exposure) rdata = create_delta_rmf(elo, ehi, e_min=elo, e_max=ehi) nchans = elo.size constant = 2.3 mdl = Const1D('flat') mdl.c0 = constant channels = np.arange(1, nchans + 1, dtype=np.int16) counts = np.ones(nchans, dtype=np.int16) pha = DataPHA('test-pha', channel=channels, counts=counts, exposure=exposure) pha.set_rmf(rdata) pha.set_analysis('wave') wrapped = RSPModelPHA(adata, rdata, pha, mdl) # Note that this is a Sherpa model, so it's normalization is # per unit x axis, so when integrated here the bins are in # Angstroms, so the bin width to multiply by is # Angstroms, not keV. # dl = (DataPHA._hc / elo) - (DataPHA._hc / ehi) expected = constant * specresp * dl out = wrapped([4, 5]) assert_allclose(out, expected)
def test_rspmodelpha_delta_call_channel(): """What happens calling a rsp with a pha (RMF is a delta fn)? Channels. I am not convinced I understand the bin width calculation here, as it doesn't seem to match the wavelength case. """ exposure = 200.1 estep = 0.025 egrid = np.arange(0.1, 0.8, estep) elo = egrid[:-1] ehi = egrid[1:] specresp = 2.4 * np.ones(elo.size, dtype=np.float32) specresp[2:5] = 0.0 specresp[16:19] = 3.2 adata = create_arf(elo, ehi, specresp, exposure=exposure) rdata = create_delta_rmf(elo, ehi, e_min=elo, e_max=ehi) nchans = elo.size constant = 2.3 mdl = Const1D('flat') mdl.c0 = constant channels = np.arange(1, nchans + 1, dtype=np.int16) counts = np.ones(nchans, dtype=np.int16) pha = DataPHA('test-pha', channel=channels, counts=counts, exposure=exposure) pha.set_rmf(rdata) pha.set_analysis('channel') wrapped = RSPModelPHA(adata, rdata, pha, mdl) # Since this is channels you might expect the bin width to be 1, # but it is actually still dE. # de = ehi - elo expected = constant * specresp * de out = wrapped([4, 5]) assert_allclose(out, expected)
def prepare_spectra(group, nH, add_gal, redshift): pha = read_pha("core_spectrum.pi") pha.set_analysis("energy") pha.notice(0.5, 7.0) tabs = ~pha.mask pha.group_counts(group, tabStops=tabs) x = pha.get_x() x = pha.apply_filter(x, pha._middle) y = pha.get_y(filter=True) pha.set_analysis("energy") model = xsphabs.abs1 * powlaw1d.srcp1 print("Fitting the spectrum") zFlag = False if (nH is not None) and (nH > 0.0): if add_gal == 1: model = xsphabs.gal * xszphabs.abs1 * powlaw1d.srcp gal.nH = nH freeze(gal.nH) zFlag = True else: model = xsphabs.abs1 * powlaw1d.srcp1 abs1.nH = nH freeze(abs1.nH) else: model = xszphabs.abs1 * powlaw1d.srcp1 zFlag = True if zFlag is True and add_gal == 1: # print('REDSHIFT',redshift) abs1.redshift = redshift freeze(abs1.redshift) full_model = RSPModelPHA(pha.get_arf(), pha.get_rmf(), pha, pha.exposure * model) print(full_model) fit = Fit(pha, full_model, method=MonCar(), stat=WStat()) res = fit.fit() print(res.format()) print(fit.est_errors()) # calculate the p-value for wstat mplot2 = ModelPlot() mplot2.prepare(pha, full_model) miu = mplot2.y * pha.exposure * 0.0146 obs = y * pha.exposure * 0.0146 c, ce, cv = gof_cstat(miu, obs) print(f"C0={c},C_e={ce},C_v={cv}") zval = (fit.calc_stat() - ce) / np.sqrt(cv) if zval > 0: pval = special.erfc(zval / np.sqrt(2)) else: pval = special.erf(abs(zval) / np.sqrt(2)) print(f"p-value for wstat = {pval}") set_data(pha) set_model(model) save_chart_spectrum("core_flux_chart.dat", elow=0.5, ehigh=7.0) # save_chart_spectrum("core_flux_chart.rdb",format='text/tsv', elow=0.5, ehigh=7.0) save_spectrum_rdb("core_flux_chart.dat")
def prepare_spectra(nH: float, group: int = 1, add_gal: bool = False, redshift: Optional[float] = None, **kwargs) -> float: """ Fit the spectra using an absorbed powerlaw model using the Wstat statistic. The function also returns a p-value for the gof. :param nH: The galactic absorption column density in units of 10^22 /cm3 :param group: The number of counts per energy bin :param add_gal: Setting this to True would add an intrinsic abrosption column density along side the galactic one :param redshift: The redshift to use in the fit. Only takes effect if add_gal is set to True ... :return: Returns the p-value of the gof. The null hypothesis states that the model and the observation differ while alternate says that the model explains the data """ pha = read_pha("core_spectrum.pi") pha.set_analysis("energy") pha.notice(0.5, 7.0) tabs = ~pha.mask pha.group_counts(group, tabStops=tabs) x = pha.get_x() x = pha.apply_filter(x, pha._middle) y = pha.get_y(filter=True) pha.set_analysis("energy") model = xsphabs.abs1 * powlaw1d.srcp1 print("Fitting the spectrum") zFlag = False if (nH is not None) and (nH > 0.0): if add_gal == 1: model = xsphabs.gal * xszphabs.abs1 * powlaw1d.srcp gal.nH = nH freeze(gal.nH) zFlag = True else: model = xsphabs.abs1 * powlaw1d.srcp1 abs1.nH = nH freeze(abs1.nH) else: model = xszphabs.abs1 * powlaw1d.srcp1 zFlag = True if zFlag is True and add_gal == 1: # print('REDSHIFT',redshift) abs1.redshift = redshift freeze(abs1.redshift) full_model = RSPModelPHA(pha.get_arf(), pha.get_rmf(), pha, pha.exposure * model) print(full_model) fit = Fit(pha, full_model, method=MonCar(), stat=WStat()) res = fit.fit() print(res.format()) print(fit.est_errors()) # calculate the p-value for wstat mplot2 = ModelPlot() mplot2.prepare(pha, full_model) miu = mplot2.y * pha.exposure * 0.0146 obs = y * pha.exposure * 0.0146 c, ce, cv = SpecUtils.estimate_gof_cstat(miu, obs) #print(f"C0={c},C_e={ce},C_v={cv}") zval = (fit.calc_stat() - ce) / np.sqrt(cv) if zval > 0: pval = special.erfc(zval / np.sqrt(2)) else: pval = special.erf(abs(zval) / np.sqrt(2)) print(f"p-value for wstat = {pval}") set_data(pha) set_model(model) save_chart_spectrum("core_flux_chart.dat", elow=0.5, ehigh=7.0) # save_chart_spectrum("core_flux_chart.rdb",format='text/tsv', elow=0.5, ehigh=7.0) SAOTraceUtils.save_spectrum_rdb("core_flux_chart.dat") return pval
plt.ylim(1e4, 3e6) plt.xlim(0, 10) plt.xlabel('Energy (keV)') plt.ylabel('Count / keV') savefig('rspmodelnopha_energy.png') from sherpa.astro.io import read_pha from sherpa.astro.instrument import RSPModelPHA pha2 = read_pha('3c273.pi') arf2 = pha2.get_arf() rmf2 = pha2.get_rmf() mdl2 = PowLaw1D('mdl2') inst2 = RSPModelPHA(arf2, rmf2, pha2, mdl2) report("inst2") dump("inst2([]).size") pha2.set_analysis('energy') report('pha2.get_filter()') report('pha2.get_filter_expr()') pha2.notice(0.5, 7.0) report('pha2.get_filter()') report('pha2.get_filter_expr()') dump("pha2.grouped") # pha2.ungroup() # report('pha2.get_filter_expr()')