From 820672ec0be0bc60a984cc21564bcdaf349bbdfe Mon Sep 17 00:00:00 2001 From: Michele Peresano Date: Mon, 20 Jan 2025 15:39:13 +0100 Subject: [PATCH 1/3] Allow LogParabola to be define using natural log --- pyirf/spectral.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyirf/spectral.py b/pyirf/spectral.py index 182657bf..4e4d4533 100644 --- a/pyirf/spectral.py +++ b/pyirf/spectral.py @@ -195,25 +195,32 @@ class LogParabola: :math:`\beta` e_ref: astropy.units.Quantity[energy] :math:`E_\text{ref}` + from_log10: bool + If True, compute the energy ration in the exponent factor + using base 10, else use natural logarithm. """ @u.quantity_input( normalization=[DIFFUSE_FLUX_UNIT, POINT_SOURCE_FLUX_UNIT], e_ref=u.TeV ) - def __init__(self, normalization, a, b, e_ref=1 * u.TeV): + def __init__(self, normalization, a, b, e_ref=1 * u.TeV, from_log10=True): """Create a new LogParabola spectrum""" self.normalization = normalization self.a = a self.b = b self.e_ref = e_ref + self.from_log10 = from_log10 @u.quantity_input(energy=u.TeV) def __call__(self, energy): e = (energy / self.e_ref).to_value(u.one) - return self.normalization * e ** (self.a + self.b * np.log10(e)) + log_factor = np.log10(e) if self.from_log10 else np.log(e) + return self.normalization * e ** (self.a + self.b * log_factor) def __repr__(self): - return f"{self.__class__.__name__}({self.normalization} * (E / {self.e_ref})**({self.a} + {self.b} * log10(E / {self.e_ref}))" + log_factor = "log10" if self.from_log10 else "ln" + str_rep = f"{self.__class__.__name__}({self.normalization} * (E / {self.e_ref})**({self.a} + {self.b} * {log_factor}(E / {self.e_ref}))" + return str_rep class PowerLawWithExponentialGaussian(PowerLaw): From a1029eba73ea05a4a5353dff627dc888226617b4 Mon Sep 17 00:00:00 2001 From: Michele Peresano Date: Mon, 20 Jan 2025 16:07:50 +0100 Subject: [PATCH 2/3] Add unit test for spectral.LogParabola --- pyirf/tests/test_spectral.py | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pyirf/tests/test_spectral.py b/pyirf/tests/test_spectral.py index fcf68712..1c5742ba 100644 --- a/pyirf/tests/test_spectral.py +++ b/pyirf/tests/test_spectral.py @@ -74,6 +74,63 @@ def test_powerlaw(): assert power_law(1 * u.TeV).unit == unit assert power_law(1 * u.GeV).unit == unit +def test_logparabola(): + from pyirf.spectral import LogParabola + + # Test initialization + normalization = 1e-12 * u.Unit("cm-2 s-1 TeV-1") + a = -2.0 + b = 0.1 + e_ref = 1 * u.TeV + lp = LogParabola(normalization, a, b, e_ref, from_log10=True) + + assert lp.normalization == normalization + assert lp.a == a + assert lp.b == b + assert lp.e_ref == e_ref + assert lp.from_log10 is True + + # Test the flux evaluation at a given energy + normalization = 1e-12 * u.Unit("cm-2 s-1 TeV-1") + a = -2.0 + b = 0.1 + e_ref = 1 * u.TeV + lp = LogParabola(normalization, a, b, e_ref, from_log10=True) + + energy = 2 * u.TeV + expected_flux = normalization * (energy / e_ref) ** ( + a + b * np.log10(energy / e_ref) + ) + calculated_flux = lp(energy) + + assert u.isclose( + calculated_flux, expected_flux, atol=1e-20 * u.Unit("cm-2 s-1 TeV-1") + ) + + # Same but for natural log + normalization = 1e-12 * u.Unit("cm-2 s-1 TeV-1") + a = -2.0 + b = 0.1 + e_ref = 1 * u.TeV + lp = LogParabola(normalization, a, b, e_ref, from_log10=False) + + energy = 2 * u.TeV + expected_flux = normalization * (energy / e_ref) ** (a + b * np.log(energy / e_ref)) + calculated_flux = lp(energy) + + assert u.isclose( + calculated_flux, expected_flux, atol=1e-20 * u.Unit("cm-2 s-1 TeV-1") + ) + + # Test string representation + normalization = 1e-12 * u.Unit("cm-2 s-1 TeV-1") + a = -2.0 + b = 0.1 + e_ref = 1 * u.TeV + lp = LogParabola(normalization, a, b, e_ref, from_log10=True) + + expected_repr = "LogParabola(1e-12 1 / (TeV s cm2) * (E / 1.0 TeV)**(-2.0 + 0.1 * log10(E / 1.0 TeV))" + assert repr(lp) == expected_repr def test_powerlaw_from_simulations(): from pyirf.simulations import SimulatedEventsInfo From bffea3dab959989cd0efc151f9cd9dff743dbdd2 Mon Sep 17 00:00:00 2001 From: Michele Peresano Date: Mon, 20 Jan 2025 16:08:41 +0100 Subject: [PATCH 3/3] Add news fragment for PR #295 --- docs/changes/295.api.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/295.api.rst diff --git a/docs/changes/295.api.rst b/docs/changes/295.api.rst new file mode 100644 index 00000000..41746f8b --- /dev/null +++ b/docs/changes/295.api.rst @@ -0,0 +1 @@ +Allow a LogParabola spectral model to be initialized with a logarithm in base 10 or a natural logarithm using the boolean attribute `from_log10`. \ No newline at end of file