def fit(self, current): self._fit.model.thawedpars = current # nm = NelderMead() # nm.config['iquad'] = 0 # nm.config['finalsimplex'] = 1 #lm = LevMar() #lm.config['maxfev'] = 5 cv = Covariance() # Use the fit method defined before called get_draws(). This way the user # does not have to pass in the fitting method and method options. fit = Fit(self._fit.data, self._fit.model, self._fit.stat, self._fit.method, cv) fit_result = fit.fit() covar_result = fit.est_errors() sigma = np.array(covar_result.extra_output) if np.isnan(sigma).any(): raise CovarError("NaNs found in covariance matrix") # cache the fitting scales self._sigma = sigma
def setup(): data = Data1D('fake', _x, _y, _err) g1 = Gauss1D('g1') g1.fwhm.set(1.0, _tiny, _max, frozen=False) g1.pos.set(1.0, -_max, _max, frozen=False) g1.ampl.set(1.0, -_max, _max, frozen=False) p1 = PowLaw1D('p1') p1.gamma.set(1.0, -10, 10, frozen=False) p1.ampl.set(1.0, 0.0, _max, frozen=False) p1.ref.set(1.0, -_max, _max, frozen=True) model = p1 + g1 method = LevMar() method.config['maxfev'] = 10000 method.config['ftol'] = float(_eps) method.config['epsfcn'] = float(_eps) method.config['gtol'] = float(_eps) method.config['xtol'] = float(_eps) method.config['factor'] = float(100) fit = Fit(data, model, Chi2DataVar(), method, Covariance()) results = fit.fit() for key in ["succeeded", "numpoints", "nfev"]: assert _fit_results_bench[key] == int(getattr(results, key)) for key in ["rstat", "qval", "statval", "dof"]: # used rel and abs tol of 1e-7 with numpy allclose assert float(getattr(results, key)) == pytest.approx(_fit_results_bench[key]) for key in ["parvals"]: try: # used rel and abs tol of 1e-4 with numpy allclose assert getattr(results, key) == pytest.approx(_fit_results_bench[key]) except AssertionError: print('parvals bench: ', _fit_results_bench[key]) print('parvals fit: ', getattr(results, key)) print('results', results) raise fields = [ 'data', 'model', 'method', 'fit', 'results', 'covresults', 'dof', 'mu', 'num' ] out = namedtuple('Results', fields) out.data = data out.model = model out.method = method out.fit = fit out.results = results out.covresults = fit.est_errors() out.dof = results.dof out.mu = numpy.array(results.parvals) out.cov = numpy.array(out.covresults.extra_output) out.num = 10 return out
def fit(self): """Fit spectrum""" from sherpa.fit import Fit from sherpa.models import ArithmeticModel, SimulFitModel from sherpa.astro.instrument import Response1D from sherpa.data import DataSimulFit # Translate model to sherpa model if necessary if isinstance(self.model, models.SpectralModel): model = self.model.to_sherpa() else: model = self.model if not isinstance(model, ArithmeticModel): raise ValueError('Model not understood: {}'.format(model)) # Make model amplitude O(1e0) val = model.ampl.val * self.FLUX_FACTOR ** (-1) model.ampl = val if self.fit_range is not None: log.info('Restricting fit range to {}'.format(self.fit_range)) fitmin = self.fit_range[0].to('keV').value fitmax = self.fit_range[1].to('keV').value # Loop over observations pha = list() folded_model = list() nobs = len(self.obs_list) for ii in range(nobs): temp = self.obs_list[ii].to_sherpa() if self.fit_range is not None: temp.notice(fitmin, fitmax) if temp.get_background() is not None: temp.get_background().notice(fitmin, fitmax) temp.ignore_bad() if temp.get_background() is not None: temp.get_background().ignore_bad() pha.append(temp) # Forward folding resp = Response1D(pha[ii]) folded_model.append(resp(model) * self.FLUX_FACTOR) data = DataSimulFit('simul fit data', pha) fitmodel = SimulFitModel('simul fit model', folded_model) log.debug(fitmodel) fit = Fit(data, fitmodel, self.statistic) fitresult = fit.fit() log.debug(fitresult) # The model instance passed to the Fit now holds the best fit values covar = fit.est_errors() log.debug(covar) for ii in range(nobs): efilter = pha[ii].get_filter() shmodel = fitmodel.parts[ii] self.result[ii].fit = _sherpa_to_fitresult(shmodel, covar, efilter, fitresult)
def tst_low_level(self, thaw_c1): if thaw_c1: self.mdl.c1.thaw() self.mdl.c2.thaw() f = Fit(self.data, self.mdl, estmethod=Confidence()) self.mdl.c2 = 1 f.fit() if not thaw_c1: self.mdl.c1.thaw() f.fit() result = f.est_errors() self.cmp_results(result)
def test_low_level(thaw_c1, setUp): data, mdl = setUp if thaw_c1: mdl.c1.thaw() mdl.c2.thaw() f = Fit(data, mdl, estmethod=Confidence()) mdl.c2 = 1 f.fit() if not thaw_c1: mdl.c1.thaw() f.fit() result = f.est_errors() cmp_results(result)
class SpectrumFit(object): """Orchestrate a 1D counts spectrum fit. After running the :func:`~gammapy.spectrum.SpectrumFit.fit` and :func:`~gammapy.spectrum.SpectrumFit.est_errors` methods, the fit results are available in :func:`~gammapy.spectrum.SpectrumFit.result`. For usage examples see :ref:`spectral_fitting` Parameters ---------- obs_list : `~gammapy.spectrum.SpectrumObservationList`, `~gammapy.spectrum.SpectrumObservation` Observation(s) to fit model : `~gammapy.spectrum.models.SpectralModel` Source model. Should return counts if ``forward_folded`` is False and a flux otherwise stat : {'wstat', 'cash'} Fit statistic forward_folded : bool, default: True Fold ``model`` with the IRFs given in ``obs_list`` fit_range : tuple of `~astropy.units.Quantity` Fit range, will be convolved with observation thresholds. If you want to control which bins are taken into account in the fit for each observation, use :func:`~gammapy.spectrum.PHACountsSpectrum.quality` background_model : `~gammapy.spectrum.models.SpectralModel`, optional Background model to be used in cash fits method : {'sherpa', 'iminuit'} Optimization backend for the fit err_method : {'sherpa'} Optimization backend for error estimation """ def __init__(self, obs_list, model, stat='wstat', forward_folded=True, fit_range=None, background_model=None, method='sherpa', err_method='sherpa'): self.obs_list = obs_list self._model = model self.stat = stat self.forward_folded = forward_folded self.fit_range = fit_range self._background_model = background_model self.method = method self.err_method = err_method self._predicted_counts = None self._statval = None self.covar_axis = None self.covariance = None self._result = None self._check_valid_fit() self._apply_fit_range() def __str__(self): ss = self.__class__.__name__ ss += '\nSource model {}'.format(self.model) ss += '\nStat {}'.format(self.stat) ss += '\nForward Folded {}'.format(self.forward_folded) ss += '\nFit range {}'.format(self.fit_range) if self.background_model is not None: ss += '\nBackground model {}'.format(self.background_model) ss += '\nBackend {}'.format(self.method) ss += '\nError Backend {}'.format(self.err_method) return ss @property def result(self): """Fit result The result is a list of length ``n``, where ``n`` ist the number of observations that participated in the fit. The best fit model is usually the same for all observations but the results differ in the fitted energy range, predicted counts, final fit statistic value etc. """ return self._result @property def model(self): """Source model The model parameters change every time the likelihood is evaluated. In order to access the best-fit model parameters, use :func:`gammapy.spectrum.SpectrumFit.result` """ return self._model @model.setter def model(self, model): self._model = model @property def background_model(self): """Background model The model parameters change every time the likelihood is evaluated. In order to access the best-fit model parameters, use :func:`gammapy.spectrum.SpectrumFit.result` """ return self._background_model @background_model.setter def background_model(self, model): self._background_model = model @property def obs_list(self): """Observations participating in the fit""" return self._obs_list @obs_list.setter def obs_list(self, obs_list): if isinstance(obs_list, SpectrumObservation): obs_list = SpectrumObservationList([obs_list]) self._obs_list = SpectrumObservationList(obs_list) @property def bins_in_fit_range(self): """Bins participating in the fit for each observation.""" return self._bins_in_fit_range @property def predicted_counts(self): """Current value of predicted counts. For each observation a tuple to counts for the on and off region is returned. """ return self._predicted_counts @property def statval(self): """Current value of statval. For each observation the statval per bin is returned. """ return self._statval @property def fit_range(self): """Fit range.""" return self._fit_range @fit_range.setter def fit_range(self, fit_range): self._fit_range = fit_range self._apply_fit_range() @property def true_fit_range(self): """True fit range for each observation. True fit range is the fit range set in the `~gammapy.spectrum.SpectrumFit` with observation threshold taken into account. """ true_range = [] for binrange, obs in zip(self.bins_in_fit_range, self.obs_list): idx = np.where(binrange)[0] if len(idx) == 0: true_range.append(None) continue e_min = obs.e_reco[idx[0]] e_max = obs.e_reco[idx[-1] + 1] fit_range = u.Quantity((e_min, e_max)) true_range.append(fit_range) return true_range def _apply_fit_range(self): """Mark bins within desired fit range for each observation.""" self._bins_in_fit_range = [] for obs in self.obs_list: # Take into account fit range energy = obs.e_reco valid_range = np.zeros(energy.nbins) if self.fit_range is not None: precision = 1e-3 # to avoid floating round precision idx_lo = np.where(energy * (1 + precision) < self.fit_range[0])[0] valid_range[idx_lo] = 1 idx_hi = np.where(energy[:-1] * (1 - precision) > self.fit_range[1])[0] if len(idx_hi) != 0: idx_hi = np.insert(idx_hi, 0, idx_hi[0] - 1) valid_range[idx_hi] = 1 # Take into account thresholds try: quality = obs.on_vector.quality except AttributeError: quality = np.zeros(obs.e_reco.nbins) convolved = np.logical_and(1 - quality, 1 - valid_range) self._bins_in_fit_range.append(convolved) def predict_counts(self): """Predict counts for all observations. The result is stored as ``predicted_counts`` attribute. """ predicted_counts = [] for obs in self.obs_list: mu_sig = self._predict_counts_helper(obs, self.model, self.forward_folded) mu_bkg = None if self.background_model is not None: # For now, never fold background model with IRFs mu_bkg = self._predict_counts_helper(obs, self.background_model, False) counts = [mu_sig, mu_bkg] predicted_counts.append(counts) self._predicted_counts = predicted_counts def _predict_counts_helper(self, obs, model, forward_folded=True): """Predict counts for one observation. Parameters ---------- obs : `~gammapy.spectrum.SpectrumObservation` Response functions model : `~gammapy.spectrum.SpectralModel` Source or background model forward_folded : bool, default: True Fold model with IRFs Returns ------ predicted_counts: `np.array` Predicted counts for one observation """ predictor = CountsPredictor(model=model) if forward_folded: predictor.aeff = obs.aeff predictor.edisp = obs.edisp else: predictor.e_true = obs.e_reco predictor.livetime = obs.livetime predictor.run() counts = predictor.npred.data.data # Check count unit (~unit of model amplitude) if counts.unit.is_equivalent(''): counts = counts.value else: raise ValueError('Predicted counts {}'.format(counts)) # Apply AREASCAL column counts *= obs.on_vector.areascal return counts def calc_statval(self): """Calc statistic for all observations. The result is stored as attribute ``statval``, bin outside the fit range are set to 0. """ statval = [] for obs, npred in zip(self.obs_list, self.predicted_counts): on_stat, off_stat = self._calc_statval_helper(obs, npred) statvals = (on_stat, off_stat) statval.append(statvals) self._statval = statval self._restrict_statval() def _calc_statval_helper(self, obs, prediction): """Calculate ``statval`` for one observation. Parameters ---------- obs : `~gammapy.spectrum.SpectrumObservation` Measured counts prediction : tuple of `~numpy.ndarray` Predicted (on counts, off counts) Returns ------ statsval : tuple of `~numpy.ndarray` Statval for (on, off) """ stats_func = getattr(stats, self.stat) # Off stat = 0 by default off_stat = np.zeros(obs.e_reco.nbins) if self.stat == 'cash' or self.stat == 'cstat': if self.background_model is not None: mu_on = prediction[0] + prediction[1] on_stat = stats_func(n_on=obs.on_vector.data.data.value, mu_on=mu_on) mu_off = prediction[1] / obs.alpha off_stat = stats_func(n_on=obs.off_vector.data.data.value, mu_on=mu_off) else: mu_on = prediction[0] on_stat = stats_func(n_on=obs.on_vector.data.data.value, mu_on=mu_on) off_stat = np.zeros_like(on_stat) elif self.stat == 'wstat': kwargs = dict(n_on=obs.on_vector.data.data.value, n_off=obs.off_vector.data.data.value, alpha=obs.alpha, mu_sig=prediction[0]) # Store the result of the profile likelihood as bkg prediction mu_bkg = stats.get_wstat_mu_bkg(**kwargs) prediction[1] = mu_bkg * obs.alpha on_stat_ = stats_func(**kwargs) # The on_stat sometime contains nan values # TODO: Handle properly on_stat = np.nan_to_num(on_stat_) off_stat = np.zeros_like(on_stat) else: raise NotImplementedError('{}'.format(self.stat)) return on_stat, off_stat def total_stat(self, parameters): """Statistic summed over all bins and all observations. This is the likelihood function that is passed to the optimizers Parameters ---------- parameters : `~gammapy.utils.fitting.ParameterList` Model parameters """ self.model.parameters = parameters self.predict_counts() self.calc_statval() total_stat = np.sum([np.sum(v) for v in self.statval], dtype=np.float64) return total_stat def _restrict_statval(self): """Apply valid fit range to statval. """ for statval, valid_range in zip(self.statval, self.bins_in_fit_range): # Find bins outside safe range idx = np.where(np.invert(valid_range))[0] statval[0][idx] = 0 statval[1][idx] = 0 def _check_valid_fit(self): """Helper function to give useful error messages.""" # Assume that settings are the same for all observations test_obs = self.obs_list[0] irfs_exist = test_obs.aeff is not None or test_obs.edisp is not None if self.forward_folded and not irfs_exist: raise ValueError('IRFs required for forward folded fit') if self.stat == 'wstat' and self.obs_list[0].off_vector is None: raise ValueError('Off vector required for WStat fit') try: test_obs.livetime except KeyError: raise ValueError('No observation livetime given') def likelihood_1d(self, model, parname, parvals): """Compute likelihood profile. TODO: Replace by something more generic Parameters ---------- model : `~gammapy.spectrum.models.SpectralModel` Model to draw likelihood profile for parname : str Parameter to calculate profile for parvals : `~astropy.units.Quantity` Parameter values """ likelihood = [] self._model = model for val in parvals: self.model.parameters[parname].value = val stat = self.total_stat(self.model.parameters) likelihood.append(stat) return np.array(likelihood) def plot_likelihood_1d(self, ax=None, **kwargs): """Plot 1-dim likelihood profile. See :func:`~gammapy.spectrum.SpectrumFit.likelihood_1d` """ import matplotlib.pyplot as plt ax = plt.gca() if ax is None else ax yy = self.likelihood_1d(**kwargs) ax.plot(kwargs['parvals'], yy) ax.set_xlabel(kwargs['parname']) return ax def fit(self): """Run the fit.""" if self.method == 'sherpa': self._fit_sherpa() elif self.method == 'iminuit': self._fit_iminuit() else: raise NotImplementedError('method: {}'.format(self.method)) def _fit_sherpa(self): """Wrapper around sherpa minimizer.""" from sherpa.fit import Fit from sherpa.data import Data1DInt from sherpa.optmethods import NelderMead from .sherpa_utils import SherpaModel, SherpaStat binning = self.obs_list[0].e_reco # The sherpa data object is not usued in the fit. It is set to the # first observation for debugging purposes, see below data = self.obs_list[0].on_vector.data.data.value data = Data1DInt('Dummy data', binning[:-1].value, binning[1:].value, data) # DEBUG # from sherpa.models import PowLaw1D # from sherpa.stats import Cash # model = PowLaw1D('sherpa') # model.ref = 0.1 # fit = Fit(data, model, Cash(), NelderMead()) # NOTE: We cannot use the Levenbergr-Marquart optimizer in Sherpa # because it relies on the fvec return value of the fit statistic (we # return None). The computation of fvec is not straightforwad, not just # stats per bin. E.g. for a cash fit the sherpa stat computes it # according to cstat # see https://github.com/sherpa/sherpa/blob/master/sherpa/include/sherpa/stats.hh#L122 self._sherpa_fit = Fit(data, SherpaModel(self), SherpaStat(self), NelderMead()) fitresult = self._sherpa_fit.fit() log.debug(fitresult) self._make_fit_result(self.model.parameters) def _fit_iminuit(self): """Iminuit minimization""" parameters, minuit = fit_minuit(parameters=self.model.parameters, function=self.total_stat) log.debug(minuit) self._make_fit_result(parameters) def _make_fit_result(self, parameters): """Bundle fit results into `~gammapy.spectrum.SpectrumFitResult`. Parameters ---------- parameters : `~gammapy.utils.modeling.ParameterList` Best fit parameters """ from . import SpectrumFitResult # run again with best fit parameters self.total_stat(parameters) model = self.model.copy() if self.background_model is not None: bkg_model = self.background_model.copy() else: bkg_model = None statname = self.stat results = [] for idx, obs in enumerate(self.obs_list): fit_range = self.true_fit_range[idx] statval = np.sum(self.statval[idx]) stat_per_bin = self.statval[idx] npred_src = copy.deepcopy(self.predicted_counts[idx][0]) npred_bkg = copy.deepcopy(self.predicted_counts[idx][1]) results.append( SpectrumFitResult(model=model, fit_range=fit_range, statname=statname, statval=statval, stat_per_bin=stat_per_bin, npred_src=npred_src, npred_bkg=npred_bkg, background_model=bkg_model, obs=obs)) self._result = results def est_errors(self): """Estimate parameter errors.""" if self.err_method == 'sherpa': self._est_errors_sherpa() else: raise NotImplementedError('{}'.format(self.err_method)) for res in self.result: res.covar_axis = self.covar_axis res.covariance = self.covariance res.model.parameters.set_parameter_covariance( self.covariance, self.covar_axis) def _est_errors_sherpa(self): """Wrapper around Sherpa error estimator.""" covar = self._sherpa_fit.est_errors() self.covar_axis = [par.split('.')[-1] for par in covar.parnames] self.covariance = copy.deepcopy(covar.extra_output) def run(self, outdir=None): """Run all steps and write result to disk. Parameters ---------- outdir : Path, str directory to write results files to (if given) """ log.info('Running {}'.format(self)) self.fit() self.est_errors() if outdir is not None: self._write_result(outdir) def _write_result(self, outdir): outdir = make_path(outdir) outdir.mkdir(exist_ok=True, parents=True) # Assume only one model is fit to all data modelname = self.result[0].model.__class__.__name__ filename = outdir / 'fit_result_{}.yaml'.format(modelname) log.info('Writing {}'.format(filename)) self.result[0].to_yaml(filename)
class test_sim(SherpaTestCase): def setUp(self): # self.startdir = os.getcwd() self.old_level = logger.getEffectiveLevel() logger.setLevel(logging.CRITICAL) datadir = SherpaTestCase.datadir if datadir is None: return pha = os.path.join(datadir, "refake_0934_1_21_1e4.fak") rmf = os.path.join(datadir, "ccdid7_default.rmf") arf = os.path.join(datadir, "quiet_0934.arf") self.simarf = os.path.join(datadir, "aref_sample.fits") self.pcaarf = os.path.join(datadir, "aref_Cedge.fits") data = read_pha(pha) data.ignore(None, 0.3) data.ignore(7.0, None) rsp = Response1D(data) self.abs1 = XSwabs("abs1") self.p1 = XSpowerlaw("p1") model = rsp(self.abs1 * self.p1) self.fit = Fit(data, model, CStat(), NelderMead(), Covariance()) def tearDown(self): # os.chdir(self.startdir) logger.setLevel(self.old_level) @needs_data def test_pragbayes_simarf(self): datadir = SherpaTestCase.datadir if datadir is None: return mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("PragBayes") mcmc.set_sampler_opt("simarf", self.simarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 7) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter) # try: # assert (covar_results.parmaxes < params.std(1)).all() # except AssertionError: # print 'covar: ', str(covar_results.parmaxes) # print 'param: ', str(params.std(1)) # raise @needs_data def test_pragbayes_pcaarf(self): datadir = SherpaTestCase.datadir if datadir is None: return mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("pragBayes") mcmc.set_sampler_opt("simarf", self.pcaarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 5) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter)
class SpectrumFit(object): """ Spectral Fit For usage examples see :ref:`spectral_fitting` Parameters ---------- obs_list : `~gammapy.spectrum.SpectrumObservationList`, `~gammapy.spectrum.SpectrumObservation` Observation(s) to fit model : `~gammapy.spectrum.models.SpectralModel` Source model. Should return counts if ``forward_folded`` is False and a flux otherwise stat : {'wstat', 'cash'} Fit statistic forward_folded : bool, default: True Fold ``model`` with the IRFs given in ``obs_list`` fit_range : tuple of `~astropy.units.Quantity``, optional Fit range background_model : `~gammapy.spectrum.model.SpectralModel`, optional Background model to be used in cash fits method : {'sherpa'} Optimization backend for the fit err_method : {'sherpa'} Optimization backend for error estimation """ def __init__(self, obs_list, model, stat='wstat', forward_folded=True, fit_range=None, background_model=None, method='sherpa', err_method='sherpa'): # TODO: add fancy converters to accept also e.g. CountsSpectrum if isinstance(obs_list, SpectrumObservation): obs_list = SpectrumObservationList([obs_list]) if not isinstance(obs_list, SpectrumObservationList): raise ValueError('Invalid input {} for parameter obs_list'.format( type(obs_list))) self.obs_list = obs_list self.model = model self.stat = stat self.forward_folded = forward_folded self.fit_range = fit_range self.background_model = background_model self.method = method self.err_method = method # TODO: Reexpose as properties to improve docs self.predicted_counts = None self.statval = None # TODO: Remove once there is a Parameter class self.covar_axis = None self.covariance = None self.result = list() def __str__(self): """String repr""" ss = self.__class__.__name__ ss += '\nData {}'.format(self.obs_list) ss += '\nSource model {}'.format(self.model) ss += '\nStat {}'.format(self.stat) ss += '\nForward Folded {}'.format(self.forward_folded) ss += '\nFit range {}'.format(self.fit_range) if self.background_model is not None: ss += '\nBackground model {}'.format(self.background_model) ss += '\nBackend {}'.format(self.method) ss += '\nError Backend {}'.format(self.err_method) return ss @property def fit_range(self): """Fit range""" return self._fit_range @fit_range.setter def fit_range(self, fit_range): self._fit_range = fit_range self._apply_fit_range() def _apply_fit_range(self): """Mark bins within desired fit range for each observation TODO: Split into smaller functions TODO: Could reuse code from PHACountsSpectrum TODO: Use True (not 0) to mark good bins TODO: Add to EnergyBounds """ self._bins_in_fit_range = list() for obs in self.obs_list: # Take into account fit range energy = obs.e_reco valid_range = np.zeros(energy.nbins) if self.fit_range is not None: idx_lo = np.where(energy < self.fit_range[0])[0] valid_range[idx_lo] = 1 idx_hi = np.where(energy[:-1] > self.fit_range[1])[0] if len(idx_hi) != 0: idx_hi = np.insert(idx_hi, 0, idx_hi[0] - 1) valid_range[idx_hi] = 1 # Take into account thresholds try: quality = obs.on_vector.quality except AttributeError: quality = np.zeros(obs.e_reco.nbins) # Convolve (see TODO above) convolved = np.logical_and(1 - quality, 1 - valid_range) self._bins_in_fit_range.append(convolved) @property def true_fit_range(self): """True fit range for each observation True fit range is the fit range set in the `~gammapy.spectrum.SpectrumFit` with observation threshold taken into account. """ true_range = list() for binrange, obs in zip(self._bins_in_fit_range, self.obs_list): idx = np.where(binrange)[0] e_min = obs.e_reco[idx[0]] e_max = obs.e_reco[idx[-1] + 1] fit_range = u.Quantity((e_min, e_max)) true_range.append(fit_range) return true_range def predict_counts(self, **kwargs): """Predict counts for all observations The result is stored as ``predicted_counts`` attribute """ predicted_counts = list() for obs in self.obs_list: mu_sig = self._predict_counts_helper(obs, self.model, self.forward_folded) mu_bkg = None if self.background_model is not None: # For now, never fold background model with IRFs mu_bkg = self._predict_counts_helper(obs, self.background_model, False) counts = [mu_sig, mu_bkg] predicted_counts.append(counts) self.predicted_counts = predicted_counts def _predict_counts_helper(self, obs, model, forward_folded=True): """Predict counts for one observation Parameters ---------- obs : `~gammapy.spectrum.SpectrumObservation` Response functions model : `~gammapy.spectrum.SpectralModel` Source or background model forward_folded : bool, default: True Fold model with IRFs Returns ------ predicted_counts: `np.array` Predicted counts for one observation """ binning = obs.e_reco if forward_folded: temp = calculate_predicted_counts(model=model, livetime=obs.livetime, aeff=obs.aeff, edisp=obs.edisp, e_reco=binning) counts = temp.data.data else: # TODO: This could also be part of calculate predicted counts counts = model.integral(binning[:-1], binning[1:], intervals=True) # Check count unit (~unit of model amplitude) cond = counts.unit.is_equivalent('ct') or counts.unit.is_equivalent('') if cond: counts = counts.value else: raise ValueError('Predicted counts {}'.format(counts)) return counts def calc_statval(self): """Calc statistic for all observations The result is stored as attribute ``statval``, bin outside the fit range are set to 0. """ statval = list() for obs, npred in zip(self.obs_list, self.predicted_counts): on_stat, off_stat = self._calc_statval_helper(obs, npred) stats = (on_stat, off_stat) statval.append(stats) self.statval = statval self._restrict_statval() def _calc_statval_helper(self, obs, prediction): """Calculate statval one observation Parameters ---------- obs : `~gammapy.spectrum.SpectrumObservation` Measured counts prediction : tuple of `~np.array` Predicted (on counts, off counts) Returns ------ statsval : tuple or `~np.array` Statval for (on, off) """ # Off stat = 0 by default off_stat = np.zeros(obs.e_reco.nbins) if self.stat == 'cash': if self.background_model is not None: mu_on = prediction[0] + prediction[1] on_stat = stats.cash(n_on=obs.on_vector.data.data.value, mu_on=mu_on) mu_off = prediction[1] / obs.alpha off_stat = stats.cash(n_on=obs.off_vector.data.data.value, mu_on=mu_off) else: mu_on = prediction[0] on_stat = stats.cash(n_on=obs.on_vector.data.data.value, mu_on=mu_on) off_stat = np.zeros_like(on_stat) elif self.stat == 'wstat': kwargs = dict(n_on=obs.on_vector.data.data.value, n_off=obs.off_vector.data.data.value, alpha=obs.alpha, mu_sig=prediction[0]) # Store the result of the profile likelihood as bkg prediction mu_bkg = stats.get_wstat_mu_bkg(**kwargs) prediction[1] = mu_bkg * obs.alpha on_stat = stats.wstat(**kwargs) off_stat = np.zeros_like(on_stat) else: raise NotImplementedError('{}'.format(self.stat)) return on_stat, off_stat @property def total_stat(self): """Statistic summed over all bins and all observations This is what is used for the fit """ total_stat = np.sum(self.statval, dtype=np.float64) return total_stat def _restrict_statval(self): """Apply valid fit range to statval """ restricted_statval = list() for statval, valid_range in zip(self.statval, self._bins_in_fit_range): # Find bins outside safe range idx = np.where(np.invert(valid_range))[0] statval[0][idx] = 0 statval[1][idx] = 0 def _check_valid_fit(self): """Helper function to give usefull error messages""" # TODO: Check if IRFs are given for forward folding if self.stat == 'wstat' and self.obs_list[0].off_vector is None: raise ValueError('Off vector required for WStat fit') def likelihood_1d(self, model, parname, parvals): """Compute likelihood profile Parameters ---------- model : `~gammapy.spectrum.models.SpectralModel` Model to draw likelihood profile for parname : str Parameter to calculate profile for parvals : `~astropy.units.Quantity` Parameter values """ likelihood = list() self.model = model for val in parvals: self.model.parameters[parname].value = val self.predict_counts() self.calc_statval() likelihood.append(self.total_stat) return np.array(likelihood) def plot_likelihood_1d(self, ax=None, **kwargs): """Plot 1D likelihood profile see :func:`~gammapy.spectrum.SpectrumFit.likelihood_1d` """ import matplotlib.pyplot as plt ax = plt.gca() if ax is None else ax yy = self.likelihood_1d(**kwargs) ax.plot(kwargs['parvals'], yy) ax.set_xlabel(kwargs['parname']) return ax def fit(self): """Run the fit""" self._check_valid_fit() if self.method == 'sherpa': self._fit_sherpa() else: raise NotImplementedError('{}'.format(self.method)) def _fit_sherpa(self): """Wrapper around sherpa minimizer """ from sherpa.fit import Fit from sherpa.data import Data1DInt from sherpa.optmethods import NelderMead binning = self.obs_list[0].e_reco # The sherpa data object is not usued in the fit. It is set to the # first observation for debugging purposes, see below data = self.obs_list[0].on_vector.data.data.value data = Data1DInt('Dummy data', binning[:-1].value, binning[1:].value, data) # DEBUG #from sherpa.models import PowLaw1D #from sherpa.stats import Cash #model = PowLaw1D('sherpa') #model.ref = 0.1 #fit = Fit(data, model, Cash(), NelderMead()) # NOTE: We cannot use the Levenbergr-Marquart optimizer in Sherpa # because it relies on the fvec return value of the fit statistic (we # return None). The computation of fvec is not straightforwad, not just # stats per bin. E.g. for a cash fit the sherpa stat computes it # according to cstat # see https://github.com/sherpa/sherpa/blob/master/sherpa/include/sherpa/stats.hh#L122 self._sherpa_fit = Fit(data, SherpaModel(self), SherpaStat(self), NelderMead()) fitresult = self._sherpa_fit.fit() log.debug(fitresult) self._make_fit_result() def _make_fit_result(self): """Bunde fit results into `~gammapy.spectrum.SpectrumFitResult` It is important to copy best fit values, because the error estimation will change the model parameters and statval again """ from . import SpectrumFitResult model = self.model.copy() if self.background_model is not None: bkg_model = self.background_model.copy() else: bkg_model = None covariance = None covar_axis = None statname = self.stat for idx, obs in enumerate(self.obs_list): fit_range = self.true_fit_range[idx] statval = np.sum(self.statval[idx]) npred_src = copy.deepcopy(self.predicted_counts[idx][0]) npred_bkg = copy.deepcopy(self.predicted_counts[idx][1]) self.result.append( SpectrumFitResult(model=model, covariance=covariance, covar_axis=covar_axis, fit_range=fit_range, statname=statname, statval=statval, npred_src=npred_src, npred_bkg=npred_bkg, background_model=bkg_model, obs=obs)) def est_errors(self): """Estimate errors""" if self.err_method == 'sherpa': self._est_errors_sherpa() else: raise NotImplementedError('{}'.format(self.err_method)) for res in self.result: res.covar_axis = self.covar_axis res.covariance = self.covariance res.model.parameters.set_parameter_covariance( self.covariance, self.covar_axis) def _est_errors_sherpa(self): """Wrapper around Sherpa error estimator""" covar = self._sherpa_fit.est_errors() covar_axis = list() for idx, par in enumerate(covar.parnames): name = par.split('.')[-1] covar_axis.append(name) self.covar_axis = covar_axis self.covariance = copy.deepcopy(covar.extra_output) def compute_fluxpoints(self, binning): """Compute `~DifferentialFluxPoints` for best fit model TODO: Implement Parameters ---------- binning : `~astropy.units.Quantity` Energy binning, see :func:`~gammapy.spectrum.utils.calculate_flux_point_binning` for a method to get flux points with a minimum significance. Returns ------- result : `~gammapy.spectrum.SpectrumResult` """ raise NotImplementedError() def run(self, outdir=None): """Run all steps and write result to disk Parameters ---------- outdir : Path, str directory to write results files to """ cwd = Path.cwd() outdir = cwd if outdir is None else make_path(outdir) outdir.mkdir(exist_ok=True) os.chdir(str(outdir)) self.fit() self.est_errors() # Assume only one model is fit to all data modelname = self.result[0].model.__class__.__name__ filename = 'fit_result_{}.yaml'.format(modelname) log.info('Writing {}'.format(filename)) self.result[0].to_yaml(filename) os.chdir(str(cwd))
def fit(self): """Fit spectrum""" from sherpa.fit import Fit from sherpa.models import ArithmeticModel, SimulFitModel from sherpa.astro.instrument import Response1D from sherpa.data import DataSimulFit # Reset results self._result = list() # Translate model to sherpa model if necessary if isinstance(self.model, models.SpectralModel): model = self.model.to_sherpa() else: model = self.model if not isinstance(model, ArithmeticModel): raise ValueError('Model not understood: {}'.format(model)) # Make model amplitude O(1e0) val = model.ampl.val * self.FLUX_FACTOR ** (-1) model.ampl = val if self.fit_range is not None: log.info('Restricting fit range to {}'.format(self.fit_range)) fitmin = self.fit_range[0].to('keV').value fitmax = self.fit_range[1].to('keV').value # Loop over observations pha = list() folded_model = list() nobs = len(self.obs_list) for ii in range(nobs): temp = self.obs_list[ii].to_sherpa() if self.fit_range is not None: temp.notice(fitmin, fitmax) if temp.get_background() is not None: temp.get_background().notice(fitmin, fitmax) temp.ignore_bad() if temp.get_background() is not None: temp.get_background().ignore_bad() pha.append(temp) log.debug('Noticed channels obs {}: {}'.format( ii, temp.get_noticed_channels())) # Forward folding resp = Response1D(pha[ii]) folded_model.append(resp(model) * self.FLUX_FACTOR) if (len(pha) == 1 and len(pha[0].get_noticed_channels()) == 1): raise ValueError('You are trying to fit one observation in only ' 'one bin, error estimation will fail') data = DataSimulFit('simul fit data', pha) log.debug(data) fitmodel = SimulFitModel('simul fit model', folded_model) log.debug(fitmodel) fit = Fit(data, fitmodel, self.statistic, method=self.method_fit) fitresult = fit.fit() log.debug(fitresult) # The model instance passed to the Fit now holds the best fit values covar = fit.est_errors() log.debug(covar) for ii in range(nobs): efilter = pha[ii].get_filter() # Skip observations not participating in the fit if efilter != '': shmodel = fitmodel.parts[ii] result = _sherpa_to_fitresult(shmodel, covar, efilter, fitresult) result.obs = self.obs_list[ii] else: result = None self._result.append(result) valid_result = np.nonzero(self.result)[0][0] global_result = copy.deepcopy(self.result[valid_result]) global_result.npred = None global_result.obs = None all_fitranges = [_.fit_range for _ in self._result if _ is not None] fit_range_min = min([_[0] for _ in all_fitranges]) fit_range_max = max([_[1] for _ in all_fitranges]) global_result.fit_range = u.Quantity((fit_range_min, fit_range_max)) self._global_result = global_result
def fit(self): """Fit spectrum""" from sherpa.fit import Fit from sherpa.models import ArithmeticModel, SimulFitModel from sherpa.astro.instrument import Response1D from sherpa.data import DataSimulFit # Reset results self._result = list() # Translate model to sherpa model if necessary if isinstance(self.model, models.SpectralModel): model = self.model.to_sherpa() else: model = self.model if not isinstance(model, ArithmeticModel): raise ValueError('Model not understood: {}'.format(model)) # Make model amplitude O(1e0) val = model.ampl.val * self.FLUX_FACTOR**(-1) model.ampl = val if self.fit_range is not None: log.info('Restricting fit range to {}'.format(self.fit_range)) fitmin = self.fit_range[0].to('keV').value fitmax = self.fit_range[1].to('keV').value # Loop over observations pha = list() folded_model = list() nobs = len(self.obs_list) for ii in range(nobs): temp = self.obs_list[ii].to_sherpa() if self.fit_range is not None: temp.notice(fitmin, fitmax) if temp.get_background() is not None: temp.get_background().notice(fitmin, fitmax) temp.ignore_bad() if temp.get_background() is not None: temp.get_background().ignore_bad() pha.append(temp) log.debug('Noticed channels obs {}: {}'.format( ii, temp.get_noticed_channels())) # Forward folding resp = Response1D(pha[ii]) folded_model.append(resp(model) * self.FLUX_FACTOR) if (len(pha) == 1 and len(pha[0].get_noticed_channels()) == 1): raise ValueError('You are trying to fit one observation in only ' 'one bin, error estimation will fail') data = DataSimulFit('simul fit data', pha) log.debug(data) fitmodel = SimulFitModel('simul fit model', folded_model) log.debug(fitmodel) fit = Fit(data, fitmodel, self.statistic) fitresult = fit.fit() log.debug(fitresult) # The model instance passed to the Fit now holds the best fit values covar = fit.est_errors() log.debug(covar) for ii in range(nobs): efilter = pha[ii].get_filter() # Skip observations not participating in the fit if efilter != '': shmodel = fitmodel.parts[ii] result = _sherpa_to_fitresult(shmodel, covar, efilter, fitresult) result.obs = self.obs_list[ii] else: result = None self._result.append(result) valid_result = np.nonzero(self.result)[0][0] global_result = copy.deepcopy(self.result[valid_result]) global_result.npred = None global_result.obs = None all_fitranges = [_.fit_range for _ in self._result if _ is not None] fit_range_min = min([_[0] for _ in all_fitranges]) fit_range_max = max([_[1] for _ in all_fitranges]) global_result.fit_range = u.Quantity((fit_range_min, fit_range_max)) self._global_result = global_result
class test_sim(SherpaTestCase): @requires_fits @requires_xspec def setUp(self): from sherpa.astro.io import read_pha from sherpa.astro.xspec import XSwabs, XSpowerlaw # self.startdir = os.getcwd() self.old_level = logger.getEffectiveLevel() logger.setLevel(logging.CRITICAL) pha = self.make_path("refake_0934_1_21_1e4.fak") # rmf = self.make_path("ccdid7_default.rmf") # arf = self.make_path("quiet_0934.arf") self.simarf = self.make_path("aref_sample.fits") self.pcaarf = self.make_path("aref_Cedge.fits") data = read_pha(pha) data.ignore(None, 0.3) data.ignore(7.0, None) rsp = Response1D(data) self.abs1 = XSwabs('abs1') self.p1 = XSpowerlaw('p1') model = rsp(self.abs1 * self.p1) self.fit = Fit(data, model, CStat(), NelderMead(), Covariance()) def tearDown(self): # os.chdir(self.startdir) if hasattr(self, 'old_level'): logger.setLevel(self.old_level) @requires_xspec @requires_data def test_pragbayes_simarf(self): mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("PragBayes") mcmc.set_sampler_opt("simarf", self.simarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 7) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter) # try: # assert (covar_results.parmaxes < params.std(1)).all() # except AssertionError: # print 'covar: ', str(covar_results.parmaxes) # print 'param: ', str(params.std(1)) # raise @requires_xspec @requires_data def test_pragbayes_pcaarf(self): mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("pragBayes") mcmc.set_sampler_opt("simarf", self.pcaarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 5) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter)
class test_sim(SherpaTestCase): @unittest.skipIf(not has_fits_support(), 'need pycrates, pyfits or astropy.io.fits') @unittest.skipIf(not has_package_from_list('sherpa.astro.xspec'), "required sherpa.astro.xspec module missing") def setUp(self): try: from sherpa.astro.io import read_pha from sherpa.astro.xspec import XSwabs, XSpowerlaw except: return #self.startdir = os.getcwd() self.old_level = logger.getEffectiveLevel() logger.setLevel(logging.CRITICAL) datadir = SherpaTestCase.datadir if datadir is None: return pha = os.path.join(datadir, "refake_0934_1_21_1e4.fak") rmf = os.path.join(datadir, "ccdid7_default.rmf") arf = os.path.join(datadir, "quiet_0934.arf") self.simarf = os.path.join(datadir, "aref_sample.fits") self.pcaarf = os.path.join(datadir, "aref_Cedge.fits") data = read_pha(pha) data.ignore(None,0.3) data.ignore(7.0,None) rsp = Response1D(data) self.abs1 = XSwabs('abs1') self.p1 = XSpowerlaw('p1') model = rsp(self.abs1*self.p1) self.fit = Fit(data, model, CStat(), NelderMead(), Covariance()) def tearDown(self): #os.chdir(self.startdir) if hasattr(self,'old_level'): logger.setLevel(self.old_level) @unittest.skipIf(not has_package_from_list('sherpa.astro.xspec'), "required sherpa.astro.xspec module missing") @unittest.skipIf(test_data_missing(), "required test data missing") def test_pragbayes_simarf(self): datadir = SherpaTestCase.datadir if datadir is None: return mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("PragBayes") mcmc.set_sampler_opt("simarf", self.simarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 7) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter) # try: # assert (covar_results.parmaxes < params.std(1)).all() # except AssertionError: # print 'covar: ', str(covar_results.parmaxes) # print 'param: ', str(params.std(1)) # raise @unittest.skipIf(not has_package_from_list('sherpa.astro.xspec'), "required sherpa.astro.xspec module missing") @unittest.skipIf(test_data_missing(), "required test data missing") def test_pragbayes_pcaarf(self): datadir = SherpaTestCase.datadir if datadir is None: return mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("pragBayes") mcmc.set_sampler_opt("simarf", self.pcaarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 5) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter)
report("res3.statval == res2.statval") report("res3.statval - res2.statval") report("res2.parvals") report("res3.parvals") for p2, p3 in zip(res2.parvals, res3.parvals): print("{:+.2e}".format(p3 - p2)) from sherpa.utils import calc_mlr report("calc_mlr(res.dof - res2.dof, res.statval - res2.statval)") report("f.estmethod.name") coverrs = f.est_errors() report("coverrs.format()") report("coverrs") dump("f.estmethod.sigma") f.estmethod.sigma = 1.6 coverrs90 = f.est_errors() report("coverrs90.format()") dump("coverrs90.percent") report("coverrs.extra_output") print([p.split('.')[1] for p in coverrs.parnames])
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
class test_sim(SherpaTestCase): _fit_results_bench = { 'rstat': 89.29503933428586, 'qval': 0.0, 'succeeded': 1, 'numpoints': 100, 'dof': 95, 'nfev': 93, 'statval': 8483.0287367571564, 'parnames': ['p1.gamma', 'p1.ampl', 'g1.fwhm', 'g1.pos', 'g1.ampl'], 'parvals': numpy.array( [1.0701938169914813, 9.1826254677279469, 2.5862083052721028, 2.601619746022207, 47.262657692418749]) } _x = numpy.arange(0.1, 10.1, 0.1) _y = numpy.array( [ 114, 47, 35, 30, 40, 27, 30, 26, 24, 20, 26, 35, 29, 28, 34, 36, 43, 39, 33, 47, 44, 46, 53, 56, 52, 53, 49, 57, 49, 36, 33, 42, 49, 45, 42, 32, 31, 34, 18, 24, 25, 11, 17, 17, 11, 9, 8, 5, 4, 10, 3, 4, 6, 3, 0, 2, 4, 4, 0, 1, 2, 0, 3, 3, 0, 2, 1, 2, 3, 0, 1, 0, 1, 0, 0, 1, 3, 3, 0, 2, 0, 0, 1, 2, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0 ] ) _err = numpy.ones(100)*0.4 def setUp(self): data = Data1D('fake', self._x, self._y, self._err) g1 = Gauss1D('g1') g1.fwhm.set(1.0, _tiny, _max, frozen=False) g1.pos.set(1.0, -_max, _max, frozen=False) g1.ampl.set(1.0, -_max, _max, frozen=False) p1 = PowLaw1D('p1') p1.gamma.set(1.0, -10, 10, frozen=False) p1.ampl.set(1.0, 0.0, _max, frozen=False) p1.ref.set(1.0, -_max, _max, frozen=True) model = p1 + g1 method = LevMar() method.config['maxfev'] = 10000 method.config['ftol'] = float(_eps) method.config['epsfcn'] = float(_eps) method.config['gtol'] = float(_eps) method.config['xtol'] = float(_eps) method.config['factor'] = float(100) self.fit = Fit(data, model, Chi2DataVar(), method, Covariance()) results = self.fit.fit() for key in ["succeeded", "numpoints", "nfev"]: assert self._fit_results_bench[key] == int(getattr(results, key)) for key in ["rstat", "qval", "statval", "dof"]: assert numpy.allclose(float(self._fit_results_bench[key]), float(getattr(results, key)), 1.e-7, 1.e-7) for key in ["parvals"]: try: assert numpy.allclose(self._fit_results_bench[key], getattr(results, key), 1.e-4, 1.e-4) except AssertionError: print 'parvals bench: ', self._fit_results_bench[key] print 'parvals fit: ', getattr(results, key) print 'results', results raise covresults = self.fit.est_errors() self.dof = results.dof self.mu = numpy.array(results.parvals) self.cov = numpy.array(covresults.extra_output) self.num = 10 def test_student_t(self): multivariate_t(self.mu, self.cov, self.dof, self.num) def test_cauchy(self): multivariate_cauchy(self.mu, self.cov, self.num) def test_parameter_scale_vector(self): ps = ParameterScaleVector() ps.get_scales(self.fit) def test_parameter_scale_matrix(self): ps = ParameterScaleMatrix() ps.get_scales(self.fit) def test_uniform_parameter_sample(self): up = UniformParameterSampleFromScaleVector() up.get_sample(self.fit, num=self.num) def test_normal_parameter_sample_vector(self): np = NormalParameterSampleFromScaleVector() np.get_sample(self.fit, num=self.num) def test_normal_parameter_sample_matrix(self): np = NormalParameterSampleFromScaleMatrix() np.get_sample(self.fit, num=self.num) def test_t_parameter_sample_matrix(self): np = StudentTParameterSampleFromScaleMatrix() np.get_sample(self.fit, self.dof, num=self.num) def test_uniform_sample(self): up = UniformSampleFromScaleVector() up.get_sample(self.fit, num=self.num) def test_normal_sample_vector(self): np = NormalSampleFromScaleVector() np.get_sample(self.fit, num=self.num) def test_normal_sample_matrix(self): np = NormalSampleFromScaleMatrix() np.get_sample(self.fit, num=self.num) def test_t_sample_matrix(self): np = StudentTSampleFromScaleMatrix() np.get_sample(self.fit, self.num, self.dof) def test_uniform_sample(self): uniform_sample(self.fit, num=self.num) def test_normal_sample(self): normal_sample(self.fit, num=self.num, correlate=False) def test_normal_sample_correlated(self): normal_sample(self.fit, num=self.num, correlate=True) def test_t_sample(self): t_sample(self.fit, self.num, self.dof) def test_lrt(self): results = LikelihoodRatioTest.run(self.fit, self.fit.model.lhs, self.fit.model, niter=25) def test_mh(self): self.fit.method = NelderMead() self.fit.stat = Cash() results = self.fit.fit() results = self.fit.est_errors() cov = results.extra_output mcmc = MCMC() samplers = mcmc.list_samplers() priors = mcmc.list_priors() for par in self.fit.model.pars: mcmc.set_prior(par, flat) prior = mcmc.get_prior(par) sampler = mcmc.get_sampler() name = mcmc.get_sampler_name() mcmc.set_sampler('MH') opt = mcmc.get_sampler_opt('defaultprior') mcmc.set_sampler_opt('defaultprior', opt) #mcmc.set_sampler_opt('verbose', True) log = logging.getLogger("sherpa") level = log.level log.setLevel(logging.ERROR) stats, accept, params = mcmc.get_draws(self.fit, cov, niter=1e2) log.setLevel(level) def test_metropolisMH(self): self.fit.method = NelderMead() self.fit.stat = CStat() results = self.fit.fit() results = self.fit.est_errors() cov = results.extra_output mcmc = MCMC() mcmc.set_sampler('MetropolisMH') #mcmc.set_sampler_opt('verbose', True) log = logging.getLogger("sherpa") level = log.level log.setLevel(logging.ERROR) stats, accept, params = mcmc.get_draws(self.fit, cov, niter=1e2) log.setLevel(level) def tearDown(self): pass
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")
class SherpaFitter(Fitter): __doc__ = """ Sherpa Fitter for astropy models. Parameters ---------- optimizer : string the name of a sherpa optimizer. posible options include {opt} statistic : string the name of a sherpa statistic. posible options include {stat} estmethod : string the name of a sherpa estmethod. possible options include {est} """.format(opt=", ".join(OptMethod._sherpa_values.keys()), stat=", ".join(Stat._sherpa_values.keys()), est=", ".join(EstMethod._sherpa_values.keys())) # is this evil? supported_constraints = ['bounds', 'fixed','tied'] def __init__(self, optimizer="levmar", statistic="leastsq", estmethod="covariance"): try: optimizer = optimizer.value except AttributeError: optimizer = OptMethod(optimizer).value try: statistic = statistic.value except AttributeError: statistic = Stat(statistic).value super(SherpaFitter, self).__init__(optimizer=optimizer, statistic=statistic) try: self._est_method = estmethod.value() except AttributeError: self._est_method = EstMethod(estmethod).value() self.fit_info = {} self._fitter = None # a handle for sherpa fit function self._fitmodel = None # a handle for sherpa fit model self._data = None # a handle for sherpa dataset self.error_info = {} setattr(self.__class__, 'opt_config', property(lambda s: s._opt_config, doc=self._opt_method.__doc__)) # sherpa doesn't currently have a docstring for est_method but maybe the future setattr(self.__class__, 'est_config', property(lambda s: s._est_config, doc=self._est_method.__doc__)) def __call__(self, models, x, y, z=None, xbinsize=None, ybinsize=None, err=None, bkg=None, bkg_scale=1, **kwargs): """ Fit the astropy model with a the sherpa fit routines. Parameters ---------- models : `astropy.modeling.FittableModel` or list of `astropy.modeling.FittableModel` model to fit to x, y, z x : array or list of arrays input coordinates (independent for 1D & 2D fits) y : array or list of arrays input coordinates (dependent for 1D fits or independent for 2D fits) z : array or list of arrays (optional) input coordinates (dependent for 2D fits) xbinsize : array or list of arrays (optional) an array of xbinsizes in x - this will be x -/+ (binsize / 2.0) ybinsize : array or list of arrays (optional) an array of xbinsizes in y - this will be y -/+ (ybinsize / 2.0) err : array or list of arrays (optional) an array of errors in dependent variable bkg : array or list of arrays (optional) this will act as background data bkg_sale : float or list of floats (optional) the scaling factor for the dataset if a single value is supplied it will be copied for each dataset **kwargs : keyword arguments will be passed on to sherpa fit routine Returns ------- model_copy : `astropy.modeling.FittableModel` or a list of models. a copy of the input model with parameters set by the fitter """ tie_list = [] try: n_inputs = models[0].n_inputs except TypeError: n_inputs = models.n_inputs self._data = Dataset(n_inputs, x, y, z, xbinsize, ybinsize, err, bkg, bkg_scale) if self._data.ndata > 1: if len(models) == 1: self._fitmodel = ConvertedModel([models.copy() for _ in range(self._data.ndata)], tie_list) # Copy the model so each data set has the same model! elif len(models) == self._data.ndata: self._fitmodel = ConvertedModel(models, tie_list) else: raise Exception("Don't know how to handle multiple models " "unless there is one foreach dataset") else: if len(models) > 1: self._data.make_simfit(len(models)) self._fitmodel = ConvertedModel(models, tie_list) else: self._fitmodel = ConvertedModel(models) self._fitter = Fit(self._data.data, self._fitmodel.sherpa_model, self._stat_method, self._opt_method, self._est_method, **kwargs) self.fit_info = self._fitter.fit() return self._fitmodel.get_astropy_model() def est_errors(self, sigma=None, maxiters=None, numcores=1, methoddict=None, parlist=None): """ Use sherpa error estimators based on the last fit. Parameters ---------- sigma: float this will be set as the confidance interval for which the errors are found too. maxiters: int the maximum number of iterations the error estimator will run before giving up. methoddict: dict !not quite sure couldn't figure this one out yet! parlist: list a list of parameters to find the confidance interval of if none are provided all free parameters will be estimated. """ if self._fitter is None: ValueError("Must complete a valid fit before errors can be calculated") if sigma is not None: self._fitter.estmethod.config['sigma'] = sigma if maxiters is not None: self._fitter.estmethod.config['maxiters'] = maxiters if 'numcores' in self._fitter.estmethod.config: if not numcores == self._fitter.estmethod.config['numcores']: self._fitter.estmethod.config['numcores'] = numcores self.error_info = self._fitter.est_errors(methoddict=methoddict, parlist=parlist) pnames = [p.split(".", 1)[-1] for p in self.error_info.parnames] # this is to remove the model name return pnames, self.error_info.parvals, self.error_info.parmins, self.error_info.parmaxes @property def _est_config(self): """This returns the est.config property""" return self._est_method.config @property def _opt_config(self): """This returns the opt.config property""" return self._opt_method.config # Here is the MCMC wrapper! @format_doc("{__doc__}\n{mcmc_doc}",mcmc_doc=SherpaMCMC.__doc__) def get_sampler(self, *args, **kwargs): """ This returns and instance of `SherpaMCMC` with it's self as the fitter """ return SherpaMCMC(self, *args, **kwargs)
class SherpaFitter(Fitter): __doc__ = """ Sherpa Fitter for astropy models. Parameters ---------- optimizer : string the name of a sherpa optimizer. posible options include {opt} statistic : string the name of a sherpa statistic. posible options include {stat} estmethod : string the name of a sherpa estmethod. possible options include {est} """.format(opt=", ".join(OptMethod._sherpa_values.keys()), stat=", ".join(Stat._sherpa_values.keys()), est=", ".join(EstMethod._sherpa_values.keys())) # is this evil? def __init__(self, optimizer="levmar", statistic="leastsq", estmethod="covariance"): try: optimizer = optimizer.value except AttributeError: optimizer = OptMethod(optimizer).value try: statistic = statistic.value except AttributeError: statistic = Stat(statistic).value super(SherpaFitter, self).__init__(optimizer=optimizer, statistic=statistic) try: self._est_method = estmethod.value() except AttributeError: self._est_method = EstMethod(estmethod).value() self.fit_info = {} self._fitter = None # a handle for sherpa fit function self._fitmodel = None # a handle for sherpa fit model self._data = None # a handle for sherpa dataset self.error_info = {} setattr(self.__class__, 'opt_config', property(lambda s: s._opt_config, doc=self._opt_method.__doc__)) # sherpa doesn't currently have a docstring for est_method but maybe the future setattr(self.__class__, 'est_config', property(lambda s: s._est_config, doc=self._est_method.__doc__)) def __call__(self, models, x, y, z=None, xbinsize=None, ybinsize=None, err=None, bkg=None, bkg_scale=1, **kwargs): """ Fit the astropy model with a the sherpa fit routines. Parameters ---------- models : `astropy.modeling.FittableModel` or list of `astropy.modeling.FittableModel` model to fit to x, y, z x : array or list of arrays input coordinates (independent for 1D & 2D fits) y : array or list of arrays input coordinates (dependent for 1D fits or independent for 2D fits) z : array or list of arrays (optional) input coordinates (dependent for 2D fits) xbinsize : array or list of arrays (optional) an array of xbinsizes in x - this will be x -/+ (binsize / 2.0) ybinsize : array or list of arrays (optional) an array of xbinsizes in y - this will be y -/+ (ybinsize / 2.0) err : array or list of arrays (optional) an array of errors in dependent variable bkg : array or list of arrays (optional) this will act as background data bkg_sale : float or list of floats (optional) the scaling factor for the dataset if a single value is supplied it will be copied for each dataset **kwargs : keyword arguments will be passed on to sherpa fit routine Returns ------- model_copy : `astropy.modeling.FittableModel` or a list of models. a copy of the input model with parameters set by the fitter """ tie_list = [] try: n_inputs = models[0].n_inputs except TypeError: n_inputs = models.n_inputs self._data = Dataset(n_inputs, x, y, z, xbinsize, ybinsize, err, bkg, bkg_scale) if self._data.ndata > 1: if len(models) == 1: self._fitmodel = ConvertedModel([models.copy() for _ in xrange(self._data.ndata)], tie_list) # Copy the model so each data set has the same model! elif len(models) == self._data.ndata: self._fitmodel = ConvertedModel(models, tie_list) else: raise Exception("Don't know how to handle multiple models " "unless there is one foreach dataset") else: if len(models) > 1: self._data.make_simfit(len(models)) self._fitmodel = ConvertedModel(models, tie_list) else: self._fitmodel = ConvertedModel(models) self._fitter = Fit(self._data.data, self._fitmodel.sherpa_model, self._stat_method, self._opt_method, self._est_method, **kwargs) self.fit_info = self._fitter.fit() return self._fitmodel.get_astropy_model() def est_errors(self, sigma=None, maxiters=None, numcores=1, methoddict=None, parlist=None): """ Use sherpa error estimators based on the last fit. Parameters ---------- sigma: float this will be set as the confidance interval for which the errors are found too. maxiters: int the maximum number of iterations the error estimator will run before giving up. methoddict: dict !not quite sure couldn't figure this one out yet! parlist: list a list of parameters to find the confidance interval of if none are provided all free parameters will be estimated. """ if self._fitter is None: ValueError("Must complete a valid fit before errors can be calculated") if sigma is not None: self._fitter.estmethod.config['sigma'] = sigma if maxiters is not None: self._fitter.estmethod.config['maxiters'] = maxiters if 'numcores' in self._fitter.estmethod.config: if not numcores == self._fitter.estmethod.config['numcores']: self._fitter.estmethod.config['numcores'] = numcores self.error_info = self._fitter.est_errors(methoddict=methoddict, parlist=parlist) pnames = [p.split(".", 1)[-1] for p in self.error_info.parnames] # this is to remove the model name return pnames, self.error_info.parvals, self.error_info.parmins, self.error_info.parmaxes @property def _est_config(self): """This returns the est.config property""" return self._est_method.config @property def _opt_config(self): """This returns the opt.config property""" return self._opt_method.config # Here is the MCMC wrapper! @format_doc("{__doc__}\n{mcmc_doc}",mcmc_doc=SherpaMCMC.__doc__) def get_sampler(self, *args, **kwargs): """ This returns and instance of `SherpaMCMC` with it's self as the fitter """ return SherpaMCMC(self, *args, **kwargs)
class test_sim(SherpaTestCase): _fit_results_bench = { 'rstat': 89.29503933428586, 'qval': 0.0, 'succeeded': 1, 'numpoints': 100, 'dof': 95, 'nfev': 93, 'statval': 8483.0287367571564, 'parnames': ['p1.gamma', 'p1.ampl', 'g1.fwhm', 'g1.pos', 'g1.ampl'], 'parvals': numpy.array([ 1.0701938169914813, 9.1826254677279469, 2.5862083052721028, 2.601619746022207, 47.262657692418749 ]) } _x = numpy.arange(0.1, 10.1, 0.1) _y = numpy.array([ 114, 47, 35, 30, 40, 27, 30, 26, 24, 20, 26, 35, 29, 28, 34, 36, 43, 39, 33, 47, 44, 46, 53, 56, 52, 53, 49, 57, 49, 36, 33, 42, 49, 45, 42, 32, 31, 34, 18, 24, 25, 11, 17, 17, 11, 9, 8, 5, 4, 10, 3, 4, 6, 3, 0, 2, 4, 4, 0, 1, 2, 0, 3, 3, 0, 2, 1, 2, 3, 0, 1, 0, 1, 0, 0, 1, 3, 3, 0, 2, 0, 0, 1, 2, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0 ]) _err = numpy.ones(100) * 0.4 def setUp(self): data = Data1D('fake', self._x, self._y, self._err) g1 = Gauss1D('g1') g1.fwhm.set(1.0, _tiny, _max, frozen=False) g1.pos.set(1.0, -_max, _max, frozen=False) g1.ampl.set(1.0, -_max, _max, frozen=False) p1 = PowLaw1D('p1') p1.gamma.set(1.0, -10, 10, frozen=False) p1.ampl.set(1.0, 0.0, _max, frozen=False) p1.ref.set(1.0, -_max, _max, frozen=True) model = p1 + g1 method = LevMar() method.config['maxfev'] = 10000 method.config['ftol'] = float(_eps) method.config['epsfcn'] = float(_eps) method.config['gtol'] = float(_eps) method.config['xtol'] = float(_eps) method.config['factor'] = float(100) self.fit = Fit(data, model, Chi2DataVar(), method, Covariance()) results = self.fit.fit() for key in ["succeeded", "numpoints", "nfev"]: assert self._fit_results_bench[key] == int(getattr(results, key)) for key in ["rstat", "qval", "statval", "dof"]: assert numpy.allclose(float(self._fit_results_bench[key]), float(getattr(results, key)), 1.e-7, 1.e-7) for key in ["parvals"]: try: assert numpy.allclose(self._fit_results_bench[key], getattr(results, key), 1.e-4, 1.e-4) except AssertionError: print 'parvals bench: ', self._fit_results_bench[key] print 'parvals fit: ', getattr(results, key) print 'results', results raise covresults = self.fit.est_errors() self.dof = results.dof self.mu = numpy.array(results.parvals) self.cov = numpy.array(covresults.extra_output) self.num = 10 def test_student_t(self): multivariate_t(self.mu, self.cov, self.dof, self.num) def test_cauchy(self): multivariate_cauchy(self.mu, self.cov, self.num) def test_parameter_scale_vector(self): ps = ParameterScaleVector() ps.get_scales(self.fit) def test_parameter_scale_matrix(self): ps = ParameterScaleMatrix() ps.get_scales(self.fit) def test_uniform_parameter_sample(self): up = UniformParameterSampleFromScaleVector() up.get_sample(self.fit, num=self.num) def test_normal_parameter_sample_vector(self): np = NormalParameterSampleFromScaleVector() np.get_sample(self.fit, num=self.num) def test_normal_parameter_sample_matrix(self): np = NormalParameterSampleFromScaleMatrix() np.get_sample(self.fit, num=self.num) def test_t_parameter_sample_matrix(self): np = StudentTParameterSampleFromScaleMatrix() np.get_sample(self.fit, self.dof, num=self.num) def test_uniform_sample(self): up = UniformSampleFromScaleVector() up.get_sample(self.fit, num=self.num) def test_normal_sample_vector(self): np = NormalSampleFromScaleVector() np.get_sample(self.fit, num=self.num) def test_normal_sample_matrix(self): np = NormalSampleFromScaleMatrix() np.get_sample(self.fit, num=self.num) def test_t_sample_matrix(self): np = StudentTSampleFromScaleMatrix() np.get_sample(self.fit, self.num, self.dof) def test_uniform_sample(self): uniform_sample(self.fit, num=self.num) def test_normal_sample(self): normal_sample(self.fit, num=self.num, correlate=False) def test_normal_sample_correlated(self): normal_sample(self.fit, num=self.num, correlate=True) def test_t_sample(self): t_sample(self.fit, self.num, self.dof) def test_lrt(self): results = LikelihoodRatioTest.run(self.fit, self.fit.model.lhs, self.fit.model, niter=25) def test_mh(self): self.fit.method = NelderMead() self.fit.stat = Cash() results = self.fit.fit() results = self.fit.est_errors() cov = results.extra_output mcmc = MCMC() samplers = mcmc.list_samplers() priors = mcmc.list_priors() for par in self.fit.model.pars: mcmc.set_prior(par, flat) prior = mcmc.get_prior(par) sampler = mcmc.get_sampler() name = mcmc.get_sampler_name() mcmc.set_sampler('MH') opt = mcmc.get_sampler_opt('defaultprior') mcmc.set_sampler_opt('defaultprior', opt) #mcmc.set_sampler_opt('verbose', True) log = logging.getLogger("sherpa") level = log.level log.setLevel(logging.ERROR) stats, accept, params = mcmc.get_draws(self.fit, cov, niter=1e2) log.setLevel(level) def test_metropolisMH(self): self.fit.method = NelderMead() self.fit.stat = CStat() results = self.fit.fit() results = self.fit.est_errors() cov = results.extra_output mcmc = MCMC() mcmc.set_sampler('MetropolisMH') #mcmc.set_sampler_opt('verbose', True) log = logging.getLogger("sherpa") level = log.level log.setLevel(logging.ERROR) stats, accept, params = mcmc.get_draws(self.fit, cov, niter=1e2) log.setLevel(level) def tearDown(self): pass
class test_sim(SherpaTestCase): @unittest.skipIf(not has_fits_support(), 'need pycrates, pyfits') @unittest.skipIf(not has_package_from_list('sherpa.astro.xspec'), "required sherpa.astro.xspec module missing") def setUp(self): try: from sherpa.astro.io import read_pha from sherpa.astro.xspec import XSwabs, XSpowerlaw except: return #self.startdir = os.getcwd() self.old_level = logger.getEffectiveLevel() logger.setLevel(logging.CRITICAL) datadir = SherpaTestCase.datadir if datadir is None: return pha = os.path.join(datadir, "refake_0934_1_21_1e4.fak") rmf = os.path.join(datadir, "ccdid7_default.rmf") arf = os.path.join(datadir, "quiet_0934.arf") self.simarf = os.path.join(datadir, "aref_sample.fits") self.pcaarf = os.path.join(datadir, "aref_Cedge.fits") data = read_pha(pha) data.ignore(None,0.3) data.ignore(7.0,None) rsp = Response1D(data) self.abs1 = XSwabs('abs1') self.p1 = XSpowerlaw('p1') model = rsp(self.abs1*self.p1) self.fit = Fit(data, model, CStat(), NelderMead(), Covariance()) def tearDown(self): #os.chdir(self.startdir) if hasattr(self,'old_level'): logger.setLevel(self.old_level) @unittest.skipIf(not has_package_from_list('sherpa.astro.xspec'), "required sherpa.astro.xspec module missing") @unittest.skipIf(test_data_missing(), "required test data missing") def test_pragbayes_simarf(self): datadir = SherpaTestCase.datadir if datadir is None: return mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("PragBayes") mcmc.set_sampler_opt("simarf", self.simarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 7) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter) # try: # assert (covar_results.parmaxes < params.std(1)).all() # except AssertionError: # print 'covar: ', str(covar_results.parmaxes) # print 'param: ', str(params.std(1)) # raise @unittest.skipIf(not has_package_from_list('sherpa.astro.xspec'), "required sherpa.astro.xspec module missing") @unittest.skipIf(test_data_missing(), "required test data missing") def test_pragbayes_pcaarf(self): datadir = SherpaTestCase.datadir if datadir is None: return mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("pragBayes") mcmc.set_sampler_opt("simarf", self.pcaarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 5) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter)
#source_model.ypos.freeze() source_model.fwhm = 0.12 source_model.ampl = 1.0 #source_model.fwhm.freeze() # Adding this constant background components the fit works with cash statistics as well #spatial_model_bkg = Const2D('spatial-model-bkg') #spectral_model_bkg = PowLaw1D('spectral-model-bkg') #bkg_model = CombinedModel3D(spatial_model=spatial_model_bkg, spectral_model=spectral_model_bkg) bkg = TableModel('bkg') bkg.load(None, bkg_data.ravel()) # Freeze bkg amplitude bkg.ampl = 1 bkg.ampl.freeze() model = bkg + 1E-11 * (source_model) # Fit # For now only Chi2 statistics seems to work, using Cash, the optimizer doesn't run at all, # maybe because of missing background model? fit = Fit(data=cube, model=model, stat=Cash(), method=NelderMead(), estmethod=Covariance()) result = fit.fit() err = fit.est_errors() print(err) fin = time.time() print fin - debut
class test_sim(SherpaTestCase): @requires_fits @requires_xspec def setUp(self): from sherpa.astro.io import read_pha from sherpa.astro.xspec import XSwabs, XSpowerlaw # self.startdir = os.getcwd() self.old_level = logger.getEffectiveLevel() logger.setLevel(logging.CRITICAL) pha = self.make_path("refake_0934_1_21_1e4.fak") # rmf = self.make_path("ccdid7_default.rmf") # arf = self.make_path("quiet_0934.arf") self.simarf = self.make_path("aref_sample.fits") self.pcaarf = self.make_path("aref_Cedge.fits") data = read_pha(pha) data.ignore(None,0.3) data.ignore(7.0,None) rsp = Response1D(data) self.abs1 = XSwabs('abs1') self.p1 = XSpowerlaw('p1') model = rsp(self.abs1*self.p1) self.fit = Fit(data, model, CStat(), NelderMead(), Covariance()) def tearDown(self): # os.chdir(self.startdir) if hasattr(self, 'old_level'): logger.setLevel(self.old_level) @requires_xspec @requires_data def test_pragbayes_simarf(self): mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("PragBayes") mcmc.set_sampler_opt("simarf", self.simarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 7) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter) # try: # assert (covar_results.parmaxes < params.std(1)).all() # except AssertionError: # print 'covar: ', str(covar_results.parmaxes) # print 'param: ', str(params.std(1)) # raise @requires_xspec @requires_data def test_pragbayes_pcaarf(self): mcmc = sim.MCMC() self.abs1.nh = 0.092886 self.p1.phoindex = 0.994544 self.p1.norm = 9.26369 mcmc.set_sampler("pragBayes") mcmc.set_sampler_opt("simarf", self.pcaarf) mcmc.set_sampler_opt("p_M", 0.5) mcmc.set_sampler_opt("nsubiter", 5) covar_results = self.fit.est_errors() cov = covar_results.extra_output niter = 10 stats, accept, params = mcmc.get_draws(self.fit, cov, niter=niter)
class SpectrumFit(object): """Orchestrate a 1D counts spectrum fit. For usage examples see :ref:`spectral_fitting` Parameters ---------- obs_list : `~gammapy.spectrum.SpectrumObservationList`, `~gammapy.spectrum.SpectrumObservation` Observation(s) to fit model : `~gammapy.spectrum.models.SpectralModel` Source model. Should return counts if ``forward_folded`` is False and a flux otherwise stat : {'wstat', 'cash'} Fit statistic forward_folded : bool, default: True Fold ``model`` with the IRFs given in ``obs_list`` fit_range : tuple of `~astropy.units.Quantity` Fit range, will be convolved with observation thresholds. If you want to control which bins are taken into account in the fit for each observations, use :func:`~gammapy.spectrum.SpectrumObservation.qualitiy` background_model : `~gammapy.spectrum.models.SpectralModel`, optional Background model to be used in cash fits method : {'sherpa'} Optimization backend for the fit err_method : {'sherpa'} Optimization backend for error estimation """ def __init__(self, obs_list, model, stat='wstat', forward_folded=True, fit_range=None, background_model=None, method='sherpa', err_method='sherpa'): self.obs_list = self._convert_obs_list(obs_list) self.model = model self.stat = stat self.forward_folded = forward_folded self.fit_range = fit_range self.background_model = background_model self.method = method self.err_method = err_method self._predicted_counts = None self._statval = None self.covar_axis = None self.covariance = None self.result = None self._check_valid_fit() self._apply_fit_range() def __str__(self): ss = self.__class__.__name__ ss += '\nSource model {}'.format(self.model) ss += '\nStat {}'.format(self.stat) ss += '\nForward Folded {}'.format(self.forward_folded) ss += '\nFit range {}'.format(self.fit_range) if self.background_model is not None: ss += '\nBackground model {}'.format(self.background_model) ss += '\nBackend {}'.format(self.method) ss += '\nError Backend {}'.format(self.err_method) return ss @staticmethod def _convert_obs_list(obs_list): """Helper function to accept different inputs for obs_list.""" if isinstance(obs_list, SpectrumObservation): obs_list = SpectrumObservationList([obs_list]) if not isinstance(obs_list, SpectrumObservationList): raise ValueError('Invalid input {} for parameter obs_list'.format( type(obs_list))) return obs_list @property def bins_in_fit_range(self): """Bins participating in the fit for each observation.""" return self._bins_in_fit_range @property def predicted_counts(self): """Current value of predicted counts. For each observation a tuple to counts for the on and off region is returned. """ return self._predicted_counts @property def statval(self): """Current value of statval. For each observation the statval per bin is returned. """ return self._statval @property def fit_range(self): """Fit range.""" return self._fit_range @fit_range.setter def fit_range(self, fit_range): self._fit_range = fit_range self._apply_fit_range() @property def true_fit_range(self): """True fit range for each observation. True fit range is the fit range set in the `~gammapy.spectrum.SpectrumFit` with observation threshold taken into account. """ true_range = [] for binrange, obs in zip(self.bins_in_fit_range, self.obs_list): idx = np.where(binrange)[0] e_min = obs.e_reco[idx[0]] e_max = obs.e_reco[idx[-1] + 1] fit_range = u.Quantity((e_min, e_max)) true_range.append(fit_range) return true_range def _apply_fit_range(self): """Mark bins within desired fit range for each observation.""" self._bins_in_fit_range = [] for obs in self.obs_list: # Take into account fit range energy = obs.e_reco valid_range = np.zeros(energy.nbins) if self.fit_range is not None: precision = 1e-3 # to avoid floating round precision idx_lo = np.where(energy * (1 + precision) < self.fit_range[0])[0] valid_range[idx_lo] = 1 idx_hi = np.where(energy[:-1] * (1 - precision) > self.fit_range[1])[0] if len(idx_hi) != 0: idx_hi = np.insert(idx_hi, 0, idx_hi[0] - 1) valid_range[idx_hi] = 1 # Take into account thresholds try: quality = obs.on_vector.quality except AttributeError: quality = np.zeros(obs.e_reco.nbins) convolved = np.logical_and(1 - quality, 1 - valid_range) self._bins_in_fit_range.append(convolved) def predict_counts(self): """Predict counts for all observations. The result is stored as ``predicted_counts`` attribute. """ predicted_counts = [] for obs in self.obs_list: mu_sig = self._predict_counts_helper(obs, self.model, self.forward_folded) mu_bkg = None if self.background_model is not None: # For now, never fold background model with IRFs mu_bkg = self._predict_counts_helper(obs, self.background_model, False) counts = [mu_sig, mu_bkg] predicted_counts.append(counts) self._predicted_counts = predicted_counts def _predict_counts_helper(self, obs, model, forward_folded=True): """Predict counts for one observation. Parameters ---------- obs : `~gammapy.spectrum.SpectrumObservation` Response functions model : `~gammapy.spectrum.SpectralModel` Source or background model forward_folded : bool, default: True Fold model with IRFs Returns ------ predicted_counts: `np.array` Predicted counts for one observation """ predictor = CountsPredictor(model=model) if forward_folded: predictor.livetime = obs.livetime predictor.aeff = obs.aeff predictor.edisp = obs.edisp else: predictor.e_true = obs.e_reco predictor.run() counts = predictor.npred.data.data # Check count unit (~unit of model amplitude) cond = counts.unit.is_equivalent('ct') or counts.unit.is_equivalent('') if cond: counts = counts.value else: raise ValueError('Predicted counts {}'.format(counts)) # Apply AREASCAL column counts *= obs.on_vector.areascal return counts def calc_statval(self): """Calc statistic for all observations. The result is stored as attribute ``statval``, bin outside the fit range are set to 0. """ statval = [] for obs, npred in zip(self.obs_list, self.predicted_counts): on_stat, off_stat = self._calc_statval_helper(obs, npred) statvals = (on_stat, off_stat) statval.append(statvals) self._statval = statval self._restrict_statval() def _calc_statval_helper(self, obs, prediction): """Calculate ``statval`` for one observation. Parameters ---------- obs : `~gammapy.spectrum.SpectrumObservation` Measured counts prediction : tuple of `~numpy.ndarray` Predicted (on counts, off counts) Returns ------ statsval : tuple of `~numpy.ndarray` Statval for (on, off) """ stats_func = getattr(stats, self.stat) # Off stat = 0 by default off_stat = np.zeros(obs.e_reco.nbins) if self.stat == 'cash' or self.stat == 'cstat': if self.background_model is not None: mu_on = prediction[0] + prediction[1] on_stat = stats_func(n_on=obs.on_vector.data.data.value, mu_on=mu_on) mu_off = prediction[1] / obs.alpha off_stat = stats_func(n_on=obs.off_vector.data.data.value, mu_on=mu_off) else: mu_on = prediction[0] on_stat = stats_func(n_on=obs.on_vector.data.data.value, mu_on=mu_on) off_stat = np.zeros_like(on_stat) elif self.stat == 'wstat': kwargs = dict(n_on=obs.on_vector.data.data.value, n_off=obs.off_vector.data.data.value, alpha=obs.alpha, mu_sig=prediction[0]) # Store the result of the profile likelihood as bkg prediction mu_bkg = stats.get_wstat_mu_bkg(**kwargs) prediction[1] = mu_bkg * obs.alpha on_stat_ = stats_func(**kwargs) # The on_stat sometime contains nan values # TODO: Handle properly on_stat = np.nan_to_num(on_stat_) off_stat = np.zeros_like(on_stat) else: raise NotImplementedError('{}'.format(self.stat)) return on_stat, off_stat @property def total_stat(self): """Statistic summed over all bins and all observations. This is what is used for the fit. """ total_stat = np.sum(self.statval, dtype=np.float64) return total_stat def _restrict_statval(self): """Apply valid fit range to statval. """ for statval, valid_range in zip(self.statval, self.bins_in_fit_range): # Find bins outside safe range idx = np.where(np.invert(valid_range))[0] statval[0][idx] = 0 statval[1][idx] = 0 def _check_valid_fit(self): """Helper function to give useful error messages.""" # Assume that settings are the same for all observations test_obs = self.obs_list[0] irfs_exist = test_obs.aeff is not None or test_obs.edisp is not None if self.forward_folded and not irfs_exist: raise ValueError('IRFs required for forward folded fit') if self.stat == 'wstat' and self.obs_list[0].off_vector is None: raise ValueError('Off vector required for WStat fit') def likelihood_1d(self, model, parname, parvals): """Compute likelihood profile. Parameters ---------- model : `~gammapy.spectrum.models.SpectralModel` Model to draw likelihood profile for parname : str Parameter to calculate profile for parvals : `~astropy.units.Quantity` Parameter values """ likelihood = [] self.model = model for val in parvals: self.model.parameters[parname].value = val self.predict_counts() self.calc_statval() likelihood.append(self.total_stat) return np.array(likelihood) def plot_likelihood_1d(self, ax=None, **kwargs): """Plot 1-dim likelihood profile. See :func:`~gammapy.spectrum.SpectrumFit.likelihood_1d` """ import matplotlib.pyplot as plt ax = plt.gca() if ax is None else ax yy = self.likelihood_1d(**kwargs) ax.plot(kwargs['parvals'], yy) ax.set_xlabel(kwargs['parname']) return ax def fit(self): """Run the fit.""" if self.method == 'sherpa': self._fit_sherpa() else: raise NotImplementedError('method: {}'.format(self.method)) def _fit_sherpa(self): """Wrapper around sherpa minimizer.""" from sherpa.fit import Fit from sherpa.data import Data1DInt from sherpa.optmethods import NelderMead from .sherpa_utils import SherpaModel, SherpaStat binning = self.obs_list[0].e_reco # The sherpa data object is not usued in the fit. It is set to the # first observation for debugging purposes, see below data = self.obs_list[0].on_vector.data.data.value data = Data1DInt('Dummy data', binning[:-1].value, binning[1:].value, data) # DEBUG # from sherpa.models import PowLaw1D # from sherpa.stats import Cash # model = PowLaw1D('sherpa') # model.ref = 0.1 # fit = Fit(data, model, Cash(), NelderMead()) # NOTE: We cannot use the Levenbergr-Marquart optimizer in Sherpa # because it relies on the fvec return value of the fit statistic (we # return None). The computation of fvec is not straightforwad, not just # stats per bin. E.g. for a cash fit the sherpa stat computes it # according to cstat # see https://github.com/sherpa/sherpa/blob/master/sherpa/include/sherpa/stats.hh#L122 self._sherpa_fit = Fit(data, SherpaModel(self), SherpaStat(self), NelderMead()) fitresult = self._sherpa_fit.fit() log.debug(fitresult) self._make_fit_result() def _make_fit_result(self): """Bundle fit results into `~gammapy.spectrum.SpectrumFitResult`. It is important to copy best fit values, because the error estimation will change the model parameters and statval again """ from . import SpectrumFitResult model = self.model.copy() if self.background_model is not None: bkg_model = self.background_model.copy() else: bkg_model = None statname = self.stat results = [] for idx, obs in enumerate(self.obs_list): fit_range = self.true_fit_range[idx] statval = np.sum(self.statval[idx]) stat_per_bin = self.statval[idx] npred_src = copy.deepcopy(self.predicted_counts[idx][0]) npred_bkg = copy.deepcopy(self.predicted_counts[idx][1]) results.append(SpectrumFitResult( model=model, fit_range=fit_range, statname=statname, statval=statval, stat_per_bin=stat_per_bin, npred_src=npred_src, npred_bkg=npred_bkg, background_model=bkg_model, obs=obs )) self.result = results def est_errors(self): """Estimate parameter errors.""" if self.err_method == 'sherpa': self._est_errors_sherpa() else: raise NotImplementedError('{}'.format(self.err_method)) for res in self.result: res.covar_axis = self.covar_axis res.covariance = self.covariance res.model.parameters.set_parameter_covariance(self.covariance, self.covar_axis) def _est_errors_sherpa(self): """Wrapper around Sherpa error estimator.""" covar = self._sherpa_fit.est_errors() self.covar_axis = [par.split('.')[-1] for par in covar.parnames] self.covariance = copy.deepcopy(covar.extra_output) def run(self, outdir=None): """Run all steps and write result to disk. Parameters ---------- outdir : Path, str directory to write results files to (if given) """ log.info('Running {}'.format(self)) self.fit() self.est_errors() if outdir is not None: self._write_result(outdir) def _write_result(self, outdir): outdir = make_path(outdir) outdir.mkdir(exist_ok=True, parents=True) # Assume only one model is fit to all data modelname = self.result[0].model.__class__.__name__ filename = outdir / 'fit_result_{}.yaml'.format(modelname) log.info('Writing {}'.format(filename)) self.result[0].to_yaml(filename)
estmethod=Covariance(), ) fit_results = fit.fit() print(fit_results.format()) fit = Fit( data=cube, model=model, stat=Cash(), method=NelderMead(), estmethod=Covariance(), ) fit_results = fit.fit() fit_results fit_results.format() print(fit_results.format()) err_est_results = fit.est_errors() print(err_est_results.format()) exclusion_region = CircleSkyRegion( center=SkyCoord(83.60, 21.88, unit='deg'), radius=Angle(0.1, "deg"), ) sky_mask_cube = counts_3d.region_mask(exclusion_region) sky_mask_cube.data = sky_mask_cube.data.astype(int) index_region_selected_3d = np.where(sky_mask_cube.data.value == 1) counts_3d = counts exclusion_region = CircleSkyRegion( center=SkyCoord(83.60, 21.88, unit='deg'), radius=Angle(0.1, "deg"), )
File "example.py", line 125, in <module> residplot.prepare(d, mdl, f.stat) File "/home/djburke/miniconda2/envs/sherpa410-py35/lib/python3.5/site-packages/sherpa-4.10.0-py3.5-linux-x86_64.egg/sherpa/plot/__init__.py", line 1140, in prepare self.yerr = staterr / staterr TypeError: unsupported operand type(s) for /: 'list' and 'list' """ d.ignore(None, 1) statinfo = f.calc_stat_info() report("statinfo") dump("statinfo.rstat == fitres3.rstat") dump("f.estmethod.name") coverrs = f.est_errors() report("coverrs.format()") dump("f.estmethod.sigma") f.estmethod.sigma = 1.6 coverrs90 = f.est_errors() report("coverrs90.format()") from sherpa.estmethods import Confidence f.estmethod = Confidence() print("*** start confidence errors") conferrs = f.est_errors() print("*** end confidence errors") report("conferrs.format()")