[1]:
# Remove input cells at runtime (nbsphinx)
import IPython.core.display as d
d.display_html('<script>jQuery(function() {if (jQuery("body.notebook_app").length == 0) { jQuery(".input_area").toggle(); jQuery(".prompt").toggle();}});</script>', raw=True)

Performance poster layout

This notebook produces a nice poster layout about the comparison between pipelines.
It is just a refurbished version of the DL3-level “IRF and sensitivity” notebook.

Latest performance results cannot be shown on this public documentation and are therefore hosted at this RedMine page .

[ ]:
## From the standard library
import os
from pathlib import Path

# From pyirf
import pyirf
from pyirf.binning import bin_center
from pyirf.utils import cone_solid_angle

# From other 3rd-party libraries
from yaml import load, FullLoader
import numpy as np
import astropy.units as u
from astropy.io import fits
from astropy.table import QTable, Table, Column
import uproot

import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter
%matplotlib inline
plt.rcParams['axes.labelsize'] = 15
plt.rcParams['xtick.labelsize'] = 15
plt.rcParams['ytick.labelsize'] = 15
[ ]:
def load_config(name):
    """Load YAML configuration file."""
    try:
        with open(name, "r") as stream:
            cfg = load(stream, Loader=FullLoader)
    except FileNotFoundError as e:
        print(e)
        raise
    return cfg
[ ]:
# EDIT THIS CELL WITH YOUR LOCAL SETUP INFORMATION
parent_dir = "" # path to 'analyses' folder
analysisName = ""
infile = "performance_protopipe_Prod3b_CTANorth_baseline_full_array_Zd20deg_180deg_Time50.00h.fits.gz"
[ ]:
config_performance = load_config(f"{parent_dir}/{analysisName}/configs/performance.yaml")
obs_time = f'{config_performance["analysis"]["obs_time"]["value"]}{config_performance["analysis"]["obs_time"]["unit"]}'
production = infile.split("protopipe_")[1].split("_Time")[0]
protopipe_file = Path(parent_dir, analysisName, "data/DL3", infile)
[ ]:
# EDIT THIS CELL WITH YOUR LOCAL SETUP INFORMATION

parent_dir_aswg = ""

# MARS performance (available here: https://forge.in2p3.fr/projects/step-by-step-reference-mars-analysis/wiki)
indir_CTAMARS = ""
infile_CTAMARS = "SubarrayLaPalma_4L15M_south_IFAE_50hours_20190630.root"
MARS_label = "CTAMARS (2019)"

# ED performance (available here: https://forge.in2p3.fr/projects/cta_analysis-and-simulations/wiki/Prod3b_based_instrument_response_functions)
indir_ED = ""
infile_ED = "CTA-Performance-North-20deg-S-50h_20181203.root"
ED_label = "EventDisplay (2018)"
[ ]:
MARS_performance = uproot.open(Path(parent_dir_aswg, indir_CTAMARS, infile_CTAMARS))
ED_performance = uproot.open(Path(parent_dir_aswg, indir_ED, infile_ED))
[ ]:
# EDIT THIS CELL WITH YOUR LOCAL SETUP INFORMATION
indir_requirements = ''
site = 'North'
obs_time = '50h'
[ ]:
infile_requirements = {"sens" : f'/{site}-{obs_time}.dat',
                       "AngRes" : f'/{site}-{obs_time}-AngRes.dat',
                       "ERes" : f'/{site}-{obs_time}-ERes.dat'}
requirements = {}

for key in infile_requirements.keys():
    requirements[key] = Table.read(indir_requirements + infile_requirements[key], format='ascii')
requirements['sens'].add_column(Column(data=(10**requirements['sens']['col1']), name='ENERGY'))
requirements['sens'].add_column(Column(data=requirements['sens']['col2'], name='SENSITIVITY'))
[ ]:
# First we check if a _plots_ folder exists already.
# If not, we create it.
Path("./plots").mkdir(parents=True, exist_ok=True)
[ ]:
fig = plt.figure(figsize = (20, 10), constrained_layout=True)

