Spectrum Fitting

In this first basic example we will perform a basic spectral analysis of the Crab nebula using the public H.E.S.S. data release (already included in gammapy).

Let’s get started by importing all the relevant stuff.

[1]:
import astropy.units as u
from astropy.coordinates import Angle, SkyCoord

from astromodels.core.model import Model
from astromodels.core.units import get_units
from astromodels.functions import Log_parabola, Log_uniform_prior, Uniform_prior
from astromodels.sources import PointSource

from gammapy.data import DataStore
from gammapy.datasets import Datasets, SpectrumDataset
from gammapy.makers import (
    ReflectedRegionsBackgroundMaker,
    SafeMaskMaker,
    SpectrumDatasetMaker,
)
from gammapy.maps import MapAxis, RegionGeom, WcsGeom
from gammapy.modeling import Fit
from gammapy.modeling.models import LogParabolaSpectralModel, SkyModel

from regions import CircleSkyRegion

from threeML import BayesianAnalysis
from threeML.data_list import DataList

from gammapy_plugin.converter import AstromodelConverter
from gammapy_plugin.gammapy_like import GammapyLike
from gammapy_plugin.test.utils import get_close
13:45:38 WARNING   The naima package is not available. Models that depend on it will not be         functions.py:43
                  available                                                                                        
         WARNING   The GSL library or the pygsl wrapper cannot be loaded. Models that depend on it  functions.py:65
                  will not be available.                                                                           
         WARNING   The ebltable package is not available. Models that depend on it will not be     absorption.py:33
                  available                                                                                        
13:45:39 INFO      Starting 3ML!                                                                     __init__.py:44
         WARNING   WARNINGs here are NOT errors                                                      __init__.py:45
         WARNING   but are inform you about optional packages that can be installed                  __init__.py:46
         WARNING    to disable these messages, turn off start_warning in your config file            __init__.py:47
         WARNING   no display variable set. using backend for graphics without display (agg)         __init__.py:53
         WARNING   ROOT minimizer not available                                                minimization.py:1208
         WARNING   Multinest minimizer not available                                           minimization.py:1218
         WARNING   PyGMO is not available                                                      minimization.py:1228
         WARNING   Could not import plugin FermiLATLike.py. Do you have the relative instrument     __init__.py:126
                  software installed and configured?                                                               
         WARNING   No fermitools installed                                              lat_transient_builder.py:44
         WARNING   Env. variable OMP_NUM_THREADS is not set. Please set it to 1 for optimal         __init__.py:335
                  performances in 3ML                                                                              
         WARNING   Env. variable MKL_NUM_THREADS is not set. Please set it to 1 for optimal         __init__.py:335
                  performances in 3ML                                                                              
         WARNING   Env. variable NUMEXPR_NUM_THREADS is not set. Please set it to 1 for optimal     __init__.py:335
                  performances in 3ML                                                                              

We will now set the astromodels energy unit to TeV. This is a feature that requires astromodels >= 2.5.1

[2]:
get_units().energy = u.TeV

Alright perfect, let’s continue by laoding the relevant data

[3]:
datastore = DataStore.from_dir("$GAMMAPY_DATA/hess-dl3-dr1/")
obs_ids = [23523, 23526, 23559, 23592]
observations = datastore.get_observations(obs_ids)

We perform a standard gammapy-workflow of creating a dataset

  • setting as target

  • excluding the target region

  • creating our energy axis

[4]:
target_position = SkyCoord(ra=83.63, dec=22.01, unit="deg", frame="icrs")

on_region_radius = Angle("0.11 deg")
on_region = CircleSkyRegion(center=target_position.galactic, radius=on_region_radius)
exclusion_region = CircleSkyRegion(
    center=SkyCoord(183.604, -8.708, unit="deg", frame="galactic"),
    radius=0.5 * u.deg,
)

skydir = target_position.galactic
geom = WcsGeom.create(
    npix=(250, 250), binsz=0.02, skydir=skydir, proj="TAN", frame="galactic"
)

