Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Effective Area 3D #281

Merged
merged 24 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
14b050e
Add 2 methods to calculate effective area in energy and 2 spacial dim…
Feb 12, 2024
3d80615
Add methods to calculate n_showers in energy and 2 spacial dimensions
Feb 12, 2024
bca9226
Merge branch 'cta-observatory:main' into effective_area_3d
luca-dib Feb 12, 2024
6b6ec8d
Add util for calculating FOV position angle
Feb 13, 2024
52f4e44
Merge branch 'effective_area_3d' of https://github.com/luca-dib/pyirf…
Feb 13, 2024
8056f6e
Fix effective_area_3d and poition angle util
Feb 13, 2024
7e0a1d3
Fix missing comma
Feb 13, 2024
4d5df74
Fix typos causing error when calling calculate_n_showers_3d_*
Feb 15, 2024
34ecab9
Remove redundant argument breaking calculate_n_showers_3d call
Feb 15, 2024
2a6d047
Fix formatting and issues from pull request Effective Area 3D (no. 28…
Feb 15, 2024
8e4da8b
Add functions to transform from sky coordinates to FOV coordinates
Mar 1, 2024
5e170e4
Update calculate_n_showers_3d functions and add tests
Mar 1, 2024
98da953
Fix position angle function, add rectangle solid angle function and t…
Mar 1, 2024
f27eef8
Update effective area 3D functions and add corresponding tests
Mar 1, 2024
777c0b1
Fix code review issues
Apr 15, 2024
44018bc
Update function names in __all__
Apr 18, 2024
c80ef58
Fix all changed occurences of gadf coordinate function calls
Apr 18, 2024
643958a
Merge branch 'cta-observatory:main' into effective_area_3d
luca-dib Apr 30, 2024
d74bd18
Change az/alt to lon/lat due to support of both AltAz and ICRS
Apr 30, 2024
554ef57
Merge branch 'effective_area_3d' of https://github.com/luca-dib/pyirf…
Apr 30, 2024
8ff9e70
Add newsfragment
Apr 30, 2024
be14460
Properly handle viewcone_min > 0, add docstring
May 2, 2024
af8a9d1
Add missing condition to all_outside check
May 14, 2024
af76243
Merge branch 'cta-observatory:main' into effective_area_3d
luca-dib May 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pyirf/irf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
effective_area,
effective_area_per_energy_and_fov,
effective_area_per_energy,
effective_area_3d_polar,
effective_area_3d_nominal,
)
from .energy_dispersion import energy_dispersion
from .psf import psf_table
Expand All @@ -11,6 +13,8 @@
"effective_area",
"effective_area_per_energy",
"effective_area_per_energy_and_fov",
"effective_area_3d_polar",
"effective_area_3d_nominal",
"energy_dispersion",
"psf_table",
"background_2d",
Expand Down
68 changes: 68 additions & 0 deletions pyirf/irf/effective_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"effective_area",
"effective_area_per_energy",
"effective_area_per_energy_and_fov",
"effective_area_3d_polar",
"effective_area_3d_nominal",
]


Expand Down Expand Up @@ -85,3 +87,69 @@
)

return effective_area(hist_selected, hist_simulated, area)

def effective_area_3d_polar(
selected_events, simulation_info, energy_bins, fov_offset_bins, fov_position_angle_bins
):
"""
Calculate effective area in bins of true energy, field of view offset, and field of view position angle.

Parameters
----------
selected_events: astropy.table.QTable
DL2 events table, required columns for this function:
- `true_energy`
- `true_source_fov_offset`
- `true_source_fov_position_angle`
simulation_info: pyirf.simulations.SimulatedEventsInfo
The overall statistics of the simulated events
true_energy_bins: astropy.units.Quantity[energy]
The true energy bin edges in which to calculate effective area.
fov_offset_bins: astropy.units.Quantity[angle]
The field of view radial bin edges in which to calculate effective area.
fov_position_angle_bins: astropy.units.Quantity[radian]
The field of view azimuthal bin edges in which to calculate effective area.
"""
area = np.pi * simulation_info.max_impact ** 2

Check warning on line 113 in pyirf/irf/effective_area.py

View check run for this annotation

Codecov / codecov/patch

pyirf/irf/effective_area.py#L113

Added line #L113 was not covered by tests

hist_simulated = simulation_info.calculate_n_showers_3d_polar(energy_bins, fov_offset_bins, fov_position_angle_bins)

Check warning on line 115 in pyirf/irf/effective_area.py

View check run for this annotation

Codecov / codecov/patch

pyirf/irf/effective_area.py#L115

Added line #L115 was not covered by tests

hist_selected,_ = np.histogramdd(

Check warning on line 117 in pyirf/irf/effective_area.py

View check run for this annotation

Codecov / codecov/patch

pyirf/irf/effective_area.py#L117

Added line #L117 was not covered by tests
np.array([selected_events['true_energy'].to_value(u.TeV), selected_events['true_source_fov_offset'].to_value(u.deg), selected_events['true_source_fov_position_angle'].to_value(u.rad)]).T,
bins=(energy_bins.to_value(u.TeV), fov_offset_bins.to_value(u.deg), fov_position_angle_bins.to_value(u.rad)),
)