gs = fig.add_gridspec(3, 3, figure=fig)

# ==========================================================================================================
#
#                                       SENSITIVITY
#
# ==========================================================================================================

ax1 = fig.add_subplot(gs[0:-1, 0:-1])

# [1:-1] removes under/overflow bins
sensitivity_protopipe = QTable.read(protopipe_file, hdu='SENSITIVITY')[1:-1]

unit = u.Unit('erg cm-2 s-1')

# Add requirements
ax1.plot(requirements['sens']['ENERGY'],
         requirements['sens']['SENSITIVITY'],
         color='black',
         ls='--',
         lw=2,
         label='Requirements'
)

# protopipe
e = sensitivity_protopipe['reco_energy_center']
w = (sensitivity_protopipe['reco_energy_high'] - sensitivity_protopipe['reco_energy_low'])
s_p = (e**2 * sensitivity_protopipe['flux_sensitivity'])
ax1.errorbar(
    e.to_value(u.TeV),
    s_p.to_value(unit),
    xerr=w.to_value(u.TeV) / 2,
    ls='',
    label='protopipe',
    color='DarkOrange'
)

# ED
s_ED, edges = ED_performance["DiffSens"].to_numpy()
yerr = ED_performance["DiffSens"].errors()
bins = 10**edges
x = bin_center(bins)
width = np.diff(bins)
ax1.errorbar(
    x,
    s_ED,
    xerr=width/2,
    yerr=yerr,
    label=ED_label,
    ls='',
    color='DarkGreen'
)

# MARS
s_MARS, edges = MARS_performance["DiffSens"].to_numpy()
yerr = MARS_performance["DiffSens"].errors()
bins = 10**edges
x = bin_center(bins)
width = np.diff(bins)
ax1.errorbar(
    x,
    s_MARS,
    xerr=width/2,
    yerr=yerr,
    label=MARS_label,
    ls='',
    color='DarkBlue'
)

# Style settings
ax1.set_xscale("log")
ax1.set_yscale("log")
ax1.set_ylabel(rf"$(E^2 \cdot \mathrm{{Flux Sensitivity}}) /$ ({unit.to_string('latex')})")
ax1.grid(which="both")
ax1.legend()

# ==========================================================================================================
#
#                                       SENSITIVITY RATIO
#
# ==========================================================================================================



ax2 = fig.add_subplot(gs[2, 0])

ax2.errorbar(
    e.to_value(u.TeV),
    s_p.to_value(unit) / s_ED,
    xerr=w.to_value(u.TeV)/2,
    ls='',
    label = "",
    color='DarkGreen'
)
ax2.errorbar(
    e.to_value(u.TeV),
    s_p.to_value(unit) / s_MARS,
    xerr=w.to_value(u.TeV)/2,
    ls='',
    label = "",
    color='DarkBlue'
)
ax2.axhline(1, color = 'DarkOrange')

ax2.set_xscale('log')
ax2.set_yscale('log')
ax2.set_xlabel("Reconstructed energy [TeV]")
ax2.set_ylabel('Sensitivity ratio')
ax2.grid()
ax2.yaxis.set_major_formatter(ScalarFormatter())

ax2.set_ylim(0.5, 2.0)
ax2.set_yticks([0.5, 2/3, 1, 3/2, 2])
ax2.set_yticks([], minor=True)

# ==========================================================================================================
#
#                                       EFFECTIVE COLLECTION AREA
#
# ==========================================================================================================

ax3 = fig.add_subplot(gs[0, 2])

# protopipe
# uncomment the other strings to see effective areas
# for the different cut levels. Left out here for better
# visibility of the final effective areas.
suffix =''
#'_NO_CUTS'
#'_ONLY_GH'
#'_ONLY_THETA'