exclusion_mask = ~geom.region_mask([exclusion_region])
energy_axis = MapAxis.from_energy_bounds(
    0.5, 40, nbin=10, per_decade=True, unit="TeV", name="energy"
)
energy_axis_true = MapAxis.from_energy_bounds(
    0.1, 100, nbin=20, per_decade=True, unit="TeV", name="energy_true"
)

geom = RegionGeom.create(region=on_region, axes=[energy_axis])
dataset_empty = SpectrumDataset.create(geom=geom, energy_axis_true=energy_axis_true)

We now have everything to create our makers and run them. In this example we use a ReflectedRegionsBackgroundMaker

[5]:
dataset_maker = SpectrumDatasetMaker(
    containment_correction=True, selection=["counts", "exposure", "edisp"]
)
bkg_maker = ReflectedRegionsBackgroundMaker(exclusion_mask=exclusion_mask)

safe_mask_maker = SafeMaskMaker(methods=["aeff-max"], aeff_percent=10)
datasets = Datasets()

for obs_id, observation in zip(obs_ids, observations):
    dataset = dataset_maker.run(dataset_empty.copy(name=str(obs_id)), observation)
    dataset_on_off = bkg_maker.run(dataset, observation)
    dataset_on_off = safe_mask_maker.run(dataset_on_off, observation)
    datasets.append(dataset_on_off)
datasets_copy = datasets.copy()

Let’s continue with the threeML steps

  • choosing and setting up a model

[6]:
logp = Log_parabola()
ps = PointSource(
    source_name="crab",
    ra=target_position.ra.deg,
    dec=target_position.dec.deg,
    spectral_shape=logp,
)
logp.K.prior = Log_uniform_prior(lower_bound=1e-13, upper_bound=1e-9)
logp.K = 1e-11 * u.Unit("TeV-1 cm-2 s-1")
logp.piv = 1 * u.TeV
logp.piv.free = False
logp.alpha.prior = Uniform_prior(lower_bound=-3, upper_bound=-1)
logp.alpha = -2
logp.beta.prior = Uniform_prior(lower_bound=0, upper_bound=2)
logp.beta = 1

model = Model(ps)
[7]:
conv = AstromodelConverter(model, frame="galactic")
gl = GammapyLike("hess", sources="crab")
gl.set_datasets(datasets)
gl.set_model(model, converted_model=conv)

ba = BayesianAnalysis(model, DataList(gl))
ba.set_sampler("ultranest")
ba.sampler.setup()
ba.sample()
res = ba.results
res
13:45:40 INFO      sampler set to ultranest                                                bayesian_analysis.py:186
[ultranest] Sampling 400 live points from prior ...
[ultranest] Explored until L=-4e+01
[ultranest] Likelihood function evaluations: 9747
[ultranest]   logZ = -53.37 +- 0.09195
[ultranest] Effective samples strategy satisfied (ESS = 1826.2, need >400)
[ultranest] Posterior uncertainty strategy is satisfied (KL: 0.46+-0.08 nat, need <0.50 nat)
[ultranest] Evidency uncertainty strategy is satisfied (dlogz=0.09, need <0.5)
[ultranest]   logZ error budget: single: 0.14 bs:0.09 tail:0.01 total:0.09 required:<0.50
[ultranest] done iterating.
13:46:11 INFO      fit restored to maximum of posterior                                         sampler_base.py:168
         INFO      fit restored to maximum of posterior                                         sampler_base.py:168
Maximum a posteriori probability (MAP) point:

result unit
parameter
crab.spectrum.main.Log_parabola.K (4.37 +/- 0.22) x 10^-11 1 / (TeV s cm2)
crab.spectrum.main.Log_parabola.alpha -2.39 -0.10 +0.17
crab.spectrum.main.Log_parabola.beta (1.6 -0.6 +1.0) x 10^-1
Values of -log(posterior) at the minimum:

-log(posterior)
hess -20.566692
total -20.566692
Values of statistical measures:

statistical measures
AIC 47.449174
BIC 54.279464
DIC 46.793867
PDIC 2.815043
log(Z) -23.169921
[7]:
<threeML.analysis_results.BayesianResults at 0x7fb12840ee40>
[8]:
_ = res.corner_plot()