return effective_area(hist_selected, hist_simulated, area)

Check warning on line 122 in pyirf/irf/effective_area.py

View check run for this annotation

Codecov / codecov/patch

pyirf/irf/effective_area.py#L122

Added line #L122 was not covered by tests

def effective_area_3d_nominal(
selected_events, simulation_info, energy_bins, fov_longitude_bins, fov_latitude_bins
):
"""
Calculate effective area in bins of true energy, field of view longitude, and field of view latitude.

Parameters
----------
selected_events: astropy.table.QTable
DL2 events table, required columns for this function:
- `true_energy`
- `true_source_fov_lon`
- `true_source_fov_lat`
simulation_info: pyirf.simulations.SimulatedEventsInfo
The overall statistics of the simulated events
true_energy_bins: astropy.units.Quantity[energy]
The true energy bin edges in which to calculate effective area.
fov_longitude_bins: astropy.units.Quantity[angle]
The field of view longitude bin edges in which to calculate effective area.
fov_latitude_bins: astropy.units.Quantity[angle]
The field of view latitude bin edges in which to calculate effective area.
"""
area = np.pi * simulation_info.max_impact ** 2

Check warning on line 146 in pyirf/irf/effective_area.py

View check run for this annotation

Codecov / codecov/patch

pyirf/irf/effective_area.py#L146

Added line #L146 was not covered by tests

hist_simulated = simulation_info.calculate_n_showers_3d_nominal(energy_bins, fov_longitude_bins, fov_latitude_bins)

Check warning on line 148 in pyirf/irf/effective_area.py

View check run for this annotation

Codecov / codecov/patch

pyirf/irf/effective_area.py#L148

Added line #L148 was not covered by tests

hist_selected, _ = np.histogramdd(

Check warning on line 150 in pyirf/irf/effective_area.py

View check run for this annotation

Codecov / codecov/patch

pyirf/irf/effective_area.py#L150

Added line #L150 was not covered by tests
np.array([selected_events['true_energy'].to_value(u.TeV), selected_events['true_source_fov_lon'].value, selected_events['true_source_fov_lat'].value]).T,
bins=(energy_bins.to_value(u.TeV), fov_longitude_bins.to_value(u.deg), fov_latitude_bins.to_value(u.deg)),
)

return effective_area(hist_selected, hist_simulated, area)

Check warning on line 155 in pyirf/irf/effective_area.py

View check run for this annotation

Codecov / codecov/patch

pyirf/irf/effective_area.py#L155

Added line #L155 was not covered by tests
76 changes: 76 additions & 0 deletions pyirf/simulations.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import astropy.units as u
import numpy as np
from .utils import cone_solid_angle

__all__ = [
'SimulatedEventsInfo',
Expand Down Expand Up @@ -168,6 +169,81 @@
fov_integral = self.calculate_n_showers_per_fov(fov_bins)
return e_integral[:, np.newaxis] * fov_integral / self.n_showers

@u.quantity_input(energy_bins=u.TeV, fov_offset_bins=u.deg, fov_position_angle_bins=u.rad)
def calculate_n_showers_3d_polar(self, energy_bins, fov_offset_bins, fov_position_angle_bins):
"""
Calculate number of showers that were simulated in the given
energy and 2D fov bins in polar coordinates.

This assumes the events were generated uniformly distributed per solid angle,
and from a powerlaw in energy like CORSIKA simulates events.

Parameters
----------
energy_bins: astropy.units.Quantity[energy]
The energy bin edges for which to calculate the number of simulated showers
fov_offset_bins: astropy.units.Quantity[angle]
The FOV radial bin edges for which to calculate the number of simulated showers
fov_position_angle_bins: astropy.units.Quantity[radian]
The FOV azimuthal bin edges for which to calculate the number of simulated showers


Returns
-------
n_showers: numpy.ndarray(ndim=3)
The expected number of events inside each of the
``energy_bins``, ``fov_offset_bins`` and ``fov_position_angle_bins``.
Dimension (n_energy_bins, n_fov_offset_bins, n_fov_position_angle_bins)
This is a floating point number.
The actual numbers will follow a poissionian distribution around this
expected value.
"""
e_fov_offset_integral = self.calculate_n_showers_per_energy_and_fov(energy_bins, fov_offset_bins)
position_angle_integral = np.ones(len(fov_position_angle_bins) - 1) * self.calculate_n_showers_per_fov(np.linspace(self.viewcone_min, self.viewcone_max, 2)) / (len(fov_position_angle_bins) - 1)

Check warning on line 202 in pyirf/simulations.py

View check run for this annotation

Codecov / codecov/patch

pyirf/simulations.py#L201-L202

Added lines #L201 - L202 were not covered by tests

return e_fov_offset_integral[:,:,np.newaxis] * position_angle_integral / self.n_showers

Check warning on line 204 in pyirf/simulations.py

View check run for this annotation

Codecov / codecov/patch

pyirf/simulations.py#L204

Added line #L204 was not covered by tests

@u.quantity_input(energy_bins=u.TeV, fov_longitude_bins=u.deg, fov_latitude_bins=u.rad)
def calculate_n_showers_3d_nominal(self, energy_bins, fov_longitude_bins, fov_latitude_bins):
"""
Calculate number of showers that were simulated in the given
energy and 2D fov bins in nominal coordinates.

This assumes the events were generated uniformly distributed per solid angle,
and from a powerlaw in energy like CORSIKA simulates events.

Parameters
----------
energy_bins: astropy.units.Quantity[energy]
The energy bin edges for which to calculate the number of simulated showers
fov_longitude_bins: astropy.units.Quantity[angle]
The FOV longitude bin edges for which to calculate the number of simulated showers
fov_latitude_bins: astropy.units.Quantity[angle]
The FOV latitude bin edges for which to calculate the number of simulated showers


Returns
-------
n_showers: numpy.ndarray(ndim=3)
The expected number of events inside each of the
``energy_bins``, ``fov_longitude_bins`` and ``fov_latitude_bins``.
Dimension (n_energy_bins, n_fov_longitude_bins, n_fov_latitude_bins)
This is a floating point number.
The actual numbers will follow a poissionian distribution around this
expected value.
"""
fov_bin_midpoints_lon, fov_bin_midpoints_lat = np.meshgrid((fov_longitude_bins[:-1]+fov_longitude_bins[1:])/2,(fov_latitude_bins[:-1]+fov_latitude_bins[1:])/2)
mask_outside_fov = np.logical_or( np.sqrt( fov_bin_midpoints_lon**2 + fov_bin_midpoints_lat**2 ) > self.viewcone_max, np.sqrt( fov_bin_midpoints_lon**2 + fov_bin_midpoints_lat**2 ) < self.viewcone_min)
A_bins = np.outer(np.diff( np.sin( fov_latitude_bins.to_value(u.rad) ) ), np.diff(fov_longitude_bins.to_value(u.rad))) * u.sr
shower_density = self.n_showers / (cone_solid_angle(self.viewcone_max) - cone_solid_angle(self.viewcone_min))

Check warning on line 238 in pyirf/simulations.py

View check run for this annotation

Codecov / codecov/patch

pyirf/simulations.py#L235-L238

Added lines #L235 - L238 were not covered by tests

fov_integral = shower_density * A_bins
e_integral = self.calculate_n_showers_per_energy(energy_bins)

Check warning on line 241 in pyirf/simulations.py

View check run for this annotation

Codecov / codecov/patch

pyirf/simulations.py#L240-L241

Added lines #L240 - L241 were not covered by tests

fov_integral[mask_outside_fov] = 0

Check warning on line 243 in pyirf/simulations.py

View check run for this annotation

Codecov / codecov/patch

pyirf/simulations.py#L243

Added line #L243 was not covered by tests

return (e_integral[:,np.newaxis,np.newaxis] * fov_integral) / self.n_showers

Check warning on line 245 in pyirf/simulations.py

View check run for this annotation

Codecov / codecov/patch

pyirf/simulations.py#L245

Added line #L245 was not covered by tests

def __repr__(self):
return (
f"{self.__class__.__name__}("
Expand Down
30 changes: 30 additions & 0 deletions pyirf/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np
import astropy.units as u
from astropy.coordinates import angular_separation
from astropy.coordinates import position_angle

from .exceptions import MissingColumns, WrongColumnUnit

Expand All @@ -9,6 +10,7 @@
"is_scalar",
"calculate_theta",
"calculate_source_fov_offset",
"calculate_source_fov_position_angle",
"check_histograms",
"cone_solid_angle",
]
Expand Down Expand Up @@ -88,6 +90,34 @@
return theta.to(u.deg)


def calculate_source_fov_position_angle(events, prefix="true"):
"""Calculate position_angle of true positions relative to pointing positions.

Parameters
----------
events : astropy.QTable
Astropy Table object containing the reconstructed events information.

prefix: str
Column prefix for az / alt, can be used to calculate reco or true
source fov position_angle.

Returns
-------
phi: astropy.units.Quantity
Position angle of the true positions relative to the pointing positions
in the sky.
"""
phi = position_angle(

Check warning on line 111 in pyirf/utils.py

View check run for this annotation

Codecov / codecov/patch

pyirf/utils.py#L111

Added line #L111 was not covered by tests
events["pointing_az"],
events["pointing_alt"],
events[f"{prefix}_az"],
events[f"{prefix}_alt"],
)

return phi.to(u.deg)

Check warning on line 118 in pyirf/utils.py

View check run for this annotation

Codecov / codecov/patch

pyirf/utils.py#L118

Added line #L118 was not covered by tests


def check_histograms(hist1, hist2, key="reco_energy"):
"""
Check if two histogram tables have the same binning
Expand Down
Loading