def test_psf1d_kernel_model(caplog): """Access the kernel data: subkernel=True""" k = Box1D() k.xlow = 1 k.xhi = 3 m = PSFModel(kernel=k) dfold = Data1D('fold', np.arange(10), np.zeros(10)) with caplog.at_level(logging.INFO, logger='sherpa'): ans = m.get_kernel(dfold) assert len(caplog.records) == 1 r = caplog.record_tuples[0] assert r[0] == 'sherpa.instrument' assert r[1] == logging.INFO assert r[2] == "PSF frac: 1.0" assert isinstance(ans, Data1D) assert ans.name == 'kernel' # integers, so treat as exact assert (ans.x == np.arange(10)).all() # box1D between 1 and 3 inclusive y = np.asarray([0, 1, 1, 1, 0, 0, 0, 0, 0, 0]) assert ans.y == pytest.approx(y / y.sum()) assert ans.staterror is None assert ans.syserror is None
def test_runtime_interp(): def tst_runtime_interp(model, requested, interp): regrid_model = mdl.regrid(requested, interp=interp) yregrid = regrid_model(xgrid) return yregrid xgrid = np.arange(2, 6, 0.1) requested = np.arange(2.5, 5.1, 0.075) mdl = Box1D() mdl.xlow = 3.1 mdl.xhi = 4.2 mdl.ampl = 0.4 yregrid = tst_runtime_interp(mdl, requested, akima.akima) assert 4.4 == approx(yregrid.sum()) yregrid = tst_runtime_interp(mdl, requested, linear_interp) assert 4.4 == approx(yregrid.sum()) yregrid = tst_runtime_interp(mdl, requested, neville) assert -5.0e6 > yregrid.sum() d = Data1D('tst', xgrid, np.ones_like(xgrid)) yexpected = d.eval_model(mdl) requested = np.arange(2.5, 7, 0.2) rmdl = mdl.regrid(requested) ygot = d.eval_model(rmdl) assert ygot == approx(yexpected)
def test_low_level_regrid1d_partial_overlap(requested): """What happens if there is partial overlap of the grid? The question becomes how do we evaluate the model "outside" the regrid range. There's at least two options: a) set to 0 b) use the original grid This test is chosen so that it holds with both possibilities: the model evaluates to 0 outside of x=3.1 - 4.2, and the partial overlaps are carefully chosen to always include this full range. See https://github.com/sherpa/sherpa/issues/722 """ # The range over which we want the model evaluated xgrid = np.arange(2, 6, 0.1) d = Data1D('tst', xgrid, np.ones_like(xgrid)) mdl = Box1D() mdl.xlow = 3.1 mdl.xhi = 4.2 mdl.ampl = 0.4 yexpected = d.eval_model(mdl) assert yexpected.min() == pytest.approx(0.0) assert yexpected.max() == pytest.approx(0.4) ygot = d.eval_model(mdl.regrid(requested)) assert ygot == pytest.approx(yexpected)
def test_wrong_kwargs(): xgrid = np.arange(2, 6, 0.1) d = Data1D('tst', xgrid, np.ones_like(xgrid)) mdl = Box1D() requested = np.arange(1, 7, 0.1) with pytest.raises(TypeError) as excinfo: ygot = d.eval_model(mdl.regrid(requested, fubar='wrong_kwargs')) assert "unknown keyword argument: 'fubar'" in str(excinfo.value)
def test_psf1d_no_kernel(): """Error out if there's no kernel""" m = PSFModel('bob') b = Box1D() with pytest.raises(PSFErr) as exc: m(b) assert "PSF kernel has not been set" == str(exc.value)
def test_cache_status_multiple(caplog): """Check cache_status for a multi-component model. Unlike test_cache_syayus_single we also have evaluated the model so we can check that the cache status has changed. """ # The model expression includes an ArithmeticConstant model (the # term 2) which does not have a cache and so is ignored by # cache_status. # p = Polynom1D() b = Box1D() c = Const1D() mdl = c * (2 * p + b) # One model is not cached b._use_caching = False mdl([0.1, 0.2, 0.3]) mdl([0.1, 0.2, 0.3]) mdl([0.1, 0.2, 0.3, 0.4]) with caplog.at_level(logging.INFO, logger='sherpa'): mdl.cache_status() assert len(caplog.records) == 3 tokens = [] for lname, lvl, msg in caplog.record_tuples: assert lname == 'sherpa.models.model' assert lvl == logging.INFO toks = msg.split() assert len(toks) == 9 assert toks[1] == 'size:' assert toks[2] == '1' assert toks[3] == 'hits:' assert toks[5] == 'misses:' assert toks[7] == 'check:' assert toks[8] == '3' tokens.append(toks) toks = tokens[0] assert toks[0] == 'const1d' assert toks[4] == '1' assert toks[6] == '2' toks = tokens[1] assert toks[0] == 'polynom1d' assert toks[4] == '1' assert toks[6] == '2' toks = tokens[2] assert toks[0] == 'box1d' assert toks[4] == '0' assert toks[6] == '0'
def test_get_mins_maxes_warns(caplog): """What happens if min/max set outside valid range""" mdl = Box1D() mdl.xlow.set(min=0, max=100, val=50) mdl.xhi.set(min=200, max=300, val=250) # Some tests can change the logging level, so make sure # we have the level set correctly. # with caplog.at_level(logging.INFO, logger='sherpa'): mdl.thawedparmins = [10, -1e40, -10] mdl.thawedparmaxes = [1e40, 290, 10] assert mdl.thawedparmins == [10, -hugeval, -10] assert mdl.thawedparmaxes == [hugeval, 290, 10] # The handling of setting min to greater > hard_max # (and vice versa) isn't as easily checked, as the # parameter value ends up causing the code to # error out, so we set the parameter value to the # limit beforehand. # mdl.xhi.set(max=hugeval, val=hugeval) with caplog.at_level(logging.INFO, logger='sherpa'): mdl.thawedparmins = [10, 1e40, -10] mdl.thawedparmaxes = [1000, 1e41, 10] mdl.xlow.set(min=-hugeval, val=-hugeval) with caplog.at_level(logging.INFO, logger='sherpa'): mdl.thawedparmins = [-1e41, hugeval, -10] mdl.thawedparmaxes = [-1e40, hugeval, 10] assert len(caplog.records) == 6 msgs = [] for (log_name, log_level, message) in caplog.record_tuples: assert log_level == logging.WARNING assert log_name == 'sherpa.models.model' msgs.append(message) assert msgs[ 0] == 'value of parameter box1d.xhi minimum is below hard minimum; setting to hard minimum' assert msgs[ 1] == 'value of parameter box1d.xlow maximum is above hard maximum; setting to hard maximum' assert msgs[ 2] == 'value of parameter box1d.xhi minimum is above hard maximum; setting to hard maximum' assert msgs[ 3] == 'value of parameter box1d.xhi maximum is above hard maximum; setting to hard maximum' assert msgs[ 4] == 'value of parameter box1d.xlow minimum is below hard minimum; setting to hard minimum' assert msgs[ 5] == 'value of parameter box1d.xlow maximum is below hard minimum; setting to hard minimum'
def test_low_level_regrid1d_non_overlapping_not_allowed(): """Integrated data space must not overlap""" c = Box1D() lo = np.linspace(1, 100, 600) hi = np.linspace(2, 101, 600) with pytest.raises(ModelErr) as excinfo: c.regrid(lo, hi) assert ModelErr.dict['needsint'] in str(excinfo.value)
def make_1d_model(): """We want to create a new model each call. This could be made a fixture but this is easier. """ box = Box1D('box1') box.xlow = 2 box.xhi = 5 return box
def test_low_level_regrid1d_non_overlapping_not_allowed(): """Integrated data space must not overlap""" tmp = np.linspace(1, 100, 10) y = np.ones((9, )) d = Data1DInt('tst', tmp[:-1], tmp[1:], np.ones((9, ))) c = Box1D() lo = np.linspace(1, 100, 600) hi = np.linspace(2, 101, 600) with pytest.raises(ModelErr) as excinfo: c.regrid(lo, hi) assert ModelErr.dict['needsint'] in str(excinfo.value)
def test_psf_set_model_to_model(kernel_func): """Can we change the model field? model This is a regression test. """ m = PSFModel(kernel=kernel_func()) with pytest.raises(AttributeError) as err: m.model = Box1D() assert str( err.value ) == "'PSFModel' object attribute 'model' cannot be replaced with a callable attribute"
def test_psf1d_no_fold(): """Error out if there's no kernel""" box = Box1D() psf = PSFModel('bob', box) cpt = Gauss1D() sm = psf(cpt) with pytest.raises(PSFErr) as exc: sm([1, 2, 3, 4, 5]) assert "PSF model has not been folded" == str(exc.value)
def test_psf1d_convolved_pars(): """What does .pars mean for a PSFModel applied to a model?""" b1 = Box1D('b1') m = PSFModel(kernel=b1) b2 = Box1D('b2') c = m(b2) b1.xlow = 1 b1.xhi = 10 b1.ampl = 0.2 b2.xlow = 4 b2.xhi = 8 b2.ampl = 0.4 bpars = b1.pars + b2.pars assert len(bpars) == 6 cpars = c.pars assert len(cpars) == 6 for bpar, cpar in zip(bpars, cpars): assert cpar == bpar
def test_read_ideal_rmf(): """Can a RMF similar to issue #862 be read in? The MATRIX column in this file is a scalar rather than array, and let's do EBOUNDS then MATRIX blocks. """ from sherpa.astro.io import read_rmf ebins = np.arange(0.15, 0.2, 0.01) elo = ebins[:-1] ehi = ebins[1:] with NamedTemporaryFile() as f: fake_rmf(f.name) r = read_rmf(f.name) # Can we read in the data # assert r.detchans == 5 assert r.energ_lo == pytest.approx(elo) assert r.energ_hi == pytest.approx(ehi) assert (r.n_grp == [1, 1, 1, 1, 1]).all() assert (r.f_chan == [1, 2, 3, 4, 5]).all() assert (r.n_chan == [1, 1, 1, 1, 1]).all() assert r.offset == 1 assert r.e_min == pytest.approx(elo) assert r.e_max == pytest.approx(ehi) assert r.ethresh == 1e-10 # Can we apply it? # # The cmdl evalutes to a value of 2 * bin width # The bmdl evalates to the bin width * x # where x = [0, 1, 0.5, 0, 0] # cmdl = Const1D() cmdl.c0 = 2 bmdl = Box1D() bmdl.xlow = 0.16 bmdl.xhi = 0.175 mdl = bmdl + cmdl # Multiply by 100 so numbers are close to unity expected = 100 * 0.01 * np.asarray([2, 3, 2.5, 2, 2]) y = 100 * r.eval_model(mdl) assert y == pytest.approx(expected, rel=2e-6)
def test_ui_regrid1d_non_overlapping_not_allowed(): """Integrated data space must not overlap""" ui.dataspace1d(1, 100, 2, dstype=Data1DInt) b1 = Box1D() ui.set_model(b1) b1.xlow = 10 b1.xhi = 80 b1.ampl.max = 100 grid_hi = np.linspace(2, 101, 600) grid_lo = np.linspace(1, 100, 600) with pytest.raises(ModelErr) as excinfo: rb1 = b1.regrid(grid_lo, grid_hi) assert ModelErr.dict['needsint'] in str(excinfo.value)
def test_low_level_regrid1d_full_overlap(requested): """Base case of test_low_level_regrid1d_partial_overlap """ # The range over which we want the model evaluated xgrid = np.arange(2, 6, 0.1) d = Data1D('tst', xgrid, np.ones_like(xgrid)) mdl = Box1D() mdl.xlow = 3.1 mdl.xhi = 4.2 mdl.ampl = 0.4 yexpected = d.eval_model(mdl) assert yexpected.min() == pytest.approx(0.0) assert yexpected.max() == pytest.approx(0.4) ygot = d.eval_model(mdl.regrid(requested)) assert ygot == pytest.approx(yexpected)
def test_low_level_regrid1d_int_full_overlap(requested, tol): """Base case of test_low_level_regrid1d_int_partial_overlap """ # The range over which we want the model evaluated dx = 0.1 xgrid = np.arange(2, 6, dx) xlo = xgrid[:-1] xhi = xgrid[1:] d = Data1DInt('tst', xlo, xhi, np.ones_like(xlo)) mdl = Box1D() mdl.xlow = 3.1 mdl.xhi = 4.2 mdl.ampl = 0.4 yexpected = d.eval_model(mdl) assert yexpected.min() == pytest.approx(0.0) assert yexpected.max() == pytest.approx(0.4 * dx) ygot = d.eval_model(mdl.regrid(requested[:-1], requested[1:])) assert ygot == pytest.approx(yexpected, abs=tol)
def test_psf1d_pars(): """What does .pars mean for a PSFModel?""" b = Box1D() m = PSFModel(kernel=b) assert m.pars == ()
def test_calc_sherpa_regrid(): """Test the CFLUX convolution model calculations (Sherpa model) Can we redshift a sherpa model and get the expected result, with a regrid applied? In this case a feature is added outside the default energy range, but in the regridded range, to check things are working. See Also -------- test_calc_xspec_regrid """ from sherpa.astro import xspec mdl = Box1D('box') mdl.xlow = 4 mdl.xhi = 12 # why is the box amplitude restricted like this? mdl.ampl.max = 2 mdl.ampl = 2 kern = xspec.XSzashift('zshift') kern.redshift = 1.0 mdl_convolved = kern(mdl) d = setup_data(elo=0.1, ehi=10, ebin=0.01) dr = setup_data(elo=0.1, ehi=13, ebin=0.005) mdl_regrid = mdl_convolved.regrid(dr.xlo, dr.xhi) yconvolved = d.eval_model(mdl_convolved) yregrid = d.eval_model(mdl_regrid) # We expect values < 2 keV to be 0 # yconvolved: > 2 keV to < 5 keV to be 0.02 (ampl / bin width) # yregrid: > 2 keV to < 6 keV to be 0.02 # above this to be 0, with some fun at the edges. # ehi = d.xhi idx = np.where(ehi < 2) ymax = np.abs(yconvolved[idx]).max() assert ymax == 0.0, 'yconvolved < 2 keV: max={}'.format(ymax) idx = np.where((ehi >= 2) & (ehi < 5)) ymax = np.abs(yconvolved[idx] - 0.02).max() assert ymax < 1e-14, 'yconvolved: 2-5 keV max={}'.format(ymax) idx = np.where(ehi >= 5) ymax = np.abs(yconvolved[idx]).max() assert ymax == 0.0, 'yconvolved: > 5 keV max={}'.format(ymax) # expect last bin to be ~ 0 but preceeding ones to be zero idx = np.where(ehi < 2) ymax = np.abs(yregrid[idx][:-1]).max() assert ymax == 0, 'yregrid < 2 keV: max={}'.format(ymax) ymax = np.abs(yregrid[idx])[-1] assert ymax < 1e-14, 'yregrid < 2 keV: max={}'.format(ymax) idx = np.where((ehi >= 2) & (ehi < 6)) ymax = np.abs(yregrid[idx] - 0.02).max() assert ymax < 2e-14, 'yregrid: 2-6 keV {}'.format(ymax) # expect first bin to be ~ 0 but following ones to be zero idx = np.where(ehi >= 6) ymax = np.abs(yregrid[idx])[0] assert ymax < 2e-14, 'yregrid: > 6 keV max={}'.format(ymax) ymax = np.abs(yregrid[idx][1:]).max() assert ymax == 0.0, 'yregrid: > 6 keV max={}'.format(ymax)
def test_low_level_regrid1d_int_partial_overlap(requested, tol): """What happens if there is partial overlap of the grid? See test_low_level_regrid1d_partial_overlap. See the comments for when the tol value is used (only for edges, not "flat" sections of the model). """ # The range over which we want the model evaluated dx = 0.1 xgrid = np.arange(2, 6, dx) xlo = xgrid[:-1] xhi = xgrid[1:] d = Data1DInt('tst', xlo, xhi, np.ones_like(xlo)) mdl = Box1D() mdl.xlow = 3.1 mdl.xhi = 4.2 mdl.ampl = 0.4 yexpected = d.eval_model(mdl) assert yexpected.min() == pytest.approx(0.0) assert yexpected.max() == pytest.approx(0.4 * dx) rlo = requested[:-1] rhi = requested[1:] ygot = d.eval_model(mdl.regrid(rlo, rhi)) # Note that due to the different bin widths of the input and # output bins, the start and end bins of the integrated box # model will not line up nicely, and so the rising and falling # edges may cause differences for more than a single bin. We # can think of there being five regions (going from left to # right along the grid): # # before # rising edge # middle of box # falling edge # after # # The expected bin values for the middle are 0.4 * dx = 0.04 # and before and after should be 0. The edges depend on the # bin widths, so the tolerance here had been adjusted until # the tests pass. # before = np.arange(0, 10) rising = np.arange(10, 12) middle = np.arange(12, 21) trailing = np.arange(21, 23) after = np.arange(23, 39) # This ensures that if xgrid is changed (in length at least) we # have a reminder to check the above ranges. # assert len(xlo) == 39 for idxs in [before, middle, after]: assert ygot[idxs] == pytest.approx(yexpected[idxs]) for idxs in [rising, trailing]: assert ygot[idxs] == pytest.approx(yexpected[idxs], abs=tol)
def test_cache_clear_multiple(caplog): """Check cache_clear for a combined model.""" p = Polynom1D() b = Box1D() c = Const1D() mdl = c * (p + 2 * b) # Ensure one component doesn't use the cache c._use_caching = False # There's no official API for accessing the cache data, # so do it directly. # assert len(p._cache) == 0 assert p._cache_ctr['check'] == 0 assert p._cache_ctr['hits'] == 0 assert p._cache_ctr['misses'] == 0 assert len(b._cache) == 0 assert b._cache_ctr['check'] == 0 assert b._cache_ctr['hits'] == 0 assert b._cache_ctr['misses'] == 0 assert len(c._cache) == 0 assert c._cache_ctr['check'] == 0 assert c._cache_ctr['hits'] == 0 assert c._cache_ctr['misses'] == 0 mdl([1, 2, 3]) mdl([1, 2, 3]) mdl([1, 2, 3, 4]) assert len(p._cache) == 1 assert p._cache_ctr['check'] == 3 assert p._cache_ctr['hits'] == 1 assert p._cache_ctr['misses'] == 2 assert len(b._cache) == 1 assert b._cache_ctr['check'] == 3 assert b._cache_ctr['hits'] == 1 assert b._cache_ctr['misses'] == 2 assert len(c._cache) == 0 assert c._cache_ctr['check'] == 3 assert c._cache_ctr['hits'] == 0 assert c._cache_ctr['misses'] == 0 mdl.cache_clear() assert len(p._cache) == 0 assert p._cache_ctr['check'] == 0 assert p._cache_ctr['hits'] == 0 assert p._cache_ctr['misses'] == 0 assert len(b._cache) == 0 assert b._cache_ctr['check'] == 0 assert b._cache_ctr['hits'] == 0 assert b._cache_ctr['misses'] == 0 assert len(c._cache) == 0 assert c._cache_ctr['check'] == 0 assert c._cache_ctr['hits'] == 0 assert c._cache_ctr['misses'] == 0