area = QTable.read(protopipe_file, hdu='EFFECTIVE_AREA' + suffix)[0]
ax3.errorbar(
    0.5 * (area['ENERG_LO'] + area['ENERG_HI']).to_value(u.TeV)[1:-1],
    area['EFFAREA'].to_value(u.m**2).T[1:-1, 0],
    xerr=0.5 * (area['ENERG_LO'] - area['ENERG_HI']).to_value(u.TeV)[1:-1],
    ls='',
    label='protopipe ' + suffix,
    color='DarkOrange'
)

# ED
y, edges = ED_performance["EffectiveAreaEtrue"].to_numpy()
yerr = ED_performance["EffectiveAreaEtrue"].errors()
x = bin_center(10**edges)
xerr = 0.5 * np.diff(10**edges)
ax3.errorbar(x,
             y,
             xerr=xerr,
             yerr=yerr,
             ls='',
             label=ED_label,
             color='DarkGreen'
            )

# MARS
y, edges = MARS_performance["EffectiveAreaEtrue"].to_numpy()
yerr = MARS_performance["EffectiveAreaEtrue"].errors()
x = bin_center(10**edges)
xerr = 0.5 * np.diff(10**edges)
ax3.errorbar(x,
             y,
             xerr=xerr,
             yerr=yerr,
             ls='',
             label=MARS_label,
             color='DarkBlue'
            )

# Style settings
ax3.set_xscale("log")
ax3.set_yscale("log")
ax3.set_xlabel("True energy [TeV]")
ax3.set_ylabel("Effective area [m²]")
ax3.grid(which="both")

# ==========================================================================================================
#
#                                       ANGULAR RESOLUTION
#
# ==========================================================================================================


ax4 = fig.add_subplot(gs[2, 1])

# protopipe
ang_res = QTable.read(protopipe_file, hdu='ANGULAR_RESOLUTION')[1:-1]

ax4.errorbar(
    0.5 * (ang_res['reco_energy_low'] + ang_res['reco_energy_high']).to_value(u.TeV),
    ang_res['angular_resolution'].to_value(u.deg),
    xerr=0.5 * (ang_res['reco_energy_high'] - ang_res['reco_energy_low']).to_value(u.TeV),
    ls='',
    label='protopipe',
    color='DarkOrange'
)

# ED
y, edges = ED_performance["AngRes"].to_numpy()
yerr = ED_performance["AngRes"].errors()
x = bin_center(10**edges)
xerr = 0.5 * np.diff(10**edges)
ax4.errorbar(x,
             y,
             xerr=xerr,
             yerr=yerr,
             ls='',
             label=ED_label,
             color='DarkGreen')

# MARS
y, edges = MARS_performance["AngRes"].to_numpy()
yerr = MARS_performance["AngRes"].errors()
x = bin_center(10**edges)
xerr = 0.5 * np.diff(10**edges)
ax4.errorbar(x,
             y,
             xerr=xerr,
             yerr=yerr,
             ls='',
             label=MARS_label,
             color='DarkBlue')

# Requirements
ax4.plot(10**requirements['AngRes']['col1'],
         requirements['AngRes']['col2'],
         color='black',
         ls='--',
         lw=2,
         label='Requirements'
)

# Style settings
ax4.set_xscale("log")
ax4.set_yscale("log")
ax4.set_xlabel("Reconstructed energy [TeV]")
ax4.set_ylabel("Angular resolution [deg]")
ax4.grid(which="both")

None # to remove clutter by mpl objects

# ==========================================================================================================
#
#                                       ENERGY RESOLUTION
#
# ==========================================================================================================


ax5 = fig.add_subplot(gs[2, 2])

# protopipe
bias_resolution = QTable.read(protopipe_file, hdu='ENERGY_BIAS_RESOLUTION')[1:-1]
ax5.errorbar(
    0.5 * (bias_resolution['reco_energy_low'] + bias_resolution['reco_energy_high']).to_value(u.TeV),
    bias_resolution['resolution'],
    xerr=0.5 * (bias_resolution['reco_energy_high'] - bias_resolution['reco_energy_low']).to_value(u.TeV),
    ls='',
    label='protopipe',
    color='DarkOrange'
)


