def find_energy(self, aeff, emin=None, emax=None): """Find energy for a given effective area. In case the solution is not unique, provide the `emin` or `emax` arguments to limit the solution to the given range. By default the peak energy of the effective area is chosen as `emax`. Parameters ---------- aeff : `~astropy.units.Quantity` Effective area value emin : `~astropy.units.Quantity` Lower bracket value in case solution is not unique. emax : `~astropy.units.Quantity` Upper bracket value in case solution is not unique. Returns ------- energy : `~astropy.units.Quantity` Energy corresponding to the given aeff. """ from gammapy.modeling.models import TemplateSpectralModel energy = self.energy.center if emin is None: emin = energy[0] if emax is None: # use the peak effective area as a default for the energy maximum emax = energy[np.argmax(self.data.data)] aeff_spectrum = TemplateSpectralModel(energy, self.data.data) return aeff_spectrum.inverse(aeff, emin=emin, emax=emax)
def test_template_spectral_model_single_value(): energy = [1] * u.TeV values = [1e-12] * u.Unit("TeV-1 s-1 cm-2") model = TemplateSpectralModel(energy=energy, values=values) result = model(energy=[0.5, 2] * u.TeV) assert_allclose(result.data, 1e-12)
def table_model(self): """Spectrum as `~gammapy.modeling.models.TemplateSpectralModel`.""" subtable = self.table[self.table["mDM"] == self.mDM.value] energies = (10**subtable["Log[10,x]"]) * self.mDM channel_name = self.channel_registry[self.channel] dN_dlogx = subtable[channel_name] dN_dE = dN_dlogx / (energies * np.log(10)) return TemplateSpectralModel(energy=energies, values=dN_dE)
def from_table(cls, table, sed_type=None, reference_model=None): """Create flux points from table Parameters ---------- table : `~astropy.table.Table` Table sed_type : {"dnde", "flux", "eflux", "e2dnde", "likelihood"} Sed type reference_model : `SpectralModel` Reference spectral model Returns ------- flux_points : `FluxPoints` Flux points """ table = table_standardise_units_copy(table) if sed_type is None: sed_type = table.meta.get("SED_TYPE", None) if sed_type is None: sed_type = cls._guess_sed_type(table) if sed_type is None: raise ValueError("Specifying the sed type is required") cls._validate_data(data=table, sed_type=sed_type) if sed_type in ["dnde", "eflux", "e2dnde", "flux"]: if reference_model is None: log.warning( "No reference model set for FluxPoints. Assuming point source with E^-2 spectrum." ) reference_model = PowerLawSpectralModel() data = cls._convert_flux_columns( table=table, reference_model=reference_model, sed_type=sed_type ) elif sed_type == "likelihood": data = cls._convert_loglike_columns(table) reference_model = TemplateSpectralModel( energy=table["e_ref"].quantity, values=table["ref_dnde"].quantity ) else: raise ValueError(f"Not a valid SED type {sed_type}") # We add the remaining maps for key in OPTIONAL_QUANTITIES_COMMON: if key in table.colnames: data[key] = table[key] data.meta["SED_TYPE"] = "likelihood" return cls(data=data, reference_spectral_model=reference_model)
def table_model(): energy = MapAxis.from_energy_bounds(0.1 * u.TeV, 100 * u.TeV, 1000).center model = PowerLawSpectralModel( index=2.3, amplitude="4 cm-2 s-1 TeV-1", reference="1 TeV" ) dnde = model(energy) return TemplateSpectralModel(energy, dnde)
def table_model(): energy_edges = energy_logspace(0.1 * u.TeV, 100 * u.TeV, 1000) energy = np.sqrt(energy_edges[:-1] * energy_edges[1:]) model = PowerLawSpectralModel(index=2.3, amplitude="4 cm-2 s-1 TeV-1", reference="1 TeV") dnde = model(energy) return TemplateSpectralModel(energy, dnde, 1)
def table_model(): energy_edges = energy_logspace(0.1 * u.TeV, 100 * u.TeV, 1000) energy = np.sqrt(energy_edges[:-1] * energy_edges[1:]) index = 2.3 * u.Unit("") amplitude = 4 / u.cm**2 / u.s / u.TeV reference = 1 * u.TeV pl = PowerLawSpectralModel(index, amplitude, reference) flux = pl(energy) return TemplateSpectralModel(energy, flux, 1 * u.Unit(""))
def table_psf_in_energy_range(self, energy_range, spectrum=None, n_bins=11, **kwargs): """Average PSF in a given energy band. Expected counts in sub energy bands given the given exposure and spectrum are used as weights. Parameters ---------- energy_range : `~astropy.units.Quantity` Energy band spectrum : `~gammapy.modeling.models.SpectralModel` Spectral model used for weighting the PSF. Default is a power law with index=2. n_bins : int Number of energy points in the energy band, used to compute the weighted PSF. Returns ------- psf : `EnergyDependentTablePSF` Table PSF """ from gammapy.modeling.models import PowerLawSpectralModel, TemplateSpectralModel if spectrum is None: spectrum = PowerLawSpectralModel() exposure = TemplateSpectralModel(self.axes["energy_true"].center, self.exposure) e_min, e_max = energy_range energy = MapAxis.from_energy_bounds(e_min, e_max, n_bins).edges[:, np.newaxis] weights = spectrum(energy) * exposure(energy) weights /= weights.sum() psf_value = self.evaluate(energy_true=energy) psf_value_weighted = weights * psf_value energy_axis = MapAxis.from_edges(energy_range, name="energy_true") data = psf_value_weighted.sum(axis=0, keepdims=True) return self.__class__(axes=[energy_axis, self.axes["rad"]], data=data.value, unit=data.unit, **kwargs)
def test_TemplateSpectralModel_evaluate_tiny(): energy = np.array([1.00000000e06, 1.25892541e06, 1.58489319e06, 1.99526231e06]) values = np.array([4.39150790e-38, 1.96639562e-38, 8.80497507e-39, 3.94262401e-39]) model = TemplateSpectralModel( energy=energy, values=values * u.Unit("MeV-1 s-1 sr-1") ) result = model.evaluate(energy) tiny = np.finfo(np.float32).tiny mask = abs(values) - tiny > tiny np.testing.assert_allclose( values[mask] / values.max(), result[mask].value / values.max() ) mask = abs(result.value) - tiny <= tiny assert np.all(result[mask] == 0.0)
def get_test_cases(): e_true = Quantity(np.logspace(-1, 2, 120), "TeV") e_reco = Quantity(np.logspace(-1, 2, 100), "TeV") return [ dict(model=PowerLawSpectralModel(amplitude="1e2 TeV-1"), e_true=e_true, npred=999), dict( model=PowerLaw2SpectralModel(amplitude="1", emin="0.1 TeV", emax="100 TeV"), e_true=e_true, npred=1, ), dict( model=PowerLawSpectralModel(amplitude="1e-11 TeV-1 cm-2 s-1"), aeff=EffectiveAreaTable.from_parametrization(e_true), livetime="10 h", npred=1448.05960, ), dict( model=PowerLawSpectralModel(reference="1 GeV", amplitude="1e-11 GeV-1 cm-2 s-1"), aeff=EffectiveAreaTable.from_parametrization(e_true), livetime="30 h", npred=4.34417881, ), dict( model=PowerLawSpectralModel(amplitude="1e-11 TeV-1 cm-2 s-1"), aeff=EffectiveAreaTable.from_parametrization(e_true), edisp=EnergyDispersion.from_gauss(e_reco=e_reco, e_true=e_true, bias=0, sigma=0.2), livetime="10 h", npred=1437.450076, ), dict( model=TemplateSpectralModel( energy=[0.1, 0.2, 0.3, 0.4] * u.TeV, values=[4.0, 3.0, 1.0, 0.1] * u.Unit("TeV-1"), ), e_true=[0.1, 0.2, 0.3, 0.4] * u.TeV, npred=0.554513062, ), ]
def table_psf_in_energy_band(self, energy_band, spectrum=None, n_bins=11, **kwargs): """Average PSF in a given energy band. Expected counts in sub energy bands given the given exposure and spectrum are used as weights. Parameters ---------- energy_band : `~astropy.units.Quantity` Energy band spectrum : `~gammapy.modeling.models.SpectralModel` Spectral model used for weighting the PSF. Default is a power law with index=2. n_bins : int Number of energy points in the energy band, used to compute the weigthed PSF. Returns ------- psf : `TablePSF` Table PSF """ from gammapy.modeling.models import PowerLawSpectralModel, TemplateSpectralModel if spectrum is None: spectrum = PowerLawSpectralModel() exposure = TemplateSpectralModel(self.energy_axis_true.center, self.exposure) e_min, e_max = energy_band energy = MapAxis.from_energy_bounds(e_min, e_max, n_bins).edges weights = spectrum(energy) * exposure(energy) weights /= weights.sum() psf_value = self.evaluate(energy=energy) psf_value_weighted = weights[:, np.newaxis] * psf_value return TablePSF(self.rad_axis, psf_value_weighted.sum(axis=0), **kwargs)
def test_TemplateSpectralModel_compound(): energy = [1.00e06, 1.25e06, 1.58e06, 1.99e06] * u.MeV values = [4.39e-7, 1.96e-7, 8.80e-7, 3.94e-7] * u.Unit("MeV-1 s-1 sr-1") template = TemplateSpectralModel(energy=energy, values=values) correction = PowerLawNormSpectralModel(norm=2) model = CompoundSpectralModel(template, correction, operator=operator.mul) assert np.allclose(model(energy), 2 * values) model_mul = template * correction assert isinstance(model_mul, CompoundSpectralModel) assert np.allclose(model_mul(energy), 2 * values) model_dict = model.to_dict() assert model_dict["spectral"]["operator"] == "mul" model_class = SPECTRAL_MODEL_REGISTRY.get_cls(model_dict["spectral"]["type"]) new_model = model_class.from_dict(model_dict) assert isinstance(new_model, CompoundSpectralModel) assert np.allclose(new_model(energy), 2 * values)
def get_bias_energy(self, bias, energy_min=None, energy_max=None): """Find energy corresponding to a given bias. In case the solution is not unique, provide the ``energy_min`` or ``energy_max`` arguments to limit the solution to the given range. By default the peak energy of the bias is chosen as ``energy_min``. Parameters ---------- bias : float Bias value. energy_min : `~astropy.units.Quantity` Lower bracket value in case solution is not unique. energy_max : `~astropy.units.Quantity` Upper bracket value in case solution is not unique. Returns ------- bias_energy : `~astropy.units.Quantity` Reconstructed energy corresponding to the given bias. """ from gammapy.modeling.models import TemplateSpectralModel energy_true = self.axes["energy_true"].center values = self.get_bias(energy_true) if energy_min is None: # use the peak bias energy as default minimum energy_min = energy_true[np.nanargmax(values)] if energy_max is None: energy_max = energy_true[-1] bias_spectrum = TemplateSpectralModel(energy=energy_true, values=values) energy_true_bias = bias_spectrum.inverse(Quantity(bias), energy_min=energy_min, energy_max=energy_max) if np.isnan(energy_true_bias[0]): energy_true_bias[0] = energy_min # return reconstructed energy return energy_true_bias * (1 + bias)
def from_table(cls, table, sed_type=None, format="gadf-sed", reference_model=None, gti=None): """Create flux points from a table. The table column names must be consistent with the sed_type Parameters ---------- table : `~astropy.table.Table` Table sed_type : {"dnde", "flux", "eflux", "e2dnde", "likelihood"} Sed type format : {"gadf-sed", "lightcurve", "profile"} Table format. reference_model : `SpectralModel` Reference spectral model gti : `GTI` Good time intervals meta : dict Meta data. Returns ------- flux_points : `FluxPoints` Flux points """ table = table_standardise_units_copy(table) if sed_type is None: sed_type = table.meta.get("SED_TYPE", None) if sed_type is None: sed_type = cls._guess_sed_type(table.colnames) if sed_type is None: raise ValueError("Specifying the sed type is required") if sed_type == "likelihood": table = cls._convert_loglike_columns(table) if reference_model is None: reference_model = TemplateSpectralModel( energy=flat_if_equal(table["e_ref"].quantity), values=flat_if_equal(table["ref_dnde"].quantity), ) maps = Maps() table.meta.setdefault("SED_TYPE", sed_type) for name in cls.all_quantities(sed_type=sed_type): if name in table.colnames: maps[name] = RegionNDMap.from_table(table=table, colname=name, format=format) meta = cls._get_meta_gadf(table) return cls.from_maps( maps=maps, reference_model=reference_model, meta=meta, sed_type=sed_type, gti=gti, )
def get_prompt(self, grb_id=None, folder=None, subfolder="ctagrbs_spop", debug=False): """ Get the prompt component associated to the afterglow. The prompt spectra are produced so that they correspond to the values provided by Giancarlo (Lorentz Factor, peak energy, photon flux, and redshift). A single set of spectra is given spanning over the T90 GRB duration. The flux is an "average" prompt spectrum over T90. Parameters ---------- grb_id : integer, optional The GRB identifier. The default is None. folder : String, optional Where to find the prompt data, below the 'lightcurves/prompt' folder. The default is None. debug : Boolean, optional If True, let's talk a bit. The default is False. Returns ------- TYPE DESCRIPTION. """ ###---------------------------- ### Check everything goes well ###---------------------------- # Define the prompt data folder from the afterglow parent folder folder = Path(folder, subfolder) if not folder.exists(): sys.exit(" Wrong prompt data folder") if grb_id == None: sys.exit(" Provide a GRB identifier") # if grb_id in [6, 30 ,191]: # failure(" GRB prompt",grb_id," simulated with a Wind profile") # failure(" It cannot be associated to the current afterglow !") # return -1, None ###---------------------------- ### Mask times beyong t90 ###---------------------------- # Find the closest time bin at t90 in the Afterglow # id90 is the index of the time following the t90 value # t90 is therefore between the index id90-1 and id90 self.id90 = np.where(self.tval >= self.t90)[0][0] if self.id90 == 0: warning( "{} : t90 = {} is before the first afterglow time bin".format( self.name, self.t90)) return None # Define a reduction for each time slice, either 0 or 1 except at t90. fraction = (self.t90 - self.tval[self.id90 - 1]) fraction = fraction / (self.tval[self.id90] - self.tval[self.id90 - 1]) self.weight = np.concatenate( (np.ones(self.id90), [fraction], np.zeros(len(self.tval) - self.id90 - 1))) ###---------------------------- ### Read prompt data ###---------------------------- # Open file - read the lines filename = Path(folder, grb_id + "-spec.dat") if not filename.exists(): failure("{} does no exist".format(filename)) return None file = open(filename, "r") lines = file.readlines() # get Gamma and z data = lines[0].split() gamma_prompt = float(data[2]) redshift = float(data[5]) # Consistency check - Zeljka data are rounded at 2 digits if np.abs(self.z - redshift)>0.01 or \ np.abs(self.G0H - gamma_prompt)>0.01: failure( " {:10s}: Afterglow / prompt : z= {:4.2f} / {:4.2f} G0H/gamma= {:5.2f} / {:5.2f} (G0W= {:5.2f})" .format(self.name, self.z, redshift, self.G0H, gamma_prompt, self.G0W)) # Get units - omit "ph" in flux unit data = lines[1].split() unit_e = data[0][1:-1] unit_flx = ''.join([x + " " for x in data[2:]])[1:-2] # Get flux points - assign units to data, use afterglow units data = lines Eval = [] fluxval = [] for line in lines[4:]: data = line.split() Eval.append(float(data[0])) fluxval.append(float(data[1])) self.E_prompt = Eval * u.Unit(unit_e) self.flux_prompt = fluxval * u.Unit(unit_flx) if (unit_e != self.Eval.unit): if debug: warning("Converting energy units from {} to {}".format( unit_e, self.Eval[0].unit)) self.E_prompt = self.E_prompt.to(self.Eval[0].unit) if (unit_flx != self.fluxval[0].unit): if debug: warning("Converting flux units from {} to {}".format( unit_flx, self.fluxval[0][0].unit)) self.flux_prompt = self.flux_prompt.to(self.fluxval[0][0].unit) ###---------------------------- ### Create a list of weighted models for non-zero weights ###---------------------------- models = [] for i in range(self.id90 + 1): flux_w = self.flux_prompt * self.weight[i] models.append( TemplateSpectralModel(energy=self.E_prompt, values=flux_w, interp_kwargs={"values_scale": "log"})) if debug: print("Prompt associated to ", self.name) print("Gamma = ", gamma_prompt, " redshift =", redshift) print(" {:8} : {:10}".format(unit_e, unit_flx)) for E, flux in zip(self.E_prompt, self.flux_prompt): print(" {:8.2f} : {:10.2e} ".format(E.value, flux.value)) islice = 0 print(" {:>8} - {:>5} ".format("Time", "Weight"), end="") for t, w in zip(self.tval, self.weight): print(" {:8.2f} - {:5.2f} ".format(t, w), end="") print("*") if islice == self.id90 else print("") islice += 1 print("Prompt is between: ", self.tval[self.id90 - 1], self.tval[self.id90]) return models
def read_prompt(cls, filename, glow=None, ebl=None, z=0 * u.dimensionless_unscaled, magnify=1): """ This function is for tests using ttime-resolved spectra. Read prompt data from a file and associate it to the afterglow information if requested (or keep the default from the constructor otherwise) Parameters ---------- cls : GammaRayBurst Class intance filename : String DESCRIPTION. glow : boolean, optional If defined, the Prompt characteritics are obatined from the afterglow with same id. The default is None. ebl : string, optional The name of an EBL absoprtion model in the Gammapy data. The default is None. z : float, optional GRB redshift. The default is 0*u.dimensionless_unscaled. magnify : float, optional Flux multiplicative factor for tests. Default is 1 Returns ------- grb : GammaRayBurst A GammaRayBurst instance """ if (glow != None): grb = glow # Copy afterglow parameters #grb.z = 0 # Flux table - Flux at a series of points grb.Eval = [0] * u.GeV grb.tval = [0] * u.s grb.fluxval = [0] * u.Unit("1 / (cm2 GeV s)") grb.spectra = [] # Gammapy models (one per t slice) else: grb = cls() # This calls the constructor hdul = fits.open(filename) grb.name = Path(filename.name).stem # Energies, times and fluxes grb.Eval = Table.read(hdul, hdu=1)["energy"].quantity * u.TeV grb.tval = Table.read(hdul, hdu=2)["time"].quantity * u.s flux = Table.read(hdul, hdu=3) flux_unit = u.Unit("1/(cm2 TeV s)") icol_t = len(flux.colnames) # column number - time jrow_E = len(flux[flux.colnames[0]]) # row number # Note the transposition from flux to fluxval grb.fluxval = np.zeros((icol_t, jrow_E)) * flux_unit for i in range(0, icol_t): for j in range(0, jrow_E): f = flux[j][i] if (f > 1): # print(i,j,f) f = 0 # Correct a bug in event #172 - to be removed grb.fluxval[i][j] = magnify * f * flux_unit # transp! for i, t in enumerate(grb.tval): # Note that TableModel makes an interpolation tab = TemplateSpectralModel(energy=grb.Eval.astype(float), values=grb.fluxval[i], norm=1., interp_kwargs={"values_scale": "log"}) model = cls.EBLabosrbed(tab, ebl) grb.spectra.append(model) hdul.close() return grb
def from_yaml(cls, data, ebl=None, magnify=1): """ Returns ------- A GammaRayBurst instance. """ cls = GammaRayBurst() # This calls the constructor cls.z = data["z"] cls.name = data["name"] cls.radec = SkyCoord(data["ra"], data["dec"], frame='icrs') cls.Eiso = u.Quantity(data["Eiso"]) cls.Epeak = u.Quantity(data['Epeak']) cls.t90 = u.Quantity(data['t90']) cls.G0H = data['G0H'] cls.G0W = data['G0W'] cls.Fluxpeak = u.Quantity(data['Fluxpeak']) cls.gamma_le = data['gamma_le'] cls.gamma_he = data['gamma_he'] cls.t_trig = Time(data["t_trig"], format="datetime", scale="utc") ### Energies, times and fluxes --- Emin = u.Quantity(data["Emin"]) Emax = u.Quantity(data["Emax"]) tmin = u.Quantity(data["tmin"]) tmax = u.Quantity(data["tmax"]) ntbin = data["ntbin"] cls.Eval = np.asarray([Emin.value, Emax.to(Emin.unit).value]) * Emin.unit if ntbin != 1: cls.tval = np.logspace(np.log10(tmin.to(u.s).value), np.log10(tmax.to(u.s).value), ntbin) * u.s else: # A single wtime window cls.tval = np.array([tmin.value, tmax.to(tmin.unit).value]) * tmin.unit flux_unit = u.Unit("1/(cm2 GeV s)") cls.fluxval = np.zeros((len(cls.tval), len(cls.Eval))) * flux_unit for i, t in enumerate(cls.tval): for j, E in enumerate(cls.Eval): dnde = (u.Quantity(data["K"]) * (E / data["E0"])**-data["gamma"] * (t / data["t0"])**-data["beta"]) #print(i,j,dnde) cls.fluxval[i][j] = magnify * dnde.to(flux_unit) ### Visibilities --- includes tval span for loc in ["North", "South"]: cls.vis[loc] = Visibility(cls, loc) # Recomputing done in the main # cls.vis[loc].compute(debug=False) ### No prompt component foreseen in this case cls.prompt = False for i, t in enumerate(cls.tval): # Note that TableModel makes an interpolation # Following a question on the Slack gammapy channel on # November 27th, and the answer by Axel Donath: # The foloowing statement later in the code gave an error # (dlist_onoff is a collection of Dataset) # dlist_onoff.write(datapath,prefix="cls",overwrite=True) # gives: # ...\gammapy\modeling\models\spectral.py", line 989, in to_dict # "data": self.energy.data.tolist(), # NotImplementedError: memoryview: unsupported format >f # This error comes from the fact that the energy list as to be # explicitely passed as a float as done below: # cls.Eval.astype(float) # (A Quantity is passed as requested but the underlying numpy # dtype is not supported by energy.data.tolist() tab = TemplateSpectralModel(energy=cls.Eval.astype(float), values=cls.fluxval[i], interp_kwargs={"values_scale": "log"}) model = cls.EBLabsorbed(tab, ebl) cls.spectra.append(model) return cls
def stacked_model(ds, ds_stack=None, first=False, debug=False): """ This function is not finalised and should be checked. It is intended to recompute an effective spectral model from the exisiting already stacked model -from the previous call- and the current model in a dataset list. It also extract the masked dataset of the first dataset in the list (when first = True) Parameters ---------- ds : Dataset The current dataset. ds_stack : Dataset, optional The current stacked dataset if first is False. The default is None. first : Boolean, optional If True, extract the masked dataset - Valid for the first dataset of the list. The default is False. debug : Boolean, optional If True, let's talk a bit. The default is False. Returns ------- model : Skymodel The current stcake model. """ # Get unmasked Reconstructed E bining from current dataset # (but they are all identical) e_axis = ds.background.geom.axes[0] # E reco sampling from the IRF if first: # The reconstructed binning is required to apply the masking # The theoretical spectrum is therefore evaluated on reconstrcuted energies which # assumes that the reconstructed energy is not too different from the true one. # e_axis = dsets[0].aeff.data.axes[0] # E true sampling from the IRF flux_org = ds.models[0].spectral_model(e_axis.center) mask_org = ds.mask_safe.data.flatten() spec = TemplateSpectralModel(energy=e_axis.center, values=flux_org * mask_org, interp_kwargs={"values_scale": "log"}) model = SkyModel(spectral_model=spec, name="Stack" + "-" + ds.name) else: flux_stacked = ds_stack.models[0].spectral_model(e_axis.center) dt_stack = ds_stack.gti.time_sum # Duration on present stack flux_org = ds.models[0].spectral_model(e_axis.center) mask_org = ds.mask_safe.data.flatten() dt = ds.gti.time_sum # Duration on present stack # Create ad-hoc new flux and model dt_new = dt + dt_stack flux_new = (dt_stack.value * flux_stacked + dt.value * flux_org * mask_org) / (dt_stack.value + dt.value) # Create a new SkyModel from the flux template model spec = TemplateSpectralModel(energy=e_axis.center, values=flux_new, interp_kwargs={"values_scale": "log"}) model = SkyModel(spectral_model=spec, name="Stack" + "-" + ds.name) if debug: livetime = ds.gti.time_sum # Duration on present stack print(72 * "-") print(" Current dataset dt={:10.2f} - {} with model {}".format( livetime, ds.name, ds.models[0].name)) print(" On stack : dt=", dt_stack, " F0 = ", flux_stacked[0]) print(" To add : dt=", dt, " F0 = ", flux_org[0]) print(" To stack : dt=", dt_new, " F0 = ", flux_new[0]) print("") return model
# Spectral correction # Corrections to templates can be applied by multiplication with a normalized spectral model, # for example `gammapy.modeling.models.PowerLawNormSpectralModel`. # This operation create a new `gammapy.modeling.models.CompoundSpectralModel` # from gammapy.modeling.models import ( Models, PowerLawNormSpectralModel, SkyModel, TemplateSpectralModel, ) energy_range = [0.1, 1] * u.TeV energy = np.array([1e6, 3e6, 1e7, 3e7]) * u.MeV values = np.array([4.4e-38, 2.0e-38, 8.8e-39, 3.9e-39]) * u.Unit("MeV-1 s-1 cm-2") template = TemplateSpectralModel(energy=energy, values=values) template.plot(energy_range) plt.grid(which="both") new_model = template * PowerLawNormSpectralModel(norm=2, tilt=0) print(new_model) # %% # YAML representation # ------------------- # Here is an example YAML file using the model: model = SkyModel(spectral_model=template, name="template-model") models = Models([model])
def generate_dataset(Eflux, flux, Erange=None, tstart=Time('2000-01-01 02:00:00', scale='utc'), tobs=100 * u.s, irf_file=None, alpha=1 / 5, name=None, fake=True, onoff=True, seed='random-seed', debug=False): """ Generate a dataset from a list of energies and flux points either as a SpectrumDataset or a SpectrumDatasetOnOff Note : - in SpectrumDataset, the backgound counts are assumed precisely know and are not fluctuated. - in SpectrumDatasetOnOff, the background counts (off counts) are fluctuated from the IRF known values. Parameters ---------- Eflux : Quantity Energies at which the flux is given. flux : Quantity Flux corresponding to the given energies. Erange : List, optional The energy boundaries within which the flux is defined, if not over all energies. The default is None. tstart : Time object, optional Start date of the dataset. The default is Time('2000-01-01 02:00:00',scale='utc'). tobs : Quantity, optional Duration of the observation. The default is 100*u.s. irf_file : String, optional The IRf file name. The default is None. alpha : Float, optional The on over off surface ratio for the On-Off analysis. The default is 1/5. name : String, optional The dataset name, also used to name tthe spectrum. The default is None. fake : Boolean, optional If True, the dataset counts are fluctuated. The default is True. onoff : Boolean, optional If True, use SpectrumDatasetOnOff, otherwise SpectrumDataSet. The default is True. seed : String, optional The seed for the randome generator; If an integer will generate the same random series at each run. The default is 'random-seed'. debug: Boolean If True, let's talk a bit. The default is False. Returns ------- ds : Dataset object The dataset. """ random_state = get_random_state(seed) ### Define on region on_pointing = SkyCoord(ra=0 * u.deg, dec=0 * u.deg, frame="icrs") # Observing region on_region = CircleSkyRegion(center=on_pointing, radius=0.5 * u.deg) # Define energy axis (see spectrum analysis notebook) # edges for SpectrumDataset - all dataset should have the same axes # Note that linear spacing is clearly problematic for powerlaw fluxes # Axes can also be defined using MapAxis unit = u.GeV E1v = min(Eflux).to(unit).value E2v = max(Eflux).to(unit).value # ereco = np.logspace(np.log10(1.1*E1v), np.log10(0.9*E2v), 20) * unit # ereco_axis = MapAxis.from_edges(ereco.to("TeV").value, # unit="TeV", # name="energy", # interp="log") ereco_axis = MapAxis.from_energy_bounds(1.1 * E1v * unit, 0.9 * E2v * unit, nbin=4, per_decade=True, name="energy") # etrue = np.logspace(np.log10( E1v), np.log10( E2v), 50) * unit # etrue_axis = MapAxis.from_edges(etrue.to("TeV").value, # unit="TeV", # name="energy_true", # interp="log") etrue_axis = MapAxis.from_energy_bounds(E1v * unit, E2v * unit, nbin=4, per_decade=True, name="energy_true") if (debug): print("Dataset ", name) print("Etrue : ", etrue_axis.edges) print("Ereco : ", ereco_axis.edges) # Load IRF irf = load_cta_irfs(irf_file) spec = TemplateSpectralModel(energy=Eflux, values=flux, interp_kwargs={"values_scale": "log"}) model = SkyModel(spectral_model=spec, name="Spec" + str(name)) obs = Observation.create(obs_id=1, pointing=on_pointing, livetime=tobs, irfs=irf, deadtime_fraction=0, reference_time=tstart) ds_empty = SpectrumDataset.create( e_reco=ereco_axis, # Ereco.edges, e_true=etrue_axis, #Etrue.edges, region=on_region, name=name) maker = SpectrumDatasetMaker(containment_correction=False, selection=["exposure", "background", "edisp"]) ds = maker.run(ds_empty, obs) ds.models = model mask = ds.mask_safe.geom.energy_mask(energy_min=Erange[0], energy_max=Erange[1]) mask = mask & ds.mask_safe.data ds.mask_safe = RegionNDMap(ds.mask_safe.geom, data=mask) ds.fake(random_state=random_state) # Fake is mandatory ? # Transform SpectrumDataset into SpectrumDatasetOnOff if needed if (onoff): ds = SpectrumDatasetOnOff.from_spectrum_dataset(dataset=ds, acceptance=1, acceptance_off=1 / alpha) print("Transformed in ONOFF") if fake: print(" Fluctuations : seed = ", seed) if (onoff): ds.fake(npred_background=ds.npred_background()) else: ds.fake(random_state=random_state) print("ds.energy_range = ", ds.energy_range) return ds
def from_fits(cls, filename, vis=None, prompt=False, ebl=None, n_night=None, Emax=None, t0=Time('1800-01-01T00:00:00', format='isot', scale='utc'), magnify=1, forced_visible=False, dbg=0): """ Read the GRB data from a fits file. Fluxes are given for a series of (t,E) values So far no flux exists beyond the last point. The spectrum is stored as a table, TemplateSpectralModel, that takes as an input a series of flux values as a function of the energy. The model will return values interpolated in log-space with math::`scipy.interpolate.interp1d`, returning zero for energies outside of the limits of the provided energy array. The trigger time, time of the GRB explosion, is given either as an absolute date or as a duration in days since an arbitrary date. In that case, a start date is provided to which the eleased times are added . The original time bins and energy bins can be limited by a maximal number of nights, or a maximal energy (This can be useful to get fast approximate results). The spectra is modifief for an EBL absorption. The afterglow flux can be adjusted by a multiplicatice factor for various tests. If requested, the associated prompt spectrum is read. Note that TemplateSpectralModel makes an interpolation. Following a question on the Slack gammapy channel on November 27th, and the answer by Axel Donath: The following statement later in the code gave an error (dlist_onoff is a collection of Dataset) dlist_onoff.write(datapath,prefix="cls",overwrite=True) gives: ...\gammapy\modeling\models\spectral.py", line 989, in to_dict "data": self.energy.data.tolist(), NotImplementedError: memoryview: unsupported format >f This error comes from the fact that the energy list as to be explicitely passed as a float as done below: cls.Eval.astype(float) (A Quantity is passed as requested but the underlying numpy dtype is not supported by energy.data.tolist()) Parameters ---------- cls : GammaRayBurst class GammaRayBurst class instance filename : Path GRB input file name prompt: Boolean If True, read information from the associated prompt component ebl : STRING, optional The EBL absorption model considered. If ebl is "built-in", uses the absorbed specrum from the data. n_night : Integer, optional Maximum number of nights (from the visibility) duting which the data are considered.The default is 10. This ensure that the data are cut after the very last night window for a visibility with less than 10 nights. Emax : Astropy Quantity, optional Maximal energy considered in the data. The default is None. t0: Astropy Time A start date to be added to the slice elapsed time when no GRB explosion date is given. magnify : float, optional Flux multiplicative factor for tests to the afterglow model. Default is 1 forced_visible: Boolean If True, the GRB is always visible, in particular the obervation is always during night. Default is False. dbg: Integer A debugging level. Default is zero, no debugging messages. Returns ------- A GammaRayBurst instance. """ # Check if file was compressed and/or if it exists # Strangely path.is_file() does not give False but an error if not os.path.exists(filename): # Current file does not exist if filename.suffix == ".gz": # If a gz file, try the non gz file filename = filename.with_suffix("") else: # If this not a gz file, try the gz file filename = filename.with_suffix(filename.suffix + ".gz") if not os.path.exists(filename): # Check that the new file exist sys.exit("grp.py: {} not found".format(filename)) # Open files and get header and data, and keys in header hdul = fits.open(filename) hdr = hdul[0].header keys_0 = list(hdul[0].header.keys()) cls = GammaRayBurst() # Default constructor cls.z = hdr['Z'] # Remove all extensions cls.name = str(filename.name).rstrip(''.join(filename.suffixes)) cls.radec = SkyCoord(hdr['RA'] * u.degree, hdr['DEC'] * u.degree, frame='icrs') cls.Eiso = hdr['EISO'] * u.erg cls.Epeak = hdr['EPEAK'] * u.keV cls.t90 = hdr['Duration'] * u.s cls.G0H = hdr['G0H'] if "G0W" in keys_0: # Not in SHORTFITS cls.G0W = hdr['G0W'] cls.Fluxpeak = hdr['PHFLUX'] * u.Unit("ph.cm-2.s-1") cls.gamma_le = hdr['LOWSP'] cls.gamma_he = hdr['HIGHSP'] # GRB trigger time if "GRBJD" in keys_0: # Not in SHORTFITS cls.t_trig = Time(hdr['GRBJD'] * u.day, format="jd", scale="utc") elif "GRBTIME" in keys_0: # Add start date cls.t_trig = Time(hdr['GRBTIME'] + t0.jd, format="jd", scale="utc") ###-------------------------- ### Time slices - so far common to afterglow and prompt ###-------------------------- tval = QTable.read(hdul["TIMES (AFTERGLOW)"]) # Temporary : in short GRB fits file, unit is omitted # The flux value is given on an interval ("col0", "col1°°. # The default is to consider that the flux is valid at the end of the # interval. if isinstance(tval[0][0], astropy.units.quantity.Quantity): cls.tval = np.array(tval[tval.colnames[0]].value) * tval[0][0].unit else: # In SHORT GRB, the time bin is given, [t1, t2]. cls.tval = np.array(tval["col1"]) * u.s ###-------------------------- ### Visibilities --- requires tval span if computed on the fly ###-------------------------- # Either read the default visibility from the GRB fits file (None) # or recompute it(a keyword has been given and a dictionnary retrieved) # or read it from the specified folder for loc in ["North", "South"]: if vis == None: cls.vis[loc] = cls.vis[loc].from_fits(cls, hdr, hdul, hdu=1, loc=loc) elif isinstance(vis, dict): cls.vis[loc] = Visibility.compute(cls, loc, param=vis, force_vis=forced_visible, debug=bool(dbg > 2)) else: name = Path(vis, cls.name + "_" + loc + "_vis.bin") cls.vis[loc] = Visibility.read(name) ###-------------------------- ### Limit the data to a certain number of nights ###-------------------------- if n_night != None: # Find end of last night to be considered (if a night is found) # Check that there are enough nights available" tmax = 0 # In days for loc in ["North", "South"]: if len(cls.vis[loc].t_twilight[0]) \ and len(cls.vis[loc].t_true[0]): # Limit to exisiting nights n_night = min(n_night, len(cls.vis[loc].t_twilight)) tmax = max( tmax, cls.vis[loc].t_twilight[n_night - 1][1].jd - cls.t_trig.jd) tmax = max(1 * u.h, tmax * u.d) if tmax < cls.tval[-1]: # tval assumed ordered warning(" Data up to {:5.3f} restricted to {:5.3f}".format( cls.tval[-1].to("d"), tmax)) cls.tval = cls.tval[cls.tval <= tmax] # Limit the visibility windows accordingly for loc in ["North", "South"]: if len(cls.vis[loc].t_true[0]): tends = [(t[1] - cls.t_trig).jd for t in cls.vis[loc].t_true] idmax = np.where( np.array(tends) <= tmax.to_value(u.day)) if len(idmax[0]): # At least one element cls.vis[loc].t_true = cls.vis[ loc].t_true[:idmax[0][-1] + 1] else: # No visibility period within n_night cls.vis[loc].vis = False cls.vis[loc].vis_night = False cls.vis[loc].t_true = [[]] ###-------------------------- ### Afterglow Energies - Limited to Emax if defined ###-------------------------- k = "Energies (afterglow)" cls.Eval = Table.read(hdul[k])[k].quantity cls.Eval = np.array(cls.Eval) * cls.Eval[0].unit if Emax != None and Emax <= cls.Eval[-1]: warning(" Data up to {:5.3f} restricted to {:5.3f}".format( cls.Eval[-1], Emax)) cls.Eval = cls.Eval[cls.Eval <= Emax] ###-------------------------- ### Afterglow flux ###-------------------------- # Get flux,possibly already absorbed if (ebl == "in-file"): # Read default absorbed model flux = QTable.read(hdul["EBL-ABS. SPECTRA (AFTERGLOW)"]) else: flux = QTable.read(hdul["SPECTRA (AFTERGLOW)"]) flux_unit = u.Unit(flux.meta["UNITS"]) / u.Unit("ph") # Removes ph # Store the flux. Note the transposition itmax = len(cls.tval) - 1 jEmax = len(cls.Eval) - 1 cls.fluxval = np.zeros((itmax + 1, jEmax + 1)) * flux_unit for i in range(0, itmax + 1): for j in range(0, jEmax + 1): cls.fluxval[i][j] = magnify * flux[j][i] * flux_unit # transp! # Build time series of interpolated spectra - limited to dtmax for i, t in enumerate(cls.tval): glow = TemplateSpectralModel(energy=cls.Eval.astype(float), values=cls.fluxval[i], interp_kwargs={"values_scale": "log"}) cls.spec_afterglow.append(glow) ###-------------------------- ### Prompt - a unique energy spectrum ###-------------------------- # Get the prompt if potentially visible and if requested cls.prompt = False # No prompt component was found if prompt: if cls.vis["North"].vis_prompt or cls.vis["South"].vis_prompt: # Deduce prompt folder from GRB path name folder = Path(filename.absolute().parents[0], "../prompt/") cls.spec_prompt = cls.get_prompt(grb_id=cls.name[5:], folder=folder, debug=bool(dbg)) if cls.spec_prompt != None: cls.prompt = True ###-------------------------- ### Total attenuated spectra ###-------------------------- # Build the total attenuated model for i, t in enumerate(cls.tval): spec_tot = cls.spec_afterglow[i] if cls.prompt: if i <= cls.id90: spec_tot += cls.spec_prompt[i] model = cls.EBLabsorbed(spec_tot, ebl) cls.spectra.append(model) hdul.close() return cls