def cmp_thread(fit_result, p1, covarerr): assert fit_result.statval == approx(37.9079, rel=1e-4) assert fit_result.rstat == approx(0.902569, rel=1e-4) assert fit_result.qval == approx(0.651155, rel=1e-4) assert fit_result.nfev == 22 assert fit_result.numpoints == 44 assert fit_result.dof == 42 p1.gamma.val == approx(2.15852, rel=1e-4) p1.ampl.val == approx(0.00022484, rel=1e-4) assert ui.calc_photon_flux() == approx(0.000469964, rel=1e-4) assert ui.calc_energy_flux() == approx(9.614847e-13, rel=1e-4) assert ui.calc_data_sum() == approx(706.85714092, rel=1e-4) assert ui.calc_model_sum() == approx(638.45693377, rel=1e-4) assert ui.calc_source_sum() == approx(0.046996409, rel=1e-4) calc = ui.eqwidth(p1, ui.get_source()) assert calc == approx(-0.57731725, rel=1e-4) calc = ui.calc_kcorr([1, 1.2, 1.4, 1.6, 1.8, 2], 0.5, 2) expected = [ 0.93132747, 0.9352768, 0.94085917, 0.94738472, 0.95415463, 0.96121113 ] assert calc == approx(expected, rel=1e-4)
def test_pha_intro(self): self.run_thread('pha_intro') # astro.ui imported as ui, instead of # being in global namespace self.assertEqualWithinTol(ui.get_fit_results().statval, 37.9079, 1e-4) self.assertEqualWithinTol(ui.get_fit_results().rstat, 0.902569, 1e-4) self.assertEqualWithinTol(ui.get_fit_results().qval, 0.651155, 1e-4) self.assertEqualWithinTol(self.locals['p1'].gamma.val, 2.15852, 1e-4) self.assertEqualWithinTol(self.locals['p1'].ampl.val, 0.00022484, 1e-4) self.assertEqualWithinTol(ui.calc_photon_flux(), 0.000469964, 1e-4) self.assertEqualWithinTol(ui.calc_energy_flux(), 9.614847e-13, 1e-4) self.assertEqualWithinTol(ui.calc_data_sum(), 706.85714092, 1e-4) self.assertEqualWithinTol(ui.calc_model_sum(), 638.45693377, 1e-4) self.assertEqualWithinTol(ui.calc_source_sum(), 0.046996409, 1e-4) self.assertEqualWithinTol( ui.eqwidth(self.locals['p1'], ui.get_source()), -0.57731725, 1e-4) self.assertEqualWithinTol( ui.calc_kcorr([1, 1.2, 1.4, 1.6, 1.8, 2], 0.5, 2), [ 0.93341286, 0.93752836, 0.94325233, 0.94990140, 0.95678054, 0.96393515 ], 1e-4) self.assertEqual(ui.get_fit_results().nfev, 22) self.assertEqual(ui.get_fit_results().numpoints, 44) self.assertEqual(ui.get_fit_results().dof, 42)
def test_pha_intro(self): self.run_thread('pha_intro') # astro.ui imported as ui, instead of # being in global namespace fit_results = ui.get_fit_results() covarerr = sqrt(fit_results.extra_output['covar'].diagonal()) assert covarerr[0] == approx(0.0790393, rel=1e-4) assert covarerr[1] == approx(1.4564e-05, rel=1e-4) assert fit_results.statval == approx(37.9079, rel=1e-4) assert fit_results.rstat == approx(0.902569, rel=1e-4) assert fit_results.qval == approx(0.651155, rel=1e-4) assert self.locals['p1'].gamma.val == approx(2.15852, rel=1e-4) assert self.locals['p1'].ampl.val == approx(0.00022484, rel=1e-4) assert ui.calc_photon_flux() == approx(0.000469964, rel=1e-4) assert ui.calc_energy_flux() == approx(9.614847e-13, rel=1e-4) assert ui.calc_data_sum() == approx(706.85714092, rel=1e-4) assert ui.calc_model_sum() == approx(638.45693377, rel=1e-4) assert ui.calc_source_sum() == approx(0.046996409, rel=1e-4) calc = ui.eqwidth(self.locals['p1'], ui.get_source()) assert calc == approx(-0.57731725, rel=1e-4) calc = ui.calc_kcorr([1, 1.2, 1.4, 1.6, 1.8, 2], 0.5, 2) expected = [0.93341286, 0.93752836, 0.94325233, 0.94990140, 0.95678054, 0.96393515] assert calc == approx(expected, rel=1e-4) self.assertEqual(ui.get_fit_results().nfev, 22) self.assertEqual(ui.get_fit_results().numpoints, 44) self.assertEqual(ui.get_fit_results().dof, 42)
def cmp_pha_intro(fit_result, p1, covarerr): assert fit_result.statval == approx(37.9079, rel=1e-4) assert fit_result.rstat == approx(0.902569, rel=1e-4) assert fit_result.qval == approx(0.651155, rel=1e-4) self.assertEqual(fit_result.nfev, 22) self.assertEqual(fit_result.numpoints, 44) self.assertEqual(fit_result.dof, 42) p1.gamma.val == approx(2.15852, rel=1e-4) p1.ampl.val == approx(0.00022484, rel=1e-4) assert ui.calc_photon_flux() == approx(0.000469964, rel=1e-4) assert ui.calc_energy_flux() == approx(9.614847e-13, rel=1e-4) assert ui.calc_data_sum() == approx(706.85714092, rel=1e-4) assert ui.calc_model_sum() == approx(638.45693377, rel=1e-4) assert ui.calc_source_sum() == approx(0.046996409, rel=1e-4) calc = ui.eqwidth(self.locals['p1'], ui.get_source()) assert calc == approx(-0.57731725, rel=1e-4) calc = ui.calc_kcorr([1, 1.2, 1.4, 1.6, 1.8, 2], 0.5, 2) # Prior to fixing #619 the expected values were # expected = [0.93341286, 0.93752836, 0.94325233, # 0.94990140, 0.95678054, 0.96393515] expected = [0.93132747, 0.9352768, 0.94085917, 0.94738472, 0.95415463, 0.96121113] assert calc == approx(expected, rel=1e-4)
def test_pha_intro(self): self.run_thread("pha_intro") # astro.ui imported as ui, instead of # being in global namespace self.assertEqualWithinTol(ui.get_fit_results().statval, 37.9079, 1e-4) self.assertEqualWithinTol(ui.get_fit_results().rstat, 0.902569, 1e-4) self.assertEqualWithinTol(ui.get_fit_results().qval, 0.651155, 1e-4) self.assertEqualWithinTol(self.locals["p1"].gamma.val, 2.15852, 1e-4) self.assertEqualWithinTol(self.locals["p1"].ampl.val, 0.00022484, 1e-4) self.assertEqualWithinTol(ui.calc_photon_flux(), 0.000469964, 1e-4) self.assertEqualWithinTol(ui.calc_energy_flux(), 9.614847e-13, 1e-4) self.assertEqualWithinTol(ui.calc_data_sum(), 706.85714092, 1e-4) self.assertEqualWithinTol(ui.calc_model_sum(), 638.45693377, 1e-4) self.assertEqualWithinTol(ui.calc_source_sum(), 0.046996409, 1e-4) self.assertEqualWithinTol(ui.eqwidth(self.locals["p1"], ui.get_source()), -0.57731725, 1e-4) self.assertEqualWithinTol( ui.calc_kcorr([1, 1.2, 1.4, 1.6, 1.8, 2], 0.5, 2), [0.93341286, 0.93752836, 0.94325233, 0.94990140, 0.95678054, 0.96393515], 1e-4, ) self.assertEqual(ui.get_fit_results().nfev, 22) self.assertEqual(ui.get_fit_results().numpoints, 44) self.assertEqual(ui.get_fit_results().dof, 42)
def test_fake_pha_multi_file(make_data_path, clean_astro_ui, reset_seed): '''Test fake_pha using multiple real input files. Note that HEG orders -1 and +1 should really be treated spearately, but for this test we just need two files to load. ''' np.random.seed(22349) ui.set_source("gauss1d.g1") g1 = ui.get_source() g1.pos = 3 g1.FWHM = .5 ui.fake_pha(None, [ make_data_path('3c120_heg_-1.arf.gz'), make_data_path('3c120_heg_1.arf.gz') ], [ make_data_path('3c120_heg_-1.rmf.gz'), make_data_path('3c120_heg_1.rmf.gz') ], 500.) data = ui.get_data() # Even with noise, maximum should be close to 3 keV assert np.isclose(data.get_x()[np.argmax(data.counts)], 3., atol=.2) # This is not a test from first principles, but at least a check of # the current behaviour assert data.counts.sum() > 5000 assert data.counts.sum() < 10000
def __init__(self, id=None, fluxtype="photon"): "If id is None the default id will be used." if id is None: self.id = ui.get_default_id() else: self.id = id if fluxtype in self._valid_fluxtypes: self.fluxtype = fluxtype else: emsg = "fluxtype set to {} but must be one of: {}".format( fluxtype, " ".join(self._valid_fluxtypes)) raise ValueError(emsg) # Set up the xlo/xhi/xmid arrays d = ui.get_data(self.id) self._calc_bins(d) self._apply_mask(d) # Important to use get_source and not get_model as we do not # want to apply any instrument model to the evaluation. # # Note that we do not hold onto the model expression object, # which is probably not an issue here. # mdl = ui.get_source(id) self.modelexpr = mdl.name # We do not use xlo/xhi but the _xlo/_xhi attributes which # contain an extra bin, in case of X-Spec models # src = mdl(self._xlo, self._xhi)[:-1] if np.any(src < 0.0): emsg = "There are negative values in your source " + \ "model (id={0})!".format(self.id) raise RuntimeError(emsg) if np.all(src <= 0.0): emsg = "The source model for id={0} ".format(self.id) + \ "evaluates to 0!" raise RuntimeError(emsg) # Conversion to a single datatype is a bit excessive here. # dtype = src.dtype if self.fluxtype == "erg": norm = _charge_e * np.sum(src * self.xmid) else: norm = np.sum(src) self.weight = src / norm self.weight = self.weight.astype(dtype) self.xlo = self.xlo.astype(dtype) self.xhi = self.xhi.astype(dtype) self.xmid = self.xmid.astype(dtype)
def fit_multiplets(multipletlist, id = None, outpath = None, plot = False, delta_lam = .2): # n_lines = np.sum([len(mult['wave']) for mult in multipletlist]) result = np.zeros(n_lines, dtype = {'names': ['multname', 'linename', 'wave', 'flux', 'errup', 'errdown', 'photons', 'photonflux'], 'formats': ['S30', 'S30', 'f4', 'f4', 'f4', 'f4', 'f4', 'f4']}) currentline = 0 # for mult in multipletlist: if outpath is not None: outfile = os.path.join(outpath, filter(lambda x: x.isalnum(), mult['name'])) else: outfile = None fit_lines(mult, id, delta_lam = delta_lam, plot = plot, outfile = outfile) # ui.conf(id) conf_res = ui.get_conf_results() source = ui.get_source(id) #set all ampl to 0 and only for 1 line to real value set_all_val('c0', 0., id) for lname, lfili, lwave in zip(mult['linename'], mult['fililiname'], mult['wave']): print 'Fitting line '+str(currentline+1)+'/'+str(n_lines) par = ui.get_model_component(lfili) indconf_res = ( np.array(conf_res.parnames) == lfili+'.ampl').nonzero()[0] set_all_val('ampl', 0., id) par.ampl.val = conf_res.parvals[indconf_res] counts = ui.calc_model_sum(None, None, id) # print(lname, counts) photonflux = ui.calc_photon_flux(None, None, id) # determine scaling between ampl and flux par.ampl.val = 1 amp2flux = ui.calc_energy_flux(None, None, id) par.ampl.val = conf_res.parvals[indconf_res] # val = conf_res.parvals[indconf_res] * amp2flux errdown = conf_res.parmins[indconf_res] * amp2flux if conf_res.parmins[indconf_res] else np.nan errup = conf_res.parmaxes[indconf_res] * amp2flux if conf_res.parmaxes[indconf_res] else np.nan # result[currentline] = (mult['name'], lname, lwave, val, errup, errdown, counts, photonflux) # currentline +=1 return result
def image_model_sherpa(exposure, psf, sources, model_image, overwrite): """Compute source model image with Sherpa. Inputs: * Source list (JSON file) * PSF (JSON file) * Exposure image (FITS file) Outputs: * Source model flux image (FITS file) * Source model excess image (FITS file) """ import sherpa.astro.ui as sau from ..image.models.psf import Sherpa from ..image.models.utils import read_json log.info('Reading exposure: {0}'.format(exposure)) # Note: We don't really need the exposure as data, # but this is a simple way to init the dataspace to the correct shape sau.load_data(exposure) sau.load_table_model('exposure', exposure) log.info('Reading PSF: {0}'.format(psf)) Sherpa(psf).set() log.info('Reading sources: {0}'.format(sources)) read_json(sources, sau.set_source) name = sau.get_source().name full_model = 'exposure * psf({})'.format(name) sau.set_full_model(full_model) log.info('Computing and writing model_image: {0}'.format(model_image)) sau.save_model(model_image, clobber=overwrite) sau.clean() sau.delete_psf()
def setUp(self): self._old_logger_level = logger.getEffectiveLevel() logger.setLevel(logging.ERROR) ui.set_stat('wstat') infile1 = self.make_path('3c273.pi') infile2 = self.make_path('9774.pi') ui.load_pha(1, infile1) ui.load_pha(2, infile2) # Since 9774.pi isn't grouped, group it. Note that this # call groups the background to 20 counts per bin. In this # case we do not want that; instead we want to use the same # grouping scheme as the source file. # # Note: this is related to issue 227 # ui.group_counts(2, 20) ui.set_grouping(2, bkg_id=1, val=ui.get_grouping(2)) # There's no need to have the same model in both datasets, # but assume the same source model can be used, with a # normalization difference. # ui.set_source(1, ui.powlaw1d.pl1) ui.set_source(2, ui.const1d.c2 * ui.get_source(1)) # The powerlaw slope and normalization are # intended to be "a reasonable approximation" # to the data, just to make sure that any statistic # calculation doesn't blow-up too much. # # Note: the model values for 3c273 are slighly different # to the single-PHA-file case, so stat results are # slightly different # ui.set_par("pl1.gamma", 1.7) ui.set_par("pl1.ampl", 1.6e-4) ui.set_par("c2.c0", 45)
def sherpa_model_image(exposure, psf, sources, model_image, overwrite): """Compute source model image with Sherpa. Inputs: * Source list (JSON file) * PSF (JSON file) * Exposure image (FITS file) Outputs: * Source model flux image (FITS file) * Source model excess image (FITS file) """ import logging logging.basicConfig(level=logging.DEBUG, format='%(levelname)s - %(message)s') import sherpa.astro.ui as sau # @UnresolvedImport from ..morphology.psf import Sherpa from ..morphology.utils import read_json logging.info('Reading exposure: {0}'.format(exposure)) # Note: We don't really need the exposure as data, # but this is a simple way to init the dataspace to the correct shape sau.load_data(exposure) sau.load_table_model('exposure', exposure) logging.info('Reading PSF: {0}'.format(psf)) Sherpa(psf).set() logging.info('Reading sources: {0}'.format(sources)) read_json(sources, sau.set_source) name = sau.get_source().name full_model = 'exposure * psf({})'.format(name) sau.set_full_model(full_model) logging.info('Computing and writing model_image: {0}'.format(model_image)) sau.save_model(model_image, clobber=overwrite)
def write_all(filename='results.json'): """Dump source, fit results and conf results to a JSON file. http://www.astropython.org/snippet/2010/7/Save-sherpa-fit-and-conf-results-to-a-JSON-file """ import sherpa.astro.ui as sau out = dict() if 0: src = sau.get_source() src_par_attrs = ('name', 'frozen', 'modelname', 'units', 'val', 'fullname') out['src'] = dict(name=src.name, pars=[ dict((attr, getattr(par, attr)) for attr in src_par_attrs) for par in src.pars ]) try: fit_attrs = ('methodname', 'statname', 'succeeded', 'statval', 'numpoints', 'dof', 'rstat', 'qval', 'nfev', 'message', 'parnames', 'parvals') fit = sau.get_fit_results() out['fit'] = dict((attr, getattr(fit, attr)) for attr in fit_attrs) except Exception as err: print(err) try: conf_attrs = ('datasets', 'methodname', 'fitname', 'statname', 'sigma', 'percent', 'parnames', 'parvals', 'parmins', 'parmaxes', 'nfits') conf = sau.get_conf_results() out['conf'] = dict((attr, getattr(conf, attr)) for attr in conf_attrs) except Exception as err: print(err) try: covar_attrs = ('datasets', 'methodname', 'fitname', 'statname', 'sigma', 'percent', 'parnames', 'parvals', 'parmins', 'parmaxes', 'nfits') covar = sau.get_covar_results() out['covar'] = dict( (attr, getattr(covar, attr)) for attr in covar_attrs) except Exception as err: print(err) if 0: out['pars'] = [] for par in src.pars: fullname = par.fullname if any(fullname == x['name'] for x in out['pars']): continue # Parameter was already processed outpar = dict(name=fullname, kind=par.name) # None implies no calculated confidence interval for Measurement parmin = None parmax = None try: if fullname in conf.parnames: # Confidence limits available from conf i = conf.parnames.index(fullname) parval = conf.parvals[i] parmin = conf.parmins[i] parmax = conf.parmaxes[i] if parmin is None: parmin = -float( 'inf' ) # None from conf means infinity, so set accordingly if parmax is None: parmax = float('inf') elif fullname in fit.parnames: # Conf failed or par is uninteresting and wasn't sent to conf i = fit.parnames.index(fullname) parval = fit.parvals[i] else: # No fit or conf value (maybe frozen) parval = par.val except Exception as err: print(err) out['pars'].append(outpar) if filename is None: return out else: json.dump(out, open(filename, 'w'), sort_keys=True, indent=4)
def _get_chart_spectrum(id=None, elow=None, ehigh=None, ewidth=None, norm=None): """Helper routine for *_chart_spectrum.""" # What source expression are we using? # get_model/source will throw an IdentifierErr if the expression # is not defined; we do not, at present catch/re-throw this # if id is None: id = s.get_default_id() mdl = s.get_source(id) # What energy grid to use? Since we do not want to restrict users # to only using PHA datasets (i.e. if I just want to create # something simple) then we have to look for a range of errors # from get_arf # if elow is None or ehigh is None or ewidth is None: try: arf = s.get_arf(id) except (IdentifierErr, ArgumentErr): # a) PHA dataset, no ARF # b) Assume this means the dataset is not derived from the # PHA class arf = None if arf is None: emsg = "No ARF found for dataset {} ".format(repr(id)) + \ "so unable to create energy grid" raise TypeError(emsg) if elow is None: elow = arf.energ_lo[0] if ehigh is None: ehigh = arf.energ_hi[-1] if ewidth is None: # Assume constant grid spacing in the ARF de = arf.energ_hi[-1] - arf.energ_lo[0] nelem = np.size(arf.energ_lo) ewidth = de * 1.0 / nelem if elow >= ehigh: emsg = "elow is >= ehigh: " + \ "elow={} ehigh={}".format(elow, ehigh) raise TypeError(emsg) if ewidth <= 0.0: raise TypeError("ewidth is <= 0.0: ewidth={0}".format(ewidth)) # The following is wasteful if we have an ARF and the user # supplies no elow, ehigh, or ewidth arguments. # # Should I check that nbins is a sensible number (e.g. >= 2)? # nbins = 1 + np.rint((ehigh - elow) / ewidth) erange = elow + ewidth * np.arange(nbins) elo = erange[:-1] ehi = erange[1:] flux = mdl(elo, ehi) emid = 0.5 * (ehi + elo) # do we want to renormalize? if norm is not None: flux *= norm return { "x": emid, "xlo": elo, "xhi": ehi, "y": flux, "id": id, "model": mdl.name }
def set_all_val(name, val, id = None): 'e.g. set_all_val("ampl", 0)' source = ui.get_source(id) for par in source.pars: if par.name == name: par.val = val
def test_xspec_con_ui_shift_regrid(make_data_path, clean_astro_ui, restore_xspec_settings): """Check shifted models from the UI layer with a response and regrid. Unlike test_xspec_con_ui_shift, the convolution model is run on an extended grid compared to the RMF. """ from sherpa.astro import xspec infile = make_data_path('3c273.pi') ui.load_pha(infile) ui.subtract() ui.ignore(None, 0.5) ui.ignore(7, None) # Ensure the grid contains the RMF grid (0.1-11 keV). # Really we should have emax to be > 11 * (1+z) but # I purposefully pick a smaller maximum to check we # get 0 values in the output # rgrid = np.arange(0.1, 20, 0.01) rlo = rgrid[:-1] rhi = rgrid[1:] msource = ui.box1d.box + ui.const1d.bgnd csource = ui.xszashift.zsh(msource) ui.set_source(ui.xsphabs.gal * csource.regrid(rlo, rhi)) mdl = ui.get_source() # What should the string representation be? # assert mdl.name == '(xsphabs.gal * regrid1d(xszashift.zsh((box1d.box + const1d.bgnd))))' assert len(mdl.pars) == 6 assert mdl.pars[0].fullname == 'gal.nH' assert mdl.pars[1].fullname == 'zsh.Redshift' assert mdl.pars[2].fullname == 'box.xlow' assert mdl.pars[3].fullname == 'box.xhi' assert mdl.pars[4].fullname == 'box.ampl' assert mdl.pars[5].fullname == 'bgnd.c0' assert isinstance(mdl.lhs, xspec.XSphabs) assert isinstance(mdl.rhs, RegridWrappedModel) gal = ui.get_model_component('gal') zsh = ui.get_model_component('zsh') box = ui.get_model_component('box') bgnd = ui.get_model_component('bgnd') assert isinstance(gal, xspec.XSphabs) assert isinstance(zsh, xspec.XSzashift) assert isinstance(box, Box1D) assert isinstance(bgnd, Const1D) zsh.redshift = 1 # turn off the absorption to make the comparison easier gal.nh = 0 # pick an energy range that exceeds the RMF maximum energy (11 keV) box.xlow = 10 box.xhi = 13 box.ampl = 0.5 bgnd.c0 = 0.001 bgnd.integrate = False mplot = ui.get_source_plot() # Expect, as z = 1 # # 0.1 for E < 5 keV or 6.5 - 10 keV # 0.6 5 - 6.5 keV # 0 > 10 keV # idx1 = (mplot.xhi <= 5) | ((mplot.xlo >= 6.5) & (mplot.xhi <= 10)) idx2 = (mplot.xlo >= 5) & (mplot.xhi <= 6.5) idx3 = mplot.xlo >= 10 # ensure we pick the "expected" range (there are 1090 bins in the # RMF energy grid) assert idx1.sum() == 840 assert idx2.sum() == 150 assert idx3.sum() == 100 # The tolerance has to be relatively large otherwise things fail # # It appears that the very-last bin of idx1 is actually ~ 0.05, # so separate that out here. It is the last bin of the "valid" # array, and so at z=1 may only have been half-filled by the # convolution. # assert mplot.y[idx1][:-1] == pytest.approx(0.1, rel=1e-5) assert mplot.y[idx1][-1] == pytest.approx(0.05, rel=3e-5) assert mplot.y[idx2] == pytest.approx(0.6, rel=3e-5) assert mplot.y[idx3] == pytest.approx(0)
def test_xspec_con_ui_shift(make_data_path, clean_astro_ui, restore_xspec_settings): """Check shifted models from the UI layer with a response. There is no regrid here, so we see the issue with the upper edge of the RMF cutting off the source. """ from sherpa.astro import xspec infile = make_data_path('3c273.pi') ui.load_pha(infile) ui.subtract() ui.ignore(None, 0.5) ui.ignore(7, None) msource = ui.box1d.box + ui.const1d.bgnd ui.set_source(ui.xsphabs.gal * ui.xszashift.zsh(msource)) mdl = ui.get_source() assert mdl.name == '(xsphabs.gal * xszashift.zsh((box1d.box + const1d.bgnd)))' assert len(mdl.pars) == 6 assert mdl.pars[0].fullname == 'gal.nH' assert mdl.pars[1].fullname == 'zsh.Redshift' assert mdl.pars[2].fullname == 'box.xlow' assert mdl.pars[3].fullname == 'box.xhi' assert mdl.pars[4].fullname == 'box.ampl' assert mdl.pars[5].fullname == 'bgnd.c0' assert isinstance(mdl.lhs, xspec.XSphabs) assert isinstance(mdl.rhs, xspec.XSConvolutionModel) gal = ui.get_model_component('gal') zsh = ui.get_model_component('zsh') box = ui.get_model_component('box') bgnd = ui.get_model_component('bgnd') assert isinstance(gal, xspec.XSphabs) assert isinstance(zsh, xspec.XSzashift) assert isinstance(box, Box1D) assert isinstance(bgnd, Const1D) zsh.redshift = 1 # turn off the absorption to make the comparison easier gal.nh = 0 # pick an energy range that exceeds the RMF maximum energy (11 keV) box.xlow = 10 box.xhi = 13 box.ampl = 0.5 bgnd.c0 = 0.001 bgnd.integrate = False mplot = ui.get_source_plot() # Expect, as z = 1 # # 0.1 for E < 5 keV (10 / (1+z)) # 0 E > 5.5 keV (11 / (1+z)) due to RMF cut off # 0.6 5 - 5.5 keV # idx1 = mplot.xhi <= 5 idx2 = mplot.xlo >= 5.5 # ensure we pick the "expected" range assert idx1.sum() == 490 assert idx2.sum() == 550 assert mplot.xhi[idx1].max() == pytest.approx(5) assert mplot.xlo[idx2].min() == pytest.approx(5.5) # use the inverse of the two index arrays to ensure we are not # missing any bins. # idx3 = ~(idx1 | idx2) # The tolerance has to be relatively large otherwise things fail assert mplot.y[idx1] == pytest.approx(0.1, rel=3e-5) assert mplot.y[idx2] == pytest.approx(0) assert mplot.y[idx3] == pytest.approx(0.6, rel=1e-5)
def test_xspec_con_ui_cflux(make_data_path, clean_astro_ui, restore_xspec_settings): """Check cflux from the UI layer with a response.""" from sherpa.astro import xspec infile = make_data_path('3c273.pi') ui.load_pha('random', infile) ui.subtract('random') ui.ignore(None, 0.5) ui.ignore(7, None) ui.set_source('random', 'xsphabs.gal * xscflux.sflux(powlaw1d.pl)') mdl = ui.get_source('random') assert mdl.name == '(xsphabs.gal * xscflux.sflux(powlaw1d.pl))' assert len(mdl.pars) == 7 assert mdl.pars[0].fullname == 'gal.nH' assert mdl.pars[1].fullname == 'sflux.Emin' assert mdl.pars[2].fullname == 'sflux.Emax' assert mdl.pars[3].fullname == 'sflux.lg10Flux' assert mdl.pars[4].fullname == 'pl.gamma' assert mdl.pars[5].fullname == 'pl.ref' assert mdl.pars[6].fullname == 'pl.ampl' assert isinstance(mdl.lhs, xspec.XSphabs) assert isinstance(mdl.rhs, xspec.XSConvolutionModel) gal = ui.get_model_component('gal') sflux = ui.get_model_component('sflux') pl = ui.get_model_component('pl') assert isinstance(gal, xspec.XSphabs) assert isinstance(sflux, xspec.XScflux) assert isinstance(pl, PowLaw1D) # the convolution model needs the normalization to be fixed # (not for this example, as we are not fitting, but do this # anyway for reference) pl.ampl.frozen = True sflux.emin = 1 sflux.emax = 5 sflux.lg10Flux = -12.3027 pl.gamma = 2.03 gal.nh = 0.039 ui.set_xsabund('angr') ui.set_xsxsect('vern') # check we get the "expected" statistic (so this is a regression # test). # ui.set_stat('chi2gehrels') sinfo = ui.get_stat_info() assert len(sinfo) == 1 sinfo = sinfo[0] assert sinfo.numpoints == 40 assert sinfo.dof == 37 assert sinfo.statval == pytest.approx(21.25762265234619) # Do we get the same flux from Sherpa's calc_energy_flux? # cflux = ui.calc_energy_flux(id='random', model=sflux(pl), lo=1, hi=5) lcflux = np.log10(cflux) assert lcflux == pytest.approx(sflux.lg10Flux.val)
def estimate_weighted_expmap(id=None, arf=None, elo=None, ehi=None, specresp=None, fluxtype="photon", par=None, pvals=None): """Estimate the weighted exposure map value for an ARF. Parameters ---------- id : int, string, or None The Sherpa dataset to use. If ``None`` then the default dataset is used. arf : string, TABLECrate, or None The ARF to use. It must contain the following columns: ``energ_lo``, ``energ_hi``, and ``specresp``. elo, ehi, specresp : array of numbers or None The ARF, where the bin edges are in KeV and the response is in cm^2. These are only checked if arf is None, in which case all three must be given and have the same size (one dimensional). fluxtype : 'photon' or 'erg' The units of the exposure map are cm^2 count / ``fluxtype``. The default is ``photon``. par : Sherpa parameter object or None If not given then the exposure map is calculated at the current parameter settings. If given, it is the Sherpa parameter to loop over, using pvals (which must be set). pvals : array of numbers or None If par is set, calcualte the exposure map at the current parameter settings whilst setting the par parameter to each of the values in pvals. The parameter value is reset to its original value when the routine exits. Return ------ expmap : scalar or array of numbers When par is None then a scalar, otherwise an array the same size as pvals. See Also -------- get_instmap_weights plot_instmap_weights save_instmap_weights Notes ----- The ARF is interpolated onto the energy grid of the dataspace. Examples -------- Calculate the exposure map over the range gamma = 0.1 to 5, with 0.1 step increments, for an absorbed power-law model and with the ARF in the file "arf.fits". >>> dataspace1d(0.3, 8.0, 0.1) >>> set_source(xsphabs.gal * powlaw1d.pl) >>> gal.nh = 0.087 >>> pl.gamma = 1.2 >>> gvals = np.arange(0.5,5,0.1) >>> evals = estimate_weighted_expmap(arf="arf.fits", par=pl.gamma, pvals=gvals) """ # Usage errors. We can not catch them all before doing actual # work. # if arf is None and \ (elo is not None or ehi is not None or specresp is not None): # we only worry about elo/ehi/specresp if arf is NOT given if elo is None or ehi is None or specresp is None: emsg = "Missing one or more of elo, ehi, and specresp." raise TypeError(emsg) if id is None: id = ui.get_default_id() if par is not None or pvals is not None: if par is None or pvals is None: emsg = "Either both par and pvals are set or they " + \ "are both None." raise TypeError(emsg) if not isinstance(par, Parameter): emsg = "par argument must be a Sherpa model parameter." raise TypeError(emsg) if not hasattr(pvals, "__iter__"): emsg = "pvals argument must be an iterable (array/list)." raise TypeError(emsg) smdl = ui.get_source(id) if par not in smdl.pars: emsg = "par argument is not a parameter of the " + \ "source model" raise TypeError(emsg) wgts = get_instmap_weights(id, fluxtype=fluxtype) if isinstance(wgts, InstMapWeights1DInt) and arf is None and \ elo is None: emsg = "The arf parameter or the elo,ehi,specresp " + \ "parameters must be given." raise TypeError(emsg) if arf is None: if elo is None: args = [] else: args = [elo, ehi, specresp] else: args = [arf] if par is None: return wgts.estimate_expmap(*args) else: # Ugh: we have to create an object for each evaluation, which # is rather wasteful. # orig = par.val out = [] try: for pval in pvals: par.val = pval wgts = get_instmap_weights(id, fluxtype=fluxtype) out.append(wgts.estimate_expmap(*args)) finally: par.val = orig return np.asarray(out)
def write_all(filename='results.json'): """Dump source, fit results and conf results to a JSON file. http://www.astropython.org/snippet/2010/7/Save-sherpa-fit-and-conf-results-to-a-JSON-file """ import sherpa.astro.ui as sau out = dict() if 0: src = sau.get_source() src_par_attrs = ('name', 'frozen', 'modelname', 'units', 'val', 'fullname') out['src'] = dict(name=src.name, pars=[dict((attr, getattr(par, attr)) for attr in src_par_attrs) for par in src.pars]) try: fit_attrs = ('methodname', 'statname', 'succeeded', 'statval', 'numpoints', 'dof', 'rstat', 'qval', 'nfev', 'message', 'parnames', 'parvals') fit = sau.get_fit_results() out['fit'] = dict((attr, getattr(fit, attr)) for attr in fit_attrs) except Exception as err: print(err) try: conf_attrs = ('datasets', 'methodname', 'fitname', 'statname', 'sigma', 'percent', 'parnames', 'parvals', 'parmins', 'parmaxes', 'nfits') conf = sau.get_conf_results() out['conf'] = dict((attr, getattr(conf, attr)) for attr in conf_attrs) except Exception as err: print(err) try: covar_attrs = ('datasets', 'methodname', 'fitname', 'statname', 'sigma', 'percent', 'parnames', 'parvals', 'parmins', 'parmaxes', 'nfits') covar = sau.get_covar_results() out['covar'] = dict((attr, getattr(covar, attr)) for attr in covar_attrs) except Exception as err: print(err) if 0: out['pars'] = [] for par in src.pars: fullname = par.fullname if any(fullname == x['name'] for x in out['pars']): continue # Parameter was already processed outpar = dict(name=fullname, kind=par.name) # None implies no calculated confidence interval for Measurement parmin = None parmax = None try: if fullname in conf.parnames: # Confidence limits available from conf i = conf.parnames.index(fullname) parval = conf.parvals[i] parmin = conf.parmins[i] parmax = conf.parmaxes[i] if parmin is None: parmin = -float('inf') # None from conf means infinity, so set accordingly if parmax is None: parmax = float('inf') elif fullname in fit.parnames: # Conf failed or par is uninteresting and wasn't sent to conf i = fit.parnames.index(fullname) parval = fit.parvals[i] else: # No fit or conf value (maybe frozen) parval = par.val except Exception as err: print(err) out['pars'].append(outpar) if filename is None: return out else: json.dump(out, open(filename, 'w'), sort_keys=True, indent=4)