# ED
y, edges = ED_performance["ERes"].to_numpy()
yerr = ED_performance["ERes"].errors()
x = bin_center(10**edges)
xerr = np.diff(10**edges) / 2
ax5.errorbar(x,
             y,
             xerr=xerr,
             yerr=yerr,
             ls='',
             label=ED_label,
             color='DarkGreen'
            )

# MARS
y, edges = MARS_performance["ERes"].to_numpy()
yerr = MARS_performance["ERes"].errors()
x = bin_center(10**edges)
xerr = np.diff(10**edges) / 2
ax5.errorbar(x,
             y,
             xerr=xerr,
             yerr=yerr,
             ls='',
             label=MARS_label,
             color='DarkBlue'
            )

# Requirements
ax5.plot(10**requirements['ERes']['col1'],
         requirements['ERes']['col2'],
         color='black',
         ls='--',
         lw=2,
         label='Requirements'
)

# Style settings
ax5.set_xlabel("Reconstructed energy [TeV]")
ax5.set_ylabel("Energy resolution")
ax5.grid(which="both")
ax5.set_xscale('log')

None # to remove clutter by mpl objects

# ==========================================================================================================
#
#                                       BACKGROUND RATE
#
# ==========================================================================================================



ax6 = fig.add_subplot(gs[1, 2])

from pyirf.utils import cone_solid_angle

# protopipe
rad_max = QTable.read(protopipe_file, hdu='RAD_MAX')[0]
bg_rate = QTable.read(protopipe_file, hdu='BACKGROUND')[0]

reco_bins = np.append(bg_rate['ENERG_LO'], bg_rate['ENERG_HI'][-1])

# first fov bin, [0, 1] deg
fov_bin = 0
rate_bin = bg_rate['BKG'].T[:, fov_bin]

# interpolate theta cut for given e reco bin
e_center_bg = 0.5 * (bg_rate['ENERG_LO'] + bg_rate['ENERG_HI'])
e_center_theta = 0.5 * (rad_max['ENERG_LO'] + rad_max['ENERG_HI'])
theta_cut = np.interp(e_center_bg, e_center_theta, rad_max['RAD_MAX'].T[:, 0])

# undo normalization
rate_bin *= cone_solid_angle(theta_cut)
rate_bin *= np.diff(reco_bins)
ax6.errorbar(
    0.5 * (bg_rate['ENERG_LO'] + bg_rate['ENERG_HI']).to_value(u.TeV)[1:-1],
    rate_bin.to_value(1 / u.s)[1:-1],
    xerr=np.diff(reco_bins).to_value(u.TeV)[1:-1] / 2,
    ls='',
    label='protopipe',
    color='DarkOrange'
)

# ED
y, edges = ED_performance["BGRate"].to_numpy()
yerr = ED_performance["BGRate"].errors()
x = bin_center(10**edges)
xerr = np.diff(10**edges) / 2
ax6.errorbar(x,
             y,
             xerr=xerr,
             yerr=yerr,
             ls='',
             label=ED_label,
             color="DarkGreen")


# MARS
y, edges = MARS_performance["BGRate"].to_numpy()
yerr = MARS_performance["BGRate"].errors()
x = bin_center(10**edges)
xerr = np.diff(10**edges) / 2
ax6.errorbar(x,
             y,
             xerr=xerr,
             yerr=yerr,
             ls='',
             label=MARS_label,
             color="DarkBlue")


# Style settings
ax6.set_xscale("log")
ax6.set_xlabel("Reconstructed energy [TeV]")
ax6.set_ylabel("Background rate [s⁻¹ TeV⁻¹] ")
ax6.grid(which="both")
ax6.set_yscale('log')

fig.suptitle(f'{production} - {obs_time}', fontsize=25)
fig.savefig(f"./plots/protopipe_{production}_{obs_time}.png")
None # to remove clutter by mpl objects
[ ]: