def test_integrate_energy(): from pyirf.simulations import SimulatedEventsInfo info = SimulatedEventsInfo( n_showers=int(1e6), energy_min=100 * u.GeV, energy_max=10 * u.TeV, max_impact=500 * u.m, spectral_index=-2, viewcone=10 * u.deg, ) # simplest case, no bins outside e_min, e_max energy_bins = np.geomspace(info.energy_min, info.energy_max, 20) assert np.isclose( info.calculate_n_showers_per_energy(energy_bins).sum(), info.n_showers, ) # simple case, e_min and e_max on bin edges energy_bins = np.geomspace(info.energy_min, info.energy_max, 20) energy_bins = np.append(0.9 * info.energy_min, energy_bins) energy_bins = np.append(energy_bins, 1.1 * info.energy_max) events = info.calculate_n_showers_per_energy(energy_bins) assert np.isclose(info.n_showers, events.sum()) assert events[0] == 0 assert events[-1] == 0 # complex case, e_min and e_max inside bins energy_bins = np.geomspace(0.5 * info.energy_min, 2 * info.energy_max, 5) events = info.calculate_n_showers_per_energy(energy_bins) assert np.isclose(info.n_showers, events.sum()) assert events[0] > 0 assert events[-1] > 0
def test_effective_area_per_energy(): from pyirf.irf import effective_area_per_energy from pyirf.simulations import SimulatedEventsInfo true_energy_bins = [0.1, 1.0, 10.0] * u.TeV selected_events = QTable({ "true_energy": np.append(np.full(1000, 0.5), np.full(10, 5)), }) # this should give 100000 events in the first bin and 10000 in the second simulation_info = SimulatedEventsInfo( n_showers=110000, energy_min=true_energy_bins[0], energy_max=true_energy_bins[-1], max_impact=100 / np.sqrt(np.pi) * u.m, # this should give a nice round area spectral_index=-2, viewcone=0 * u.deg, ) area = effective_area_per_energy(selected_events, simulation_info, true_energy_bins) assert area.shape == (len(true_energy_bins) - 1, ) assert area.unit == u.m**2 assert u.allclose(area, [100, 10] * u.m**2)
def read_sim_info(path): key = "/simulation/run_config" log.info(f"Reading sim info for file {path} and key {key}") run_info = read_table(path, key) e_min = np.unique(run_info.columns["energy_range_min"]) assert len(e_min) == 1 e_max = np.unique(run_info.columns["energy_range_max"]) assert len(e_max) == 1 max_impact = np.unique(run_info.columns["max_scatter_range"]) assert len(max_impact) == 1 index = np.unique(run_info.columns["spectral_index"]) assert len(index) == 1 view_cone = np.unique(run_info.columns["max_viewcone_radius"]) assert len(view_cone) == 1 sim_info = SimulatedEventsInfo( n_showers=(run_info.columns["shower_reuse"] * run_info.columns["num_showers"]).sum(), energy_min=u.Quantity(e_min[0], u.TeV), energy_max=u.Quantity(e_max[0], u.TeV), max_impact=u.Quantity(max_impact[0], u.m), spectral_index=index[0], viewcone=u.Quantity(view_cone[0], u.deg), ) return sim_info
def test_integrate_energy_fov_pointlike(): from pyirf.simulations import SimulatedEventsInfo info = SimulatedEventsInfo( n_showers=int(1e6), energy_min=100 * u.GeV, energy_max=10 * u.TeV, max_impact=500 * u.m, spectral_index=-2, viewcone=0 * u.deg, ) fov_bins = [0, 9, 11, 20] * u.deg energy_bins = np.geomspace(info.energy_min, info.energy_max, 20) # make sure we raise an error on invalid input with pytest.raises(ValueError): info.calculate_n_showers_per_energy_and_fov(energy_bins, fov_bins)
def read_mc_dl2_to_QTable(filename): """ Read MC DL2 files from lstchain and convert into pyirf internal format - astropy.table.QTable Parameters ---------- filename: path Returns ------- `astropy.table.QTable`, `pyirf.simulations.SimulatedEventsInfo` """ # mapping name_mapping = { "mc_energy": "true_energy", "mc_alt": "true_alt", "mc_az": "true_az", "mc_alt_tel": "pointing_alt", "mc_az_tel": "pointing_az", "gammaness": "gh_score", } unit_mapping = { "true_energy": u.TeV, "reco_energy": u.TeV, "pointing_alt": u.rad, "pointing_az": u.rad, "true_alt": u.rad, "true_az": u.rad, "reco_alt": u.rad, "reco_az": u.rad, } simu_info = read_simu_info_merged_hdf5(filename) pyirf_simu_info = SimulatedEventsInfo( n_showers=simu_info.num_showers * simu_info.shower_reuse, energy_min=simu_info.energy_range_min, energy_max=simu_info.energy_range_max, max_impact=simu_info.max_scatter_range, spectral_index=simu_info.spectral_index, viewcone=simu_info.max_viewcone_radius, ) events = pd.read_hdf( filename, key=dl2_params_lstcam_key).rename(columns=name_mapping) events = QTable.from_pandas(events) for k, v in unit_mapping.items(): events[k] *= v return events, pyirf_simu_info
def read_file(infile): log.debug(f"Reading {infile}") events = read_h5py(infile, key='events', columns=list(COLUMN_MAP.keys())) sim_runs = read_h5py(infile, key='corsika_runs') events.rename(columns=COLUMN_MAP, inplace=True) n_showers = np.sum(sim_runs.num_showers * sim_runs.shower_reuse) log.debug(f"Number of events from corsika_runs: {n_showers}") sim_info = SimulatedEventsInfo( n_showers=n_showers, energy_min=u.Quantity(sim_runs["energy_range_min"][0], u.TeV), energy_max=u.Quantity(sim_runs["energy_range_max"][0], u.TeV), max_impact=u.Quantity(sim_runs["max_scatter_range"][0], u.m), spectral_index=sim_runs["spectral_index"][0], viewcone=u.Quantity( sim_runs["max_viewcone_radius"][0] - sim_runs["min_viewcone_radius"][0], u.deg), ) return table.QTable.from_pandas(events, units=UNIT_MAP), sim_info
def test_effective_area_energy_fov(): from pyirf.irf import effective_area_per_energy_and_fov from pyirf.simulations import SimulatedEventsInfo true_energy_bins = [0.1, 1.0, 10.0] * u.TeV # choose edges so that half are in each bin in fov fov_offset_bins = [0, np.arccos(0.98), np.arccos(0.96)] * u.rad center_1, center_2 = 0.5 * (fov_offset_bins[:-1] + fov_offset_bins[1:]).to_value(u.deg) selected_events = QTable({ "true_energy": np.concatenate([ np.full(1000, 0.5), np.full(10, 5), np.full(500, 0.5), np.full(5, 5), ]) * u.TeV, "true_source_fov_offset": np.append(np.full(1010, center_1), np.full(505, center_2)) * u.deg, }) # this should give 100000 events in the first bin and 10000 in the second simulation_info = SimulatedEventsInfo( n_showers=110000, energy_min=true_energy_bins[0], energy_max=true_energy_bins[-1], max_impact=100 / np.sqrt(np.pi) * u.m, # this should give a nice round area spectral_index=-2, viewcone=fov_offset_bins[-1], ) area = effective_area_per_energy_and_fov(selected_events, simulation_info, true_energy_bins, fov_offset_bins) assert area.shape == (len(true_energy_bins) - 1, len(fov_offset_bins) - 1) assert area.unit == u.m**2 assert u.allclose(area[:, 0], [200, 20] * u.m**2) assert u.allclose(area[:, 1], [100, 10] * u.m**2)
def test_integrate_energy_fov(): from pyirf.simulations import SimulatedEventsInfo # simple case, max viewcone on bin edge info = SimulatedEventsInfo( n_showers=int(1e6), energy_min=100 * u.GeV, energy_max=10 * u.TeV, max_impact=500 * u.m, spectral_index=-2, viewcone=10 * u.deg, ) fov_bins = [0, 10, 20] * u.deg energy_bins = np.geomspace(info.energy_min, info.energy_max, 20) n_events = info.calculate_n_showers_per_energy_and_fov( energy_bins, fov_bins) assert np.all(n_events[:, 1:] == 0) assert np.isclose(np.sum(n_events), int(1e6)) # viewcone inside of bin info = SimulatedEventsInfo( n_showers=int(1e6), energy_min=100 * u.GeV, energy_max=10 * u.TeV, max_impact=500 * u.m, spectral_index=-2, viewcone=10 * u.deg, ) fov_bins = [0, 9, 11, 20] * u.deg energy_bins = np.geomspace(info.energy_min, info.energy_max, 20) n_events = info.calculate_n_showers_per_energy_and_fov( energy_bins, fov_bins) assert np.all(n_events[:, 1:2] > 0) assert np.all(n_events[:, 2:] == 0) assert np.isclose(np.sum(n_events), int(1e6))
def read_mc_dl2_to_QTable(filename): """ Read MC DL2 files from lstchain and convert into pyirf internal format - astropy.table.QTable Parameters ---------- filename: path Returns ------- `astropy.table.QTable`, `pyirf.simulations.SimulatedEventsInfo` """ # mapping name_mapping = { "mc_energy": "true_energy", "mc_alt": "true_alt", "mc_az": "true_az", "mc_alt_tel": "pointing_alt", "mc_az_tel": "pointing_az", "gammaness": "gh_score", } unit_mapping = { "true_energy": u.TeV, "reco_energy": u.TeV, "pointing_alt": u.rad, "pointing_az": u.rad, "true_alt": u.rad, "true_az": u.rad, "reco_alt": u.rad, "reco_az": u.rad, } # add alpha for source-dependent analysis srcdep_flag = dl2_params_src_dep_lstcam_key in get_dataset_keys(filename) if srcdep_flag: unit_mapping['alpha'] = u.deg simu_info = read_simu_info_merged_hdf5(filename) pyirf_simu_info = SimulatedEventsInfo( n_showers=simu_info.num_showers * simu_info.shower_reuse, energy_min=simu_info.energy_range_min, energy_max=simu_info.energy_range_max, max_impact=simu_info.max_scatter_range, spectral_index=simu_info.spectral_index, viewcone=simu_info.max_viewcone_radius, ) events = pd.read_hdf(filename, key=dl2_params_lstcam_key) if srcdep_flag: events_srcdep = get_srcdep_params(filename, 'on') events = pd.concat([events, events_srcdep], axis=1) events = events.rename(columns=name_mapping) events = QTable.from_pandas(events) for k, v in unit_mapping.items(): events[k] *= v return events, pyirf_simu_info
def read_DL2_pyirf(infile, run_header): """ Read a DL2 HDF5 protopipe file and adapt them to pyirf format. Parameters ---------- infile: str or pathlib.Path Path to the input fits file run_header: dict Dictionary with info about simulated particle informations Returns ------- events: astropy.QTable Astropy Table object containing the reconstructed events information. simulated_events: ``~pyirf.simulations.SimulatedEventsInfo`` """ log = logging.getLogger("pyirf") log.debug(f"Reading {infile}") df = pd.read_hdf(infile, "/reco_events") events = QTable( [ list(df['obs_id']), list(df['event_id']), list(df['true_energy']) * u.TeV, list(df['reco_energy']) * u.TeV, list(df['gammaness']), list(df['NTels_reco']), list(df['reco_alt']) * u.deg, list(df['reco_az']) * u.deg, list(df['true_alt']) * u.deg, list(df['true_az']) * u.deg, list(df['pointing_alt']) * u.deg, list(df['pointing_az']) * u.deg, list(df['success']), ], names=('obs_id', 'event_id', 'true_energy', 'reco_energy', 'gh_score', 'multiplicity', 'reco_alt', 'reco_az', 'true_alt', 'true_az', 'pointing_alt', 'pointing_az', 'success'), ) # Select only DL2 events marked as fully reconstructed mask = events['success'] events = events[mask] n_runs = len(set(events['obs_id'])) log.info(f"Estimated number of runs from obs ids: {n_runs}") n_showers = n_runs * run_header["num_use"] * run_header["num_showers"] log.debug(f"Number of events from n_runs and run header: {n_showers}") sim_info = SimulatedEventsInfo( n_showers=n_showers, energy_min=u.Quantity(run_header["e_min"], u.TeV), energy_max=u.Quantity(run_header["e_max"], u.TeV), max_impact=u.Quantity(run_header["gen_radius"], u.m), spectral_index=run_header["gen_gamma"], viewcone=u.Quantity(run_header["diff_cone"], u.deg), ) return events, sim_info