def calculate_sensitivities(events, energy_bin_edges, alpha, n_draws=1): SensCalc = SensitivityPointSource( reco_energies={'g': events['g']['MC_Energy'].values * u.TeV, 'p': events['p']['MC_Energy'].values * u.TeV, 'e': events['e']['MC_Energy'].values * u.TeV}, mc_energies={'g': events['g']['MC_Energy'].values * u.TeV, 'p': events['p']['MC_Energy'].values * u.TeV, 'e': events['e']['MC_Energy'].values * u.TeV}, flux_unit=flux_unit) SensCalc.generate_event_weights( n_simulated_events={'g': meta_gammas["n_simulated"], 'p': meta_proton["n_simulated"], 'e': meta_electr["n_simulated"]}, generator_areas={'g': np.pi * (meta_gammas["gen_radius"] * u.m)**2, 'p': np.pi * (meta_proton["gen_radius"] * u.m)**2, 'e': np.pi * (meta_electr["gen_radius"] * u.m)**2}, observation_time=observation_time, spectra={'g': crab_source_rate, 'p': cr_background_rate, 'e': electron_spectrum}, e_min_max={'g': (meta_gammas["e_min"], meta_gammas["e_max"]) * u.TeV, 'p': (meta_proton["e_min"], meta_proton["e_max"]) * u.TeV, 'e': (meta_electr["e_min"], meta_electr["e_max"]) * u.TeV}, extensions={'p': meta_proton["diff_cone"] * u.deg, 'e': meta_electr["diff_cone"] * u.deg}, generator_gamma={'g': meta_gammas["gen_gamma"], 'p': meta_proton["gen_gamma"], 'e': meta_electr["gen_gamma"]}) SensCalc.get_sensitivity( alpha=alpha, n_draws=n_draws, max_background_ratio=.05, sensitivity_energy_bin_edges=sensitivity_energy_bin_edges) return SensCalc
def test_SensitivityPointSource_sensitivity_MC(): np.random.seed(314159) alpha = 1. n_sig = 20 n_bgr = 10 # one bin with the bin-centre at 1 TeV energy_bin_edges = np.array([.1, 10]) * u.TeV energies_sig = np.ones(n_sig) energies_bgr = np.ones(n_bgr) sens = SensitivityPointSource(reco_energies={ 's': energies_sig * u.TeV, 'b': energies_bgr * u.TeV }) # sens.event_weights = {'s': energies_sig, 'b': energies_bgr} sensitivity = sens.get_sensitivity( alpha=alpha, signal_list=("s"), mode="MC", sensitivity_source_flux=crab_source_rate, sensitivity_energy_bin_edges=energy_bin_edges, min_n=0, max_background_ratio=1) # the ratio between sensitivity and reference flux (i.e. from the crab nebula) should # be the scaling factor that needs to be applied to n_sig to produce a 5 sigma result # in the lima formula ratio = sensitivity["Sensitivity"][0] / (crab_source_rate(1 * u.TeV)).value np.testing.assert_allclose( [sigma_lima(ratio * n_sig + alpha * n_bgr, n_bgr, alpha)], [5], atol=0.0066)
def test_SensitivityPointSource_sensitivity_MC(): np.random.seed(314159) alpha = 1. n_sig = 20 n_bgr = 10 # one bin with the bin-centre at 1 TeV energy_bin_edges = np.array([.1, 10]) * u.TeV energies_sig = np.ones(n_sig) energies_bgr = np.ones(n_bgr) sens = SensitivityPointSource(reco_energies={'s': energies_sig * u.TeV, 'b': energies_bgr * u.TeV}) # sens.event_weights = {'s': energies_sig, 'b': energies_bgr} sensitivity = sens.get_sensitivity(alpha=alpha, signal_list=("s"), mode="MC", sensitivity_source_flux=crab_source_rate, sensitivity_energy_bin_edges=energy_bin_edges, min_n=0, max_background_ratio=1) # the ratio between sensitivity and reference flux (i.e. from the crab nebula) should # be the scaling factor that needs to be applied to n_sig to produce a 5 sigma result # in the lima formula ratio = sensitivity["Sensitivity"][0] / (crab_source_rate(1 * u.TeV)).value np.testing.assert_allclose( [sigma_lima(ratio * n_sig + alpha * n_bgr, n_bgr, alpha)], [5], atol=0.0066)
def test_SensitivityPointSource_sensitivity_data(): alpha = 1. n_on = 30 n_off = 10 # one bin with the bin-centre at 1 TeV energy_bin_edges = np.array([.1, 10]) * u.TeV energies_sig = np.ones(n_on) energies_bgr = np.ones(n_off) sens = SensitivityPointSource(reco_energies={ 's': energies_sig * u.TeV, 'b': energies_bgr * u.TeV }) sensitivity = sens.get_sensitivity( alpha=alpha, signal_list=("s"), mode="data", sensitivity_source_flux=crab_source_rate, sensitivity_energy_bin_edges=energy_bin_edges, min_n=0, max_background_ratio=1) # the ratio between sensitivity and reference flux (i.e. from the crab nebula) should # be the scaling factor that needs to be applied to n_sig=n_on-n_off*alpha to produce # a 5 sigma result in the lima formula ratio = sensitivity["Sensitivity"][0] / (crab_source_rate(1 * u.TeV)).value np.testing.assert_allclose([ sigma_lima(ratio * (n_on - n_off * alpha) + n_off * alpha, n_off, alpha) ], [5])
def test_SensitivityPointSource_effective_area(): # areas used in the event generator gen_area_g = np.pi * (1000 * u.m)**2 gen_area_p = np.pi * (2000 * u.m)**2 # energy list of "selected" events energy_sel_gammas = np.logspace(2, 6, 200, False) * u.TeV energy_sel_electr = np.logspace(2, 6, 200, False) * u.TeV energy_sel_proton = np.logspace(2, 6, 400, False) * u.TeV # energy list of "generated" events energy_sim_gammas = np.logspace(2, 6, 400, False) * u.TeV energy_sim_electr = np.logspace(2, 6, 400, False) * u.TeV energy_sim_proton = np.logspace(2, 6, 800, False) * u.TeV # binning for the energy histograms energy_edges = np.logspace(2, 6, 41) * u.TeV # energy histogram for the generated events energy_sim_hist_gamma = np.histogram(energy_sim_gammas, bins=energy_edges)[0] energy_sim_hist_elect = np.histogram(energy_sim_electr, bins=energy_edges)[0] energy_sim_hist_proton = np.histogram(energy_sim_proton, bins=energy_edges)[0] # constructer gets fed with energy lists and desired energy binning sens = SensitivityPointSource(mc_energies={ 'g': energy_sel_gammas, 'p': energy_sel_proton, 'e': energy_sel_electr }, energy_bin_edges={ 'g': energy_edges, 'p': energy_edges, 'e': energy_edges }) # effective areas sens.get_effective_areas(generator_energy_hists={ 'g': energy_sim_hist_gamma, 'p': energy_sim_hist_proton, 'e': energy_sim_hist_elect }, generator_areas={ 'g': gen_area_g, 'p': gen_area_p, 'e': gen_area_g }) # midway result are the effective areas eff_a = sens.effective_areas eff_area_g, eff_area_p, eff_area_e = eff_a['g'], eff_a['p'], eff_a['e'] # "selected" events are half of the "generated" events # so effective areas should be half of generator areas, too np.testing.assert_allclose(eff_area_g.value, gen_area_g.value / 2) np.testing.assert_allclose(eff_area_p.value, gen_area_p.value / 2)
def test_SensitivityPointSource_effective_area(): # areas used in the event generator gen_area_g = np.pi * (1000 * u.m)**2 gen_area_p = np.pi * (2000 * u.m)**2 # energy list of "selected" events energy_sel_gammas = np.logspace(2, 6, 200, False) * u.TeV energy_sel_electr = np.logspace(2, 6, 200, False) * u.TeV energy_sel_proton = np.logspace(2, 6, 400, False) * u.TeV # energy list of "generated" events energy_sim_gammas = np.logspace(2, 6, 400, False) * u.TeV energy_sim_electr = np.logspace(2, 6, 400, False) * u.TeV energy_sim_proton = np.logspace(2, 6, 800, False) * u.TeV # binning for the energy histograms energy_edges = np.logspace(2, 6, 41) * u.TeV # energy histogram for the generated events energy_sim_hist_gamma = np.histogram(energy_sim_gammas, bins=energy_edges)[0] energy_sim_hist_elect = np.histogram(energy_sim_electr, bins=energy_edges)[0] energy_sim_hist_proton = np.histogram(energy_sim_proton, bins=energy_edges)[0] # constructer gets fed with energy lists and desired energy binning sens = SensitivityPointSource( mc_energies={'g': energy_sel_gammas, 'p': energy_sel_proton, 'e': energy_sel_electr}, energy_bin_edges={'g': energy_edges, 'p': energy_edges, 'e': energy_edges}) # effective areas sens.get_effective_areas( generator_energy_hists={'g': energy_sim_hist_gamma, 'p': energy_sim_hist_proton, 'e': energy_sim_hist_elect}, generator_areas={'g': gen_area_g, 'p': gen_area_p, 'e': gen_area_g}) # midway result are the effective areas eff_a = sens.effective_areas eff_area_g, eff_area_p, eff_area_e = eff_a['g'], eff_a['p'], eff_a['e'] # "selected" events are half of the "generated" events # so effective areas should be half of generator areas, too np.testing.assert_allclose(eff_area_g.value, gen_area_g.value / 2) np.testing.assert_allclose(eff_area_p.value, gen_area_p.value / 2)
def test_draw_events_from_flux_histogram(): np.random.seed(2) e_min = 20 e_max = 50 n_bins = 30 n_draws = 1000 energy_edges = np.linspace(e_min, e_max, n_bins + 1, True) * u.TeV energy = np.random.uniform(e_min, e_max, 50000) * u.TeV target_distribution = make_mock_event_rate(lambda e: (e / u.TeV)**-3, energy_edges, norm=n_draws, log_e=False) indices = SensitivityPointSource.draw_events_from_flux_histogram( {'g': energy}, {'g': target_distribution}, {'g': energy_edges}) hist, _ = np.histogram(energy[indices['g']], bins=energy_edges[::]) # checking the χ² between `target_distribution` and the drawn one chisquare = scipy.stats.chisquare(target_distribution, hist)[0] # the test that the reduced χ² is close to 1 (tollorance of 1) np.testing.assert_allclose([chisquare / n_bins], [1], atol=0.1)
def test_draw_events_with_flux_weight(): np.random.seed(1) e_min = 20 e_max = 50 n_draws = 10000 energy = np.random.uniform(e_min, e_max, 50000) gamma_old = 0 # since events are drawn from np.random.uniform gamma_new = 3 # this is a simple approach on how to determine event weights (i.e. probabilities to # get picked in the random draw) to generate a set of events with a different energy # spectrum # # Parameters # ---------- # energy : numpy array # the energies of the MC events # gamma_new : float # the spectral index the drawn set of events is supposed to follow # gamma_old : float # the spectral index the MC events have been generated with # this is zero here but usually 2 in MC generators # # Returns # ------- # weights : numpy array # list of event weights normalised to 1 # to be used as PDF in `np.random.choice` energy_weights = energy**(gamma_old - gamma_new) energy_weights /= np.sum(energy_weights) indices = SensitivityPointSource.draw_events_from_flux_weight( {'g': energy}, {'g': energy_weights}, {'g': n_draws}) assert len(energy[indices['g']]) == n_draws
def calculate_sensitivities(events, energy_bin_edges, alpha): SensCalc = SensitivityPointSource( reco_energies={'g': events['g']['MC_Energy'].values * u.TeV, 'p': events['p']['MC_Energy'].values * u.TeV, 'e': events['e']['MC_Energy'].values * u.TeV}, mc_energies={'g': events['g']['MC_Energy'].values * u.TeV, 'p': events['p']['MC_Energy'].values * u.TeV, 'e': events['e']['MC_Energy'].values * u.TeV}, flux_unit=flux_unit) SensCalc.generate_event_weights( n_simulated_events={'g': meta_gammas["n_simulated"], 'p': meta_proton["n_simulated"], 'e': meta_electr["n_simulated"]}, generator_areas={'g': np.pi * (meta_gammas["gen_radius"] * u.m)**2, 'p': np.pi * (meta_proton["gen_radius"] * u.m)**2, 'e': np.pi * (meta_electr["gen_radius"] * u.m)**2}, observation_time=observation_time, spectra={'g': crab_source_rate, 'p': cr_background_rate, 'e': electron_spectrum}, e_min_max={'g': (meta_gammas["e_min"], meta_gammas["e_max"]) * u.TeV, 'p': (meta_proton["e_min"], meta_proton["e_max"]) * u.TeV, 'e': (meta_electr["e_min"], meta_electr["e_max"]) * u.TeV}, extensions={'p': meta_proton["diff_cone"] * u.deg, 'e': meta_electr["diff_cone"] * u.deg}, generator_gamma={'g': meta_gammas["gen_gamma"], 'p': meta_proton["gen_gamma"], 'e': meta_electr["gen_gamma"]}) SensCalc.get_sensitivity( alpha=alpha, n_draws=1, max_background_ratio=.05, sensitivity_energy_bin_edges=sensitivity_energy_bin_edges) return SensCalc
def make_performance_plots(events_w, events_t, which=None): if not which or "theta_square" in which: fig = plt.figure() for channels in [events_w]: for events in channels: plt.hist(events["off_angle"]**2, weights=events["event_weights"], bins=np.linspace(0, 3, 10)) plt.xlabel("off_angle**2 / degree**2") plt.show() if (not which or "multiplicity" in which) and False: fig = plt.figure() plt.hist(events_w['g']["NTels_reco"], alpha=.5, bins=np.arange(0, 51, 2)) plt.hist(events_t['g']["NTels_reco"], alpha=.5, bins=np.arange(0, 51, 2)) if args.write: save_fig(args.plots_dir + "multiplicity") plt.pause(.1) if not which or "multiplicity_by_size" in which: fig, axs = plt.subplots(1, 3, figsize=(10, 5)) plt.sca(axs[0]) plt.hist(events_w['g']["NTels_reco_lst"], alpha=.5, bins=np.arange(0, 5, 1)) plt.hist(events_t['g']["NTels_reco_lst"], alpha=.5, bins=np.arange(0, 5, 1)) plt.suptitle("LST") plt.sca(axs[1]) plt.hist(events_w['g']["NTels_reco_mst"], alpha=.5, bins=np.arange(0, 15, 1)) plt.hist(events_t['g']["NTels_reco_mst"], alpha=.5, bins=np.arange(0, 15, 1)) plt.suptitle("MST") plt.sca(axs[2]) plt.hist(events_w['g']["NTels_reco_sst"], alpha=.5, bins=np.arange(0, 15, 1)) plt.hist(events_t['g']["NTels_reco_sst"], alpha=.5, bins=np.arange(0, 15, 1)) plt.suptitle("SST") if args.write: save_fig(args.plots_dir + "multiplicity_by_size") plt.pause(.1) if (not which or "ang_res_verbose" in which) and False: fig, axes = plt.subplots(1, 2) n_tel_max = 50 # np.max(gammas_w["NTels_reco"]) # plt.subplots_adjust(left=0.11, right=0.97, hspace=0.39, wspace=0.29) plot_hex_and_violin(events_w['g']["NTels_reco"], np.log10(events_w['g']["off_angle"]), np.arange(0, n_tel_max + 1, 5), xlabel=r"$N_\mathrm{Tels}$", ylabel=r"$\log_{10}(\xi / ^\circ)$", do_hex=False, axis=axes[0], extent=[0, n_tel_max, -3, 0]) plot_hex_and_violin(np.log10(events_w['g']["reco_Energy"]), np.log10(events_w['g']["off_angle"]), np.linspace(-1, 3, 17), xlabel=r"$\log_{10}(E_\mathrm{reco}$ / TeV)", ylabel=r"$\log_{10}(\xi / ^\circ)$", v_padding=0.015, axis=axes[1], extent=[-.5, 2.5, -3, 0]) plt.suptitle("wavelet") if args.write: save_fig(args.plots_dir + "ang_res_verbose_wave") plt.pause(.1) fig, axes = plt.subplots(1, 2) n_tel_max = 50 # np.max(gammas_w["NTels_reco"]) # plt.subplots_adjust(left=0.11, right=0.97, hspace=0.39, wspace=0.29) plot_hex_and_violin(events_t['g']["NTels_reco"], np.log10(events_t['g']["off_angle"]), np.arange(0, n_tel_max + 1, 5), xlabel=r"$N_\mathrm{Tels}$", ylabel=r"$\log_{10}(\xi / ^\circ)$", do_hex=False, axis=axes[0], extent=[0, n_tel_max, -3, 0]) plot_hex_and_violin(np.log10(events_t['g']["reco_Energy"]), np.log10(events_t['g']["off_angle"]), np.linspace(-1, 3, 17), xlabel=r"$\log_{10}(E_\mathrm{reco}$ / TeV)", ylabel=r"$\log_{10}(\xi / ^\circ)$", v_padding=0.015, axis=axes[1], extent=[-.5, 2.5, -3, 0]) plt.suptitle("tailcuts") if args.write: save_fig(args.plots_dir + "ang_res_verbose_tail") plt.pause(.1) # angular resolutions if not which or ("ang_res" in which or "xi" in which): plt.figure() for key in events_w: xi_68_w = percentiles(events_w[key]["off_angle"], events_w[key]["reco_Energy"], e_bin_edges.value, 68) xi_68_t = percentiles(events_t[key]["off_angle"], events_t[key]["reco_Energy"], e_bin_edges.value, 68) plt.plot(e_bin_centres.value, xi_68_t, color="darkorange", marker=channel_marker_map[key], ls=channel_linestyle_map[key], label="--".join([channel_map[key], "tail"])) plt.plot(e_bin_centres.value, xi_68_w, color="darkred", marker=channel_marker_map[key], ls=channel_linestyle_map[key], label="--".join([channel_map[key], "wave"])) plt.title("angular resolution") plt.xlabel(r"$E_\mathrm{reco}$ / TeV") plt.ylabel(r"$\xi_\mathrm{68} / ^\circ$") plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.grid() plt.legend() if args.write: save_fig(args.plots_dir + "xi") plt.pause(.1) if not which or "gammaness" in which: # gammaness plots show_gammaness(events_w, "wavelets") show_gammaness(events_t, "tailcuts") if not which or "energy_migration" in which: # MC Energy vs. reco Energy 2D histograms for events, mode in zip([events_w, events_t], ["wavelets", "tailcuts"]): fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True) for i, key in enumerate(events): ax = axs[i] plt.sca(ax) counts, _, _ = np.histogram2d(events[key]["reco_Energy"], events[key]["MC_Energy"], bins=(e_bin_fine_edges, e_bin_fine_edges)) ax.pcolormesh(e_bin_fine_edges.value, e_bin_fine_edges.value, counts.T) plt.plot(e_bin_fine_edges.value[[0, -1]], e_bin_fine_edges.value[[0, -1]], color="darkgreen") plt.title(channel_map[key]) ax.set_xlabel(r"$E_\mathrm{reco}$ / TeV") if i == 0: ax.set_ylabel(r"$E_\mathrm{MC}$ / TeV") ax.set_xscale("log") ax.set_yscale("log") plt.grid() plt.suptitle(mode) plt.subplots_adjust(left=.1, wspace=.1) if args.write: save_fig(args.plots_dir + "_".join(["energy_migration", mode])) plt.pause(.1) if not which or "DeltaE" in which: # (reco Energy - MC Energy) 2D histograms for events, mode in zip([events_w, events_t], ["wavelets", "tailcuts"]): # (reco Energy - MC Energy) vs. reco Energy 2D histograms fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True) for i, key in enumerate(events): ax = axs[i] plt.sca(ax) counts, _, _ = np.histogram2d( events[key]["reco_Energy"], events[key]["reco_Energy"] - events[key]["MC_Energy"], bins=(e_bin_fine_edges, np.linspace(-1, 1, 100))) ax.pcolormesh(e_bin_fine_edges.value, np.linspace(-1, 1, 100), np.sqrt(counts.T)) plt.plot(e_bin_fine_edges.value[[0, -1]], [0, 0], color="darkgreen") plt.title(channel_map[key]) ax.set_xlabel(r"$E_\mathrm{reco}$ / TeV") if i == 0: ax.set_ylabel(r"$(E_\mathrm{reco} - E_\mathrm{MC})$ / TeV") ax.set_xscale("log") plt.grid() plt.suptitle(mode) plt.subplots_adjust(left=.1, wspace=.1) if args.write: save_fig(args.plots_dir + "_".join(["DeltaE_vs_recoE", mode])) plt.pause(.1) # (reco Energy - MC Energy) vs. MC Energy 2D histograms fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True) for i, key in enumerate(events): ax = axs[i] plt.sca(ax) counts, _, _ = np.histogram2d( events[key]["MC_Energy"], events[key]["reco_Energy"] - events[key]["MC_Energy"], bins=(e_bin_fine_edges, np.linspace(-1, 1, 100))) ax.pcolormesh(e_bin_fine_edges.value, np.linspace(-1, 1, 100), np.sqrt(counts.T)) plt.plot(e_bin_fine_edges.value[[0, -1]], [0, 0], color="darkgreen") plt.title(channel_map[key]) ax.set_xlabel(r"$E_\mathrm{MC}$ / TeV") if i == 0: ax.set_ylabel(r"$(E_\mathrm{reco} - E_\mathrm{MC})$ / TeV") ax.set_xscale("log") plt.grid() plt.suptitle(mode) plt.subplots_adjust(left=.1, wspace=.1) if args.write: save_fig(args.plots_dir + "_".join(["DeltaE_vs_MCE", mode])) plt.pause(.1) if not which or "Energy_resolution" in which: # energy resolution as 68th percentile of the relative reconstructed error binned # in reconstructed energy rel_DeltaE_w = np.abs(events_w['g']["reco_Energy"] - events_w['g']["MC_Energy"])/events_w['g']["reco_Energy"] DeltaE68_w_ebinned = percentiles(rel_DeltaE_w, events_w['g']["reco_Energy"], e_bin_edges.value, 68) rel_DeltaE_t = np.abs(events_t['g']["reco_Energy"] - events_t['g']["MC_Energy"])/events_t['g']["reco_Energy"] DeltaE68_t_ebinned = percentiles(rel_DeltaE_t, events_t['g']["reco_Energy"], e_bin_edges.value, 68) plt.figure() plt.plot(e_bin_centres.value, DeltaE68_t_ebinned, label="gamma -- tail", marker='v', color="darkorange") plt.plot(e_bin_centres.value, DeltaE68_w_ebinned, label="gamma -- wave", marker='^', color="darkred") plt.title("Energy Resolution") plt.xlabel(r"$E_\mathrm{reco}$ / TeV") plt.ylabel(r"$(|E_\mathrm{reco} - E_\mathrm{MC}|)_{68}/E_\mathrm{reco}$") plt.gca().set_xscale("log") plt.grid() plt.legend() if args.write: save_fig(args.plots_dir + "Energy_resolution_vs_recoE") plt.pause(.1) # energy resolution binned in MC Energy for key in ['g', 'e']: plt.figure() for events, mode in zip([events_w, events_t], ["wavelets", "tailcuts"]): rel_DeltaE = np.abs(events[key]["reco_Energy"] - events[key]["MC_Energy"]) / events[key]["MC_Energy"] DeltaE68_ebinned = percentiles(rel_DeltaE, events[key]["MC_Energy"], e_bin_edges.value, 68) plt.plot(e_bin_centres.value, DeltaE68_ebinned, label=" -- ".join([channel_map[key], mode]), marker=channel_marker_map[key], color="darkred" if "wave" in mode else "darkorange") plt.title("Energy Resolution") plt.xlabel(r"$E_\mathrm{MC}$ / TeV") plt.ylabel(r"$(|E_\mathrm{reco} - E_\mathrm{MC}|)_{68}/E_\mathrm{MC}$") plt.gca().set_xscale("log") plt.grid() plt.legend() if args.write: save_fig(args.plots_dir + "Energy_resolution_" + channel_map[key]) plt.pause(.1) if not which or "Energy_bias" in which: # Ebias as median of 1-E_reco/E_MC for key in ['g', 'e']: plt.figure() for events, mode in zip([events_w, events_t], ["wavelets", "tailcuts"]): Ebias = 1 - (events[key]["reco_Energy"] / events[key]["MC_Energy"]) Ebias_medians = percentiles(Ebias, events[key]["reco_Energy"], e_bin_edges.value, 50) plt.plot(e_bin_centres.value, Ebias_medians, label=" -- ".join([channel_map[key], mode]), marker=channel_marker_map[key], color="darkred" if "wave" in mode else "darkorange") plt.title("Energy Bias") plt.xlabel(r"$E_\mathrm{reco}$ / TeV") plt.ylabel(r"$(1 - E_\mathrm{reco}/E_\mathrm{MC})_{50}$") plt.ylim([-0.2, .3]) plt.gca().set_xscale("log") plt.legend() plt.grid() if args.write: save_fig(args.plots_dir + "Energy_bias_" + channel_map[key]) plt.pause(.1) if not which or any(what in which for what in ["gen_spectrum", "expected_events", "effective_areas", "event_rate"]): bin_centres, bin_widths = {}, {} bin_centres['g'] = (edges_gammas[:-1] + edges_gammas[1:]) / 2 bin_centres['p'] = (edges_proton[:-1] + edges_proton[1:]) / 2 bin_centres['e'] = (edges_electr[:-1] + edges_electr[1:]) / 2 bin_widths['g'] = np.diff(edges_gammas) bin_widths['p'] = np.diff(edges_proton) bin_widths['e'] = np.diff(edges_electr) for events, mode in zip([events_t, events_w], ["tailcuts", "wavelets"]): SensCalc = SensitivityPointSource( reco_energies={'g': events['g']['reco_Energy'].values * u.TeV, 'p': events['p']['reco_Energy'].values * u.TeV, 'e': events['e']['reco_Energy'].values * u.TeV}, mc_energies={'g': events['g']['MC_Energy'].values * u.TeV, 'p': events['p']['MC_Energy'].values * u.TeV, 'e': events['e']['MC_Energy'].values * u.TeV}, energy_bin_edges={'g': edges_gammas, 'p': edges_proton, 'e': edges_electr}, flux_unit=flux_unit) SensCalc.generate_event_weights( n_simulated_events={'g': meta_gammas["n_simulated"], 'p': meta_proton["n_simulated"], 'e': meta_electr["n_simulated"]}, generator_areas={'g': np.pi * (meta_gammas["gen_radius"] * u.m)**2, 'p': np.pi * (meta_proton["gen_radius"] * u.m)**2, 'e': np.pi * (meta_electr["gen_radius"] * u.m)**2}, observation_time=observation_time, spectra={'g': crab_source_rate, 'p': cr_background_rate, 'e': electron_spectrum}, e_min_max={'g': (meta_gammas["e_min"], meta_gammas["e_max"]) * u.TeV, 'p': (meta_proton["e_min"], meta_proton["e_max"]) * u.TeV, 'e': (meta_electr["e_min"], meta_electr["e_max"]) * u.TeV}, extensions={'p': meta_proton["diff_cone"] * u.deg, 'e': meta_electr["diff_cone"] * u.deg}, generator_gamma={'g': meta_gammas["gen_gamma"], 'p': meta_proton["gen_gamma"], 'e': meta_electr["gen_gamma"]}) SensCalc.get_effective_areas( generator_areas={'g': np.pi * (meta_gammas["gen_radius"] * u.m)**2, 'p': np.pi * (meta_proton["gen_radius"] * u.m)**2, 'e': np.pi * (meta_electr["gen_radius"] * u.m)**2}, n_simulated_events={'g': meta_gammas["n_simulated"], 'p': meta_proton["n_simulated"], 'e': meta_electr["n_simulated"]}, generator_spectra={'g': e_minus_2, 'p': e_minus_2, 'e': e_minus_2}, ) SensCalc.get_expected_events() if not which or "gen_spectrum" in which: # plot MC generator spectrum and selected spectrum fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True) for i, key in enumerate(events): ax = axs[i] plt.sca(ax) plt.plot(bin_centres[key].value, SensCalc.generator_energy_hists[key], label="generated", # align="center", width=bin_widths[key].value ) plt.plot(bin_centres[key].value, SensCalc.selected_events[key], label="selected", # align="center", width=bin_widths[key].value ) plt.xlabel( r"$E_\mathrm{MC} / \mathrm{" + str(bin_centres[key].unit) + "}$") if i == 0: plt.ylabel("number of (unweighted) events") plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.title(channel_map[key]) plt.legend() plt.suptitle(mode) plt.subplots_adjust(left=.1, wspace=.1) if args.write: save_fig(args.plots_dir + "generator_events_" + mode) plt.pause(.1) if not which or "expected_events" in which: # plot the number of expected events in each energy bin plt.figure() for key in ['p', 'e', 'g']: plt.plot( bin_centres[key] / energy_unit, SensCalc.exp_events_per_energy_bin[key], label=channel_map[key], color=channel_color_map[key], # align="center", width=bin_widths[key].value, alpha=.75 ) plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.xlabel(r"$E_\mathrm{MC} / \mathrm{" + str(energy_unit) + "}$") plt.ylabel("expected events in {}".format(observation_time)) plt.legend() if args.write: save_fig(args.plots_dir + "expected_events_" + mode) plt.pause(.1) if not which or "event_rate" in which: # plot the number of expected events in each energy bin plt.figure() for key in ['p', 'e', 'g']: plt.plot( bin_centres[key] / energy_unit, (SensCalc.exp_events_per_energy_bin[key] / observation_time).to(u.s**-1).value * (1 if key == 'g' else alpha), label=channel_map[key], marker=channel_marker_map[key], color=channel_color_map[key], # align="center", width=bin_widths[key].value, alpha=.75 ) plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.xlabel(r"$E_\mathrm{MC} / \mathrm{" + str(energy_unit) + "}$") plt.ylabel(r"event rate: $\frac{dN}{dt} / \mathrm{s}^{-1}$") plt.legend() if args.write: save_fig(args.plots_dir + "event_rate_" + mode) plt.pause(.1) if not which or "effective_areas" in which: # plot effective area plt.figure() # figsize=(16, 8)) plt.suptitle("Effective Areas") for key in ['p', 'e', 'g']: plt.plot( bin_centres[key] / energy_unit, SensCalc.effective_areas[key] / u.m**2, label=channel_map[key], color=channel_color_map[key], marker=channel_marker_map[key]) plt.xlabel(r"$E_\mathrm{MC} / \mathrm{" + str(energy_unit) + "}$") plt.ylabel(r"$A_\mathrm{eff} / \mathrm{m}^2$") plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.title(mode) plt.legend() if args.write: save_fig(args.plots_dir + "effective_areas_" + mode) plt.pause(.1)
def make_performance_plots(events_w, events_t, which=None): if (not which or "multiplicity" in which) and False: fig = plt.figure() plt.hist(events_w['g']["NTels_reco"], alpha=.5, bins=np.arange(0, 51, 2)) plt.hist(events_t['g']["NTels_reco"], alpha=.5, bins=np.arange(0, 51, 2)) if args.write: save_fig(args.plots_dir + "multiplicity") plt.pause(.1) if not which or "multiplicity_by_size" in which: fig, axs = plt.subplots(1, 3, figsize=(10, 5)) plt.sca(axs[0]) plt.hist(events_w['g']["NTels_reco_lst"], alpha=.5, bins=np.arange(0, 5, 1)) plt.hist(events_t['g']["NTels_reco_lst"], alpha=.5, bins=np.arange(0, 5, 1)) plt.suptitle("LST") plt.sca(axs[1]) plt.hist(events_w['g']["NTels_reco_mst"], alpha=.5, bins=np.arange(0, 15, 1)) plt.hist(events_t['g']["NTels_reco_mst"], alpha=.5, bins=np.arange(0, 15, 1)) plt.suptitle("MST") plt.sca(axs[2]) plt.hist(events_w['g']["NTels_reco_sst"], alpha=.5, bins=np.arange(0, 15, 1)) plt.hist(events_t['g']["NTels_reco_sst"], alpha=.5, bins=np.arange(0, 15, 1)) plt.suptitle("SST") if args.write: save_fig(args.plots_dir + "multiplicity_by_size") plt.pause(.1) if (not which or "ang_res_verbose" in which) and False: fig, axes = plt.subplots(1, 2) n_tel_max = 50 # np.max(gammas_w["NTels_reco"]) # plt.subplots_adjust(left=0.11, right=0.97, hspace=0.39, wspace=0.29) plot_hex_and_violin(events_w['g']["NTels_reco"], np.log10(events_w['g']["off_angle"]), np.arange(0, n_tel_max + 1, 5), xlabel=r"$N_\mathrm{Tels}$", ylabel=r"$\log_{10}(\xi / ^\circ)$", do_hex=False, axis=axes[0], extent=[0, n_tel_max, -3, 0]) plot_hex_and_violin(np.log10(events_w['g']["reco_Energy"]), np.log10(events_w['g']["off_angle"]), np.linspace(-1, 3, 17), xlabel=r"$\log_{10}(E_\mathrm{reco}$ / TeV)", ylabel=r"$\log_{10}(\xi / ^\circ)$", v_padding=0.015, axis=axes[1], extent=[-.5, 2.5, -3, 0]) plt.suptitle("wavelet") if args.write: save_fig(args.plots_dir + "ang_res_verbose_wave") plt.pause(.1) fig, axes = plt.subplots(1, 2) n_tel_max = 50 # np.max(gammas_w["NTels_reco"]) # plt.subplots_adjust(left=0.11, right=0.97, hspace=0.39, wspace=0.29) plot_hex_and_violin(events_t['g']["NTels_reco"], np.log10(events_t['g']["off_angle"]), np.arange(0, n_tel_max + 1, 5), xlabel=r"$N_\mathrm{Tels}$", ylabel=r"$\log_{10}(\xi / ^\circ)$", do_hex=False, axis=axes[0], extent=[0, n_tel_max, -3, 0]) plot_hex_and_violin(np.log10(events_t['g']["reco_Energy"]), np.log10(events_t['g']["off_angle"]), np.linspace(-1, 3, 17), xlabel=r"$\log_{10}(E_\mathrm{reco}$ / TeV)", ylabel=r"$\log_{10}(\xi / ^\circ)$", v_padding=0.015, axis=axes[1], extent=[-.5, 2.5, -3, 0]) plt.suptitle("tailcuts") if args.write: save_fig(args.plots_dir + "ang_res_verbose_tail") plt.pause(.1) # angular resolutions if not which or ("ang_res" in which or "xi" in which): plt.figure() for key in events_w: xi_68_w = percentiles(events_w[key]["off_angle"], events_w[key]["reco_Energy"], e_bin_edges.value, 68) xi_68_t = percentiles(events_t[key]["off_angle"], events_t[key]["reco_Energy"], e_bin_edges.value, 68) plt.plot(e_bin_centres.value, xi_68_t, color="darkorange", marker=channel_marker_map[key], ls=channel_linestyle_map[key], label="--".join([channel_map[key], "tail"])) plt.plot(e_bin_centres.value, xi_68_w, color="darkred", marker=channel_marker_map[key], ls=channel_linestyle_map[key], label="--".join([channel_map[key], "wave"])) plt.title("angular resolution") plt.xlabel(r"$E_\mathrm{reco}$ / TeV") plt.ylabel(r"$\xi_\mathrm{68} / ^\circ$") plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.grid() plt.legend() if args.write: save_fig(args.plots_dir + "xi") plt.pause(.1) if not which or "gammaness" in which: # gammaness plots show_gammaness(events_w, "wavelets") show_gammaness(events_t, "tailcuts") if not which or "energy_migration" in which: # MC Energy vs. reco Energy 2D histograms for events, mode in zip([events_w, events_t], ["wavelets", "tailcuts"]): fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True) for i, key in enumerate(events): ax = axs[i] plt.sca(ax) counts, _, _ = np.histogram2d(events[key]["reco_Energy"], events[key]["MC_Energy"], bins=(e_bin_fine_edges, e_bin_fine_edges)) ax.pcolormesh(e_bin_fine_edges.value, e_bin_fine_edges.value, counts.T) plt.plot(e_bin_fine_edges.value[[0, -1]], e_bin_fine_edges.value[[0, -1]], color="darkgreen") plt.title(channel_map[key]) ax.set_xlabel(r"$E_\mathrm{reco}$ / TeV") if i == 0: ax.set_ylabel(r"$E_\mathrm{MC}$ / TeV") ax.set_xscale("log") ax.set_yscale("log") plt.grid() plt.suptitle(mode) plt.subplots_adjust(left=.1, wspace=.1) if args.write: save_fig(args.plots_dir + "_".join(["energy_migration", mode])) plt.pause(.1) if not which or "DeltaE" in which: # (reco Energy - MC Energy) 2D histograms for events, mode in zip([events_w, events_t], ["wavelets", "tailcuts"]): # (reco Energy - MC Energy) vs. reco Energy 2D histograms fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True) for i, key in enumerate(events): ax = axs[i] plt.sca(ax) counts, _, _ = np.histogram2d( events[key]["reco_Energy"], events[key]["reco_Energy"] - events[key]["MC_Energy"], bins=(e_bin_fine_edges, np.linspace(-1, 1, 100))) ax.pcolormesh(e_bin_fine_edges.value, np.linspace(-1, 1, 100), np.sqrt(counts.T)) plt.plot(e_bin_fine_edges.value[[0, -1]], [0, 0], color="darkgreen") plt.title(channel_map[key]) ax.set_xlabel(r"$E_\mathrm{reco}$ / TeV") if i == 0: ax.set_ylabel(r"$(E_\mathrm{reco} - E_\mathrm{MC})$ / TeV") ax.set_xscale("log") plt.grid() plt.suptitle(mode) plt.subplots_adjust(left=.1, wspace=.1) if args.write: save_fig(args.plots_dir + "_".join(["DeltaE_vs_recoE", mode])) plt.pause(.1) # (reco Energy - MC Energy) vs. MC Energy 2D histograms fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True) for i, key in enumerate(events): ax = axs[i] plt.sca(ax) counts, _, _ = np.histogram2d( events[key]["MC_Energy"], events[key]["reco_Energy"] - events[key]["MC_Energy"], bins=(e_bin_fine_edges, np.linspace(-1, 1, 100))) ax.pcolormesh(e_bin_fine_edges.value, np.linspace(-1, 1, 100), np.sqrt(counts.T)) plt.plot(e_bin_fine_edges.value[[0, -1]], [0, 0], color="darkgreen") plt.title(channel_map[key]) ax.set_xlabel(r"$E_\mathrm{MC}$ / TeV") if i == 0: ax.set_ylabel(r"$(E_\mathrm{reco} - E_\mathrm{MC})$ / TeV") ax.set_xscale("log") plt.grid() plt.suptitle(mode) plt.subplots_adjust(left=.1, wspace=.1) if args.write: save_fig(args.plots_dir + "_".join(["DeltaE_vs_MCE", mode])) plt.pause(.1) if not which or "Energy_resolution" in which: # energy resolution as 68th percentile of the relative reconstructed error binned # in reconstructed energy rel_DeltaE_w = np.abs(events_w['g']["reco_Energy"] - events_w['g']["MC_Energy"])/events_w['g']["reco_Energy"] DeltaE68_w_ebinned = percentiles(rel_DeltaE_w, events_w['g']["reco_Energy"], e_bin_edges.value, 68) rel_DeltaE_t = np.abs(events_t['g']["reco_Energy"] - events_t['g']["MC_Energy"])/events_t['g']["reco_Energy"] DeltaE68_t_ebinned = percentiles(rel_DeltaE_t, events_t['g']["reco_Energy"], e_bin_edges.value, 68) plt.figure() plt.plot(e_bin_centres.value, DeltaE68_t_ebinned, label="gamma -- tail", marker='v', color="darkorange") plt.plot(e_bin_centres.value, DeltaE68_w_ebinned, label="gamma -- wave", marker='^', color="darkred") plt.title("Energy Resolution") plt.xlabel(r"$E_\mathrm{reco}$ / TeV") plt.ylabel(r"$(|E_\mathrm{reco} - E_\mathrm{MC}|)_{68}/E_\mathrm{reco}$") plt.gca().set_xscale("log") plt.grid() plt.legend() if args.write: save_fig(args.plots_dir + "Energy_resolution_vs_recoE") plt.pause(.1) # energy resolution binned in MC Energy for key in ['g', 'e']: plt.figure() for events, mode in zip([events_w, events_t], ["wavelets", "tailcuts"]): rel_DeltaE = np.abs(events[key]["reco_Energy"] - events[key]["MC_Energy"]) / events[key]["MC_Energy"] DeltaE68_ebinned = percentiles(rel_DeltaE, events[key]["MC_Energy"], e_bin_edges.value, 68) plt.plot(e_bin_centres.value, DeltaE68_ebinned, label=" -- ".join([channel_map[key], mode]), marker=channel_marker_map[key], color="darkred" if "wave" in mode else "darkorange") plt.title("Energy Resolution") plt.xlabel(r"$E_\mathrm{MC}$ / TeV") plt.ylabel(r"$(|E_\mathrm{reco} - E_\mathrm{MC}|)_{68}/E_\mathrm{MC}$") plt.gca().set_xscale("log") plt.grid() plt.legend() if args.write: save_fig(args.plots_dir + "Energy_resolution_" + channel_map[key]) plt.pause(.1) if not which or "Energy_bias" in which: # Ebias as median of 1-E_reco/E_MC for key in ['g', 'e']: plt.figure() for events, mode in zip([events_w, events_t], ["wavelets", "tailcuts"]): Ebias = 1 - (events[key]["reco_Energy"] / events[key]["MC_Energy"]) Ebias_medians = percentiles(Ebias, events[key]["reco_Energy"], e_bin_edges.value, 50) plt.plot(e_bin_centres.value, Ebias_medians, label=" -- ".join([channel_map[key], mode]), marker=channel_marker_map[key], color="darkred" if "wave" in mode else "darkorange") plt.title("Energy Bias") plt.xlabel(r"$E_\mathrm{reco}$ / TeV") plt.ylabel(r"$(1 - E_\mathrm{reco}/E_\mathrm{MC})_{50}$") plt.ylim([-0.2, .3]) plt.gca().set_xscale("log") plt.legend() plt.grid() if args.write: save_fig(args.plots_dir + "Energy_bias_" + channel_map[key]) plt.pause(.1) if not which or any(what in which for what in ["gen_spectrum", "expected_events", "effective_areas", "event_rate"]): bin_centres, bin_widths = {}, {} bin_centres['g'] = (edges_gammas[:-1] + edges_gammas[1:]) / 2 bin_centres['p'] = (edges_proton[:-1] + edges_proton[1:]) / 2 bin_centres['e'] = (edges_electr[:-1] + edges_electr[1:]) / 2 bin_widths['g'] = np.diff(edges_gammas) bin_widths['p'] = np.diff(edges_proton) bin_widths['e'] = np.diff(edges_electr) # for events, mode in zip([events_t, events_w], ["tailcuts", "wavelets"]): for events, mode in zip([events_w], ["wavelets"]): SensCalc = SensitivityPointSource( reco_energies={'g': events['g']['reco_Energy'].values * u.TeV, 'p': events['p']['reco_Energy'].values * u.TeV, 'e': events['e']['reco_Energy'].values * u.TeV}, mc_energies={'g': events['g']['MC_Energy'].values * u.TeV, 'p': events['p']['MC_Energy'].values * u.TeV, 'e': events['e']['MC_Energy'].values * u.TeV}, energy_bin_edges={'g': edges_gammas, 'p': edges_proton, 'e': edges_electr}, flux_unit=flux_unit) SensCalc.generate_event_weights( n_simulated_events={'g': meta_gammas["n_simulated"], 'p': meta_proton["n_simulated"], 'e': meta_electr["n_simulated"]}, generator_areas={'g': np.pi * (meta_gammas["gen_radius"] * u.m)**2, 'p': np.pi * (meta_proton["gen_radius"] * u.m)**2, 'e': np.pi * (meta_electr["gen_radius"] * u.m)**2}, observation_time=observation_time, spectra={'g': crab_source_rate, 'p': cr_background_rate, 'e': electron_spectrum}, e_min_max={'g': (meta_gammas["e_min"], meta_gammas["e_max"]) * u.TeV, 'p': (meta_proton["e_min"], meta_proton["e_max"]) * u.TeV, 'e': (meta_electr["e_min"], meta_electr["e_max"]) * u.TeV}, extensions={'p': meta_proton["diff_cone"] * u.deg, 'e': meta_electr["diff_cone"] * u.deg}, generator_gamma={'g': meta_gammas["gen_gamma"], 'p': meta_proton["gen_gamma"], 'e': meta_electr["gen_gamma"]}) SensCalc.get_effective_areas( generator_areas={'g': np.pi * (meta_gammas["gen_radius"] * u.m)**2, 'p': np.pi * (meta_proton["gen_radius"] * u.m)**2, 'e': np.pi * (meta_electr["gen_radius"] * u.m)**2}, n_simulated_events={'g': meta_gammas["n_simulated"], 'p': meta_proton["n_simulated"], 'e': meta_electr["n_simulated"]}, generator_spectra={'g': e_minus_2, 'p': e_minus_2, 'e': e_minus_2} ) print("expected events:") for ch in ['g', 'p', 'e']: print(f"{ch}:", np.sum(SensCalc.event_weights[ch])) SensCalc.get_expected_events() SensCalc.get_expected_events_in_reco_e() if not which or "gen_spectrum" in which: # plot MC generator spectrum and selected spectrum fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True) for i, key in enumerate(events): ax = axs[i] plt.sca(ax) plt.plot(bin_centres[key].value, SensCalc.generator_energy_hists[key], label="generated", # align="center", width=bin_widths[key].value ) plt.plot(bin_centres[key].value, SensCalc.selected_events[key], label="selected", # align="center", width=bin_widths[key].value ) plt.xlabel( r"$E_\mathrm{MC} / \mathrm{" + str(bin_centres[key].unit) + "}$") if i == 0: plt.ylabel("number of (unweighted) events") plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.title(channel_map[key]) plt.legend() plt.suptitle(mode) plt.subplots_adjust(left=.1, wspace=.1) if args.write: save_fig(args.plots_dir + "generator_events_" + mode) plt.pause(.1) if not which or "expected_events" in which: # plot the number of expected events in each energy bin plt.figure() for key in ['g', 'p', 'e']: plt.plot( bin_centres[key] / energy_unit, SensCalc.exp_events_per_energy_bin[key], label=channel_map[key], color=channel_color_map[key], # align="center", width=bin_widths[key].value, alpha=.75 ) plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.title(mode) plt.xlabel(r"$E_\mathrm{MC} / \mathrm{" + str(energy_unit) + "}$") plt.ylabel("expected events in {}".format(observation_time)) plt.legend() if args.write: save_fig(args.plots_dir + "expected_events_" + mode) plt.pause(.1) if not which or "event_rate" in which: # plot the number of expected events in each energy bin plt.figure() for key in ['p', 'e', 'g']: plt.plot( bin_centres[key] / energy_unit, (SensCalc.exp_events_per_reco_energy_bin[key] / observation_time).to(u.s**-1).value * (1 if key == 'g' else alpha), label=channel_map[key], marker=channel_marker_map[key], color=channel_color_map[key], # align="center", width=bin_widths[key].value, alpha=.75 ) plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.xlabel(r"$E_\mathrm{MC} / \mathrm{" + str(energy_unit) + "}$") plt.ylabel(r"event rate: $\frac{dN}{dt} / \mathrm{s}^{-1}$") plt.title(mode) plt.legend() if args.write: save_fig(args.plots_dir + "event_rate_" + mode) plt.pause(.1) if not which or "effective_areas" in which: # plot effective area plt.figure() plt.suptitle("Effective Areas") for key in ['g', 'p', 'e']: plt.plot( bin_centres[key] / energy_unit, SensCalc.effective_areas[key] / u.m**2, label=channel_map[key], color=channel_color_map[key], marker=channel_marker_map[key]) plt.xlabel(r"$E_\mathrm{MC} / \mathrm{" + str(energy_unit) + "}$") plt.ylabel(r"$A_\mathrm{eff} / \mathrm{m}^2$") plt.gca().set_xscale("log") plt.gca().set_yscale("log") plt.title(mode) plt.legend() if args.write: save_fig(args.plots_dir + "effective_areas_" + mode) plt.pause(.1)