def test_norm_works(self): # Check that the norm parameter for additive models # works, as it is handled separately from the other # parameters. import sherpa.astro.xspec as xs # need an additive model mdl = xs.XSpowerlaw() mdl.PhoIndex = 2 egrid = [0.1, 0.2, 0.3, 0.4] mdl.norm = 1.2 y1 = mdl(egrid) mfactor = 2.1 mdl.norm = mdl.norm.val * mfactor y2 = mdl(egrid) # check that the sum is not 0 and that it # scales as expected. s1 = y1.sum() s2 = y2.sum() self.assertGreater(s1, 0.0, msg='powerlaw is positive') self.assertAlmostEqual(s2, mfactor * s1, msg='powerlaw norm scaling')
def test_norm_works(clean_astro_ui): # Check that the norm parameter for additive models # works, as it is handled separately from the other # parameters. import sherpa.astro.xspec as xs # need an additive model mdl = xs.XSpowerlaw() mdl.PhoIndex = 2 egrid = [0.1, 0.2, 0.3, 0.4] mdl.norm = 1.2 y1 = mdl(egrid) mfactor = 2.1 mdl.norm = mdl.norm.val * mfactor y2 = mdl(egrid) # check that the sum is not 0 and that it # scales as expected. s1 = y1.sum() s2 = y2.sum() assert s1 > 0.0, 'powerlaw is positive' assert s2 == pytest.approx(mfactor * s1)
def test_checks_input_length(self): import sherpa.astro.xspec as xs mdl = xs.XSpowerlaw() # Check when input array is too small (< 2 elements) self.assertRaises(TypeError, mdl, [0.1]) # Check when input arrays are not the same size (when the # low and high bin edges are given) self.assertRaises(TypeError, mdl, [0.1, 0.2, 0.3], [0.2, 0.3]) self.assertRaises(TypeError, mdl, [0.1, 0.2], [0.2, 0.3, 0.4])
def test_xspec_expression(): """Check we can enforce XSPEC expressions""" from sherpa.astro import xspec # pick an additive and multiplicative model a1 = xspec.XSpowerlaw() m1 = xspec.XSwabs() m = 2 * (a1 + m1) assert a1.ndim == 1 assert m1.ndim == 1 assert m.ndim == 1
def test_checks_input_length(clean_astro_ui): import sherpa.astro.xspec as xs mdl = xs.XSpowerlaw() # Check when input array is too small (< 2 elements) with pytest.raises(TypeError): mdl([0.1]) # Check when input arrays are not the same size (when the # low and high bin edges are given) with pytest.raises(TypeError): mdl([0.1, 0.2, 0.3], [0.2, 0.3]) with pytest.raises(TypeError): mdl([0.1, 0.2], [0.2, 0.3, 0.4])
def test_xspec_convolutionmodel_requires_bin_edges_low_level(): """Check we can not call a convolution model with a single grid (calc). This used to be supported in Sherpa 4.13 and before. """ import sherpa.astro.xspec as xs m1 = xs.XSpowerlaw() m2 = xs.XScflux() mdl = m2(m1) emsg = r'calc\(\) requires pars,lo,hi arguments, sent 2 arguments' with pytest.warns(FutureWarning, match=emsg): mdl.calc([p.val for p in mdl.pars], [0.1, 0.2, 0.3, 0.4])
def test_instrument_model(make_data_path): """Check the full response model""" from sherpa.astro import xspec s = Session() s.load_pha(make_data_path('3c273.pi')) a1 = xspec.XSpowerlaw() m1 = xspec.XSwabs() s.set_source(m1 * a1) src = s.get_source() mdl = s.get_model() assert src.ndim == 1 assert mdl.ndim == 1
def test_cflux_nbins(): """Check that the number of bins created by cflux is correct. The test_cflux_calc_xxx routines do include a test of the number of bins, but that is just for a Data1DInt dataset, so the model only ever gets called with explicit lo and hi edges. This test calls the model directly to check both 1 and 2 argument variants. Notes ----- There's no check of a non-contiguous grid. """ from sherpa.astro import xspec spl = PowLaw1D('sherpa') xpl = xspec.XSpowerlaw('xspec') spl.gamma = 0.7 xpl.phoindex = 0.7 egrid = np.arange(0.1, 2, 0.01) elo = egrid[:-1] ehi = egrid[1:] nbins = elo.size def check_bins(lbl, mdl): y1 = mdl(egrid) y2 = mdl(elo, ehi) assert y1.size == nbins + 1, '{}: egrid'.format(lbl) assert y2.size == nbins, '{}: elo/ehi'.format(lbl) # verify assumptions # check_bins('Sherpa model', spl) check_bins('XSPEC model', xpl) # Now try the convolved versions # cflux = xspec.XScflux("conv") check_bins('Convolved Sherpa', cflux(spl)) check_bins('Convolved XSPEC', cflux(xpl))
def test_calc_xspec_regrid(): """Test the CFLUX convolution model calculations (XSPEC model) Can we regrid the model and get a similar result to the direct version? This uses zashift but with 0 redshift. See Also -------- test_calc_sherpa_regrid """ from sherpa.astro import xspec mdl = xspec.XSpowerlaw('xspec') mdl.phoindex = 1.7 mdl.norm = 0.025 # rather than use the 'cflux' convolution model, try # the redshift model but with 0 redshift. # kern = xspec.XSzashift('zshift') kern.redshift = 0 mdl_convolved = kern(mdl) d = setup_data(elo=0.2, ehi=5, ebin=0.01) dr = np.arange(0.1, 7, 0.005) mdl_regrid = mdl_convolved.regrid(dr[:-1], dr[1:]) yconvolved = d.eval_model(mdl_convolved) yregrid = d.eval_model(mdl_regrid) # Do a per-pixel comparison (this will automatically catch any # difference in the output sizes). # ydiff = np.abs(yconvolved - yregrid) mdiff = ydiff.max() # rdiff = ydiff / yconvolved # in testing see the max difference being ~ 3e-17 assert mdiff < 1e-15, \ 'can rebin an XSPEC powerlaw: max diff={}'.format(mdiff)
def test_cflux_nbins(): """Check that the number of bins created by cflux is correct. The test_cflux_calc_xxx routines do include a test of the number of bins, but that is just for a Data1DInt dataset, so the model only ever gets called with explicit lo and hi edges. Now that we no-longer support evaluating the model with a single grid this test may not add much power, but leave in for now. Notes ----- There's no check of a non-contiguous grid. """ from sherpa.astro import xspec spl = PowLaw1D('sherpa') xpl = xspec.XSpowerlaw('xspec') spl.gamma = 0.7 xpl.phoindex = 0.7 egrid = np.arange(0.1, 2, 0.01) elo = egrid[:-1] ehi = egrid[1:] nbins = elo.size def check_bins(lbl, mdl): y = mdl(elo, ehi) assert y.size == nbins, f'{lbl}: elo/ehi' # verify assumptions # check_bins('Sherpa model', spl) check_bins('XSPEC model', xpl) # Now try the convolved versions # cflux = xspec.XScflux("conv") check_bins('Convolved Sherpa', cflux(spl)) check_bins('Convolved XSPEC', cflux(xpl))
def test_cflux_calc_xspec(): """Test the CFLUX convolution model calculations (XSPEC model) This is a test of the convolution interface, as the results of the convolution can be easily checked. The model being convolved is an XSPEC model. See Also -------- test_cflux_calc_sherpa """ from sherpa.astro import xspec mdl = xspec.XSpowerlaw('xspec') mdl.phoindex = 1.7 mdl.norm = 0.025 _test_cflux_calc(mdl, mdl.phoindex.val, mdl.norm.val)
def test_checks_input_length(): import sherpa.astro.xspec as xs mdl = xs.XSpowerlaw() # Check when input array is too small (< 2 elements) with pytest.raises(TypeError) as exc1: mdl([0.1], [0.2]) assert str( exc1.value) == "input array must have at least 2 elements, found 1" # Check when input arrays are not the same size. with pytest.raises(TypeError) as exc2: mdl([0.1, 0.2, 0.3], [0.2, 0.3]) assert str(exc2.value) == "input arrays are not the same size: 3 and 2" with pytest.raises(TypeError) as exc3: mdl([0.1, 0.2], [0.2, 0.3, 0.4]) assert str(exc3.value) == "input arrays are not the same size: 2 and 3"
def test_convolution_model_cflux(clean_astro_ui): # Use the cflux convolution model, since this gives # an easily-checked result. At present the only # interface to these models is via direct access # to the functions (i.e. there are no model classes # providing access to this functionality). # import sherpa.astro.xspec as xs if not hasattr(xs._xspec, 'C_cflux'): pytest.skip('cflux convolution model is missing') # The energy grid should extend beyond the energy grid # used to evaluate the model, to avoid any edge effects. # It also makes things easier if the elo/ehi values align # with the egrid bins. elo = 0.55 ehi = 1.45 egrid = numpy.linspace(0.5, 1.5, 101) mdl1 = xs.XSpowerlaw() mdl1.PhoIndex = 2 # flux of mdl1 over the energy range of interest; converting # from a flux in photon/cm^2/s to erg/cm^2/s, when the # energy grid is in keV. y1 = mdl1(egrid) idx, = numpy.where((egrid >= elo) & (egrid < ehi)) # To match XSpec, need to multiply by (Ehi^2-Elo^2)/(Ehi-Elo) # which means that we need the next bin to get Ehi. Due to # the form of the where statement, we should be missing the # Ehi value of the last bin e1 = egrid[idx] e2 = egrid[idx + 1] f1 = 8.01096e-10 * ((e2 * e2 - e1 * e1) * y1[idx] / (e2 - e1)).sum() # The cflux parameters are elo, ehi, and the log of the # flux within this range (this is log base 10 of the # flux in erg/cm^2/s). The parameters chosen for the # powerlaw, and energy range, should have f1 ~ 1.5e-9 # (log 10 of this is -8.8). lflux = -5.0 pars = [elo, ehi, lflux] y2 = xs._xspec.C_cflux(pars, y1, egrid) assert_is_finite(y2, "cflux", "energy") elo = egrid[:-1] ehi = egrid[1:] wgrid = _hc / egrid whi = wgrid[:-1] wlo = wgrid[1:] expected = y1 * 10**lflux / f1 numpy.testing.assert_allclose(expected, y2, err_msg='energy, single grid') y1 = mdl1(wgrid) y2 = xs._xspec.C_cflux(pars, y1, wgrid) assert_is_finite(y2, "cflux", "wavelength") numpy.testing.assert_allclose(expected, y2, err_msg='wavelength, single grid') expected = expected[:-1] y1 = mdl1(elo, ehi) y2 = xs._xspec.C_cflux(pars, y1, elo, ehi) numpy.testing.assert_allclose(expected, y2, err_msg='energy, two arrays') y1 = mdl1(wlo, whi) y2 = xs._xspec.C_cflux(pars, y1, wlo, whi) numpy.testing.assert_allclose(expected, y2, err_msg='wavelength, two arrays')
def simulate_data_old(obs_time, gamma, d): """ Simulate data with a power law using sherpa/XSPEC Power law will be normalized according to the observation time and will have the power law index Returns: energy bins, spectrum with pile up, spectrum without pile up, raw counts """ #Load fake PHA data to get energy bins #datadir = "/Users/mlazz/Dropbox/UW/PileupABC/PileupABC/Margaret/" #ui.load_data(id="p1", filename=datadir+"fake_acis.pha") #d = ui.get_data("p1") bin_lo = d.bin_lo bin_hi = d.bin_hi bin_mid = bin_lo + (bin_hi - bin_lo) / 2.0 #Extract counts and read in ARF and RMF counts = d.counts arf = d.get_arf() # get out an ARF object rmf = d.get_rmf() # get out an RMF object #Get exposure time, and energy bins from ARF exposure = arf.exposure # exposure time in seconds specresp = arf.specresp # the actual response in the ARF, i.e. effective area + stuff energ_lo = arf.energ_lo energ_hi = arf.energ_hi norm = obs_time / 1.0e7 # input power law normalization PhoIndex = gamma # input power law photon index (is negative by default) #Use XSPEC to generate simple power law spectral model with given observation time and photon index p = xspec.XSpowerlaw() p.norm = norm p.PhoIndex = PhoIndex base_model = p(energ_lo, energ_hi) #Apply ARF and RMF to power law spectrum base_arf = arf.apply_arf(base_model) * exposure base_spec = rmf.apply_rmf(base_arf) #Implement pile up: #np.random.seed(200) # set the seed for reproducibility nphot = np.random.poisson( np.sum(base_spec) ) #number of photons is Poisson distributed around expected value tstart = 0 tend = tstart + exposure phot_times = np.random.uniform(tstart, tend, size=nphot) phot_times = np.sort(phot_times) spec_pdf = base_spec / np.sum(base_spec) phot_energies = np.random.choice(energ_lo, size=nphot, replace=True, p=spec_pdf) frametime = 3.2 intervals = np.arange( tstart, tend + frametime, frametime) #set the times that the detector will be read #Apply pileup with binned_statistic which counts up number of photons that arrive within a read time summed_erg, bins, bin_idx = scipy.stats.binned_statistic(phot_times, phot_energies, bins=intervals, statistic="sum") n_per_bin = np.bincount( bin_idx - 1) # bin_idx is one-indexed for reasons I don't understand! phot_per_bin, bins2 = np.histogram(phot_times, bins=intervals) summed_erg = summed_erg[(summed_erg > 0) & (summed_erg < np.max(energ_hi))] energ_intervals = np.hstack([energ_lo, energ_hi[-1]]) spec_nopileup, spec_bins = np.histogram(phot_energies, bins=energ_intervals) spec_pileup, spec_bins = np.histogram(summed_erg, bins=energ_intervals) pileup_fraction = len(n_per_bin[n_per_bin > 1]) / len(n_per_bin) #print("%i percent of the frames have piled events in them."%(pileup_fraction*100)) return energ_intervals, spec_pileup, spec_nopileup, d.counts
def test_convolution_model_cflux(): """This tests the low-level interfce of the convolution model""" # Use the cflux convolution model, since this gives # an easily-checked result. # import sherpa.astro.xspec as xs if not hasattr(xs._xspec, 'C_cflux'): pytest.skip('cflux convolution model is missing') # The energy grid should extend beyond the energy grid # used to evaluate the model, to avoid any edge effects. # It also makes things easier if the elo/ehi values align # with the egrid bins. elo = 0.55 ehi = 1.45 egrid = numpy.linspace(0.5, 1.5, 101) eg1 = egrid[:-1] eg2 = egrid[1:] mdl1 = xs.XSpowerlaw() mdl1.PhoIndex = 2 # flux of mdl1 over the energy range of interest; converting # from a flux in photon/cm^2/s to erg/cm^2/s, when the # energy grid is in keV. y1 = mdl1(eg1, eg2) idx, = numpy.where((egrid >= elo) & (egrid < ehi)) # To match XSpec, need to multiply by (Ehi^2-Elo^2)/(Ehi-Elo) # which means that we need the next bin to get Ehi. Due to # the form of the where statement, we should be missing the # Ehi value of the last bin e1 = egrid[idx] e2 = egrid[idx + 1] f1 = 8.01096e-10 * ((e2 * e2 - e1 * e1) * y1[idx] / (e2 - e1)).sum() # The cflux parameters are elo, ehi, and the log of the # flux within this range (this is log base 10 of the # flux in erg/cm^2/s). The parameters chosen for the # powerlaw, and energy range, should have f1 ~ 1.5e-9 # (log 10 of this is -8.8). lflux = -5.0 pars = [elo, ehi, lflux] y1_a = numpy.zeros(y1.size + 1) y1_a[:-1] = y1 y2_a = xs._xspec.C_cflux(pars, y1_a, egrid) y2_b = xs._xspec.C_cflux(pars, y1, eg1, eg2) assert y2_a[:-1] == pytest.approx(y2_b) assert y2_a[-1] == 0.0 assert_is_finite(y2_b, "cflux", "energy") elo = egrid[:-1] ehi = egrid[1:] wgrid = _hc / egrid whi = wgrid[:-1] wlo = wgrid[1:] expected = y1 * 10**lflux / f1 assert y2_b == pytest.approx(expected) y1 = mdl1(elo, ehi) y2 = xs._xspec.C_cflux(pars, y1, elo, ehi) assert y2 == pytest.approx(expected) y1 = mdl1(wlo, whi) y2 = xs._xspec.C_cflux(pars, y1, wlo, whi) assert y2 == pytest.approx(expected)