diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 000000000..781f8c50c --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: bdd2f0394e95d8143d0e021fe6383c5e +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/AUTHORS.html b/AUTHORS.html new file mode 100644 index 000000000..dc8c39d5b --- /dev/null +++ b/AUTHORS.html @@ -0,0 +1,149 @@ + + + + + + + Authors — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Authors

+

To see who contributed to pyirf, please visit the +GitHub contributors page +or run

+
git shortlog -sne
+
+
+

pyirf started as part of protopipe by Julien Lefaucher, +but was largely rewritten in September 2020, making use of code from the +previous version, the pyfact module and the +FACT irf package.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_images/notebooks_fact_example_20_2.png b/_images/notebooks_fact_example_20_2.png new file mode 100644 index 000000000..e6e33d64a Binary files /dev/null and b/_images/notebooks_fact_example_20_2.png differ diff --git a/_images/notebooks_fact_example_25_1.png b/_images/notebooks_fact_example_25_1.png new file mode 100644 index 000000000..6fb4b88dd Binary files /dev/null and b/_images/notebooks_fact_example_25_1.png differ diff --git a/_images/notebooks_fact_example_29_0.png b/_images/notebooks_fact_example_29_0.png new file mode 100644 index 000000000..f7ba6835c Binary files /dev/null and b/_images/notebooks_fact_example_29_0.png differ diff --git a/_modules/astropy/units/core.html b/_modules/astropy/units/core.html new file mode 100644 index 000000000..1dc58c93e --- /dev/null +++ b/_modules/astropy/units/core.html @@ -0,0 +1,2811 @@ + + + + + + astropy.units.core — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for astropy.units.core

+# Licensed under a 3-clause BSD style license - see LICENSE.rst
+
+"""
+Core units classes and functions.
+"""
+
+
+import inspect
+import operator
+import textwrap
+import warnings
+
+import numpy as np
+
+from astropy.utils.decorators import lazyproperty
+from astropy.utils.exceptions import AstropyWarning
+from astropy.utils.misc import isiterable
+
+from . import format as unit_format
+from .utils import (
+    is_effectively_unity,
+    resolve_fractions,
+    sanitize_power,
+    sanitize_scale,
+    validate_power,
+)
+
+__all__ = [
+    "UnitsError",
+    "UnitsWarning",
+    "UnitConversionError",
+    "UnitTypeError",
+    "UnitBase",
+    "NamedUnit",
+    "IrreducibleUnit",
+    "Unit",
+    "CompositeUnit",
+    "PrefixUnit",
+    "UnrecognizedUnit",
+    "def_unit",
+    "get_current_unit_registry",
+    "set_enabled_units",
+    "add_enabled_units",
+    "set_enabled_equivalencies",
+    "add_enabled_equivalencies",
+    "set_enabled_aliases",
+    "add_enabled_aliases",
+    "dimensionless_unscaled",
+    "one",
+]
+
+UNITY = 1.0
+
+
+def _flatten_units_collection(items):
+    """
+    Given a list of sequences, modules or dictionaries of units, or
+    single units, return a flat set of all the units found.
+    """
+    if not isinstance(items, list):
+        items = [items]
+
+    result = set()
+    for item in items:
+        if isinstance(item, UnitBase):
+            result.add(item)
+        else:
+            if isinstance(item, dict):
+                units = item.values()
+            elif inspect.ismodule(item):
+                units = vars(item).values()
+            elif isiterable(item):
+                units = item
+            else:
+                continue
+
+            for unit in units:
+                if isinstance(unit, UnitBase):
+                    result.add(unit)
+
+    return result
+
+
+def _normalize_equivalencies(equivalencies):
+    """Normalizes equivalencies ensuring each is a 4-tuple.
+
+    The resulting tuple is of the form::
+
+        (from_unit, to_unit, forward_func, backward_func)
+
+    Parameters
+    ----------
+    equivalencies : list of equivalency pairs
+
+    Raises
+    ------
+    ValueError if an equivalency cannot be interpreted
+    """
+    if equivalencies is None:
+        return []
+
+    normalized = []
+
+    for i, equiv in enumerate(equivalencies):
+        if len(equiv) == 2:
+            funit, tunit = equiv
+            a = b = lambda x: x
+        elif len(equiv) == 3:
+            funit, tunit, a = equiv
+            b = a
+        elif len(equiv) == 4:
+            funit, tunit, a, b = equiv
+        else:
+            raise ValueError(f"Invalid equivalence entry {i}: {equiv!r}")
+        if not (
+            funit is Unit(funit)
+            and (tunit is None or tunit is Unit(tunit))
+            and callable(a)
+            and callable(b)
+        ):
+            raise ValueError(f"Invalid equivalence entry {i}: {equiv!r}")
+        normalized.append((funit, tunit, a, b))
+
+    return normalized
+
+
+class _UnitRegistry:
+    """
+    Manages a registry of the enabled units.
+    """
+
+    def __init__(self, init=[], equivalencies=[], aliases={}):
+        if isinstance(init, _UnitRegistry):
+            # If passed another registry we don't need to rebuild everything.
+            # but because these are mutable types we don't want to create
+            # conflicts so everything needs to be copied.
+            self._equivalencies = init._equivalencies.copy()
+            self._aliases = init._aliases.copy()
+            self._all_units = init._all_units.copy()
+            self._registry = init._registry.copy()
+            self._non_prefix_units = init._non_prefix_units.copy()
+            # The physical type is a dictionary containing sets as values.
+            # All of these must be copied otherwise we could alter the old
+            # registry.
+            self._by_physical_type = {
+                k: v.copy() for k, v in init._by_physical_type.items()
+            }
+
+        else:
+            self._reset_units()
+            self._reset_equivalencies()
+            self._reset_aliases()
+            self.add_enabled_units(init)
+            self.add_enabled_equivalencies(equivalencies)
+            self.add_enabled_aliases(aliases)
+
+    def _reset_units(self):
+        self._all_units = set()
+        self._non_prefix_units = set()
+        self._registry = {}
+        self._by_physical_type = {}
+
+    def _reset_equivalencies(self):
+        self._equivalencies = set()
+
+    def _reset_aliases(self):
+        self._aliases = {}
+
+    @property
+    def registry(self):
+        return self._registry
+
+    @property
+    def all_units(self):
+        return self._all_units
+
+    @property
+    def non_prefix_units(self):
+        return self._non_prefix_units
+
+    def set_enabled_units(self, units):
+        """
+        Sets the units enabled in the unit registry.
+
+        These units are searched when using
+        `UnitBase.find_equivalent_units`, for example.
+
+        Parameters
+        ----------
+        units : list of sequence, dict, or module
+            This is a list of things in which units may be found
+            (sequences, dicts or modules), or units themselves.  The
+            entire set will be "enabled" for searching through by
+            methods like `UnitBase.find_equivalent_units` and
+            `UnitBase.compose`.
+        """
+        self._reset_units()
+        return self.add_enabled_units(units)
+
+    def add_enabled_units(self, units):
+        """
+        Adds to the set of units enabled in the unit registry.
+
+        These units are searched when using
+        `UnitBase.find_equivalent_units`, for example.
+
+        Parameters
+        ----------
+        units : list of sequence, dict, or module
+            This is a list of things in which units may be found
+            (sequences, dicts or modules), or units themselves.  The
+            entire set will be added to the "enabled" set for
+            searching through by methods like
+            `UnitBase.find_equivalent_units` and `UnitBase.compose`.
+        """
+        units = _flatten_units_collection(units)
+
+        for unit in units:
+            # Loop through all of the names first, to ensure all of them
+            # are new, then add them all as a single "transaction" below.
+            for st in unit._names:
+                if st in self._registry and unit != self._registry[st]:
+                    raise ValueError(
+                        f"Object with name {st!r} already exists in namespace. "
+                        "Filter the set of units to avoid name clashes before "
+                        "enabling them."
+                    )
+
+            for st in unit._names:
+                self._registry[st] = unit
+
+            self._all_units.add(unit)
+            if not isinstance(unit, PrefixUnit):
+                self._non_prefix_units.add(unit)
+
+            hash = unit._get_physical_type_id()
+            self._by_physical_type.setdefault(hash, set()).add(unit)
+
+    def get_units_with_physical_type(self, unit):
+        """
+        Get all units in the registry with the same physical type as
+        the given unit.
+
+        Parameters
+        ----------
+        unit : UnitBase instance
+        """
+        return self._by_physical_type.get(unit._get_physical_type_id(), set())
+
+    @property
+    def equivalencies(self):
+        return list(self._equivalencies)
+
+    def set_enabled_equivalencies(self, equivalencies):
+        """
+        Sets the equivalencies enabled in the unit registry.
+
+        These equivalencies are used if no explicit equivalencies are given,
+        both in unit conversion and in finding equivalent units.
+
+        This is meant in particular for allowing angles to be dimensionless.
+        Use with care.
+
+        Parameters
+        ----------
+        equivalencies : list of tuple
+            List of equivalent pairs, e.g., as returned by
+            `~astropy.units.equivalencies.dimensionless_angles`.
+        """
+        self._reset_equivalencies()
+        return self.add_enabled_equivalencies(equivalencies)
+
+    def add_enabled_equivalencies(self, equivalencies):
+        """
+        Adds to the set of equivalencies enabled in the unit registry.
+
+        These equivalencies are used if no explicit equivalencies are given,
+        both in unit conversion and in finding equivalent units.
+
+        This is meant in particular for allowing angles to be dimensionless.
+        Use with care.
+
+        Parameters
+        ----------
+        equivalencies : list of tuple
+            List of equivalent pairs, e.g., as returned by
+            `~astropy.units.equivalencies.dimensionless_angles`.
+        """
+        # pre-normalize list to help catch mistakes
+        equivalencies = _normalize_equivalencies(equivalencies)
+        self._equivalencies |= set(equivalencies)
+
+    @property
+    def aliases(self):
+        return self._aliases
+
+    def set_enabled_aliases(self, aliases):
+        """
+        Set aliases for units.
+
+        Parameters
+        ----------
+        aliases : dict of str, Unit
+            The aliases to set. The keys must be the string aliases, and values
+            must be the `astropy.units.Unit` that the alias will be mapped to.
+
+        Raises
+        ------
+        ValueError
+            If the alias already defines a different unit.
+
+        """
+        self._reset_aliases()
+        self.add_enabled_aliases(aliases)
+
+    def add_enabled_aliases(self, aliases):
+        """
+        Add aliases for units.
+
+        Parameters
+        ----------
+        aliases : dict of str, Unit
+            The aliases to add. The keys must be the string aliases, and values
+            must be the `astropy.units.Unit` that the alias will be mapped to.
+
+        Raises
+        ------
+        ValueError
+            If the alias already defines a different unit.
+
+        """
+        for alias, unit in aliases.items():
+            if alias in self._registry and unit != self._registry[alias]:
+                raise ValueError(
+                    f"{alias} already means {self._registry[alias]}, so "
+                    f"cannot be used as an alias for {unit}."
+                )
+            if alias in self._aliases and unit != self._aliases[alias]:
+                raise ValueError(
+                    f"{alias} already is an alias for {self._aliases[alias]}, so "
+                    f"cannot be used as an alias for {unit}."
+                )
+
+        for alias, unit in aliases.items():
+            if alias not in self._registry and alias not in self._aliases:
+                self._aliases[alias] = unit
+
+
+class _UnitContext:
+    def __init__(self, init=[], equivalencies=[]):
+        _unit_registries.append(_UnitRegistry(init=init, equivalencies=equivalencies))
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, type, value, tb):
+        _unit_registries.pop()
+
+
+_unit_registries = [_UnitRegistry()]
+
+
+def get_current_unit_registry():
+    return _unit_registries[-1]
+
+
+def set_enabled_units(units):
+    """
+    Sets the units enabled in the unit registry.
+
+    These units are searched when using
+    `UnitBase.find_equivalent_units`, for example.
+
+    This may be used either permanently, or as a context manager using
+    the ``with`` statement (see example below).
+
+    Parameters
+    ----------
+    units : list of sequence, dict, or module
+        This is a list of things in which units may be found
+        (sequences, dicts or modules), or units themselves.  The
+        entire set will be "enabled" for searching through by methods
+        like `UnitBase.find_equivalent_units` and `UnitBase.compose`.
+
+    Examples
+    --------
+    >>> from astropy import units as u
+    >>> with u.set_enabled_units([u.pc]):
+    ...     u.m.find_equivalent_units()
+    ...
+      Primary name | Unit definition | Aliases
+    [
+      pc           | 3.08568e+16 m   | parsec  ,
+    ]
+    >>> u.m.find_equivalent_units()
+      Primary name | Unit definition | Aliases
+    [
+      AU           | 1.49598e+11 m   | au, astronomical_unit            ,
+      Angstrom     | 1e-10 m         | AA, angstrom                     ,
+      cm           | 0.01 m          | centimeter                       ,
+      earthRad     | 6.3781e+06 m    | R_earth, Rearth                  ,
+      jupiterRad   | 7.1492e+07 m    | R_jup, Rjup, R_jupiter, Rjupiter ,
+      lsec         | 2.99792e+08 m   | lightsecond                      ,
+      lyr          | 9.46073e+15 m   | lightyear                        ,
+      m            | irreducible     | meter                            ,
+      micron       | 1e-06 m         |                                  ,
+      pc           | 3.08568e+16 m   | parsec                           ,
+      solRad       | 6.957e+08 m     | R_sun, Rsun                      ,
+    ]
+    """
+    # get a context with a new registry, using equivalencies of the current one
+    context = _UnitContext(equivalencies=get_current_unit_registry().equivalencies)
+    # in this new current registry, enable the units requested
+    get_current_unit_registry().set_enabled_units(units)
+    return context
+
+
+def add_enabled_units(units):
+    """
+    Adds to the set of units enabled in the unit registry.
+
+    These units are searched when using
+    `UnitBase.find_equivalent_units`, for example.
+
+    This may be used either permanently, or as a context manager using
+    the ``with`` statement (see example below).
+
+    Parameters
+    ----------
+    units : list of sequence, dict, or module
+        This is a list of things in which units may be found
+        (sequences, dicts or modules), or units themselves.  The
+        entire set will be added to the "enabled" set for searching
+        through by methods like `UnitBase.find_equivalent_units` and
+        `UnitBase.compose`.
+
+    Examples
+    --------
+    >>> from astropy import units as u
+    >>> from astropy.units import imperial
+    >>> with u.add_enabled_units(imperial):
+    ...     u.m.find_equivalent_units()
+    ...
+      Primary name | Unit definition | Aliases
+    [
+      AU           | 1.49598e+11 m   | au, astronomical_unit            ,
+      Angstrom     | 1e-10 m         | AA, angstrom                     ,
+      cm           | 0.01 m          | centimeter                       ,
+      earthRad     | 6.3781e+06 m    | R_earth, Rearth                  ,
+      ft           | 0.3048 m        | foot                             ,
+      fur          | 201.168 m       | furlong                          ,
+      inch         | 0.0254 m        |                                  ,
+      jupiterRad   | 7.1492e+07 m    | R_jup, Rjup, R_jupiter, Rjupiter ,
+      lsec         | 2.99792e+08 m   | lightsecond                      ,
+      lyr          | 9.46073e+15 m   | lightyear                        ,
+      m            | irreducible     | meter                            ,
+      mi           | 1609.34 m       | mile                             ,
+      micron       | 1e-06 m         |                                  ,
+      mil          | 2.54e-05 m      | thou                             ,
+      nmi          | 1852 m          | nauticalmile, NM                 ,
+      pc           | 3.08568e+16 m   | parsec                           ,
+      solRad       | 6.957e+08 m     | R_sun, Rsun                      ,
+      yd           | 0.9144 m        | yard                             ,
+    ]
+    """
+    # get a context with a new registry, which is a copy of the current one
+    context = _UnitContext(get_current_unit_registry())
+    # in this new current registry, enable the further units requested
+    get_current_unit_registry().add_enabled_units(units)
+    return context
+
+
+def set_enabled_equivalencies(equivalencies):
+    """
+    Sets the equivalencies enabled in the unit registry.
+
+    These equivalencies are used if no explicit equivalencies are given,
+    both in unit conversion and in finding equivalent units.
+
+    This is meant in particular for allowing angles to be dimensionless.
+    Use with care.
+
+    Parameters
+    ----------
+    equivalencies : list of tuple
+        list of equivalent pairs, e.g., as returned by
+        `~astropy.units.equivalencies.dimensionless_angles`.
+
+    Examples
+    --------
+    Exponentiation normally requires dimensionless quantities.  To avoid
+    problems with complex phases::
+
+        >>> from astropy import units as u
+        >>> with u.set_enabled_equivalencies(u.dimensionless_angles()):
+        ...     phase = 0.5 * u.cycle
+        ...     np.exp(1j*phase)  # doctest: +FLOAT_CMP
+        <Quantity -1.+1.2246468e-16j>
+    """
+    # get a context with a new registry, using all units of the current one
+    context = _UnitContext(get_current_unit_registry())
+    # in this new current registry, enable the equivalencies requested
+    get_current_unit_registry().set_enabled_equivalencies(equivalencies)
+    return context
+
+
+def add_enabled_equivalencies(equivalencies):
+    """
+    Adds to the equivalencies enabled in the unit registry.
+
+    These equivalencies are used if no explicit equivalencies are given,
+    both in unit conversion and in finding equivalent units.
+
+    This is meant in particular for allowing angles to be dimensionless.
+    Since no equivalencies are enabled by default, generally it is recommended
+    to use `set_enabled_equivalencies`.
+
+    Parameters
+    ----------
+    equivalencies : list of tuple
+        list of equivalent pairs, e.g., as returned by
+        `~astropy.units.equivalencies.dimensionless_angles`.
+    """
+    # get a context with a new registry, which is a copy of the current one
+    context = _UnitContext(get_current_unit_registry())
+    # in this new current registry, enable the further equivalencies requested
+    get_current_unit_registry().add_enabled_equivalencies(equivalencies)
+    return context
+
+
+def set_enabled_aliases(aliases):
+    """
+    Set aliases for units.
+
+    This is useful for handling alternate spellings for units, or
+    misspelled units in files one is trying to read.
+
+    Parameters
+    ----------
+    aliases : dict of str, Unit
+        The aliases to set. The keys must be the string aliases, and values
+        must be the `astropy.units.Unit` that the alias will be mapped to.
+
+    Raises
+    ------
+    ValueError
+        If the alias already defines a different unit.
+
+    Examples
+    --------
+    To temporarily allow for a misspelled 'Angstroem' unit::
+
+        >>> from astropy import units as u
+        >>> with u.set_enabled_aliases({'Angstroem': u.Angstrom}):
+        ...     print(u.Unit("Angstroem", parse_strict="raise") == u.Angstrom)
+        True
+
+    """
+    # get a context with a new registry, which is a copy of the current one
+    context = _UnitContext(get_current_unit_registry())
+    # in this new current registry, enable the further equivalencies requested
+    get_current_unit_registry().set_enabled_aliases(aliases)
+    return context
+
+
+def add_enabled_aliases(aliases):
+    """
+    Add aliases for units.
+
+    This is useful for handling alternate spellings for units, or
+    misspelled units in files one is trying to read.
+
+    Since no aliases are enabled by default, generally it is recommended
+    to use `set_enabled_aliases`.
+
+    Parameters
+    ----------
+    aliases : dict of str, Unit
+        The aliases to add. The keys must be the string aliases, and values
+        must be the `astropy.units.Unit` that the alias will be mapped to.
+
+    Raises
+    ------
+    ValueError
+        If the alias already defines a different unit.
+
+    Examples
+    --------
+    To temporarily allow for a misspelled 'Angstroem' unit::
+
+        >>> from astropy import units as u
+        >>> with u.add_enabled_aliases({'Angstroem': u.Angstrom}):
+        ...     print(u.Unit("Angstroem", parse_strict="raise") == u.Angstrom)
+        True
+
+    """
+    # get a context with a new registry, which is a copy of the current one
+    context = _UnitContext(get_current_unit_registry())
+    # in this new current registry, enable the further equivalencies requested
+    get_current_unit_registry().add_enabled_aliases(aliases)
+    return context
+
+
+class UnitsError(Exception):
+    """
+    The base class for unit-specific exceptions.
+    """
+
+
+class UnitScaleError(UnitsError, ValueError):
+    """
+    Used to catch the errors involving scaled units,
+    which are not recognized by FITS format.
+    """
+
+    pass
+
+
+class UnitConversionError(UnitsError, ValueError):
+    """
+    Used specifically for errors related to converting between units or
+    interpreting units in terms of other units.
+    """
+
+
+class UnitTypeError(UnitsError, TypeError):
+    """
+    Used specifically for errors in setting to units not allowed by a class.
+
+    E.g., would be raised if the unit of an `~astropy.coordinates.Angle`
+    instances were set to a non-angular unit.
+    """
+
+
+class UnitsWarning(AstropyWarning):
+    """
+    The base class for unit-specific warnings.
+    """
+
+
+class UnitBase:
+    """
+    Abstract base class for units.
+
+    Most of the arithmetic operations on units are defined in this
+    base class.
+
+    Should not be instantiated by users directly.
+    """
+
+    # Make sure that __rmul__ of units gets called over the __mul__ of Numpy
+    # arrays to avoid element-wise multiplication.
+    __array_priority__ = 1000
+
+    _hash = None
+    _type_id = None
+
+    def __deepcopy__(self, memo):
+        # This may look odd, but the units conversion will be very
+        # broken after deep-copying if we don't guarantee that a given
+        # physical unit corresponds to only one instance
+        return self
+
+    def _repr_latex_(self):
+        """
+        Generate latex representation of unit name.  This is used by
+        the IPython notebook to print a unit with a nice layout.
+
+        Returns
+        -------
+        Latex string
+        """
+        return unit_format.Latex.to_string(self)
+
+    def __bytes__(self):
+        """Return string representation for unit."""
+        return unit_format.Generic.to_string(self).encode("unicode_escape")
+
+    def __str__(self):
+        """Return string representation for unit."""
+        return unit_format.Generic.to_string(self)
+
+    def __repr__(self):
+        string = unit_format.Generic.to_string(self)
+
+        return f'Unit("{string}")'
+
+    def _get_physical_type_id(self):
+        """
+        Returns an identifier that uniquely identifies the physical
+        type of this unit.  It is comprised of the bases and powers of
+        this unit, without the scale.  Since it is hashable, it is
+        useful as a dictionary key.
+        """
+        if self._type_id is None:
+            unit = self.decompose()
+            self._type_id = tuple(zip((base.name for base in unit.bases), unit.powers))
+
+        return self._type_id
+
+    @property
+    def names(self):
+        """
+        Returns all of the names associated with this unit.
+        """
+        raise AttributeError(
+            "Can not get names from unnamed units. Perhaps you meant to_string()?"
+        )
+
+    @property
+    def name(self):
+        """
+        Returns the canonical (short) name associated with this unit.
+        """
+        raise AttributeError(
+            "Can not get names from unnamed units. Perhaps you meant to_string()?"
+        )
+
+    @property
+    def aliases(self):
+        """
+        Returns the alias (long) names for this unit.
+        """
+        raise AttributeError(
+            "Can not get aliases from unnamed units. Perhaps you meant to_string()?"
+        )
+
+    @property
+    def scale(self):
+        """
+        Return the scale of the unit.
+        """
+        return 1.0
+
+    @property
+    def bases(self):
+        """
+        Return the bases of the unit.
+        """
+        return [self]
+
+    @property
+    def powers(self):
+        """
+        Return the powers of the unit.
+        """
+        return [1]
+
+    def to_string(self, format=unit_format.Generic, **kwargs):
+        r"""Output the unit in the given format as a string.
+
+        Parameters
+        ----------
+        format : `astropy.units.format.Base` instance or str
+            The name of a format or a formatter object.  If not
+            provided, defaults to the generic format.
+
+        **kwargs
+            Further options forwarded to the formatter. Currently
+            recognized is ``fraction``, which can take the following values:
+
+            - `False` : display unit bases with negative powers as they are;
+            - 'inline' or `True` : use a single-line fraction;
+            - 'multiline' : use a multiline fraction (available for the
+              'latex', 'console' and 'unicode' formats only).
+
+        Raises
+        ------
+        TypeError
+            If ``format`` is of the wrong type.
+        ValueError
+            If ``format`` or ``fraction`` are not recognized.
+
+        Examples
+        --------
+        >>> import astropy.units as u
+        >>> kms = u.Unit('km / s')
+        >>> kms.to_string()  # Generic uses fraction='inline' by default
+        'km / s'
+        >>> kms.to_string('latex')  # Latex uses fraction='multiline' by default
+        '$\\mathrm{\\frac{km}{s}}$'
+        >>> print(kms.to_string('unicode', fraction=False))
+        km s⁻¹
+        >>> print(kms.to_string('unicode', fraction='inline'))
+        km / s
+        >>> print(kms.to_string('unicode', fraction='multiline'))
+        km
+        ──
+        s
+        """
+        f = unit_format.get_format(format)
+        return f.to_string(self, **kwargs)
+
+    def __format__(self, format_spec):
+        """Try to format units using a formatter."""
+        try:
+            return self.to_string(format=format_spec)
+        except ValueError:
+            return format(str(self), format_spec)
+
+    @staticmethod
+    def _normalize_equivalencies(equivalencies):
+        """Normalizes equivalencies, ensuring each is a 4-tuple.
+
+        The resulting tuple is of the form::
+
+            (from_unit, to_unit, forward_func, backward_func)
+
+        Parameters
+        ----------
+        equivalencies : list of equivalency pairs, or None
+
+        Returns
+        -------
+        A normalized list, including possible global defaults set by, e.g.,
+        `set_enabled_equivalencies`, except when `equivalencies`=`None`,
+        in which case the returned list is always empty.
+
+        Raises
+        ------
+        ValueError if an equivalency cannot be interpreted
+        """
+        normalized = _normalize_equivalencies(equivalencies)
+        if equivalencies is not None:
+            normalized += get_current_unit_registry().equivalencies
+
+        return normalized
+
+    def __pow__(self, p):
+        p = validate_power(p)
+        return CompositeUnit(1, [self], [p], _error_check=False)
+
+    def __truediv__(self, m):
+        if isinstance(m, (bytes, str)):
+            m = Unit(m)
+
+        if isinstance(m, UnitBase):
+            if m.is_unity():
+                return self
+            return CompositeUnit(1, [self, m], [1, -1], _error_check=False)
+
+        try:
+            # Cannot handle this as Unit, re-try as Quantity
+            from .quantity import Quantity
+
+            return Quantity(1, self) / m
+        except TypeError:
+            return NotImplemented
+
+    def __rtruediv__(self, m):
+        if isinstance(m, (bytes, str)):
+            return Unit(m) / self
+
+        try:
+            # Cannot handle this as Unit.  Here, m cannot be a Quantity,
+            # so we make it into one, fasttracking when it does not have a
+            # unit, for the common case of <array> / <unit>.
+            from .quantity import Quantity
+
+            if hasattr(m, "unit"):
+                result = Quantity(m)
+                result /= self
+                return result
+            else:
+                return Quantity(m, self ** (-1))
+        except TypeError:
+            return NotImplemented
+
+    def __mul__(self, m):
+        if isinstance(m, (bytes, str)):
+            m = Unit(m)
+
+        if isinstance(m, UnitBase):
+            if m.is_unity():
+                return self
+            elif self.is_unity():
+                return m
+            return CompositeUnit(1, [self, m], [1, 1], _error_check=False)
+
+        # Cannot handle this as Unit, re-try as Quantity.
+        try:
+            from .quantity import Quantity
+
+            return Quantity(1, unit=self) * m
+        except TypeError:
+            return NotImplemented
+
+    def __rmul__(self, m):
+        if isinstance(m, (bytes, str)):
+            return Unit(m) * self
+
+        # Cannot handle this as Unit.  Here, m cannot be a Quantity,
+        # so we make it into one, fasttracking when it does not have a unit
+        # for the common case of <array> * <unit>.
+        try:
+            from .quantity import Quantity
+
+            if hasattr(m, "unit"):
+                result = Quantity(m)
+                result *= self
+                return result
+            else:
+                return Quantity(m, unit=self)
+        except TypeError:
+            return NotImplemented
+
+    def __rlshift__(self, m):
+        try:
+            from .quantity import Quantity
+
+            return Quantity(m, self, copy=False, subok=True)
+        except Exception:
+            return NotImplemented
+
+    def __rrshift__(self, m):
+        warnings.warn(
+            ">> is not implemented. Did you mean to convert "
+            f"to a Quantity with unit {m} using '<<'?",
+            AstropyWarning,
+        )
+        return NotImplemented
+
+    def __hash__(self):
+        if self._hash is None:
+            parts = (
+                [str(self.scale)]
+                + [x.name for x in self.bases]
+                + [str(x) for x in self.powers]
+            )
+            self._hash = hash(tuple(parts))
+        return self._hash
+
+    def __getstate__(self):
+        # If we get pickled, we should *not* store the memoized members since
+        # hashes of strings vary between sessions.
+        state = self.__dict__.copy()
+        state.pop("_hash", None)
+        state.pop("_type_id", None)
+        return state
+
+    def __eq__(self, other):
+        if self is other:
+            return True
+
+        try:
+            other = Unit(other, parse_strict="silent")
+        except (ValueError, UnitsError, TypeError):
+            return NotImplemented
+
+        # Other is unit-like, but the test below requires it is a UnitBase
+        # instance; if it is not, give up (so that other can try).
+        if not isinstance(other, UnitBase):
+            return NotImplemented
+
+        try:
+            return is_effectively_unity(self._to(other))
+        except UnitsError:
+            return False
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __le__(self, other):
+        scale = self._to(Unit(other))
+        return scale <= 1.0 or is_effectively_unity(scale)
+
+    def __ge__(self, other):
+        scale = self._to(Unit(other))
+        return scale >= 1.0 or is_effectively_unity(scale)
+
+    def __lt__(self, other):
+        return not (self >= other)
+
+    def __gt__(self, other):
+        return not (self <= other)
+
+    def __neg__(self):
+        return self * -1.0
+
+    def is_equivalent(self, other, equivalencies=[]):
+        """
+        Returns `True` if this unit is equivalent to ``other``.
+
+        Parameters
+        ----------
+        other : `~astropy.units.Unit`, str, or tuple
+            The unit to convert to. If a tuple of units is specified, this
+            method returns true if the unit matches any of those in the tuple.
+
+        equivalencies : list of tuple
+            A list of equivalence pairs to try if the units are not
+            directly convertible.  See :ref:`astropy:unit_equivalencies`.
+            This list is in addition to possible global defaults set by, e.g.,
+            `set_enabled_equivalencies`.
+            Use `None` to turn off all equivalencies.
+
+        Returns
+        -------
+        bool
+        """
+        equivalencies = self._normalize_equivalencies(equivalencies)
+
+        if isinstance(other, tuple):
+            return any(self.is_equivalent(u, equivalencies) for u in other)
+
+        other = Unit(other, parse_strict="silent")
+
+        return self._is_equivalent(other, equivalencies)
+
+    def _is_equivalent(self, other, equivalencies=[]):
+        """Returns `True` if this unit is equivalent to `other`.
+        See `is_equivalent`, except that a proper Unit object should be
+        given (i.e., no string) and that the equivalency list should be
+        normalized using `_normalize_equivalencies`.
+        """
+        if isinstance(other, UnrecognizedUnit):
+            return False
+
+        if self._get_physical_type_id() == other._get_physical_type_id():
+            return True
+        elif len(equivalencies):
+            unit = self.decompose()
+            other = other.decompose()
+            for a, b, forward, backward in equivalencies:
+                if b is None:
+                    # after canceling, is what's left convertible
+                    # to dimensionless (according to the equivalency)?
+                    try:
+                        (other / unit).decompose([a])
+                        return True
+                    except Exception:
+                        pass
+                elif (a._is_equivalent(unit) and b._is_equivalent(other)) or (
+                    b._is_equivalent(unit) and a._is_equivalent(other)
+                ):
+                    return True
+
+        return False
+
+    def _apply_equivalencies(self, unit, other, equivalencies):
+        """
+        Internal function (used from `_get_converter`) to apply
+        equivalence pairs.
+        """
+
+        def make_converter(scale1, func, scale2):
+            def convert(v):
+                return func(_condition_arg(v) / scale1) * scale2
+
+            return convert
+
+        for funit, tunit, a, b in equivalencies:
+            if tunit is None:
+                ratio = other.decompose() / unit.decompose()
+                try:
+                    ratio_in_funit = ratio.decompose([funit])
+                    return make_converter(ratio_in_funit.scale, a, 1.0)
+                except UnitsError:
+                    pass
+            else:
+                try:
+                    scale1 = funit._to(unit)
+                    scale2 = tunit._to(other)
+                    return make_converter(scale1, a, scale2)
+                except UnitsError:
+                    pass
+                try:
+                    scale1 = tunit._to(unit)
+                    scale2 = funit._to(other)
+                    return make_converter(scale1, b, scale2)
+                except UnitsError:
+                    pass
+
+        def get_err_str(unit):
+            unit_str = unit.to_string("unscaled")
+            physical_type = unit.physical_type
+            if physical_type != "unknown":
+                unit_str = f"'{unit_str}' ({physical_type})"
+            else:
+                unit_str = f"'{unit_str}'"
+            return unit_str
+
+        unit_str = get_err_str(unit)
+        other_str = get_err_str(other)
+
+        raise UnitConversionError(f"{unit_str} and {other_str} are not convertible")
+
+    def _get_converter(self, other, equivalencies=[]):
+        """Get a converter for values in ``self`` to ``other``.
+
+        If no conversion is necessary, returns ``unit_scale_converter``
+        (which is used as a check in quantity helpers).
+
+        """
+        # First see if it is just a scaling.
+        try:
+            scale = self._to(other)
+        except UnitsError:
+            pass
+        else:
+            if scale == 1.0:
+                return unit_scale_converter
+            else:
+                return lambda val: scale * _condition_arg(val)
+
+        # if that doesn't work, maybe we can do it with equivalencies?
+        try:
+            return self._apply_equivalencies(
+                self, other, self._normalize_equivalencies(equivalencies)
+            )
+        except UnitsError as exc:
+            # Last hope: maybe other knows how to do it?
+            # We assume the equivalencies have the unit itself as first item.
+            # TODO: maybe better for other to have a `_back_converter` method?
+            if hasattr(other, "equivalencies"):
+                for funit, tunit, a, b in other.equivalencies:
+                    if other is funit:
+                        try:
+                            converter = self._get_converter(tunit, equivalencies)
+                        except Exception:
+                            pass
+                        else:
+                            return lambda v: b(converter(v))
+
+            raise exc
+
+    def _to(self, other):
+        """
+        Returns the scale to the specified unit.
+
+        See `to`, except that a Unit object should be given (i.e., no
+        string), and that all defaults are used, i.e., no
+        equivalencies and value=1.
+        """
+        # There are many cases where we just want to ensure a Quantity is
+        # of a particular unit, without checking whether it's already in
+        # a particular unit.  If we're being asked to convert from a unit
+        # to itself, we can short-circuit all of this.
+        if self is other:
+            return 1.0
+
+        # Don't presume decomposition is possible; e.g.,
+        # conversion to function units is through equivalencies.
+        if isinstance(other, UnitBase):
+            self_decomposed = self.decompose()
+            other_decomposed = other.decompose()
+
+            # Check quickly whether equivalent.  This is faster than
+            # `is_equivalent`, because it doesn't generate the entire
+            # physical type list of both units.  In other words it "fails
+            # fast".
+            if self_decomposed.powers == other_decomposed.powers and all(
+                self_base is other_base
+                for (self_base, other_base) in zip(
+                    self_decomposed.bases, other_decomposed.bases
+                )
+            ):
+                return self_decomposed.scale / other_decomposed.scale
+
+        raise UnitConversionError(f"'{self!r}' is not a scaled version of '{other!r}'")
+
+    def to(self, other, value=UNITY, equivalencies=[]):
+        """
+        Return the converted values in the specified unit.
+
+        Parameters
+        ----------
+        other : unit-like
+            The unit to convert to.
+
+        value : int, float, or scalar array-like, optional
+            Value(s) in the current unit to be converted to the
+            specified unit.  If not provided, defaults to 1.0
+
+        equivalencies : list of tuple
+            A list of equivalence pairs to try if the units are not
+            directly convertible.  See :ref:`astropy:unit_equivalencies`.
+            This list is in addition to possible global defaults set by, e.g.,
+            `set_enabled_equivalencies`.
+            Use `None` to turn off all equivalencies.
+
+        Returns
+        -------
+        values : scalar or array
+            Converted value(s). Input value sequences are returned as
+            numpy arrays.
+
+        Raises
+        ------
+        UnitsError
+            If units are inconsistent
+        """
+        if other is self and value is UNITY:
+            return UNITY
+        else:
+            return self._get_converter(Unit(other), equivalencies)(value)
+
+    def in_units(self, other, value=1.0, equivalencies=[]):
+        """
+        Alias for `to` for backward compatibility with pynbody.
+        """
+        return self.to(other, value=value, equivalencies=equivalencies)
+
+    def decompose(self, bases=set()):
+        """
+        Return a unit object composed of only irreducible units.
+
+        Parameters
+        ----------
+        bases : sequence of UnitBase, optional
+            The bases to decompose into.  When not provided,
+            decomposes down to any irreducible units.  When provided,
+            the decomposed result will only contain the given units.
+            This will raises a `UnitsError` if it's not possible
+            to do so.
+
+        Returns
+        -------
+        unit : `~astropy.units.CompositeUnit`
+            New object containing only irreducible unit objects.
+        """
+        raise NotImplementedError()
+
+    def _compose(
+        self, equivalencies=[], namespace=[], max_depth=2, depth=0, cached_results=None
+    ):
+        def is_final_result(unit):
+            # Returns True if this result contains only the expected
+            # units
+            return all(base in namespace for base in unit.bases)
+
+        unit = self.decompose()
+        key = hash(unit)
+
+        cached = cached_results.get(key)
+        if cached is not None:
+            if isinstance(cached, Exception):
+                raise cached
+            return cached
+
+        # Prevent too many levels of recursion
+        # And special case for dimensionless unit
+        if depth >= max_depth:
+            cached_results[key] = [unit]
+            return [unit]
+
+        # Make a list including all of the equivalent units
+        units = [unit]
+        for funit, tunit, a, b in equivalencies:
+            if tunit is not None:
+                if self._is_equivalent(funit):
+                    scale = funit.decompose().scale / unit.scale
+                    units.append(Unit(a(1.0 / scale) * tunit).decompose())
+                elif self._is_equivalent(tunit):
+                    scale = tunit.decompose().scale / unit.scale
+                    units.append(Unit(b(1.0 / scale) * funit).decompose())
+            else:
+                if self._is_equivalent(funit):
+                    units.append(Unit(unit.scale))
+
+        # Store partial results
+        partial_results = []
+        # Store final results that reduce to a single unit or pair of
+        # units
+        if len(unit.bases) == 0:
+            final_results = [{unit}, set()]
+        else:
+            final_results = [set(), set()]
+
+        for tunit in namespace:
+            tunit_decomposed = tunit.decompose()
+            for u in units:
+                # If the unit is a base unit, look for an exact match
+                # to one of the bases of the target unit.  If found,
+                # factor by the same power as the target unit's base.
+                # This allows us to factor out fractional powers
+                # without needing to do an exhaustive search.
+                if len(tunit_decomposed.bases) == 1:
+                    for base, power in zip(u.bases, u.powers):
+                        if tunit_decomposed._is_equivalent(base):
+                            tunit = tunit**power
+                            tunit_decomposed = tunit_decomposed**power
+                            break
+
+                composed = (u / tunit_decomposed).decompose()
+                factored = composed * tunit
+                len_bases = len(composed.bases)
+                if is_final_result(factored) and len_bases <= 1:
+                    final_results[len_bases].add(factored)
+                else:
+                    partial_results.append((len_bases, composed, tunit))
+
+        # Do we have any minimal results?
+        for final_result in final_results:
+            if len(final_result):
+                results = final_results[0].union(final_results[1])
+                cached_results[key] = results
+                return results
+
+        partial_results.sort(key=operator.itemgetter(0))
+
+        # ...we have to recurse and try to further compose
+        results = []
+        for len_bases, composed, tunit in partial_results:
+            try:
+                composed_list = composed._compose(
+                    equivalencies=equivalencies,
+                    namespace=namespace,
+                    max_depth=max_depth,
+                    depth=depth + 1,
+                    cached_results=cached_results,
+                )
+            except UnitsError:
+                composed_list = []
+            for subcomposed in composed_list:
+                results.append((len(subcomposed.bases), subcomposed, tunit))
+
+        if len(results):
+            results.sort(key=operator.itemgetter(0))
+
+            min_length = results[0][0]
+            subresults = set()
+            for len_bases, composed, tunit in results:
+                if len_bases > min_length:
+                    break
+
+                factored = composed * tunit
+                if is_final_result(factored):
+                    subresults.add(factored)
+
+            if len(subresults):
+                cached_results[key] = subresults
+                return subresults
+
+        if not is_final_result(self):
+            result = UnitsError(
+                f"Cannot represent unit {self} in terms of the given units"
+            )
+            cached_results[key] = result
+            raise result
+
+        cached_results[key] = [self]
+        return [self]
+
+    def compose(
+        self, equivalencies=[], units=None, max_depth=2, include_prefix_units=None
+    ):
+        """
+        Return the simplest possible composite unit(s) that represent
+        the given unit.  Since there may be multiple equally simple
+        compositions of the unit, a list of units is always returned.
+
+        Parameters
+        ----------
+        equivalencies : list of tuple
+            A list of equivalence pairs to also list.  See
+            :ref:`astropy:unit_equivalencies`.
+            This list is in addition to possible global defaults set by, e.g.,
+            `set_enabled_equivalencies`.
+            Use `None` to turn off all equivalencies.
+
+        units : set of `~astropy.units.Unit`, optional
+            If not provided, any known units may be used to compose
+            into.  Otherwise, ``units`` is a dict, module or sequence
+            containing the units to compose into.
+
+        max_depth : int, optional
+            The maximum recursion depth to use when composing into
+            composite units.
+
+        include_prefix_units : bool, optional
+            When `True`, include prefixed units in the result.
+            Default is `True` if a sequence is passed in to ``units``,
+            `False` otherwise.
+
+        Returns
+        -------
+        units : list of `CompositeUnit`
+            A list of candidate compositions.  These will all be
+            equally simple, but it may not be possible to
+            automatically determine which of the candidates are
+            better.
+        """
+        # if units parameter is specified and is a sequence (list|tuple),
+        # include_prefix_units is turned on by default.  Ex: units=[u.kpc]
+        if include_prefix_units is None:
+            include_prefix_units = isinstance(units, (list, tuple))
+
+        # Pre-normalize the equivalencies list
+        equivalencies = self._normalize_equivalencies(equivalencies)
+
+        # The namespace of units to compose into should be filtered to
+        # only include units with bases in common with self, otherwise
+        # they can't possibly provide useful results.  Having too many
+        # destination units greatly increases the search space.
+
+        def has_bases_in_common(a, b):
+            if len(a.bases) == 0 and len(b.bases) == 0:
+                return True
+            for ab in a.bases:
+                for bb in b.bases:
+                    if ab == bb:
+                        return True
+            return False
+
+        def has_bases_in_common_with_equiv(unit, other):
+            if has_bases_in_common(unit, other):
+                return True
+            for funit, tunit, a, b in equivalencies:
+                if tunit is not None:
+                    if unit._is_equivalent(funit):
+                        if has_bases_in_common(tunit.decompose(), other):
+                            return True
+                    elif unit._is_equivalent(tunit):
+                        if has_bases_in_common(funit.decompose(), other):
+                            return True
+                else:
+                    if unit._is_equivalent(funit):
+                        if has_bases_in_common(dimensionless_unscaled, other):
+                            return True
+            return False
+
+        def filter_units(units):
+            filtered_namespace = set()
+            for tunit in units:
+                if (
+                    isinstance(tunit, UnitBase)
+                    and (include_prefix_units or not isinstance(tunit, PrefixUnit))
+                    and has_bases_in_common_with_equiv(decomposed, tunit.decompose())
+                ):
+                    filtered_namespace.add(tunit)
+            return filtered_namespace
+
+        decomposed = self.decompose()
+
+        if units is None:
+            units = filter_units(self._get_units_with_same_physical_type(equivalencies))
+            if len(units) == 0:
+                units = get_current_unit_registry().non_prefix_units
+        elif isinstance(units, dict):
+            units = set(filter_units(units.values()))
+        elif inspect.ismodule(units):
+            units = filter_units(vars(units).values())
+        else:
+            units = filter_units(_flatten_units_collection(units))
+
+        def sort_results(results):
+            if not len(results):
+                return []
+
+            # Sort the results so the simplest ones appear first.
+            # Simplest is defined as "the minimum sum of absolute
+            # powers" (i.e. the fewest bases), and preference should
+            # be given to results where the sum of powers is positive
+            # and the scale is exactly equal to 1.0
+            results = list(results)
+            results.sort(key=lambda x: np.abs(x.scale))
+            results.sort(key=lambda x: np.sum(np.abs(x.powers)))
+            results.sort(key=lambda x: np.sum(x.powers) < 0.0)
+            results.sort(key=lambda x: not is_effectively_unity(x.scale))
+
+            last_result = results[0]
+            filtered = [last_result]
+            for result in results[1:]:
+                if str(result) != str(last_result):
+                    filtered.append(result)
+                last_result = result
+
+            return filtered
+
+        return sort_results(
+            self._compose(
+                equivalencies=equivalencies,
+                namespace=units,
+                max_depth=max_depth,
+                depth=0,
+                cached_results={},
+            )
+        )
+
+    def to_system(self, system):
+        """
+        Converts this unit into ones belonging to the given system.
+        Since more than one result may be possible, a list is always
+        returned.
+
+        Parameters
+        ----------
+        system : module
+            The module that defines the unit system.  Commonly used
+            ones include `astropy.units.si` and `astropy.units.cgs`.
+
+            To use your own module it must contain unit objects and a
+            sequence member named ``bases`` containing the base units of
+            the system.
+
+        Returns
+        -------
+        units : list of `CompositeUnit`
+            The list is ranked so that units containing only the base
+            units of that system will appear first.
+        """
+        bases = set(system.bases)
+
+        def score(compose):
+            # In case that compose._bases has no elements we return
+            # 'np.inf' as 'score value'.  It does not really matter which
+            # number we would return. This case occurs for instance for
+            # dimensionless quantities:
+            compose_bases = compose.bases
+            if len(compose_bases) == 0:
+                return np.inf
+            else:
+                sum = 0
+                for base in compose_bases:
+                    if base in bases:
+                        sum += 1
+
+                return sum / float(len(compose_bases))
+
+        x = self.decompose(bases=bases)
+        composed = x.compose(units=system)
+        composed = sorted(composed, key=score, reverse=True)
+        return composed
+
+    @lazyproperty
+    def si(self):
+        """
+        Returns a copy of the current `Unit` instance in SI units.
+        """
+        from . import si
+
+        return self.to_system(si)[0]
+
+    @lazyproperty
+    def cgs(self):
+        """
+        Returns a copy of the current `Unit` instance with CGS units.
+        """
+        from . import cgs
+
+        return self.to_system(cgs)[0]
+
+    @property
+    def physical_type(self):
+        """
+        Physical type(s) dimensionally compatible with the unit.
+
+        Returns
+        -------
+        `~astropy.units.physical.PhysicalType`
+            A representation of the physical type(s) of a unit.
+
+        Examples
+        --------
+        >>> from astropy import units as u
+        >>> u.m.physical_type
+        PhysicalType('length')
+        >>> (u.m ** 2 / u.s).physical_type
+        PhysicalType({'diffusivity', 'kinematic viscosity'})
+
+        Physical types can be compared to other physical types
+        (recommended in packages) or to strings.
+
+        >>> area = (u.m ** 2).physical_type
+        >>> area == u.m.physical_type ** 2
+        True
+        >>> area == "area"
+        True
+
+        `~astropy.units.physical.PhysicalType` objects can be used for
+        dimensional analysis.
+
+        >>> number_density = u.m.physical_type ** -3
+        >>> velocity = (u.m / u.s).physical_type
+        >>> number_density * velocity
+        PhysicalType('particle flux')
+        """
+        from . import physical
+
+        return physical.get_physical_type(self)
+
+    def _get_units_with_same_physical_type(self, equivalencies=[]):
+        """
+        Return a list of registered units with the same physical type
+        as this unit.
+
+        This function is used by Quantity to add its built-in
+        conversions to equivalent units.
+
+        This is a private method, since end users should be encouraged
+        to use the more powerful `compose` and `find_equivalent_units`
+        methods (which use this under the hood).
+
+        Parameters
+        ----------
+        equivalencies : list of tuple
+            A list of equivalence pairs to also pull options from.
+            See :ref:`astropy:unit_equivalencies`.  It must already be
+            normalized using `_normalize_equivalencies`.
+        """
+        unit_registry = get_current_unit_registry()
+        units = set(unit_registry.get_units_with_physical_type(self))
+        for funit, tunit, a, b in equivalencies:
+            if tunit is not None:
+                if self.is_equivalent(funit) and tunit not in units:
+                    units.update(unit_registry.get_units_with_physical_type(tunit))
+                if self._is_equivalent(tunit) and funit not in units:
+                    units.update(unit_registry.get_units_with_physical_type(funit))
+            else:
+                if self.is_equivalent(funit):
+                    units.add(dimensionless_unscaled)
+        return units
+
+    class EquivalentUnitsList(list):
+        """
+        A class to handle pretty-printing the result of
+        `find_equivalent_units`.
+        """
+
+        HEADING_NAMES = ("Primary name", "Unit definition", "Aliases")
+        ROW_LEN = 3  # len(HEADING_NAMES), but hard-code since it is constant
+        NO_EQUIV_UNITS_MSG = "There are no equivalent units"
+
+        def __repr__(self):
+            if len(self) == 0:
+                return self.NO_EQUIV_UNITS_MSG
+            else:
+                lines = self._process_equivalent_units(self)
+                lines.insert(0, self.HEADING_NAMES)
+                widths = [0] * self.ROW_LEN
+                for line in lines:
+                    for i, col in enumerate(line):
+                        widths[i] = max(widths[i], len(col))
+
+                f = "  {{0:<{}s}} | {{1:<{}s}} | {{2:<{}s}}".format(*widths)
+                lines = [f.format(*line) for line in lines]
+                lines = lines[0:1] + ["["] + [f"{x} ," for x in lines[1:]] + ["]"]
+                return "\n".join(lines)
+
+        def _repr_html_(self):
+            """
+            Outputs a HTML table representation within Jupyter notebooks.
+            """
+            if len(self) == 0:
+                return f"<p>{self.NO_EQUIV_UNITS_MSG}</p>"
+            else:
+                # HTML tags to use to compose the table in HTML
+                blank_table = '<table style="width:50%">{}</table>'
+                blank_row_container = "<tr>{}</tr>"
+                heading_row_content = "<th>{}</th>" * self.ROW_LEN
+                data_row_content = "<td>{}</td>" * self.ROW_LEN
+
+                # The HTML will be rendered & the table is simple, so don't
+                # bother to include newlines & indentation for the HTML code.
+                heading_row = blank_row_container.format(
+                    heading_row_content.format(*self.HEADING_NAMES)
+                )
+                data_rows = self._process_equivalent_units(self)
+                all_rows = heading_row
+                for row in data_rows:
+                    html_row = blank_row_container.format(data_row_content.format(*row))
+                    all_rows += html_row
+                return blank_table.format(all_rows)
+
+        @staticmethod
+        def _process_equivalent_units(equiv_units_data):
+            """
+            Extract attributes, and sort, the equivalent units pre-formatting.
+            """
+            processed_equiv_units = []
+            for u in equiv_units_data:
+                irred = u.decompose().to_string()
+                if irred == u.name:
+                    irred = "irreducible"
+                processed_equiv_units.append((u.name, irred, ", ".join(u.aliases)))
+            processed_equiv_units.sort()
+            return processed_equiv_units
+
+    def find_equivalent_units(
+        self, equivalencies=[], units=None, include_prefix_units=False
+    ):
+        """
+        Return a list of all the units that are the same type as ``self``.
+
+        Parameters
+        ----------
+        equivalencies : list of tuple
+            A list of equivalence pairs to also list.  See
+            :ref:`astropy:unit_equivalencies`.
+            Any list given, including an empty one, supersedes global defaults
+            that may be in effect (as set by `set_enabled_equivalencies`)
+
+        units : set of `~astropy.units.Unit`, optional
+            If not provided, all defined units will be searched for
+            equivalencies.  Otherwise, may be a dict, module or
+            sequence containing the units to search for equivalencies.
+
+        include_prefix_units : bool, optional
+            When `True`, include prefixed units in the result.
+            Default is `False`.
+
+        Returns
+        -------
+        units : list of `UnitBase`
+            A list of unit objects that match ``u``.  A subclass of
+            `list` (``EquivalentUnitsList``) is returned that
+            pretty-prints the list of units when output.
+        """
+        results = self.compose(
+            equivalencies=equivalencies,
+            units=units,
+            max_depth=1,
+            include_prefix_units=include_prefix_units,
+        )
+        results = {
+            x.bases[0] for x in results if len(x.bases) == 1 and x.powers[0] == 1
+        }
+        return self.EquivalentUnitsList(results)
+
+    def is_unity(self):
+        """
+        Returns `True` if the unit is unscaled and dimensionless.
+        """
+        return False
+
+
+class NamedUnit(UnitBase):
+    """
+    The base class of units that have a name.
+
+    Parameters
+    ----------
+    st : str, list of str, 2-tuple
+        The name of the unit.  If a list of strings, the first element
+        is the canonical (short) name, and the rest of the elements
+        are aliases.  If a tuple of lists, the first element is a list
+        of short names, and the second element is a list of long
+        names; all but the first short name are considered "aliases".
+        Each name *should* be a valid Python identifier to make it
+        easy to access, but this is not required.
+
+    namespace : dict, optional
+        When provided, inject the unit, and all of its aliases, in the
+        given namespace dictionary.  If a unit by the same name is
+        already in the namespace, a ValueError is raised.
+
+    doc : str, optional
+        A docstring describing the unit.
+
+    format : dict, optional
+        A mapping to format-specific representations of this unit.
+        For example, for the ``Ohm`` unit, it might be nice to have it
+        displayed as ``\\Omega`` by the ``latex`` formatter.  In that
+        case, `format` argument should be set to::
+
+            {'latex': r'\\Omega'}
+
+    Raises
+    ------
+    ValueError
+        If any of the given unit names are already in the registry.
+
+    ValueError
+        If any of the given unit names are not valid Python tokens.
+    """
+
+    def __init__(self, st, doc=None, format=None, namespace=None):
+        UnitBase.__init__(self)
+
+        if isinstance(st, (bytes, str)):
+            self._names = [st]
+            self._short_names = [st]
+            self._long_names = []
+        elif isinstance(st, tuple):
+            if not len(st) == 2:
+                raise ValueError("st must be string, list or 2-tuple")
+            self._names = st[0] + [n for n in st[1] if n not in st[0]]
+            if not len(self._names):
+                raise ValueError("must provide at least one name")
+            self._short_names = st[0][:]
+            self._long_names = st[1][:]
+        else:
+            if len(st) == 0:
+                raise ValueError("st list must have at least one entry")
+            self._names = st[:]
+            self._short_names = [st[0]]
+            self._long_names = st[1:]
+
+        if format is None:
+            format = {}
+        self._format = format
+
+        if doc is None:
+            doc = self._generate_doc()
+        else:
+            doc = textwrap.dedent(doc)
+            doc = textwrap.fill(doc)
+
+        self.__doc__ = doc
+
+        self._inject(namespace)
+
+    def _generate_doc(self):
+        """
+        Generate a docstring for the unit if the user didn't supply
+        one.  This is only used from the constructor and may be
+        overridden in subclasses.
+        """
+        names = self.names
+        if len(self.names) > 1:
+            return f"{names[1]} ({names[0]})"
+        else:
+            return names[0]
+
+    def get_format_name(self, format):
+        """
+        Get a name for this unit that is specific to a particular
+        format.
+
+        Uses the dictionary passed into the `format` kwarg in the
+        constructor.
+
+        Parameters
+        ----------
+        format : str
+            The name of the format
+
+        Returns
+        -------
+        name : str
+            The name of the unit for the given format.
+        """
+        return self._format.get(format, self.name)
+
+    @property
+    def names(self):
+        """
+        Returns all of the names associated with this unit.
+        """
+        return self._names
+
+    @property
+    def name(self):
+        """
+        Returns the canonical (short) name associated with this unit.
+        """
+        return self._names[0]
+
+    @property
+    def aliases(self):
+        """
+        Returns the alias (long) names for this unit.
+        """
+        return self._names[1:]
+
+    @property
+    def short_names(self):
+        """
+        Returns all of the short names associated with this unit.
+        """
+        return self._short_names
+
+    @property
+    def long_names(self):
+        """
+        Returns all of the long names associated with this unit.
+        """
+        return self._long_names
+
+    def _inject(self, namespace=None):
+        """
+        Injects the unit, and all of its aliases, in the given
+        namespace dictionary.
+        """
+        if namespace is None:
+            return
+
+        # Loop through all of the names first, to ensure all of them
+        # are new, then add them all as a single "transaction" below.
+        for name in self._names:
+            if name in namespace and self != namespace[name]:
+                raise ValueError(
+                    f"Object with name {name!r} already exists in "
+                    f"given namespace ({namespace[name]!r})."
+                )
+
+        for name in self._names:
+            namespace[name] = self
+
+
+def _recreate_irreducible_unit(cls, names, registered):
+    """
+    This is used to reconstruct units when passed around by
+    multiprocessing.
+    """
+    registry = get_current_unit_registry().registry
+    if names[0] in registry:
+        # If in local registry return that object.
+        return registry[names[0]]
+    else:
+        # otherwise, recreate the unit.
+        unit = cls(names)
+        if registered:
+            # If not in local registry but registered in origin registry,
+            # enable unit in local registry.
+            get_current_unit_registry().add_enabled_units([unit])
+
+        return unit
+
+
+class IrreducibleUnit(NamedUnit):
+    """
+    Irreducible units are the units that all other units are defined
+    in terms of.
+
+    Examples are meters, seconds, kilograms, amperes, etc.  There is
+    only once instance of such a unit per type.
+    """
+
+    def __reduce__(self):
+        # When IrreducibleUnit objects are passed to other processes
+        # over multiprocessing, they need to be recreated to be the
+        # ones already in the subprocesses' namespace, not new
+        # objects, or they will be considered "unconvertible".
+        # Therefore, we have a custom pickler/unpickler that
+        # understands how to recreate the Unit on the other side.
+        registry = get_current_unit_registry().registry
+        return (
+            _recreate_irreducible_unit,
+            (self.__class__, list(self.names), self.name in registry),
+            self.__getstate__(),
+        )
+
+    @property
+    def represents(self):
+        """The unit that this named unit represents.
+
+        For an irreducible unit, that is always itself.
+        """
+        return self
+
+    def decompose(self, bases=set()):
+        if len(bases) and self not in bases:
+            for base in bases:
+                try:
+                    scale = self._to(base)
+                except UnitsError:
+                    pass
+                else:
+                    if is_effectively_unity(scale):
+                        return base
+                    else:
+                        return CompositeUnit(scale, [base], [1], _error_check=False)
+
+            raise UnitConversionError(
+                f"Unit {self} can not be decomposed into the requested bases"
+            )
+
+        return self
+
+
+class UnrecognizedUnit(IrreducibleUnit):
+    """
+    A unit that did not parse correctly.  This allows for
+    round-tripping it as a string, but no unit operations actually work
+    on it.
+
+    Parameters
+    ----------
+    st : str
+        The name of the unit.
+    """
+
+    # For UnrecognizedUnits, we want to use "standard" Python
+    # pickling, not the special case that is used for
+    # IrreducibleUnits.
+    __reduce__ = object.__reduce__
+
+    def __repr__(self):
+        return f"UnrecognizedUnit({self})"
+
+    def __bytes__(self):
+        return self.name.encode("ascii", "replace")
+
+    def __str__(self):
+        return self.name
+
+    def to_string(self, format=None):
+        return self.name
+
+    def _unrecognized_operator(self, *args, **kwargs):
+        raise ValueError(
+            f"The unit {self.name!r} is unrecognized, so all arithmetic operations "
+            "with it are invalid."
+        )
+
+    __pow__ = __truediv__ = __rtruediv__ = __mul__ = __rmul__ = _unrecognized_operator
+    __lt__ = __gt__ = __le__ = __ge__ = __neg__ = _unrecognized_operator
+
+    def __eq__(self, other):
+        try:
+            other = Unit(other, parse_strict="silent")
+        except (ValueError, UnitsError, TypeError):
+            return NotImplemented
+
+        return isinstance(other, type(self)) and self.name == other.name
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def is_equivalent(self, other, equivalencies=None):
+        self._normalize_equivalencies(equivalencies)
+        return self == other
+
+    def _get_converter(self, other, equivalencies=None):
+        self._normalize_equivalencies(equivalencies)
+        raise ValueError(
+            f"The unit {self.name!r} is unrecognized.  It can not be converted "
+            "to other units."
+        )
+
+    def get_format_name(self, format):
+        return self.name
+
+    def is_unity(self):
+        return False
+
+
+class _UnitMetaClass(type):
+    """
+    This metaclass exists because the Unit constructor should
+    sometimes return instances that already exist.  This "overrides"
+    the constructor before the new instance is actually created, so we
+    can return an existing one.
+    """
+
+    def __call__(
+        self,
+        s="",
+        represents=None,
+        format=None,
+        namespace=None,
+        doc=None,
+        parse_strict="raise",
+    ):
+        # Short-circuit if we're already a unit
+        if hasattr(s, "_get_physical_type_id"):
+            return s
+
+        # turn possible Quantity input for s or represents into a Unit
+        from .quantity import Quantity
+
+        if isinstance(represents, Quantity):
+            if is_effectively_unity(represents.value):
+                represents = represents.unit
+            else:
+                represents = CompositeUnit(
+                    represents.value * represents.unit.scale,
+                    bases=represents.unit.bases,
+                    powers=represents.unit.powers,
+                    _error_check=False,
+                )
+
+        if isinstance(s, Quantity):
+            if is_effectively_unity(s.value):
+                s = s.unit
+            else:
+                s = CompositeUnit(
+                    s.value * s.unit.scale,
+                    bases=s.unit.bases,
+                    powers=s.unit.powers,
+                    _error_check=False,
+                )
+
+        # now decide what we really need to do; define derived Unit?
+        if isinstance(represents, UnitBase):
+            # This has the effect of calling the real __new__ and
+            # __init__ on the Unit class.
+            return super().__call__(
+                s, represents, format=format, namespace=namespace, doc=doc
+            )
+
+        # or interpret a Quantity (now became unit), string or number?
+        if isinstance(s, UnitBase):
+            return s
+
+        elif isinstance(s, (bytes, str)):
+            if len(s.strip()) == 0:
+                # Return the NULL unit
+                return dimensionless_unscaled
+
+            if format is None:
+                format = unit_format.Generic
+
+            f = unit_format.get_format(format)
+            if isinstance(s, bytes):
+                s = s.decode("ascii")
+
+            try:
+                return f.parse(s)
+            except NotImplementedError:
+                raise
+            except Exception as e:
+                if parse_strict == "silent":
+                    pass
+                else:
+                    # Deliberately not issubclass here. Subclasses
+                    # should use their name.
+                    if f is not unit_format.Generic:
+                        format_clause = f.name + " "
+                    else:
+                        format_clause = ""
+                    msg = (
+                        f"'{s}' did not parse as {format_clause}unit: {str(e)} "
+                        "If this is meant to be a custom unit, "
+                        "define it with 'u.def_unit'. To have it "
+                        "recognized inside a file reader or other code, "
+                        "enable it with 'u.add_enabled_units'. "
+                        "For details, see "
+                        "https://docs.astropy.org/en/latest/units/combining_and_defining.html"
+                    )
+                    if parse_strict == "raise":
+                        raise ValueError(msg)
+                    elif parse_strict == "warn":
+                        warnings.warn(msg, UnitsWarning)
+                    else:
+                        raise ValueError(
+                            "'parse_strict' must be 'warn', 'raise' or 'silent'"
+                        )
+                return UnrecognizedUnit(s)
+
+        elif isinstance(s, (int, float, np.floating, np.integer)):
+            return CompositeUnit(s, [], [], _error_check=False)
+
+        elif isinstance(s, tuple):
+            from .structured import StructuredUnit
+
+            return StructuredUnit(s)
+
+        elif s is None:
+            raise TypeError("None is not a valid Unit")
+
+        else:
+            raise TypeError(f"{s} can not be converted to a Unit")
+
+
+class Unit(NamedUnit, metaclass=_UnitMetaClass):
+    """
+    The main unit class.
+
+    There are a number of different ways to construct a Unit, but
+    always returns a `UnitBase` instance.  If the arguments refer to
+    an already-existing unit, that existing unit instance is returned,
+    rather than a new one.
+
+    - From a string::
+
+        Unit(s, format=None, parse_strict='silent')
+
+      Construct from a string representing a (possibly compound) unit.
+
+      The optional `format` keyword argument specifies the format the
+      string is in, by default ``"generic"``.  For a description of
+      the available formats, see `astropy.units.format`.
+
+      The optional ``parse_strict`` keyword controls what happens when an
+      unrecognized unit string is passed in.  It may be one of the following:
+
+         - ``'raise'``: (default) raise a ValueError exception.
+
+         - ``'warn'``: emit a Warning, and return an
+           `UnrecognizedUnit` instance.
+
+         - ``'silent'``: return an `UnrecognizedUnit` instance.
+
+    - From a number::
+
+        Unit(number)
+
+      Creates a dimensionless unit.
+
+    - From a `UnitBase` instance::
+
+        Unit(unit)
+
+      Returns the given unit unchanged.
+
+    - From no arguments::
+
+        Unit()
+
+      Returns the dimensionless unit.
+
+    - The last form, which creates a new `Unit` is described in detail
+      below.
+
+    See also: https://docs.astropy.org/en/stable/units/
+
+    Parameters
+    ----------
+    st : str or list of str
+        The name of the unit.  If a list, the first element is the
+        canonical (short) name, and the rest of the elements are
+        aliases.
+
+    represents : UnitBase instance
+        The unit that this named unit represents.
+
+    doc : str, optional
+        A docstring describing the unit.
+
+    format : dict, optional
+        A mapping to format-specific representations of this unit.
+        For example, for the ``Ohm`` unit, it might be nice to have it
+        displayed as ``\\Omega`` by the ``latex`` formatter.  In that
+        case, `format` argument should be set to::
+
+            {'latex': r'\\Omega'}
+
+    namespace : dict, optional
+        When provided, inject the unit (and all of its aliases) into
+        the given namespace.
+
+    Raises
+    ------
+    ValueError
+        If any of the given unit names are already in the registry.
+
+    ValueError
+        If any of the given unit names are not valid Python tokens.
+    """
+
+    def __init__(self, st, represents=None, doc=None, format=None, namespace=None):
+        represents = Unit(represents)
+        self._represents = represents
+
+        NamedUnit.__init__(self, st, namespace=namespace, doc=doc, format=format)
+
+    @property
+    def represents(self):
+        """The unit that this named unit represents."""
+        return self._represents
+
+    def decompose(self, bases=set()):
+        return self._represents.decompose(bases=bases)
+
+    def is_unity(self):
+        return self._represents.is_unity()
+
+    def __hash__(self):
+        if self._hash is None:
+            self._hash = hash((self.name, self._represents))
+        return self._hash
+
+    @classmethod
+    def _from_physical_type_id(cls, physical_type_id):
+        # get string bases and powers from the ID tuple
+        bases = [cls(base) for base, _ in physical_type_id]
+        powers = [power for _, power in physical_type_id]
+
+        if len(physical_type_id) == 1 and powers[0] == 1:
+            unit = bases[0]
+        else:
+            unit = CompositeUnit(1, bases, powers, _error_check=False)
+
+        return unit
+
+
+class PrefixUnit(Unit):
+    """
+    A unit that is simply a SI-prefixed version of another unit.
+
+    For example, ``mm`` is a `PrefixUnit` of ``.001 * m``.
+
+    The constructor is the same as for `Unit`.
+    """
+
+
+class CompositeUnit(UnitBase):
+    """
+    Create a composite unit using expressions of previously defined
+    units.
+
+    Direct use of this class is not recommended. Instead use the
+    factory function `Unit` and arithmetic operators to compose
+    units.
+
+    Parameters
+    ----------
+    scale : number
+        A scaling factor for the unit.
+
+    bases : sequence of `UnitBase`
+        A sequence of units this unit is composed of.
+
+    powers : sequence of numbers
+        A sequence of powers (in parallel with ``bases``) for each
+        of the base units.
+    """
+
+    _decomposed_cache = None
+
+    def __init__(
+        self,
+        scale,
+        bases,
+        powers,
+        decompose=False,
+        decompose_bases=set(),
+        _error_check=True,
+    ):
+        # There are many cases internal to astropy.units where we
+        # already know that all the bases are Unit objects, and the
+        # powers have been validated.  In those cases, we can skip the
+        # error checking for performance reasons.  When the private
+        # kwarg `_error_check` is False, the error checking is turned
+        # off.
+        if _error_check:
+            for base in bases:
+                if not isinstance(base, UnitBase):
+                    raise TypeError("bases must be sequence of UnitBase instances")
+            powers = [validate_power(p) for p in powers]
+
+        if not decompose and len(bases) == 1 and powers[0] >= 0:
+            # Short-cut; with one unit there's nothing to expand and gather,
+            # as that has happened already when creating the unit.  But do only
+            # positive powers, since for negative powers we need to re-sort.
+            unit = bases[0]
+            power = powers[0]
+            if power == 1:
+                scale *= unit.scale
+                self._bases = unit.bases
+                self._powers = unit.powers
+            elif power == 0:
+                self._bases = []
+                self._powers = []
+            else:
+                scale *= unit.scale**power
+                self._bases = unit.bases
+                self._powers = [
+                    sanitize_power(operator.mul(*resolve_fractions(p, power)))
+                    for p in unit.powers
+                ]
+
+            self._scale = sanitize_scale(scale)
+        else:
+            # Regular case: use inputs as preliminary scale, bases, and powers,
+            # then "expand and gather" identical bases, sanitize the scale, &c.
+            self._scale = scale
+            self._bases = bases
+            self._powers = powers
+            self._expand_and_gather(decompose=decompose, bases=decompose_bases)
+
+    def __repr__(self):
+        if len(self._bases):
+            return super().__repr__()
+        else:
+            if self._scale != 1.0:
+                return f"Unit(dimensionless with a scale of {self._scale})"
+            else:
+                return "Unit(dimensionless)"
+
+    @property
+    def scale(self):
+        """
+        Return the scale of the composite unit.
+        """
+        return self._scale
+
+    @property
+    def bases(self):
+        """
+        Return the bases of the composite unit.
+        """
+        return self._bases
+
+    @property
+    def powers(self):
+        """
+        Return the powers of the composite unit.
+        """
+        return self._powers
+
+    def _expand_and_gather(self, decompose=False, bases=set()):
+        def add_unit(unit, power, scale):
+            if bases and unit not in bases:
+                for base in bases:
+                    try:
+                        scale *= unit._to(base) ** power
+                    except UnitsError:
+                        pass
+                    else:
+                        unit = base
+                        break
+
+            if unit in new_parts:
+                a, b = resolve_fractions(new_parts[unit], power)
+                new_parts[unit] = a + b
+            else:
+                new_parts[unit] = power
+            return scale
+
+        new_parts = {}
+        scale = self._scale
+
+        for b, p in zip(self._bases, self._powers):
+            if decompose and b not in bases:
+                b = b.decompose(bases=bases)
+
+            if isinstance(b, CompositeUnit):
+                scale *= b._scale**p
+                for b_sub, p_sub in zip(b._bases, b._powers):
+                    a, b = resolve_fractions(p_sub, p)
+                    scale = add_unit(b_sub, a * b, scale)
+            else:
+                scale = add_unit(b, p, scale)
+
+        new_parts = [x for x in new_parts.items() if x[1] != 0]
+        new_parts.sort(key=lambda x: (-x[1], getattr(x[0], "name", "")))
+
+        self._bases = [x[0] for x in new_parts]
+        self._powers = [sanitize_power(x[1]) for x in new_parts]
+        self._scale = sanitize_scale(scale)
+
+    def __copy__(self):
+        """
+        For compatibility with python copy module.
+        """
+        return CompositeUnit(self._scale, self._bases[:], self._powers[:])
+
+    def decompose(self, bases=set()):
+        if len(bases) == 0 and self._decomposed_cache is not None:
+            return self._decomposed_cache
+
+        for base in self.bases:
+            if not isinstance(base, IrreducibleUnit) or (
+                len(bases) and base not in bases
+            ):
+                break
+        else:
+            if len(bases) == 0:
+                self._decomposed_cache = self
+            return self
+
+        x = CompositeUnit(
+            self.scale, self.bases, self.powers, decompose=True, decompose_bases=bases
+        )
+        if len(bases) == 0:
+            self._decomposed_cache = x
+        return x
+
+    def is_unity(self):
+        unit = self.decompose()
+        return len(unit.bases) == 0 and unit.scale == 1.0
+
+
+si_prefixes = [
+    (["Q"], ["quetta"], 1e30),
+    (["R"], ["ronna"], 1e27),
+    (["Y"], ["yotta"], 1e24),
+    (["Z"], ["zetta"], 1e21),
+    (["E"], ["exa"], 1e18),
+    (["P"], ["peta"], 1e15),
+    (["T"], ["tera"], 1e12),
+    (["G"], ["giga"], 1e9),
+    (["M"], ["mega"], 1e6),
+    (["k"], ["kilo"], 1e3),
+    (["h"], ["hecto"], 1e2),
+    (["da"], ["deka", "deca"], 1e1),
+    (["d"], ["deci"], 1e-1),
+    (["c"], ["centi"], 1e-2),
+    (["m"], ["milli"], 1e-3),
+    (["u"], ["micro"], 1e-6),
+    (["n"], ["nano"], 1e-9),
+    (["p"], ["pico"], 1e-12),
+    (["f"], ["femto"], 1e-15),
+    (["a"], ["atto"], 1e-18),
+    (["z"], ["zepto"], 1e-21),
+    (["y"], ["yocto"], 1e-24),
+    (["r"], ["ronto"], 1e-27),
+    (["q"], ["quecto"], 1e-30),
+]
+
+
+binary_prefixes = [
+    (["Ki"], ["kibi"], 2**10),
+    (["Mi"], ["mebi"], 2**20),
+    (["Gi"], ["gibi"], 2**30),
+    (["Ti"], ["tebi"], 2**40),
+    (["Pi"], ["pebi"], 2**50),
+    (["Ei"], ["exbi"], 2**60),
+]
+
+
+def _add_prefixes(u, excludes=[], namespace=None, prefixes=False):
+    """
+    Set up all of the standard metric prefixes for a unit.  This
+    function should not be used directly, but instead use the
+    `prefixes` kwarg on `def_unit`.
+
+    Parameters
+    ----------
+    excludes : list of str, optional
+        Any prefixes to exclude from creation to avoid namespace
+        collisions.
+
+    namespace : dict, optional
+        When provided, inject the unit (and all of its aliases) into
+        the given namespace dictionary.
+
+    prefixes : list, optional
+        When provided, it is a list of prefix definitions of the form:
+
+            (short_names, long_tables, factor)
+    """
+    if prefixes is True:
+        prefixes = si_prefixes
+    elif prefixes is False:
+        prefixes = []
+
+    for short, full, factor in prefixes:
+        names = []
+        format = {}
+        for prefix in short:
+            if prefix in excludes:
+                continue
+
+            for alias in u.short_names:
+                names.append(prefix + alias)
+
+                # This is a hack to use Greek mu as a prefix
+                # for some formatters.
+                if prefix == "u":
+                    format["latex"] = r"\mu " + u.get_format_name("latex")
+                    format["unicode"] = "\N{MICRO SIGN}" + u.get_format_name("unicode")
+
+                for key, val in u._format.items():
+                    format.setdefault(key, prefix + val)
+
+        for prefix in full:
+            if prefix in excludes:
+                continue
+
+            for alias in u.long_names:
+                names.append(prefix + alias)
+
+        if len(names):
+            PrefixUnit(
+                names,
+                CompositeUnit(factor, [u], [1], _error_check=False),
+                namespace=namespace,
+                format=format,
+            )
+
+
+def def_unit(
+    s,
+    represents=None,
+    doc=None,
+    format=None,
+    prefixes=False,
+    exclude_prefixes=[],
+    namespace=None,
+):
+    """
+    Factory function for defining new units.
+
+    Parameters
+    ----------
+    s : str or list of str
+        The name of the unit.  If a list, the first element is the
+        canonical (short) name, and the rest of the elements are
+        aliases.
+
+    represents : UnitBase instance, optional
+        The unit that this named unit represents.  If not provided,
+        a new `IrreducibleUnit` is created.
+
+    doc : str, optional
+        A docstring describing the unit.
+
+    format : dict, optional
+        A mapping to format-specific representations of this unit.
+        For example, for the ``Ohm`` unit, it might be nice to
+        have it displayed as ``\\Omega`` by the ``latex``
+        formatter.  In that case, `format` argument should be set
+        to::
+
+            {'latex': r'\\Omega'}
+
+    prefixes : bool or list, optional
+        When `True`, generate all of the SI prefixed versions of the
+        unit as well.  For example, for a given unit ``m``, will
+        generate ``mm``, ``cm``, ``km``, etc.  When a list, it is a list of
+        prefix definitions of the form:
+
+            (short_names, long_tables, factor)
+
+        Default is `False`.  This function always returns the base
+        unit object, even if multiple scaled versions of the unit were
+        created.
+
+    exclude_prefixes : list of str, optional
+        If any of the SI prefixes need to be excluded, they may be
+        listed here.  For example, ``Pa`` can be interpreted either as
+        "petaannum" or "Pascal".  Therefore, when defining the
+        prefixes for ``a``, ``exclude_prefixes`` should be set to
+        ``["P"]``.
+
+    namespace : dict, optional
+        When provided, inject the unit (and all of its aliases and
+        prefixes), into the given namespace dictionary.
+
+    Returns
+    -------
+    unit : `~astropy.units.UnitBase`
+        The newly-defined unit, or a matching unit that was already
+        defined.
+    """
+    if represents is not None:
+        result = Unit(s, represents, namespace=namespace, doc=doc, format=format)
+    else:
+        result = IrreducibleUnit(s, namespace=namespace, doc=doc, format=format)
+
+    if prefixes:
+        _add_prefixes(
+            result, excludes=exclude_prefixes, namespace=namespace, prefixes=prefixes
+        )
+    return result
+
+
+def _condition_arg(value):
+    """
+    Validate value is acceptable for conversion purposes.
+
+    Will convert into an array if not a scalar, and can be converted
+    into an array
+
+    Parameters
+    ----------
+    value : int or float value, or sequence of such values
+
+    Returns
+    -------
+    Scalar value or numpy array
+
+    Raises
+    ------
+    ValueError
+        If value is not as expected
+    """
+    if isinstance(value, (np.ndarray, float, int, complex, np.void)):
+        return value
+
+    avalue = np.array(value)
+    if avalue.dtype.kind not in ["i", "f", "c"]:
+        raise ValueError(
+            "Value not scalar compatible or convertible to "
+            "an int, float, or complex array"
+        )
+    return avalue
+
+
+def unit_scale_converter(val):
+    """Function that just multiplies the value by unity.
+
+    This is a separate function so it can be recognized and
+    discarded in unit conversion.
+    """
+    return 1.0 * _condition_arg(val)
+
+
+dimensionless_unscaled = CompositeUnit(1, [], [], _error_check=False)
+# Abbreviation of the above, see #1980
+one = dimensionless_unscaled
+
+# Maintain error in old location for backward compatibility
+# TODO: Is this still needed? Should there be a deprecation warning?
+unit_format.fits.UnitScaleError = UnitScaleError
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 000000000..015875ff8 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,157 @@ + + + + + + Overview: module code — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/benchmarks/angular_resolution.html b/_modules/pyirf/benchmarks/angular_resolution.html new file mode 100644 index 000000000..f3a1d356e --- /dev/null +++ b/_modules/pyirf/benchmarks/angular_resolution.html @@ -0,0 +1,202 @@ + + + + + + pyirf.benchmarks.angular_resolution — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.benchmarks.angular_resolution

+import numpy as np
+from astropy.table import QTable
+from scipy.stats import norm
+import astropy.units as u
+
+from ..binning import calculate_bin_indices
+
+
+ONE_SIGMA_QUANTILE = norm.cdf(1) - norm.cdf(-1)
+
+
+
+[docs] +def angular_resolution( + events, energy_bins, + energy_type="true", + quantile=ONE_SIGMA_QUANTILE, +): + """ + Calculate the angular resolution. + + This implementation corresponds to the 68% containment of the angular + distance distribution. + + Parameters + ---------- + events : astropy.table.QTable + Astropy Table object containing the reconstructed events information. + energy_bins: numpy.ndarray(dtype=float, ndim=1) + Bin edges in energy. + energy_type: str + Either "true" or "reco" energy. + Default is "true". + quantile : float + Which quantile to use for the angular resolution, + by default, the containment of the 1-sigma region + of the normal distribution (~68%) is used. + + Returns + ------- + result : astropy.table.QTable + QTable containing the 68% containment of the angular + distance distribution per each reconstructed energy bin. + """ + # create a table to make use of groupby operations + energy_key = f"{energy_type}_energy" + table = QTable(events[[energy_key, "theta"]]) + + bin_index, valid = calculate_bin_indices(table[energy_key], energy_bins) + + result = QTable() + result[f"{energy_key}_low"] = energy_bins[:-1] + result[f"{energy_key}_high"] = energy_bins[1:] + result[f"{energy_key}_center"] = 0.5 * (energy_bins[:-1] + energy_bins[1:]) + result["n_events"] = 0 + + key = "angular_resolution" + result[key] = u.Quantity(np.nan, table["theta"].unit) + + # if we get an empty input (no selected events available) + # we return the table filled with NaNs + if len(events) == 0: + return result + + # use groupby operations to calculate the percentile in each bin + by_bin = table[valid].group_by(bin_index[valid]) + for bin_idx, group in zip(by_bin.groups.keys, by_bin.groups): + result[key][bin_idx] = np.nanquantile(group["theta"], quantile) + result["n_events"][bin_idx] = len(group) + return result
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/benchmarks/energy_bias_resolution.html b/_modules/pyirf/benchmarks/energy_bias_resolution.html new file mode 100644 index 000000000..fe4c4fde1 --- /dev/null +++ b/_modules/pyirf/benchmarks/energy_bias_resolution.html @@ -0,0 +1,292 @@ + + + + + + pyirf.benchmarks.energy_bias_resolution — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.benchmarks.energy_bias_resolution

+import numpy as np
+from scipy.stats import norm
+from astropy.table import QTable
+import astropy.units as u
+
+from ..binning import calculate_bin_indices, UNDERFLOW_INDEX, OVERFLOW_INDEX
+
+
+NORM_LOWER_SIGMA, NORM_UPPER_SIGMA = norm(0, 1).cdf([-1, 1])
+ONE_SIGMA_COVERAGE = NORM_UPPER_SIGMA - NORM_LOWER_SIGMA
+MEDIAN = 0.5
+
+
+def energy_resolution_absolute_68(rel_error):
+    """Calculate the energy resolution as the central 68% interval.
+
+    Utility function for pyirf.benchmarks.energy_bias_resolution
+
+    Parameters
+    ----------
+    rel_error : numpy.ndarray(dtype=float, ndim=1)
+        Array of float on which the quantile is calculated.
+
+    Returns
+    -------
+    resolution: numpy.ndarray(dtype=float, ndim=1)
+        Array containing the 68% intervals
+    """
+    return np.nanquantile(np.abs(rel_error), ONE_SIGMA_COVERAGE)
+
+
+def inter_quantile_distance(rel_error):
+    """Calculate the energy resolution as the half of the 68% containment.
+
+    Percentile equivalent of the standard deviation.
+    Utility function for pyirf.benchmarks.energy_bias_resolution
+
+    Parameters
+    ----------
+    rel_error : numpy.ndarray(dtype=float, ndim=1)
+        Array of float on which the quantile is calculated.
+
+    Returns
+    -------
+    resolution: numpy.ndarray(dtype=float, ndim=1)
+        Array containing the resolution values.
+    """
+    upper_sigma = np.nanquantile(rel_error, NORM_UPPER_SIGMA)
+    lower_sigma = np.nanquantile(rel_error, NORM_LOWER_SIGMA)
+    resolution = 0.5 * (upper_sigma - lower_sigma)
+    return resolution
+
+
+
+[docs] +def energy_bias_resolution( + events, + energy_bins, + energy_type="true", + bias_function=np.nanmedian, + resolution_function=inter_quantile_distance, +): + """ + Calculate bias and energy resolution. + + Parameters + ---------- + events: astropy.table.QTable + Astropy Table object containing the reconstructed events information. + energy_bins: numpy.ndarray(dtype=float, ndim=1) + Bin edges in energy. + energy_type: str + Either "true" or "reco" energy. + Default is "true". + bias_function: callable + Function used to calculate the energy bias + resolution_function: callable + Function used to calculate the energy resolution + + Returns + ------- + result : astropy.table.QTable + QTable containing the energy bias and resolution + per each bin in true energy. + """ + + # create a table to make use of groupby operations + table = QTable(events[["true_energy", "reco_energy"]], copy=False) + table["rel_error"] = (events["reco_energy"] / events["true_energy"]).to_value(u.one) - 1 + + energy_key = f"{energy_type}_energy" + + result = QTable() + result[f"{energy_key}_low"] = energy_bins[:-1] + result[f"{energy_key}_high"] = energy_bins[1:] + result[f"{energy_key}_center"] = 0.5 * (energy_bins[:-1] + energy_bins[1:]) + + result["n_events"] = 0 + result["bias"] = np.nan + result["resolution"] = np.nan + + if not len(events): + # if we get an empty input (no selected events available) + # we return the table filled with NaNs + return result + + + # use groupby operations to calculate the percentile in each bin + bin_index, valid = calculate_bin_indices(table[energy_key], energy_bins) + by_bin = table.group_by(bin_index) + + # use groupby operations to calculate the percentile in each bin + by_bin = table[valid].group_by(bin_index[valid]) + for bin_idx, group in zip(by_bin.groups.keys, by_bin.groups): + result["n_events"][bin_idx] = len(group) + result["bias"][bin_idx] = bias_function(group["rel_error"]) + result["resolution"][bin_idx] = resolution_function(group["rel_error"]) + return result
+ + +
+[docs] +def energy_bias_resolution_from_energy_dispersion( + energy_dispersion, + migration_bins, +): + """ + Calculate bias and energy resolution. + + Parameters + ---------- + edisp: + Energy dispersion matrix of shape + (n_energy_bins, n_migra_bins, n_source_offset_bins) + migration_bins: numpy.ndarray + Bin edges for the relative energy migration (``reco_energy / true_energy``) + """ + + bin_width = np.diff(migration_bins) + cdf = np.cumsum(energy_dispersion * bin_width[np.newaxis, :, np.newaxis], axis=1) + + n_energy_bins, _, n_fov_bins = energy_dispersion.shape + + bias = np.full((n_energy_bins, n_fov_bins), np.nan) + resolution = np.full((n_energy_bins, n_fov_bins), np.nan) + + for energy_bin in range(n_energy_bins): + for fov_bin in range(n_fov_bins): + if np.count_nonzero(cdf[energy_bin, :, fov_bin]) == 0: + continue + + low, median, high = np.interp( + [NORM_LOWER_SIGMA, MEDIAN, NORM_UPPER_SIGMA], + cdf[energy_bin, :, fov_bin], + migration_bins[1:] # cdf is defined at upper bin edge + ) + bias[energy_bin, fov_bin] = median - 1 + resolution[energy_bin, fov_bin] = 0.5 * (high - low) + + return bias, resolution
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/binning.html b/_modules/pyirf/binning.html new file mode 100644 index 000000000..1d8e8e7b6 --- /dev/null +++ b/_modules/pyirf/binning.html @@ -0,0 +1,449 @@ + + + + + + pyirf.binning — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.binning

+"""
+Utility functions for binning
+"""
+
+import numpy as np
+from scipy.interpolate import interp1d
+import astropy.units as u
+from astropy.table import QTable
+
+
+#: Index returned by `calculate_bin_indices` for underflown values
+UNDERFLOW_INDEX = np.iinfo(np.int64).min
+#: Index returned by `calculate_bin_indices` for overflown values
+OVERFLOW_INDEX = np.iinfo(np.int64).max
+
+
+
+[docs] +def bin_center(edges): + return 0.5 * (edges[:-1] + edges[1:])
+ + + +
+[docs] +def join_bin_lo_hi(bin_lo, bin_hi): + """ + Function joins bins into lo and hi part, + e.g. [0, 1, 2] and [1, 2, 4] into [0, 1, 2, 4] + It works on multidimentional arrays as long as the binning is in the last axis + + Parameters + ---------- + bin_lo: np.array or u.Quantity + Lo bin edges array + bin_hi: np.array or u.Quantity + Hi bin edges array + + Returns + ------- + bins: np.array of u.Quantity + The joint bins + """ + + if np.allclose(bin_lo[...,1:], bin_hi[...,:-1], rtol=1.e-5): + last_axis=len(bin_lo.shape)-1 + bins = np.concatenate((bin_lo, bin_hi[...,-1:]), axis=last_axis) + return bins + else: + raise ValueError('Not matching bin edges')
+ + + +
+[docs] +def split_bin_lo_hi(bins): + """ + Inverted function to join_bin_hi_lo, + e.g. it splits [0, 1, 2, 4] into [0, 1, 2] and [1, 2, 4] + + Parameters + ---------- + bins: np.array of u.Quantity + The joint bins + + Returns + ------- + bin_lo: np.array or u.Quantity + Lo bin edges array + bin_hi: np.array or u.Quantity + Hi bin edges array + """ + bin_lo=bins[...,:-1] + bin_hi=bins[...,1:] + return bin_lo, bin_hi
+ + +
+[docs] +def add_overflow_bins(bins, positive=True): + """ + Add under and overflow bins to a bin array. + + Parameters + ---------- + bins: np.array or u.Quantity + Bin edges array + positive: bool + If True, the underflow array will start at 0, if not at ``-np.inf`` + """ + lower = 0 if positive else -np.inf + upper = np.inf + + if hasattr(bins, "unit"): + lower *= bins.unit + upper *= bins.unit + + if bins[0] > lower: + bins = np.append(lower, bins) + + if bins[-1] < upper: + bins = np.append(bins, upper) + + return bins
+ + + +
+[docs] +@u.quantity_input(e_min=u.TeV, e_max=u.TeV) +def create_bins_per_decade(e_min, e_max, bins_per_decade=5): + """ + Create a bin array with bins equally spaced in logarithmic energy + with ``bins_per_decade`` bins per decade. + + Parameters + ---------- + e_min: u.Quantity[energy] + Minimum energy, inclusive + e_max: u.Quantity[energy] + Maximum energy, non-inclusive + If the endpoint exactly matches the ``n_bins_per_decade`` requirement, + it will be included. + n_bins_per_decade: int + number of bins per decade + + Returns + ------- + bins: u.Quantity[energy] + The created bin array, will have units of e_min + + """ + unit = e_min.unit + log_lower = np.log10(e_min.to_value(unit)) + log_upper = np.log10(e_max.to_value(unit)) + + step = 1 / bins_per_decade + # include endpoint if reasonably close + eps = step / 10000 + bins = 10 ** np.arange(log_lower, log_upper + eps, step) + return u.Quantity(bins, e_min.unit, copy=False)
+ + + +
+[docs] +def calculate_bin_indices(data, bins): + """ + Calculate bin indices of inidividula entries of the given data array using + the supplied binning. Underflow will be indicated by `UNDERFLOW_INDEX` and + overflow by `OVERFLOW_INDEX`. + + If the bins already include underflow / overflow bins, e.g. + `bins[0] = -np.inf` and `bins[-1] = np.inf`, using the result of this + function will always be a valid index into the resulting histogram. + + + Parameters + ---------- + data: ``~np.ndarray`` or ``~astropy.units.Quantity`` + Array with the data + + bins: ``~np.ndarray`` or ``~astropy.units.Quantity`` + Array or Quantity of bin edges. Must have the same unit as ``data`` if a Quantity. + + + Returns + ------- + bin_index: np.ndarray[int] + Indices of the histogram bin the values in data belong to. + Under- and overflown values will have values of `UNDERFLOW_INDEX` + and `OVERFLOW_INDEX` respectively. + + valid: np.ndarray[bool] + Boolean mask indicating if a given value belongs into one of the defined bins. + False indicates that an entry fell into the over- or underflow bins. + """ + + if hasattr(data, "unit"): + if not hasattr(bins, "unit"): + raise TypeError(f"If ``data`` is a Quantity, so must ``bins``, got {bins}") + unit = data.unit + data = data.to_value(unit) + bins = bins.to_value(unit) + + n_bins = len(bins) - 1 + idx = np.digitize(data, bins) - 1 + + underflow = (idx < 0) + overflow = (idx >= n_bins) + idx[underflow] = UNDERFLOW_INDEX + idx[overflow] = OVERFLOW_INDEX + valid = ~underflow & ~overflow + return idx, valid
+ + + +
+[docs] +def create_histogram_table(events, bins, key="reco_energy"): + """ + Histogram a variable from events data into an astropy table. + + Parameters + ---------- + events : ``astropy.QTable`` + Astropy Table object containing the reconstructed events information. + bins: ``~np.ndarray`` or ``~astropy.units.Quantity`` + Array or Quantity of bin edges. + It must have the same units as ``data`` if a Quantity. + key : ``string`` + Variable to histogram from the events table. + + Returns + ------- + hist: ``astropy.QTable`` + Astropy table containg the histogram. + """ + hist = QTable() + hist[key + "_low"] = bins[:-1] + hist[key + "_high"] = bins[1:] + hist[key + "_center"] = 0.5 * (hist[key + "_low"] + hist[key + "_high"]) + hist["n"], _ = np.histogram(events[key], bins) + + # also calculate weighted number of events + if "weight" in events.colnames: + hist["n_weighted"], _ = np.histogram( + events[key], bins, weights=events["weight"] + ) + else: + hist["n_weighted"] = hist["n"] + + # create counts per particle type, only works if there is at least 1 event + if 'particle_type' in events.colnames and len(events) > 0: + by_particle = events.group_by('particle_type') + + for group_key, group in zip(by_particle.groups.keys, by_particle.groups): + particle = group_key['particle_type'] + + hist["n_" + particle], _ = np.histogram(group[key], bins) + + # also calculate weighted number of events + col = "n_" + particle + if "weight" in events.colnames: + hist[col + "_weighted"], _ = np.histogram( + group[key], bins, weights=group["weight"] + ) + else: + hist[col + "_weighted"] = hist[col] + + return hist
+ + + +
+[docs] +def resample_histogram1d(data, old_edges, new_edges, axis=0): + """ + Rebinning of a histogram by interpolation along a given axis. + + Parameters + ---------- + data : ``numpy.ndarray`` or ``astropy.units.Quantity`` + Histogram. + old_edges : ``numpy.array`` or ``astropy.units.Quantity`` + Binning used to calculate ``data``. + ``len(old_edges) - 1`` needs to equal the length of ``data`` + along interpolation axis (``axis``). + If quantity, needs to be compatible to ``new_edges``. + new_edges : ``numpy.array`` or ``astropy.units.Quantity`` + Binning of new histogram. + If quantity, needs to be compatible to ``old_edges``. + axis : int + Interpolation axis. + + Returns + ------- + ``numpy.ndarray`` or ``astropy.units.Quantity`` + Interpolated histogram with dimension according to ``data`` and ``new_edges``. + If ``data`` is a quantity, this has the same unit. + """ + + data_unit = None + if isinstance(data, u.Quantity): + data_unit = data.unit + data = data.to_value(data_unit) + + over_underflow_bin_width = old_edges[-2] - old_edges[1] + old_edges = u.Quantity( + np.nan_to_num( + old_edges, + posinf=old_edges[-2] + over_underflow_bin_width, + neginf=old_edges[1] - over_underflow_bin_width, + ) + ) + + new_edges = u.Quantity(np.nan_to_num(new_edges)) + + old_edges = old_edges.to(new_edges.unit) + + cumsum = np.insert(data.cumsum(axis=axis), 0, 0, axis=axis) + + norm = data.sum(axis=axis, keepdims=True) + norm[norm == 0] = 1 + cumsum /= norm + + f_integral = interp1d( + old_edges, cumsum, bounds_error=False, + fill_value=(0, 1), kind="quadratic", + axis=axis, + ) + + values = np.diff(f_integral(new_edges), axis=axis) * norm + if data_unit: + values = u.Quantity(values, unit=data_unit) + + return values
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/cut_optimization.html b/_modules/pyirf/cut_optimization.html new file mode 100644 index 000000000..a7ee4a2e9 --- /dev/null +++ b/_modules/pyirf/cut_optimization.html @@ -0,0 +1,288 @@ + + + + + + pyirf.cut_optimization — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.cut_optimization

+import numpy as np
+from astropy.table import QTable
+import astropy.units as u
+from tqdm import tqdm
+import operator
+
+from .cuts import evaluate_binned_cut, calculate_percentile_cut
+from .sensitivity import calculate_sensitivity, estimate_background
+from .binning import create_histogram_table, bin_center
+
+
+__all__ = [
+    "optimize_gh_cut",
+]
+
+
+
+[docs] +def optimize_gh_cut( + signal, + background, + reco_energy_bins, + gh_cut_efficiencies, + theta_cuts, + op=operator.ge, + fov_offset_min=0 * u.deg, + fov_offset_max=1 * u.deg, + alpha=1.0, + progress=True, + **kwargs +): + """ + Optimize the gh-score cut in every energy bin of reconstructed energy + for best sensitivity. + + This procedure is EventDisplay-like, since it only applies a + pre-computed theta cut and then optimizes only the gamma/hadron separation + cut. + + Parameters + ---------- + signal: astropy.table.QTable + event list of simulated signal events. + Required columns are `theta`, `reco_energy`, 'weight', `gh_score` + No directional (theta) or gamma/hadron cut should already be applied. + background: astropy.table.QTable + event list of simulated background events. + Required columns are `reco_source_fov_offset`, `reco_energy`, + 'weight', `gh_score`. + No directional (theta) or gamma/hadron cut should already be applied. + reco_energy_bins: astropy.units.Quantity[energy] + Bins in reconstructed energy to use for sensitivity computation + gh_cut_efficiencies: np.ndarray[float, ndim=1] + The cut efficiencies to scan for best sensitivity. + theta_cuts: astropy.table.QTable + cut definition of the energy dependent theta cut, + e.g. as created by ``calculate_percentile_cut`` + op: comparison function with signature f(a, b) -> bool + The comparison function to use for the gamma hadron score. + Returning true means an event passes the cut, so is not discarded. + E.g. for gammaness-like score, use `operator.ge` (>=) and for a + hadroness-like score use `operator.le` (<=). + fov_offset_min: astropy.units.Quantity[angle] + Minimum distance from the fov center for background events to be taken into account + fov_offset_max: astropy.units.Quantity[angle] + Maximum distance from the fov center for background events to be taken into account + alpha: float + Size ratio of off region / on region. Will be used to + scale the background rate. + progress: bool + If True, show a progress bar during cut optimization + **kwargs are passed to ``calculate_sensitivity`` + """ + + # we apply each cut for all reco_energy_bins globally, calculate the + # sensitivity and then lookup the best sensitivity for each + # bin independently + + signal_selected_theta = evaluate_binned_cut( + signal['theta'], signal['reco_energy'], theta_cuts, + op=operator.le, + ) + + sensitivities = [] + gh_cuts = [] + for efficiency in tqdm(gh_cut_efficiencies, disable=not progress): + + # calculate necessary percentile needed for + # ``calculate_percentile_cut`` with the correct efficiency. + # Depends on the operator, since we need to invert the + # efficiency if we compare using >=, since percentile is + # defines as <=. + if op(-1, 1): # if operator behaves like "<=", "<" etc: + percentile = 100 * efficiency + fill_value = signal['gh_score'].min() + else: # operator behaves like ">=", ">" + percentile = 100 * (1 - efficiency) + fill_value = signal['gh_score'].max() + + gh_cut = calculate_percentile_cut( + signal['gh_score'], signal['reco_energy'], + bins=reco_energy_bins, + fill_value=fill_value, percentile=percentile, + ) + gh_cuts.append(gh_cut) + + # apply the current cut + signal_selected = evaluate_binned_cut( + signal["gh_score"], signal["reco_energy"], gh_cut, op, + ) & signal_selected_theta + + background_selected = evaluate_binned_cut( + background["gh_score"], background["reco_energy"], gh_cut, op, + ) + + # create the histograms + signal_hist = create_histogram_table( + signal[signal_selected], reco_energy_bins, "reco_energy" + ) + + background_hist = estimate_background( + events=background[background_selected], + reco_energy_bins=reco_energy_bins, + theta_cuts=theta_cuts, + alpha=alpha, + fov_offset_min=fov_offset_min, + fov_offset_max=fov_offset_max, + ) + + sensitivity = calculate_sensitivity( + signal_hist, background_hist, alpha=alpha, + **kwargs, + ) + sensitivities.append(sensitivity) + + best_cut_table = QTable() + best_cut_table["low"] = reco_energy_bins[0:-1] + best_cut_table["center"] = bin_center(reco_energy_bins) + best_cut_table["high"] = reco_energy_bins[1:] + best_cut_table["cut"] = np.nan + + best_sensitivity = sensitivities[0].copy() + for bin_id in range(len(reco_energy_bins) - 1): + sensitivities_bin = [s["relative_sensitivity"][bin_id] for s in sensitivities] + + if not np.all(np.isnan(sensitivities_bin)): + # nanargmin won't return the index of nan entries + best = np.nanargmin(sensitivities_bin) + else: + # if all are invalid, just use the first one + best = 0 + + best_sensitivity[bin_id] = sensitivities[best][bin_id] + best_cut_table["cut"][bin_id] = gh_cuts[best]["cut"][bin_id] + + return best_sensitivity, best_cut_table
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/cuts.html b/_modules/pyirf/cuts.html new file mode 100644 index 000000000..4a20a5d16 --- /dev/null +++ b/_modules/pyirf/cuts.html @@ -0,0 +1,295 @@ + + + + + + pyirf.cuts — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.cuts

+import numpy as np
+from astropy.table import Table, QTable
+from scipy.ndimage import gaussian_filter1d
+import astropy.units as u
+
+from .binning import calculate_bin_indices, bin_center
+
+__all__ = [
+    'calculate_percentile_cut',
+    'evaluate_binned_cut',
+    'compare_irf_cuts',
+]
+
+
+
+[docs] +def calculate_percentile_cut( + values, bin_values, bins, fill_value, percentile=68, min_value=None, max_value=None, + smoothing=None, min_events=10, +): + """ + Calculate cuts as the percentile of a given quantity in bins of another + quantity. + + Parameters + ---------- + values: ``~numpy.ndarray`` or ``~astropy.units.Quantity`` + The values for which the cut should be calculated + bin_values: ``~numpy.ndarray`` or ``~astropy.units.Quantity`` + The values used to sort the ``values`` into bins + edges: ``~numpy.ndarray`` or ``~astropy.units.Quantity`` + Bin edges + fill_value: float or quantity + Value for bins with less than ``min_events``, + must have same unit as values + percentile: float + The percentile to calculate in each bin as a percentage, + i.e. 0 <= percentile <= 100. + min_value: float or quantity or None + If given, cuts smaller than this value are replaced with ``min_value`` + max_value: float or quantity or None + If given, cuts larger than this value are replaced with ``max_value`` + smoothing: float or None + If given, apply a gaussian filter of width ``sigma`` in terms + of bins. + min_events: int + Bins with less events than this number are replaced with ``fill_value`` + """ + # create a table to make use of groupby operations + # we use a normal table here to avoid astropy/astropy#13840 + table = Table({"values": values}, copy=False) + unit = table["values"].unit + + # make sure units match + if unit is not None: + fill_value = u.Quantity(fill_value).to(unit) + + if min_value is not None: + min_value = u.Quantity(min_value).to_value(unit) + + if max_value is not None: + max_value = u.Quantity(max_value).to_value(unit) + + bin_index, valid = calculate_bin_indices(bin_values, bins) + by_bin = table[valid].group_by(bin_index[valid]) + + cut_table = QTable() + cut_table["low"] = bins[:-1] + cut_table["high"] = bins[1:] + cut_table["center"] = bin_center(bins) + cut_table["n_events"] = 0 + cut_table["cut"] = np.asanyarray(fill_value, values.dtype) + + for bin_idx, group in zip(by_bin.groups.keys, by_bin.groups): + # replace bins with too few events with fill_value + n_events = len(group) + cut_table["n_events"][bin_idx] = n_events + + if n_events < min_events: + cut_table["cut"][bin_idx] = fill_value + else: + value = np.nanpercentile(group["values"], percentile) + if min_value is not None or max_value is not None: + value = np.clip(value, min_value, max_value) + + cut_table["cut"].value[bin_idx] = value + + if smoothing is not None: + cut_table['cut'].value[:] = gaussian_filter1d( + cut_table["cut"].value, + smoothing, + mode='nearest', + ) + + return cut_table
+ + + +
+[docs] +def evaluate_binned_cut(values, bin_values, cut_table, op): + """ + Evaluate a binned cut as defined in cut_table on given events. + + Events with bin_values outside the bin edges defined in cut table + will be set to False. + + Parameters + ---------- + values: ``~numpy.ndarray`` or ``~astropy.units.Quantity`` + The values on which the cut should be evaluated + bin_values: ``~numpy.ndarray`` or ``~astropy.units.Quantity`` + The values used to sort the ``values`` into bins + cut_table: ``~astropy.table.Table`` + A table describing the binned cuts, e.g. as created by + ``~pyirf.cuts.calculate_percentile_cut``. + Required columns: + - `low`: lower edges of the bins + - `high`: upper edges of the bins, + - `cut`: cut value + op: callable(a, b) -> bool + A function taking two arguments, comparing element-wise and + returning an array of booleans. + Must support vectorized application. + + + Returns + ------- + result: np.ndarray[bool] + A mask for each entry in ``values`` indicating if the event + passes the bin specific cut given in cut table. + """ + if not isinstance(cut_table, QTable): + raise ValueError('cut_table needs to be an astropy.table.QTable') + + bins = np.append(cut_table["low"], cut_table["high"][-1]) + bin_index, valid = calculate_bin_indices(bin_values, bins) + + result = np.zeros(len(values), dtype=bool) + result[valid] = op(values[valid], cut_table["cut"][bin_index[valid]]) + return result
+ + + +
+[docs] +def compare_irf_cuts(cuts): + """ + checks if the same cuts have been applied in all of them + + Parameters + ---------- + cuts: list of QTables + list of cuts each entry in the list correspond to one set of IRFs + Returns + ------- + match: Boolean + if the cuts are the same in all the files + """ + for i in range(len(cuts) - 1): + if (cuts[i] != cuts[i + 1]).any(): + return False + return True
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/gammapy.html b/_modules/pyirf/gammapy.html new file mode 100644 index 000000000..a41563565 --- /dev/null +++ b/_modules/pyirf/gammapy.html @@ -0,0 +1,278 @@ + + + + + + pyirf.gammapy — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.gammapy

+try:
+    import gammapy
+except ImportError:
+    raise ImportError('You need gammapy installed to use this module of pyirf') from None
+
+from gammapy.irf import EffectiveAreaTable2D, PSF3D, EnergyDispersion2D
+from gammapy.maps import MapAxis
+import astropy.units as u
+
+
+
+def _create_offset_axis(fov_offset_bins):
+    return MapAxis.from_edges(fov_offset_bins, name="offset")
+
+def _create_energy_axis_true(true_energy_bins):
+    return MapAxis.from_edges(true_energy_bins, name="energy_true")
+
+
+
+[docs] +@u.quantity_input( + effective_area=u.m ** 2, true_energy_bins=u.TeV, fov_offset_bins=u.deg +) +def create_effective_area_table_2d( + effective_area, + true_energy_bins, + fov_offset_bins, +): + ''' + Create a :py:class:`gammapy.irf.EffectiveAreaTable2D` from pyirf outputs. + + Parameters + ---------- + effective_area: astropy.units.Quantity[area] + Effective area array, must have shape (n_energy_bins, n_fov_offset_bins) + true_energy_bins: astropy.units.Quantity[energy] + Bin edges in true energy + fov_offset_bins: astropy.units.Quantity[angle] + Bin edges in the field of view offset. + For Point-Like IRFs, only giving a single bin is appropriate. + + Returns + ------- + gammapy.irf.EffectiveAreaTable2D + aeff2d: gammapy.irf.EffectiveAreaTable2D + ''' + offset_axis = _create_offset_axis(fov_offset_bins) + energy_axis_true = _create_energy_axis_true(true_energy_bins) + + return EffectiveAreaTable2D( + axes = [energy_axis_true, + offset_axis], + data=effective_area, + )
+ + + + +
+[docs] +@u.quantity_input( + psf=u.sr ** -1, + true_energy_bins=u.TeV, + source_offset_bins=u.deg, + fov_offset_bins=u.deg, +) +def create_psf_3d( + psf, + true_energy_bins, + source_offset_bins, + fov_offset_bins, +): + """ + Create a :py:class:`gammapy.irf.PSF3D` from pyirf outputs. + + Parameters + ---------- + psf: astropy.units.Quantity[(solid angle)^-1] + Point spread function array, must have shape + (n_energy_bins, n_fov_offset_bins, n_source_offset_bins) + true_energy_bins: astropy.units.Quantity[energy] + Bin edges in true energy + source_offset_bins: astropy.units.Quantity[angle] + Bin edges in the source offset. + fov_offset_bins: astropy.units.Quantity[angle] + Bin edges in the field of view offset. + For Point-Like IRFs, only giving a single bin is appropriate. + + Returns + ------- + psf: gammapy.irf.PSF3D + """ + offset_axis = _create_offset_axis(fov_offset_bins) + energy_axis_true = _create_energy_axis_true(true_energy_bins) + rad_axis = MapAxis.from_edges(source_offset_bins, name='rad') + + return PSF3D( + axes = [energy_axis_true, + offset_axis, + rad_axis], + data = psf + )
+ + + +
+[docs] +@u.quantity_input( + true_energy_bins=u.TeV, fov_offset_bins=u.deg, +) +def create_energy_dispersion_2d( + energy_dispersion, + true_energy_bins, + migration_bins, + fov_offset_bins, +): + """ + Create a :py:class:`gammapy.irf.EnergyDispersion2D` from pyirf outputs. + + Parameters + ---------- + energy_dispersion: numpy.ndarray + Energy dispersion array, must have shape + (n_energy_bins, n_migra_bins, n_source_offset_bins) + true_energy_bins: astropy.units.Quantity[energy] + Bin edges in true energy + migration_bins: numpy.ndarray + Bin edges for the relative energy migration (``reco_energy / true_energy``) + fov_offset_bins: astropy.units.Quantity[angle] + Bin edges in the field of view offset. + For Point-Like IRFs, only giving a single bin is appropriate. + + Returns + ------- + edisp: gammapy.irf.EnergyDispersion2D + """ + offset_axis = _create_offset_axis(fov_offset_bins) + energy_axis_true = _create_energy_axis_true(true_energy_bins) + migra_axis = MapAxis.from_edges(migration_bins, name="migra") + + return EnergyDispersion2D( + axes = [energy_axis_true, + migra_axis, + offset_axis], + data = energy_dispersion, + )
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/interpolation/base_extrapolators.html b/_modules/pyirf/interpolation/base_extrapolators.html new file mode 100644 index 000000000..d071768c0 --- /dev/null +++ b/_modules/pyirf/interpolation/base_extrapolators.html @@ -0,0 +1,261 @@ + + + + + + pyirf.interpolation.base_extrapolators — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.interpolation.base_extrapolators

+"""Base classes for extrapolators"""
+from abc import ABCMeta, abstractmethod
+
+import numpy as np
+from pyirf.binning import bin_center
+from pyirf.interpolation.base_interpolators import PDFNormalization
+
+__all__ = ["BaseExtrapolator", "ParametrizedExtrapolator", "DiscretePDFExtrapolator"]
+
+
+
+[docs] +class BaseExtrapolator(metaclass=ABCMeta): + """ + Base class for all extrapolators, only knowing grid-points, + providing a common __call__-interface. + """ + + def __init__(self, grid_points): + """BaseExtrapolator + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims): + Grid points at which templates exist + + """ + self.grid_points = grid_points + if self.grid_points.ndim == 1: + self.grid_points = self.grid_points.reshape(*self.grid_points.shape, 1) + self.N = self.grid_points.shape[0] + self.grid_dim = self.grid_points.shape[1] + +
+[docs] + @abstractmethod + def extrapolate(self, target_point): + """Overridable function for the actual extrapolation code"""
+ + +
+[docs] + def __call__(self, target_point): + """Providing a common __call__ interface + + Parameters + ---------- + target_point: np.ndarray, shape=(1, n_dims) + Target for extrapolation + + Returns + ------- + Extrapolated result. + """ + return self.extrapolate(target_point=target_point)
+
+ + + +
+[docs] +class ParametrizedExtrapolator(BaseExtrapolator): + """ + Base class for all extrapolators used with IRF components that can be + treated independently, e.g. parametrized ones like 3Gauss + but also AEff. Derived from pyirf.interpolation.BaseExtrapolator + """ + + def __init__(self, grid_points, params): + """ParametrizedExtrapolator + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which templates exist + params: np.ndarray, shape=(n_points, ..., n_params) + Corresponding parameter values at each point in grid_points. + First dimesion has to correspond to number of grid_points + + Note + ---- + Also calls pyirf.interpolation.BaseExtrapolators.__init__ + """ + super().__init__(grid_points) + + self.params = params + + if self.params.ndim == 1: + self.params = self.params[..., np.newaxis]
+ + + +
+[docs] +class DiscretePDFExtrapolator(BaseExtrapolator): + """ + Base class for all extrapolators used with binned IRF components like EDisp. + Derived from pyirf.interpolation.BaseExtrapolator + """ + + def __init__( + self, grid_points, bin_edges, binned_pdf, normalization=PDFNormalization.AREA + ): + """DiscretePDFExtrapolator + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which templates exist + bin_edges: np.ndarray, shape=(n_bins+1) + Edges of the data binning + binned_pdf: np.ndarray, shape=(n_points, ..., n_bins) + Content of each bin in bin_edges for + each point in grid_points. First dimesion has to correspond to number + of grid_points, last dimension has to correspond to number of bins for + the quantity that should be extrapolated (e.g. the Migra axis for EDisp) + normalization: PDFNormalization + How the PDF is normalized + + Note + ---- + Also calls pyirf.interpolation.BaseExtrapolators.__init__ + """ + super().__init__(grid_points) + + self.normalization = normalization + self.bin_edges = bin_edges + self.bin_mids = bin_center(self.bin_edges) + self.binned_pdf = binned_pdf
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/interpolation/base_interpolators.html b/_modules/pyirf/interpolation/base_interpolators.html new file mode 100644 index 000000000..91ac69518 --- /dev/null +++ b/_modules/pyirf/interpolation/base_interpolators.html @@ -0,0 +1,281 @@ + + + + + + pyirf.interpolation.base_interpolators — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.interpolation.base_interpolators

+"""Base classes for interpolators"""
+from abc import ABCMeta, abstractmethod
+import enum
+
+import numpy as np
+
+from ..binning import bin_center
+
+__all__ = [
+    "BaseInterpolator",
+    "ParametrizedInterpolator",
+    "DiscretePDFInterpolator",
+    "PDFNormalization",
+]
+
+
+
+[docs] +class PDFNormalization(enum.Enum): + """How a discrete PDF is normalized""" + + #: PDF is normalized to a "normal" area integral of 1 + AREA = enum.auto() + #: PDF is normalized to 1 over the solid angle integral where the bin + #: edges represent the opening angles of cones in radian. + CONE_SOLID_ANGLE = enum.auto()
+ + + +
+[docs] +class BaseInterpolator(metaclass=ABCMeta): + """ + Base class for all interpolators, only knowing grid-points, + providing a common __call__-interface. + """ + + def __init__(self, grid_points): + """BaseInterpolator + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims): + Grid points at which interpolation templates exist + + """ + self.grid_points = grid_points + if self.grid_points.ndim == 1: + self.grid_points = self.grid_points.reshape(*self.grid_points.shape, 1) + self.n_points = self.grid_points.shape[0] + self.grid_dim = self.grid_points.shape[1] + +
+[docs] + @abstractmethod + def interpolate(self, target_point): + """Overridable function for the actual interpolation code"""
+ + +
+[docs] + def __call__(self, target_point): + """Providing a common __call__ interface + + Parameters + ---------- + target_point: np.ndarray, shape=(1, n_dims) + Target for inter-/extrapolation + When target_point is outside of the grids convex hull but extrapolator is None + + Returns + ------- + Interpolated result. + """ + return self.interpolate(target_point=target_point)
+
+ + + +
+[docs] +class ParametrizedInterpolator(BaseInterpolator): + """ + Base class for all interpolators used with IRF components that can be + independently interpolated, e.g. parametrized ones like 3Gauss + but also AEff. Derived from pyirf.interpolation.BaseInterpolator + """ + + def __init__(self, grid_points, params): + """ParametrizedInterpolator + + Parameters + ---------- + grid_points, np.ndarray, shape=(n_points, n_dims) + Grid points at which interpolation templates exist + params: np.ndarray, shape=(n_points, ..., n_params) + Corresponding parameter values at each point in grid_points. + First dimesion has to correspond to number of grid_points + + Note + ---- + Also calls pyirf.interpolation.BaseInterpolators.__init__ + """ + super().__init__(grid_points) + + self.params = params + + if self.params.ndim == 1: + self.params = self.params[..., np.newaxis]
+ + + +
+[docs] +class DiscretePDFInterpolator(BaseInterpolator): + """ + Base class for all interpolators used with binned IRF components like EDisp. + Derived from pyirf.interpolation.BaseInterpolator + """ + + def __init__( + self, grid_points, bin_edges, binned_pdf, normalization=PDFNormalization.AREA + ): + """DiscretePDFInterpolator + + Parameters + ---------- + grid_points : np.ndarray, shape=(n_points, n_dims) + Grid points at which interpolation templates exist + bin_edges : np.ndarray, shape=(n_bins+1) + Edges of the data binning + binned_pdf : np.ndarray, shape=(n_points, ..., n_bins) + Content of each bin in bin_edges for + each point in grid_points. First dimesion has to correspond to number + of grid_points, last dimension has to correspond to number of bins for + the quantity that should be interpolated (e.g. the Migra axis for EDisp) + normalization : PDFNormalization + How the PDF is normalized + + Note + ---- + Also calls pyirf.interpolation.BaseInterpolators.__init__ + """ + super().__init__(grid_points) + + self.bin_edges = bin_edges + self.bin_mids = bin_center(self.bin_edges) + self.binned_pdf = binned_pdf + self.normalization = normalization
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/interpolation/component_estimators.html b/_modules/pyirf/interpolation/component_estimators.html new file mode 100644 index 000000000..b6b4ab450 --- /dev/null +++ b/_modules/pyirf/interpolation/component_estimators.html @@ -0,0 +1,1023 @@ + + + + + + pyirf.interpolation.component_estimators — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.interpolation.component_estimators

+"""Classes to estimate (interpolate/extrapolate) actual IRF HDUs"""
+import warnings
+
+import astropy.units as u
+import numpy as np
+from scipy.spatial import Delaunay
+
+from .base_extrapolators import DiscretePDFExtrapolator, ParametrizedExtrapolator
+from .base_interpolators import (
+    DiscretePDFInterpolator,
+    ParametrizedInterpolator,
+    PDFNormalization,
+)
+from .griddata_interpolator import GridDataInterpolator
+from .nearest_neighbor_searcher import BaseNearestNeighborSearcher
+from .quantile_interpolator import QuantileInterpolator
+from .utils import find_nearest_simplex
+
+__all__ = [
+    "BaseComponentEstimator",
+    "DiscretePDFComponentEstimator",
+    "ParametrizedComponentEstimator",
+    "EffectiveAreaEstimator",
+    "RadMaxEstimator",
+    "EnergyDispersionEstimator",
+    "PSFTableEstimator",
+]
+
+
+
+[docs] +class BaseComponentEstimator: + """ + Base class for all Estimators working on specific IRF components. + + While usable, it is encouraged to use the actual class for the respective IRF + component as it ensures further checks and if necessary e.g. unit handling. + """ + + def __init__(self, grid_points): + """ + Base __init__, doing sanity checks on the grid, building a + triangulated version of the grid and instantiating inter- and extrapolator. + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims): + + Raises + ------ + TypeError: + When grid_points is not a np.ndarray of float compatible values + TypeError: + When grid_point has dtype object + ValueError: + When there are too few points in grid_points to span a volume + in the grid dimension. + """ + if not isinstance(grid_points, np.ndarray): + raise TypeError("Input grid_points is not a numpy array.") + if grid_points.dtype == "O": + raise TypeError("Input grid_points array cannot be of dtype object.") + if not np.can_cast(grid_points.dtype, np.float128): + raise TypeError("Input grid_points dtype incompatible with float.") + + self.grid_points = grid_points + if self.grid_points.ndim == 1: + self.grid_points = self.grid_points.reshape(*self.grid_points.shape, 1) + self.n_points = self.grid_points.shape[0] + self.grid_dim = self.grid_points.shape[1] + + # Check, if number of grid point theoretically suffices to span a volume + # in the dimension indicated by grid + if self.n_points < self.grid_dim + 1: + raise ValueError( + f"To few points for grid dimension, grid-dim is {self.grid_dim}," + f" while there are only {self.n_points}. At least {self.grid_dim+1}" + f" points needed to span a volume in {self.grid_dim} dimensions." + ) + + # Build triangulation to check if target is inside of the grid for + # more then 1 dimension + if self.grid_dim > 1: + self.triangulation = Delaunay(self.grid_points) + + def _target_in_grid(self, target_point): + """Check whether target_point lies within grids convex hull, uses + simple comparison for 1D and Delaunay triangulation for >1D.""" + if self.grid_dim == 1: + return (target_point >= self.grid_points.min()) and ( + target_point <= self.grid_points.max() + ) + else: + # Delaunay.find_simplex() returns -1 for points outside the grids convex hull + simplex_ind = self.triangulation.find_simplex(target_point) + return simplex_ind >= 0 + +
+[docs] + def __call__(self, target_point): + """Inter-/ Extrapolation as needed and sanity checking of + the target point + + Parameters + ---------- + target_point: np.ndarray, shape=(1, n_dims) + Target for inter-/extrapolation + + Raises + ------ + TypeError: + When target_point is not an np.ndarray + ValueError: + When more then one target_point is given + ValueError: + When target_point and grid_points have miss-matching dimensions + ValueError: + When target_point is outside of the grids convex hull but extrapolator is None + Warning: + When target_points need extrapolation + + Returns + ------- + Interpolated or, if necessary extrapolated, result. + """ + if not isinstance(target_point, np.ndarray): + raise TypeError("Target point is not a numpy array.") + + if target_point.ndim == 1: + target_point = target_point.reshape(1, *target_point.shape) + elif target_point.shape[0] != 1: + raise ValueError("Only one target_point per call supported.") + + if target_point.shape[1] != self.grid_dim: + raise ValueError( + "Mismatch between target-point and grid dimension." + f" Grid has dimension {self.grid_dim}, target has dimension" + f" {target_point.shape[1]}." + ) + + if self._target_in_grid(target_point): + return self.interpolator(target_point) + elif self.extrapolator is not None: + warnings.warn(f"Target point {target_point} has to be extrapolated.") + return self.extrapolator(target_point) + else: + raise ValueError( + "Target point outside grids convex hull and no extrapolator given." + )
+
+ + + +
+[docs] +class DiscretePDFComponentEstimator(BaseComponentEstimator): + """ + Base class for all Estimators working on IRF components that represent discretized PDFs. + + While usable, it is encouraged to use the actual class for the respective IRF + component as it ensures further checks and if necessary e.g. unit handling. + """ + + def __init__( + self, + grid_points, + bin_edges, + binned_pdf, + interpolator_cls=QuantileInterpolator, + interpolator_kwargs=None, + extrapolator_cls=None, + extrapolator_kwargs=None, + ): + """ + __init__ for all discrete PDF components, calls BaseComponentEstimator's + __init__ and instantiates inter- and extrapolator objects. + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims): + Grid points at which interpolation templates exist + bin_edges: np.ndarray, shape=(n_bins+1) + Common set of bin-edges for all discretized PDFs. + binned_pdf: np.ndarray, shape=(n_points, ..., n_bins) + Discretized PDFs for all grid points and arbitrary further dimensions + (in IRF term e.g. field-of-view offset bins). Actual interpolation dimension, + meaning the dimensions that contain actual histograms, have to be along + the last axis. + interpolator_cls: + pyirf interpolator class, defaults to QuantileInterpolator. + interpolator_kwargs: dict + Dict of all kwargs that are passed to the interpolator, defaults to + None which is the same as passing an empty dict. + extrapolator_cls: + pyirf extrapolator class. Can be and defaults to ``None``, + which raises an error if a target_point is outside the grid + and extrapolation would be needed. + extrapolator_kwargs: dict + Dict of all kwargs that are passed to the extrapolator, defaults to + None which is the same as passing an empty dict. + + Raises + ------ + TypeError: + When bin_edges is not a np.ndarray. + TypeError: + When binned_pdf is not a np.ndarray. + TypeError: + When interpolator_cls is not a DiscretePDFInterpolator subclass. + TypeError: + When extrapolator_cls is not a DiscretePDFExtrapolator subclass. + ValueError: + When number of bins in bin_edges and contents in binned_pdf is + not matching. + ValueError: + When number of histograms in binned_pdf and points in grid_points + is not matching. + + Note + ---- + Also calls pyirf.interpolation.BaseComponentEstimator.__init__ + """ + + super().__init__( + grid_points, + ) + + if not isinstance(binned_pdf, np.ndarray): + raise TypeError("Input binned_pdf is not a numpy array.") + elif self.n_points != binned_pdf.shape[0]: + raise ValueError( + f"Shape mismatch, number of grid_points ({self.n_points}) and " + f"number of histograms in binned_pdf ({binned_pdf.shape[0]}) " + "not matching." + ) + elif not isinstance(bin_edges, np.ndarray): + raise TypeError("Input bin_edges is not a numpy array.") + elif binned_pdf.shape[-1] != (bin_edges.shape[0] - 1): + raise ValueError( + f"Shape mismatch, bin_edges ({bin_edges.shape[0] - 1} bins) " + f"and binned_pdf ({binned_pdf.shape[-1]} bins) not matching." + ) + + # Make sure that 1D input is sorted in increasing order + if self.grid_dim == 1: + sorting_inds = np.argsort(self.grid_points.squeeze()) + + self.grid_points = self.grid_points[sorting_inds] + binned_pdf = binned_pdf[sorting_inds] + + if interpolator_kwargs is None: + interpolator_kwargs = {} + + if extrapolator_kwargs is None: + extrapolator_kwargs = {} + + if not issubclass(interpolator_cls, DiscretePDFInterpolator): + raise TypeError( + f"interpolator_cls must be a DiscretePDFInterpolator subclass, got {interpolator_cls}" + ) + + self.interpolator = interpolator_cls( + self.grid_points, bin_edges, binned_pdf, **interpolator_kwargs + ) + + if extrapolator_cls is None: + self.extrapolator = None + elif not issubclass(extrapolator_cls, DiscretePDFExtrapolator): + raise TypeError( + f"extrapolator_cls must be a DiscretePDFExtrapolator subclass, got {extrapolator_cls}" + ) + else: + self.extrapolator = extrapolator_cls( + self.grid_points, bin_edges, binned_pdf, **extrapolator_kwargs + )
+ + + +
+[docs] +class ParametrizedComponentEstimator(BaseComponentEstimator): + """ + Base class for all Estimators working on IRF components that represent parametrized + or scalar quantities. + + While usable, it is encouraged to use the actual class for the respective IRF + component as it ensures further checks and if necessary e.g. unit handling. + """ + + def __init__( + self, + grid_points, + params, + interpolator_cls=GridDataInterpolator, + interpolator_kwargs=None, + extrapolator_cls=None, + extrapolator_kwargs=None, + ): + """ + __init__ for all parametrized components, calls BaseComponentEstimator's + __init__ and instantiates inter- and extrapolator objects. + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims): + Grid points at which interpolation templates exist + params: np.ndarray, shape=(n_points, ..., n_params) + Corresponding parameter values at each point in grid_points. + First dimension has to correspond to the number of grid_points. + interpolator_cls: + pyirf interpolator class, defaults to GridDataInterpolator. + interpolator_kwargs: dict + Dict of all kwargs that are passed to the interpolator, defaults to + None which is the same as passing an empty dict. + extrapolator_cls: + pyirf extrapolator class. Can be and defaults to ``None``, + which raises an error if a target_point is outside the grid + and extrapolation would be needed. + extrapolator_kwargs: dict + Dict of all kwargs that are passed to the extrapolator, defaults to + None which is the same as passing an empty dict. + + Raises + ------ + TypeError: + When interpolator_cls is not a ParametrizedInterpolator subclass. + TypeError: + When extrapolator_cls is not a ParametrizedExtrapolator subclass. + TypeError: + When params is not a np.ndarray. + ValueError: + When number of points grid_points and params do not match. + + Note + ---- + Also calls pyirf.interpolation.BaseComponentEstimator.__init__ + """ + + super().__init__( + grid_points, + ) + + if not isinstance(params, np.ndarray): + raise TypeError("Input params is not a numpy array.") + elif self.n_points != params.shape[0]: + raise ValueError( + "Shape mismatch, number of grid_points and rows in params not matching." + ) + + # Make sure that 1D input is sorted in increasing order + if self.grid_dim == 1: + sorting_inds = np.argsort(self.grid_points.squeeze()) + + self.grid_points = self.grid_points[sorting_inds] + params = params[sorting_inds] + + if interpolator_kwargs is None: + interpolator_kwargs = {} + + if extrapolator_kwargs is None: + extrapolator_kwargs = {} + + if not issubclass(interpolator_cls, ParametrizedInterpolator): + raise TypeError( + f"interpolator_cls must be a ParametrizedInterpolator subclass, got {interpolator_cls}" + ) + + self.interpolator = interpolator_cls( + self.grid_points, params, **interpolator_kwargs + ) + + if extrapolator_cls is None: + self.extrapolator = None + elif not issubclass(extrapolator_cls, ParametrizedExtrapolator): + raise TypeError( + f"extrapolator_cls must be a ParametrizedExtrapolator subclass, got {extrapolator_cls}" + ) + else: + self.extrapolator = extrapolator_cls( + self.grid_points, params, **extrapolator_kwargs + )
+ + + +
+[docs] +class EffectiveAreaEstimator(ParametrizedComponentEstimator): + """Estimator class for effective area tables (AEFF_2D).""" + + @u.quantity_input(effective_area=u.m**2, min_effective_area=u.m**2) + def __init__( + self, + grid_points, + effective_area, + interpolator_cls=GridDataInterpolator, + interpolator_kwargs=None, + extrapolator_cls=None, + extrapolator_kwargs=None, + min_effective_area=1 * u.m**2, + ): + """ + Takes a grid of effective areas for a bunch of different parameters + and inter-/extrapolates (log) effective areas to given value of + those parameters. + + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims): + Grid points at which interpolation templates exist + effective_area: np.ndarray of astropy.units.Quantity[area], shape=(n_points, ...) + Grid of effective area. Dimensions but the first can in principle be freely + chosen. Class is AEFF2D compatible, which would require + shape=(n_points, n_energy_bins, n_fov_offset_bins). + interpolator_cls: + pyirf interpolator class, defaults to GridDataInterpolator. + interpolator_kwargs: dict + Dict of all kwargs that are passed to the interpolator, defaults to + None which is the same as passing an empty dict. + extrapolator_cls: + pyirf extrapolator class. Can be and defaults to ``None``, + which raises an error if a target_point is outside the grid + and extrapolation would be needed. + extrapolator_kwargs: dict + Dict of all kwargs that are passed to the extrapolator, defaults to + None which is the same as passing an empty dict. + min_effective_area: astropy.units.Quantity[area] + Minimum value of effective area to be considered for interpolation. Values + lower than this value are set to this value. Defaults to 1 m**2. + + + Note + ---- + Also calls __init__ of pyirf.interpolation.BaseComponentEstimator + and pyirf.interpolation.ParametrizedEstimator + """ + + # get rid of units + effective_area = effective_area.to_value(u.m**2) + min_effective_area = min_effective_area.to_value(u.m**2) + + self.min_effective_area = min_effective_area + + # remove zeros and log it + effective_area[ + effective_area < self.min_effective_area + ] = self.min_effective_area + effective_area = np.log(effective_area) + + super().__init__( + grid_points=grid_points, + params=effective_area, + interpolator_cls=interpolator_cls, + interpolator_kwargs=interpolator_kwargs, + extrapolator_cls=extrapolator_cls, + extrapolator_kwargs=extrapolator_kwargs, + ) + +
+[docs] + def __call__(self, target_point): + """ + Estimating effective area at target_point, inter-/extrapolates as needed and + specified in __init__. + + Parameters + ---------- + target_point: np.ndarray, shape=(1, n_dims) + Target for inter-/extrapolation + + Returns + ------- + aeff_interp: np.ndarray of (astropy.units.m)**2, shape=(n_points, ...) + Interpolated Effective area array with same shape as input + effective areas. For AEFF2D of shape (n_energy_bins, n_fov_offset_bins). + Values lower or equal to __init__'s min_effective_area are set + to zero. + """ + + aeff_interp = super().__call__(target_point) + + # exp it and set to zero too low values + aeff_interp = np.exp(aeff_interp) + # remove entries manipulated by min_effective_area + aeff_interp[aeff_interp < self.min_effective_area] = 0 + + return u.Quantity(aeff_interp, u.m**2, copy=False)
+
+ + + +
+[docs] +class RadMaxEstimator(ParametrizedComponentEstimator): + """Estimator class for rad-max tables (RAD_MAX, RAD_MAX_2D).""" + + def __init__( + self, + grid_points, + rad_max, + fill_value=None, + interpolator_cls=GridDataInterpolator, + interpolator_kwargs=None, + extrapolator_cls=None, + extrapolator_kwargs=None, + ): + """ + Takes a grid of rad max values for a bunch of different parameters + and inter-/extrapolates rad max values to given value of those parameters. + + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims): + Grid points at which interpolation templates exist + rad_max: np.ndarray, shape=(n_points, ...) + Grid of theta cuts. Dimensions but the first can in principle be freely + chosen. Class is RAD_MAX_2D compatible, which would require + shape=(n_points, n_energy_bins, n_fov_offset_bins). + fill_val: + Indicator of fill-value handling. If None, fill values are regarded as + normal values and no special handeling is applied. If "infer", fill values + will be infered as max of all values, if a value is provided, + it is used to flag fill values. Flagged fill-values are + not used for interpolation. Fill-value handling is only supported in + up to two grid dimensions. + interpolator_cls: + pyirf interpolator class, defaults to GridDataInterpolator. + interpolator_kwargs: dict + Dict of all kwargs that are passed to the interpolator, defaults to + None which is the same as passing an empty dict. + extrapolator_cls: + pyirf extrapolator class. Can be and defaults to ``None``, + which raises an error if a target_point is outside the grid + and extrapolation would be needed. + extrapolator_kwargs: dict + Dict of all kwargs that are passed to the extrapolator, defaults to + None which is the same as passing an empty dict. + + Note + ---- + Also calls __init__ of pyirf.interpolation.BaseComponentEstimator + and pyirf.interpolation.ParametrizedEstimator + """ + + super().__init__( + grid_points=grid_points, + params=rad_max, + interpolator_cls=interpolator_cls, + interpolator_kwargs=interpolator_kwargs, + extrapolator_cls=extrapolator_cls, + extrapolator_kwargs=extrapolator_kwargs, + ) + + self.params = rad_max + + if fill_value is None: + self.fill_val = None + elif fill_value == "infer": + self.fill_val = np.max(self.params) + else: + self.fill_val = fill_value + + # Raise error if fill-values should be handled in >=3 dims + if self.fill_val and self.grid_dim >= 3: + raise ValueError( + "Fill-value handling only supported in up to two grid dimensions." + ) + + # If fill-values should be handled in 2D, construct a trinangulation + # to later determine in which simplex the target values is + if self.fill_val and (self.grid_dim == 2): + self.triangulation = Delaunay(self.grid_points) + +
+[docs] + def __call__(self, target_point): + """ + Estimating rad max table at target_point, inter-/extrapolates as needed and + specified in __init__. + + Parameters + ---------- + target_point: np.ndarray, shape=(1, n_dims) + Target for inter-/extrapolation + + Returns + ------- + rad_max_interp: np.ndarray, shape=(n_points, ...) + Interpolated RAD_MAX table with same shape as input + effective areas. For RAD_MAX_2D of shape (n_energy_bins, n_fov_offset_bins) + + """ + # First, construct estimation without handling fill-values + full_estimation = super().__call__(target_point) + # Safeguard against extreme extrapolation cases + np.clip(full_estimation, 0, None, out=full_estimation) + + # Early exit if fill_values should not be handled + if not self.fill_val: + return full_estimation + + # Early exit if a nearest neighbor estimation would be overwritten + # Complex setup is needed to catch settings where the user mixes approaches and + # e.g. uses nearest neighbors for extrapolation and an actual interpolation otherwise + if self.grid_dim == 1: + if ( + (target_point < self.grid_points.min()) + or (target_point > self.grid_points.max()) + ) and issubclass(self.extrapolator.__class__, BaseNearestNeighborSearcher): + return full_estimation + elif issubclass(self.interpolator.__class__, BaseNearestNeighborSearcher): + return full_estimation + elif self.grid_dim == 2: + target_simplex = self.triangulation.find_simplex(target_point) + + if (target_simplex == -1) and issubclass( + self.extrapolator.__class__, BaseNearestNeighborSearcher + ): + return full_estimation + elif issubclass(self.interpolator.__class__, BaseNearestNeighborSearcher): + return full_estimation + + # Actual fill-value handling + if self.grid_dim == 1: + # Locate target in grid + if target_point < self.grid_points.min(): + segment_inds = np.array([0, 1], "int") + elif target_point > self.grid_points.max(): + segment_inds = np.array([-2, -1], "int") + else: + target_bin = np.digitize( + target_point.squeeze(), self.grid_points.squeeze() + ) + segment_inds = np.array([target_bin - 1, target_bin], "int") + + mask_left = self.params[segment_inds[0]] >= self.fill_val + mask_right = self.params[segment_inds[1]] >= self.fill_val + # Indicate, wether one of the neighboring entries is a fill-value + mask = np.logical_or(mask_left, mask_right) + elif self.grid_dim == 2: + # Locate target + target_simplex = self.triangulation.find_simplex(target_point) + + if target_simplex == -1: + target_simplex = find_nearest_simplex(self.triangulation, target_point) + + simplex_nodes_indices = self.triangulation.simplices[ + target_simplex + ].squeeze() + + mask0 = self.params[simplex_nodes_indices[0]] >= self.fill_val + mask1 = self.params[simplex_nodes_indices[1]] >= self.fill_val + mask2 = self.params[simplex_nodes_indices[2]] >= self.fill_val + + # This collected mask now counts for each entry in the estimation how many + # of the entries used for extrapolation contained fill-values + intermediate_mask = ( + mask0.astype("int") + mask1.astype("int") + mask2.astype("int") + ) + mask = np.full_like(intermediate_mask, True, dtype=bool) + + # Simplest cases: All or none entries were fill-values, so either return + # a fill-value or the actual estimation + mask[intermediate_mask == 0] = False + mask[intermediate_mask == 3] = True + + # If two out of three values were fill-values return a fill-value as estimate + mask[intermediate_mask == 2] = True + + # If only one out of three values was a fill-value use the smallest value of the + # remaining two + mask[intermediate_mask == 1] = False + full_estimation = np.where( + intermediate_mask[np.newaxis, :] == 1, + np.min(self.params[simplex_nodes_indices], axis=0), + full_estimation, + ) + + # Set all flagged values to fill-value + full_estimation[mask[np.newaxis, :]] = self.fill_val + + # Safeguard against extreme extrapolation cases + full_estimation[full_estimation > self.fill_val] = self.fill_val + + return full_estimation
+
+ + + +
+[docs] +class EnergyDispersionEstimator(DiscretePDFComponentEstimator): + """Estimator class for energy dispersions (EDISP_2D).""" + + def __init__( + self, + grid_points, + migra_bins, + energy_dispersion, + interpolator_cls=QuantileInterpolator, + interpolator_kwargs=None, + extrapolator_cls=None, + extrapolator_kwargs=None, + axis=-2, + ): + """ + Takes a grid of energy dispersions for a bunch of different parameters and + inter-/extrapolates energy dispersions to given value of those parameters. + + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which interpolation templates exist + migra_bins: np.ndarray, shape=(n_migration_bins+1) + Common bin edges along migration axis. + energy_dispersion: np.ndarray, shape=(n_points, ..., n_migration_bins, ...) + EDISP MATRIX. Class is EDISP_2D compatible, which would require + shape=(n_points, n_energy_bins, n_migration_bins, n_fov_offset_bins). + This is assumed as default. If these axes are in different order + or e.g. missing a fov_offset axis, the axis containing n_migration_bins + has to be specified through axis. + interpolator_cls: + pyirf interpolator class, defaults to QuantileInterpolator. + interpolator_kwargs: dict + Dict of all kwargs that are passed to the interpolator, defaults to + None which is the same as passing an empty dict. + extrapolator_cls: + pyirf extrapolator class. Can be and defaults to ``None``, + which raises an error if a target_point is outside the grid + and extrapolation would be needed. + extrapolator_kwargs: dict + Dict of all kwargs that are passed to the extrapolator, defaults to + None which is the same as passing an empty dict. + axis: + Axis, along which the actual n_migration_bins are. Input is assumed to + be EDISP_2D compatible, so this defaults to -2 + + Note + ---- + Also calls __init__ of pyirf.interpolation.BaseComponentEstimator + and pyirf.interpolation.ParametrizedEstimator + """ + + self.axis = axis + + super().__init__( + grid_points=grid_points, + bin_edges=migra_bins, + binned_pdf=np.swapaxes(energy_dispersion, axis, -1), + interpolator_cls=interpolator_cls, + interpolator_kwargs=interpolator_kwargs, + extrapolator_cls=extrapolator_cls, + extrapolator_kwargs=extrapolator_kwargs, + ) + +
+[docs] + def __call__(self, target_point): + """ + Estimating energy dispersions at target_point, inter-/extrapolates as needed and + specified in __init__. + + Parameters + ---------- + target_point: np.ndarray, shape=(1, n_dims) + Target for inter-/extrapolation + + Returns + ------- + edisp_interp: np.ndarray, shape=(n_points, ..., n_migration_bins, ...) + Interpolated EDISP matrix with same shape as input matrices. For EDISP_2D + of shape (n_points, n_energy_bins, n_migration_bins, n_fov_offset_bins) + + """ + + return np.swapaxes(super().__call__(target_point), -1, self.axis)
+
+ + + +
+[docs] +class PSFTableEstimator(DiscretePDFComponentEstimator): + """Estimator class for point spread function tables (PSF_TABLE).""" + + @u.quantity_input(psf=u.sr**-1, source_offset_bins=u.deg) + def __init__( + self, + grid_points, + source_offset_bins, + psf, + interpolator_cls=QuantileInterpolator, + interpolator_kwargs=None, + extrapolator_cls=None, + extrapolator_kwargs=None, + axis=-1, + ): + """ + Takes a grid of psfs or a bunch of different parameters and + inter-/extrapolates psfs to given value of those parameters. + + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which interpolation templates exist + source_offset_bins: np.ndarray, shape=(n_source_offset_bins+1) of astropy.units.Quantity[deg] + Common bin edges along source offset axis. + psf: np.ndarray, shape=(n_points, ..., n_source_offset_bins) of astropy.units.Quantity[sr**-1] + PSF Tables. Class is PSF_TABLE compatible, which would require + shape=(n_points, n_energy_bins, n_fov_offset_bins, n_source_offset_bins). + This is assumed as default. If these axes are in different order + the axis containing n_source_offset_bins has to be specified through axis. + interpolator_cls: + pyirf interpolator class, defaults to QuantileInterpolator. + interpolator_kwargs: dict + Dict of all kwargs that are passed to the interpolator, defaults to + None which is the same as passing an empty dict. + extrapolator_cls: + pyirf extrapolator class. Can be and defaults to ``None``, + which raises an error if a target_point is outside the grid + and extrapolation would be needed. + extrapolator_kwargs: dict + Dict of all kwargs that are passed to the extrapolator, defaults to + None which is the same as passing an empty dict. + axis: + Axis, along which the actual n_source_offset_bins are. Input is assumed to + be PSF_TABLE compatible, so this defaults to -1 + + Note + ---- + Also calls __init__ of pyirf.interpolation.BaseComponentEstimator + and pyirf.interpolation.ParametrizedEstimator + """ + + self.axis = axis + + psf = np.swapaxes(psf, axis, -1) + + if interpolator_kwargs is None: + interpolator_kwargs = {} + + if extrapolator_kwargs is None: + extrapolator_kwargs = {} + + interpolator_kwargs.setdefault( + "normalization", PDFNormalization.CONE_SOLID_ANGLE + ) + extrapolator_kwargs.setdefault( + "normalization", PDFNormalization.CONE_SOLID_ANGLE + ) + + super().__init__( + grid_points=grid_points, + bin_edges=source_offset_bins.to_value(u.rad), + binned_pdf=psf, + interpolator_cls=interpolator_cls, + interpolator_kwargs=interpolator_kwargs, + extrapolator_cls=extrapolator_cls, + extrapolator_kwargs=extrapolator_kwargs, + ) + +
+[docs] + def __call__(self, target_point): + """ + Estimating psf tables at target_point, inter-/extrapolates as needed and + specified in __init__. + + Parameters + ---------- + target_point: np.ndarray, shape=(1, n_dims) + Target for inter-/extrapolation + + Returns + ------- + psf_interp: u.Quantity[sr-1], shape=(n_points, ..., n_source_offset_bins) + Interpolated psf table with same shape as input matrices. For PSF_TABLE + of shape (n_points, n_energy_bins, n_fov_offset_bins, n_source_offset_bins) + + """ + + interpolated_psf_normed = super().__call__(target_point) + + # Undo normalisation to get a proper PSF and return + return u.Quantity( + np.swapaxes(interpolated_psf_normed, -1, self.axis), u.sr**-1, copy=False + )
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/interpolation/griddata_interpolator.html b/_modules/pyirf/interpolation/griddata_interpolator.html new file mode 100644 index 000000000..8b4aae4e1 --- /dev/null +++ b/_modules/pyirf/interpolation/griddata_interpolator.html @@ -0,0 +1,207 @@ + + + + + + pyirf.interpolation.griddata_interpolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.interpolation.griddata_interpolator

+"""
+Simple wrapper around scipy.interpolate.griddata to interpolate parametrized quantities
+"""
+from scipy.interpolate import griddata
+
+from .base_interpolators import ParametrizedInterpolator
+
+__all__ = ["GridDataInterpolator"]
+
+
+
+[docs] +class GridDataInterpolator(ParametrizedInterpolator): + """ "Wrapper arounf scipy.interpolate.griddata.""" + + def __init__(self, grid_points, params, **griddata_kwargs): + """Parametrized Interpolator using scipy.interpolate.griddata + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which interpolation templates exist + params: np.ndarray, shape=(n_points, ..., n_params) + Structured array of corresponding parameter values at each + point in grid_points. + First dimesion has to correspond to number of grid_points + griddata_kwargs: dict + Keyword-Arguments passed to scipy.griddata [1], e.g. + interpolation method. Defaults to None, which uses scipy's + defaults + + Raises + ------ + TypeError: + When params is not a np.ndarray + ValueError: + When number of points grid_points and params is not matching + + References + ---------- + .. [1] Scipy Documentation, scipy.interpolate.griddata + https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.griddata.html + + """ + super().__init__(grid_points, params) + + self.griddata_kwargs = griddata_kwargs + +
+[docs] + def interpolate(self, target_point): + """ + Wrapper around scipy.interpolate.griddata [1] + + Parameters + ---------- + target_point: np.ndarray, shape=(1, n_dims) + Target point for interpolation + + Returns + ------- + interpolant: np.ndarray, shape=(1, ..., n_params) + Interpolated parameter values + + References + ---------- + .. [1] Scipy Documentation, scipy.interpolate.griddata + https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.griddata.html + """ + interpolant = griddata( + self.grid_points, self.params, target_point, **self.griddata_kwargs + ).squeeze() + + return interpolant.reshape(1, *self.params.shape[1:])
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/interpolation/moment_morph_interpolator.html b/_modules/pyirf/interpolation/moment_morph_interpolator.html new file mode 100644 index 000000000..7f68a3d70 --- /dev/null +++ b/_modules/pyirf/interpolation/moment_morph_interpolator.html @@ -0,0 +1,505 @@ + + + + + + pyirf.interpolation.moment_morph_interpolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.interpolation.moment_morph_interpolator

+import numpy as np
+from pyirf.binning import bin_center, calculate_bin_indices
+from scipy.spatial import Delaunay
+
+from .base_interpolators import DiscretePDFInterpolator, PDFNormalization
+from .utils import get_bin_width
+
+__all__ = [
+    "MomentMorphInterpolator",
+]
+
+
+def _estimate_mean_std(bin_edges, binned_pdf, normalization):
+    """
+    Function to roughly estimate mean and standard deviation from a histogram.
+
+    Parameters
+    ----------
+    bin_edges: np.ndarray, shape=(n_bins+1)
+        Array of common bin-edges for binned_pdf
+    binned_pdf: np.ndarray, shape=(n_points, ..., n_bins)
+        PDF values from which to compute mean and std
+    normalization : PDFNormalization
+        How the PDF is normalized
+
+    Returns
+    -------
+    mean: np.ndarray, shape=(n_points, ...)
+        Estimated mean for each input template
+    std: np.ndarray, shape=(n_points, ...)
+        Estimated standard deviation for each input template. Set to width/2 if only one bin in
+        the input template is =/= 0
+    """
+    # Create an 2darray where the 1darray mids is repeated n_template times
+    mids = np.broadcast_to(bin_center(bin_edges), binned_pdf.shape)
+
+    width = get_bin_width(bin_edges, normalization)
+
+    # integrate pdf to get probability in each bin
+    probability = binned_pdf * width
+    # Weighted averages to compute mean and std
+    mean = np.average(mids, weights=probability, axis=-1)
+    std = np.sqrt(
+        np.average((mids - mean[..., np.newaxis]) ** 2, weights=probability, axis=-1)
+    )
+
+    # Set std to 0.5*width for all those templates that have only one bin =/= 0. In those
+    # cases mids-mean = 0 and therefore std = 0. Uses the width of the one bin with
+    # binned_pdf!=0
+    mask = std == 0
+    if np.any(mask):
+        width = np.diff(bin_edges)
+        # std of a uniform distribution inside the bin
+        uniform_std = np.broadcast_to(np.sqrt(1 / 12) * width, binned_pdf[mask].shape)
+        std[mask] = uniform_std[binned_pdf[mask, :] != 0]
+
+    return mean, std
+
+
+def _lookup(bin_edges, binned_pdf, x):
+    """
+    Function to return the bin-height at a desired point.
+
+    Parameters
+    ----------
+    bin_edges: np.ndarray, shape=(n_bins+1)
+        Array of common bin-edges for binned_pdf
+    binned_pdf: np.ndarray, shape=(n_points, ..., n_bins)
+        Array of bin-entries, actual
+    x: numpy.ndarray, shape=(n_points, ..., n_bins)
+        Array of n_bins points for each input template, where the histogram-value (bin-height) should be found
+
+    Returns
+    -------
+    y: numpy.ndarray, shape=(n_points, ..., n_bins)
+        Array of the bin-heights at the n_bins points x, set to 0 at each point outside the histogram
+
+    """
+    # Find the bin where each point x is located in
+    binnr, valid = calculate_bin_indices(x, bin_edges)
+
+    # Set under/overflow-bins (invalid bins) to 0 to avoid errors below
+    binnr[~valid] = 0
+
+    # Loop over every combination of flattend input histograms and flattend binning
+    lu = np.array(
+        [
+            cont[binnr_row]
+            for cont, binnr_row in zip(
+                binned_pdf.reshape(-1, binned_pdf.shape[-1]),
+                binnr.reshape(-1, binnr.shape[-1]),
+            )
+        ]
+    ).reshape(binned_pdf.shape)
+
+    # Set all invalid bins to 0
+    lu[~valid] = 0
+
+    # Set under-/ overflowbins to 0, reshape to original shape
+    return lu
+
+
+def linesegment_1D_interpolation_coefficients(grid_points, target_point):
+    """
+    Compute 1D interpolation coefficients for moment morph interpolation,
+    as in eq. (6) of [1]
+
+    Parameters
+    ----------
+    grid_points: np.ndarray, shape=(2, 1)
+        Points spanning a triangle in which
+    target_point: numpy.ndarray, shape=(1, 1)
+        Value at which the histogram should be interpolated
+
+    Returns
+    -------
+    coefficients: numpy.ndarray, shape=(2,)
+        Interpolation coefficients for all three interpolation simplex vertices
+        to interpolate to the target_point
+
+    References
+    ----------
+    .. [1] M. Baak, S. Gadatsch, R. Harrington and W. Verkerke (2015). Interpolation between
+           multi-dimensional histograms using a new non-linear moment morphing method
+           Nucl. Instrum. Methods Phys. Res. A 771, 39-48. https://doi.org/10.1016/j.nima.2014.10.033
+    """
+    # Set zeroth grid point as reference value
+    m0 = grid_points[0, :]
+
+    # Compute matrix M as in eq. (2) of [1]
+    j = np.arange(0, grid_points.shape[0], 1)
+    m_ij = (grid_points - m0) ** j
+
+    # Compute coefficients, eq. (6) from [1]
+    return np.einsum("...j, ji -> ...i", (target_point - m0) ** j, np.linalg.inv(m_ij))
+
+
+def barycentric_2D_interpolation_coefficients(grid_points, target_point):
+    """
+    Compute barycentric 2D interpolation coefficients for triangular
+    interpolation, see e.g. [1]
+
+    Parameters
+    ----------
+    grid_points: np.ndarray, shape=(3, 2)
+        Points spanning a triangle in which
+    target_point: np.ndarray, shape=(1, 2)
+        Value at which barycentric interpolation is needed
+
+    Returns
+    -------
+    coefficients: numpy.ndarray, shape=(3,)
+        Interpolation coefficients for all three interpolation simplex vertices
+        to interpolate to the target_point
+
+    References
+    ----------
+    .. [1] https://codeplea.com/triangular-interpolation
+    """
+    # Compute distance vectors between the grid points
+    d13 = grid_points[0, :] - grid_points[2, :]
+    d23 = grid_points[1, :] - grid_points[2, :]
+
+    # Compute distance vector between target and third grid point
+    dp3 = target_point.squeeze() - grid_points[2, :]
+
+    # Compute first and second weight
+    w1 = ((d23[1] * dp3[0]) + (-d23[0] * dp3[1])) / (
+        (d23[1] * d13[0]) + (-d23[0] * d13[1])
+    )
+    w2 = ((-d13[1] * dp3[0]) + (d13[0] * dp3[1])) / (
+        (d23[1] * d13[0]) + (-d23[0] * d13[1])
+    )
+
+    # Use w1+w2+w3 = 1 for third weight
+    w3 = 1 - w1 - w2
+
+    coefficients = np.array([w1, w2, w3])
+
+    return coefficients
+
+
+def moment_morph_estimation(bin_edges, binned_pdf, coefficients, normalization):
+    """
+    Function that wraps up the moment morph procedure [1] adopted for histograms.
+
+    Parameters
+    ----------
+    bin_edges: np.ndarray, shape=(n_bins+1)
+        Array of common bin-edges for binned_pdf
+    binned_pdf: np.ndarray, shape=(n_points, ..., n_bins)
+        Array of bin-entries, actual
+    coefficients: np.ndarray, shape=(n_points)
+        Estimation coefficients for each entry in binned_pdf
+    normalization : PDFNormalization
+        How the PDF is normalized
+
+    Returns
+    -------
+    f_new: numpy.ndarray, shape=(1, n_bins)
+        Interpolated histogram
+
+    References
+    ----------
+    .. [1] M. Baak, S. Gadatsch, R. Harrington and W. Verkerke (2015). Interpolation between
+           multi-dimensional histograms using a new non-linear moment morphing method
+           Nucl. Instrum. Methods Phys. Res. A 771, 39-48. https://doi.org/10.1016/j.nima.2014.10.033
+    """
+    bin_mids = bin_center(bin_edges)
+
+    # Catch all those templates, where at least one template histogram is all zeros.
+    zero_templates = ~np.all(~np.isclose(np.sum(binned_pdf, axis=-1), 0), 0)
+
+    # Manipulate those templates so that computations pass without error
+    binned_pdf[:, zero_templates] = np.full(len(bin_mids), 1 / len(bin_mids))
+
+    # Estimate mean and std for each input template histogram. First adaption needed to extend
+    # the moment morph procedure to histograms
+    mus, sigs = _estimate_mean_std(
+        bin_edges=bin_edges, binned_pdf=binned_pdf, normalization=normalization
+    )
+    coefficients = coefficients.reshape(
+        binned_pdf.shape[0], *np.ones(mus.ndim - 1, "int")
+    )
+
+    # Transform mean and std as in eq. (11) and (12) in [1]
+    # cs = np.broadcast_to(cs, mus.shape)
+    mu_prime = np.sum(coefficients * mus, axis=0)
+    sig_prime = np.sum(coefficients * sigs, axis=0)
+
+    # Compute slope and offset as in eq. (14) and (15) in [1]
+    aij = sigs / sig_prime
+    bij = mus - mu_prime * aij
+
+    # Transformation as in eq. (13) in [1]
+    mids = np.broadcast_to(bin_mids, binned_pdf.shape)
+    transf_mids = aij[..., np.newaxis] * mids + bij[..., np.newaxis]
+
+    # Compute the morphed historgram according to eq. (18) in [1]. The function "lookup" "resamples"
+    # the histogram at the transformed bin-mids by using the templates historgam value at the transformed
+    # bin-mid as new value for a whole transformed bin. Second adaption needed to extend
+    # the moment morph procedure to histograms, adaptes the behaviour of eq. (16)
+
+    transf_hist = _lookup(bin_edges=bin_edges, binned_pdf=binned_pdf, x=transf_mids)
+
+    f_new = np.sum(
+        np.expand_dims(coefficients, -1) * transf_hist * np.expand_dims(aij, -1), axis=0
+    )
+
+    # Reset interpolation resolts for those templates with partially zero entries from above to 0
+    f_new[zero_templates] = np.zeros(len(bin_mids))
+
+    # Re-Normalize, needed, as the estimation of the std used above is not exact but the result is scaled with
+    # the estimated std
+    bin_widths = get_bin_width(bin_edges, normalization)
+    norm = np.expand_dims(np.sum(f_new * bin_widths, axis=-1), -1)
+
+    return np.divide(f_new, norm, out=np.zeros_like(f_new), where=norm != 0).reshape(
+        1, *binned_pdf.shape[1:]
+    )
+
+
+
+[docs] +class MomentMorphInterpolator(DiscretePDFInterpolator): + """Interpolator class providing Moment Morphing to interpolate discretized PDFs.""" + + def __init__( + self, grid_points, bin_edges, binned_pdf, normalization=PDFNormalization.AREA + ): + """ + Interpolator class using moment morphing. + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which interpolation templates exist. May be one ot two dimensional. + bin_edges: np.ndarray, shape=(n_bins+1) + Edges of the data binning + binned_pdf: np.ndarray, shape=(n_points, ..., n_bins) + Content of each bin in bin_edges for + each point in grid_points. First dimesion has to correspond to number + of grid_points. Interpolation dimension, meaning the + the quantity that should be interpolated (e.g. the Migra axis for EDisp) + has to be at axis specified by axis-keyword as well as having entries + corresponding to the number of bins given through bin_edges keyword. + normalization : PDFNormalization + How the PDF is normalized + + Note + ---- + Also calls pyirf.interpolation.DiscretePDFInterpolator.__init__. + """ + super().__init__(grid_points, bin_edges, binned_pdf, normalization) + + if self.grid_dim == 2: + self.triangulation = Delaunay(self.grid_points) + elif self.grid_dim > 2: + raise NotImplementedError( + "Interpolation in more then two dimension not impemented." + ) + + def _interpolate1D(self, target_point): + """ + Function to find target inside 1D self.grid_points and interpolate + on this subset. + """ + target_bin = np.digitize(target_point.squeeze(), self.grid_points.squeeze()) + segment_inds = np.array([target_bin - 1, target_bin], "int") + coefficients = linesegment_1D_interpolation_coefficients( + grid_points=self.grid_points[segment_inds], + target_point=target_point, + ) + + return moment_morph_estimation( + bin_edges=self.bin_edges, + binned_pdf=self.binned_pdf[segment_inds], + coefficients=coefficients, + normalization=self.normalization, + ) + + def _interpolate2D(self, target_point): + """ + Function to find target inside 2D self.grid_points and interpolate + on this subset. + """ + simplex_inds = self.triangulation.simplices[ + self.triangulation.find_simplex(target_point) + ].squeeze() + coefficients = barycentric_2D_interpolation_coefficients( + grid_points=self.grid_points[simplex_inds], + target_point=target_point, + ) + + return moment_morph_estimation( + bin_edges=self.bin_edges, + binned_pdf=self.binned_pdf[simplex_inds], + coefficients=coefficients, + normalization=self.normalization, + ) + +
+[docs] + def interpolate(self, target_point): + """ + Takes a grid of binned pdfs for a bunch of different parameters + and interpolates it to given value of those parameters. + This function calls implementations of the moment morphing interpolation + pocedure introduced in [1]. + + Parameters + ---------- + target_point: numpy.ndarray, shape=(1, n_dims) + Value for which the interpolation is performed (target point) + + Returns + ------- + f_new: numpy.ndarray, shape=(1,...,n_bins) + Interpolated and binned pdf + + References + ---------- + .. [1] M. Baak, S. Gadatsch, R. Harrington and W. Verkerke (2015). Interpolation between + multi-dimensional histograms using a new non-linear moment morphing method + Nucl. Instrum. Methods Phys. Res. A 771, 39-48. https://doi.org/10.1016/j.nima.2014.10.033 + """ + if self.grid_dim == 1: + interpolant = self._interpolate1D(target_point) + elif self.grid_dim == 2: + interpolant = self._interpolate2D(target_point) + + return interpolant
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/interpolation/nearest_neighbor_searcher.html b/_modules/pyirf/interpolation/nearest_neighbor_searcher.html new file mode 100644 index 000000000..4a0b927af --- /dev/null +++ b/_modules/pyirf/interpolation/nearest_neighbor_searcher.html @@ -0,0 +1,314 @@ + + + + + + pyirf.interpolation.nearest_neighbor_searcher — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.interpolation.nearest_neighbor_searcher

+import numpy as np
+
+from .base_extrapolators import DiscretePDFExtrapolator, ParametrizedExtrapolator
+from .base_interpolators import (
+    BaseInterpolator,
+    DiscretePDFInterpolator,
+    ParametrizedInterpolator,
+)
+
+__all__ = [
+    "BaseNearestNeighborSearcher",
+    "DiscretePDFNearestNeighborSearcher",
+    "ParametrizedNearestNeighborSearcher",
+]
+
+
+
+[docs] +class BaseNearestNeighborSearcher(BaseInterpolator): + """ + Dummy NearestNeighbor approach usable instead of + actual Interpolation/Extrapolation + """ + + def __init__(self, grid_points, values, norm_ord=2): + """ + BaseNearestNeighborSearcher + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which templates exist + values: np.ndarray, shape=(n_points, ...) + Corresponding IRF values at grid_points + norm_ord: non-zero int + Order of the norm which is used to compute the distances, + passed to numpy.linalg.norm [1]. Defaults to 2, + which uses the euclidean norm. + + Raises + ------ + TypeError: + If norm_ord is not non-zero integer + + Notes + ----- + Also calls pyirf.interpolation.BaseInterpolators.__init__ + """ + super().__init__(grid_points) + + self.values = values + + # Test wether norm_ord is a number + try: + norm_ord > 0 + except TypeError: + raise ValueError( + f"Only positiv integers allowed for norm_ord, got {norm_ord}." + ) + + # Test wether norm_ord is a finite, positive integer + if (norm_ord <= 0) or ~np.isfinite(norm_ord) or (norm_ord != int(norm_ord)): + raise ValueError( + f"Only positiv integers allowed for norm_ord, got {norm_ord}." + ) + + self.norm_ord = norm_ord + +
+[docs] + def interpolate(self, target_point): + """ + Takes a grid of IRF values for a bunch of different parameters and returns + the values at the nearest grid point as seen from the target point. + + Parameters + ---------- + target_point: numpy.ndarray, shape=(1, n_dims) + Value for which the nearest neighbor should be found (target point) + + Returns + ------- + content_new: numpy.ndarray, shape=(1, ...) + values at nearest neighbor + + Notes + ----- + In case of multiple nearest neighbors, the values corresponding + to the first one are returned. + """ + + if target_point.ndim == 1: + target_point = target_point.reshape(1, *target_point.shape) + + distances = np.linalg.norm( + self.grid_points - target_point, ord=self.norm_ord, axis=1 + ) + + index = np.argmin(distances) + + return self.values[index, :]
+
+ + + +
+[docs] +class DiscretePDFNearestNeighborSearcher(BaseNearestNeighborSearcher): + """ + Dummy NearestNeighbor approach usable instead of + actual interpolation/extrapolation. + Compatible with discretized PDF IRF component API. + """ + + def __init__(self, grid_points, bin_edges, binned_pdf, norm_ord=2): + """ + NearestNeighborSearcher compatible with discretized PDF IRF components API + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which templates exist + bin_edges: np.ndarray, shape=(n_bins+1) + Edges of the data binning. Ignored for nearest neighbor searching. + binned_pdf: np.ndarray, shape=(n_points, ..., n_bins) + Content of each bin in bin_edges for + each point in grid_points. First dimesion has to correspond to number + of grid_points, last dimension has to correspond to number of bins for + the quantity that should be interpolated (e.g. the Migra axis for EDisp) + norm_ord: non-zero int + Order of the norm which is used to compute the distances, + passed to numpy.linalg.norm [1]. Defaults to 2, + which uses the euclidean norm. + + Notes + ----- + Also calls pyirf.interpolation.BaseNearestNeighborSearcher.__init__ + """ + + super().__init__(grid_points=grid_points, values=binned_pdf, norm_ord=norm_ord)
+ + + +DiscretePDFInterpolator.register(DiscretePDFNearestNeighborSearcher) +DiscretePDFExtrapolator.register(DiscretePDFNearestNeighborSearcher) + + +
+[docs] +class ParametrizedNearestNeighborSearcher(BaseNearestNeighborSearcher): + """ + Dummy NearestNeighbor approach usable instead of + actual interpolation/extrapolation + Compatible with parametrized IRF component API. + """ + + def __init__(self, grid_points, params, norm_ord=2): + """ + NearestNeighborSearcher compatible with parametrized IRF components API + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which templates exist + params: np.ndarray, shape=(n_points, ..., n_params) + Corresponding parameter values at each point in grid_points. + First dimesion has to correspond to number of grid_points + norm_ord: non-zero int + Order of the norm which is used to compute the distances, + passed to numpy.linalg.norm [1]. Defaults to 2, + which uses the euclidean norm. + + Notes + ---- + Also calls pyirf.interpolation.BaseNearestNeighborSearcher.__init__ + """ + + super().__init__(grid_points=grid_points, values=params, norm_ord=norm_ord)
+ + + +ParametrizedInterpolator.register(ParametrizedNearestNeighborSearcher) +ParametrizedExtrapolator.register(ParametrizedNearestNeighborSearcher) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/interpolation/nearest_simplex_extrapolator.html b/_modules/pyirf/interpolation/nearest_simplex_extrapolator.html new file mode 100644 index 000000000..c514f34f8 --- /dev/null +++ b/_modules/pyirf/interpolation/nearest_simplex_extrapolator.html @@ -0,0 +1,394 @@ + + + + + + pyirf.interpolation.nearest_simplex_extrapolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.interpolation.nearest_simplex_extrapolator

+"""
+Extrapolators for Parametrized and DiscretePDF components extrapolating from the 
+nearest simplex
+"""
+import warnings
+
+import numpy as np
+from scipy.spatial import Delaunay
+
+from .base_extrapolators import (
+    DiscretePDFExtrapolator,
+    ParametrizedExtrapolator,
+    PDFNormalization,
+)
+from .moment_morph_interpolator import (
+    barycentric_2D_interpolation_coefficients,
+    linesegment_1D_interpolation_coefficients,
+    moment_morph_estimation,
+)
+from .utils import find_nearest_simplex, get_bin_width
+
+__all__ = [
+    "MomentMorphNearestSimplexExtrapolator",
+    "ParametrizedNearestSimplexExtrapolator",
+]
+
+
+
+[docs] +class ParametrizedNearestSimplexExtrapolator(ParametrizedExtrapolator): + """Extrapolator class extending linear or baryzentric interpolation outside a grid's convex hull.""" + + def __init__(self, grid_points, params): + """ + Extrapolator class using linear or baryzentric extrapolation in one ore two + grid-dimensions. + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which templates exist. May be one ot two dimensional. + Have to be sorted in accending order for 1D. + params: np.ndarray, shape=(n_points, ...) + Array of corresponding parameter values at each point in grid_points. + First dimesion has to correspond to number of grid_points + + Note + ---- + Also calls pyirf.interpolation.ParametrizedInterpolator.__init__. + """ + super().__init__(grid_points, params) + + if self.grid_dim == 2: + self.triangulation = Delaunay(self.grid_points) + elif self.grid_dim > 2: + raise NotImplementedError( + "Extrapolation in more then two dimension not impemented." + ) + + def _extrapolate1D(self, segment_inds, target_point): + """ + Function to compute extrapolation coefficients for a target_point on a + specified grid segment and extrapolate from this subset + """ + coefficients = linesegment_1D_interpolation_coefficients( + grid_points=self.grid_points[segment_inds], + target_point=target_point.squeeze(), + ) + + # reshape to be broadcastable + coefficients = coefficients.reshape( + coefficients.shape[0], *np.ones(self.params.ndim - 1, "int") + ) + + return np.sum(coefficients * self.params[segment_inds, :], axis=0)[ + np.newaxis, : + ] + + def _extrapolate2D(self, simplex_inds, target_point): + """ + Function to compute extrapolation coeficcients and extrapolate from the nearest + simplex by extending barycentric interpolation + """ + coefficients = barycentric_2D_interpolation_coefficients( + grid_points=self.grid_points[simplex_inds], + target_point=target_point, + ) + + # reshape to be broadcastable + coefficients = coefficients.reshape( + coefficients.shape[0], *np.ones(self.params.ndim - 1, "int") + ) + + return np.sum(coefficients * self.params[simplex_inds, :], axis=0)[ + np.newaxis, : + ] + +
+[docs] + def extrapolate(self, target_point): + """ + Takes a grid of scalar values for a bunch of different parameters + and extrapolates it to given value of those parameters. + + Parameters + ---------- + target_point: numpy.ndarray, shape=(1, n_dims) + Value for which the extrapolation is performed (target point) + + Returns + ------- + values: numpy.ndarray, shape=(1,...) + Extrapolated values + + """ + if self.grid_dim == 1: + if target_point < self.grid_points.min(): + segment_inds = np.array([0, 1], "int") + else: + segment_inds = np.array([-2, -1], "int") + + extrapolant = self._extrapolate1D(segment_inds, target_point) + elif self.grid_dim == 2: + nearest_simplex_ind = find_nearest_simplex( + self.triangulation, target_point.squeeze() + ) + simplex_indices = self.triangulation.simplices[nearest_simplex_ind] + + extrapolant = self._extrapolate2D(simplex_indices, target_point) + + return extrapolant
+
+ + + +
+[docs] +class MomentMorphNearestSimplexExtrapolator(DiscretePDFExtrapolator): + """Extrapolator class extending moment morphing interpolation outside a grid's convex hull.""" + + def __init__( + self, grid_points, bin_edges, binned_pdf, normalization=PDFNormalization.AREA + ): + """ + Extrapolator class extending/reusing parts of Moment Morphing + by allowing for negative extrapolation coefficients computed + by from the nearest simplex in 1D or 2D (so either a line-segement + or a triangle). + + Parameters + ---------- + grid_points: np.ndarray, shape=(n_points, n_dims) + Grid points at which templates exist. May be one ot two dimensional. + Have to be sorted in accending order for 1D. + bin_edges: np.ndarray, shape=(n_bins+1) + Edges of the data binning + binned_pdf: np.ndarray, shape=(n_points, ..., n_bins) + Content of each bin in bin_edges for + each point in grid_points. First dimesion has to correspond to number + of grid_points. Extrapolation dimension, meaning the + the quantity that should be interpolated (e.g. the Migra axis for EDisp) + has to be at the last axis as well as having entries + corresponding to the number of bins given through bin_edges keyword. + normalization : PDFNormalization + How the PDF is normalized + + Note + ---- + Also calls pyirf.interpolation.DiscretePDFExtrapolator.__init__. + """ + super().__init__(grid_points, bin_edges, binned_pdf, normalization) + + if self.grid_dim == 2: + self.triangulation = Delaunay(self.grid_points) + elif self.grid_dim > 2: + raise NotImplementedError( + "Extrapolation in more then two dimension not impemented." + ) + + def _extrapolate1D(self, segment_inds, target_point): + """ + Function to compute extrapolation coefficients for a target_point on a + specified grid segment and extrapolate from this subset + """ + coefficients = linesegment_1D_interpolation_coefficients( + grid_points=self.grid_points[segment_inds], + target_point=target_point.squeeze(), + ) + + return moment_morph_estimation( + bin_edges=self.bin_edges, + binned_pdf=self.binned_pdf[segment_inds], + coefficients=coefficients, + normalization=self.normalization, + ) + + def _extrapolate2D(self, simplex_inds, target_point): + """ + Function to compute extrapolation coeficcients and extrapolate from the nearest + simplex by extending barycentric interpolation + """ + coefficients = barycentric_2D_interpolation_coefficients( + grid_points=self.grid_points[simplex_inds], + target_point=target_point, + ) + + return moment_morph_estimation( + bin_edges=self.bin_edges, + binned_pdf=self.binned_pdf[simplex_inds], + coefficients=coefficients, + normalization=self.normalization, + ) + +
+[docs] + def extrapolate(self, target_point): + """ + Takes a grid of discretized PDFs for a bunch of different parameters + and extrapolates it to given value of those parameters. + + Parameters + ---------- + target_point: numpy.ndarray, shape=(1, n_dims) + Value for which the extrapolation is performed (target point) + + Returns + ------- + values: numpy.ndarray, shape=(1, ..., n_bins) + Extrapolated discretized PDFs + + """ + if self.grid_dim == 1: + if target_point < self.grid_points.min(): + segment_inds = np.array([0, 1], "int") + else: + segment_inds = np.array([-2, -1], "int") + + extrapolant = self._extrapolate1D(segment_inds, target_point) + elif self.grid_dim == 2: + nearest_simplex_ind = find_nearest_simplex( + self.triangulation, target_point.squeeze() + ) + simplex_indices = self.triangulation.simplices[nearest_simplex_ind] + + extrapolant = self._extrapolate2D(simplex_indices, target_point) + + if not np.all(extrapolant >= 0): + warnings.warn( + "Some bin-entries where below zero after extrapolation and " + "thus cut off. Check result carefully." + ) + + # cut off values below 0 and re-normalize + extrapolant[extrapolant < 0] = 0 + bin_width = get_bin_width(self.bin_edges, self.normalization) + norm = np.expand_dims(np.sum(extrapolant * bin_width, axis=-1), -1) + return np.divide( + extrapolant, norm, out=np.zeros_like(extrapolant), where=norm != 0 + ).reshape(1, *self.binned_pdf.shape[1:]) + else: + return extrapolant
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/interpolation/quantile_interpolator.html b/_modules/pyirf/interpolation/quantile_interpolator.html new file mode 100644 index 000000000..76163f932 --- /dev/null +++ b/_modules/pyirf/interpolation/quantile_interpolator.html @@ -0,0 +1,403 @@ + + + + + + pyirf.interpolation.quantile_interpolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.interpolation.quantile_interpolator

+import numpy as np
+from scipy.interpolate import griddata, interp1d
+
+from .base_interpolators import DiscretePDFInterpolator, PDFNormalization
+from .utils import get_bin_width
+
+__all__ = ["QuantileInterpolator"]
+
+
+def cdf_values(binned_pdf, bin_edges, normalization):
+    """
+    compute cdf values and assure they are normed to unity
+    """
+    bin_widths = get_bin_width(bin_edges, normalization)
+    cdfs = np.cumsum(binned_pdf * bin_widths, axis=-1)
+
+    # assure the last cdf value is 1, ignore errors for empty pdfs as they are reset to 0 by nan_to_num
+    with np.errstate(invalid="ignore"):
+        cdfs = np.nan_to_num(cdfs / np.max(cdfs, axis=-1)[..., np.newaxis])
+
+    return cdfs
+
+
+def ppf_values(bin_mids, cdfs, quantiles):
+    """
+    Compute ppfs from cdfs and interpolate them to the desired interpolation point
+
+    Parameters
+    ----------
+    bin_mids: numpy.ndarray, shape=(n_bins)
+        Bin-mids for each bin along interpolation axis
+
+    cdfs: numpy.ndarray, shape=(n_points,...,n_bins)
+        Corresponding cdf-values for all quantiles
+
+    quantiles: numpy.ndarray, shape=(n_quantiles)
+        Quantiles for which the ppf-values should be estimated
+
+    Returns
+    -------
+    ppfs: numpy.ndarray, shape=(1,...,n_quantiles)
+        Corresponding ppf-values for all quantiles at the target interpolation point
+    """
+
+    def cropped_interp(cdf):
+        """
+        create ppf-values through inverse interpolation of the cdf, avoiding repeating 0 and 1 entries
+        around the first and last bins as well as repeating values due to zero bins
+        in between filled bins. Both cases would result in division by zero errors when computing
+        the interpolation polynom.
+        """
+
+        # Find last 0 and first 1 entry
+        last_0 = np.nonzero(cdf == 0)[0][-1] if cdf[0] == 0 else 0
+        first_1 = np.nonzero(cdf == 1.0)[0][0]
+
+        # Predefine selection mask
+        selection = np.ones(len(cdf), dtype=bool)
+
+        # Keep only the first of subsequently matching values
+        selection[1:] = cdf[1:] != cdf[:-1]
+
+        # Keep only the last 0 and first 1 entry
+        selection[:last_0] = False
+        selection[last_0] = True
+        selection[first_1 + 1 :] = False
+        selection[first_1] = True
+
+        # create ppf values from selected bins
+        return interp1d(
+            cdf[selection],
+            bin_mids[selection],
+            bounds_error=False,
+            fill_value="extrapolate",
+        )(quantiles)
+
+    # create ppf values from cdf samples via interpolation of the cdfs
+    # return nan for empty pdfs
+    ppfs = np.apply_along_axis(
+        lambda cdf: cropped_interp(cdf)
+        if np.sum(cdf) > 0
+        else np.full_like(quantiles, np.nan),
+        -1,
+        cdfs,
+    )
+    # nD interpolation of ppf values
+    return ppfs
+
+
+def pdf_from_ppf(bin_edges, interp_ppfs, quantiles):
+    """
+    Reconstruct pdf from ppf and evaluate at desired points.
+
+    Parameters
+    ----------
+    bin_edges: numpy.ndarray, shape=(n_bins+1)
+        Edges of the bins in which the final pdf should be binned
+
+    interp_ppfs: numpy.ndarray, shape=(1,...,n_quantiles)
+        Corresponding ppf-values for all quantiles at the target_point,
+        not to be confused with QunatileInterpolators self.ppfs, the ppfs
+        computed from the input distributions.
+
+    quantiles: numpy.ndarray, shape=(n_quantiles)
+        Quantiles corresponding to the ppf-values in interp_ppfs
+
+    Returns
+    -------
+    pdf_values: numpy.ndarray, shape=(1,...,n_bins)
+        Recomputed, binned pdf at target_point
+    """
+    # recalculate pdf values through numerical differentiation
+    pdf_interpolant = np.nan_to_num(np.diff(quantiles) / np.diff(interp_ppfs, axis=-1))
+
+    # Unconventional solution to make this usable with np.apply_along_axis for readability
+    # The ppf bin-mids are computed since the pdf-values are derived through derivation
+    # from the ppf-values
+    xyconcat = np.concatenate(
+        (interp_ppfs[..., :-1] + np.diff(interp_ppfs) / 2, pdf_interpolant), axis=-1
+    )
+
+    def interpolate_ppf(xy):
+        ppf = xy[: len(xy) // 2]
+        pdf = xy[len(xy) // 2 :]
+        interpolate = interp1d(ppf, pdf, bounds_error=False, fill_value=(0, 0))
+        result = np.nan_to_num(interpolate(bin_edges[:-1]))
+        return result
+
+    # Interpolate pdf samples and evaluate at bin edges, weight with the bin_width to estimate
+    # correct bin height via the midpoint rule formulation of the trapezoidal rule
+    pdf_values = np.apply_along_axis(interpolate_ppf, -1, xyconcat)
+
+    return pdf_values
+
+
+def norm_pdf(pdf_values, bin_edges, normalization):
+    """
+    Normalize binned_pdf to a sum of 1
+    """
+    norm = np.sum(pdf_values, axis=-1)
+    width = get_bin_width(bin_edges, normalization)
+
+    # Norm all binned_pdfs to unity that are not empty
+    normed_pdf_values = np.divide(
+        pdf_values,
+        norm[..., np.newaxis] * width,
+        out=np.zeros_like(pdf_values),
+        where=norm[..., np.newaxis] != 0,
+    )
+
+    return normed_pdf_values
+
+
+
+[docs] +class QuantileInterpolator(DiscretePDFInterpolator): + """Interpolator class providing quantile interpoalation.""" + + def __init__( + self, + grid_points, + bin_edges, + binned_pdf, + quantile_resolution=1e-3, + normalization=PDFNormalization.AREA, + ): + """ + Parameters + ---------- + grid_points : np.ndarray, shape=(n_points, n_dims) + Grid points at which interpolation templates exist + bin_edges : np.ndarray, shape=(n_bins+1) + Edges of the data binning + binned_pdf : np.ndarray, shape(n_points, ..., n_bins) + Content of each bin in bin_edges for + each point in grid_points. First dimesion has to correspond to number + of grid_points, the last axis has to correspond to number + of bins for the quantity that should be interpolated + (e.g. the Migra axis for EDisp) + quantile_resolution : float + Spacing between quantiles + normalization : PDFNormalization + How the discrete PDF is normalized + + Raises + ------ + ValueError: + When last axis in binned_pdf and number of bins are not equal. + + Note + ---- + Also calls __init__ of pyirf.interpolation.BaseInterpolator and + DiscretePDFInterpolator + """ + # Remember input shape + self.input_shape = binned_pdf.shape + + if self.input_shape[-1] != len(bin_edges) - 1: + raise ValueError( + "Number of bins along last axis and those specified by bin_edges not matching." + ) + + # include 0-bin at first position in each pdf to avoid edge-effects where the CDF would otherwise + # start at a value != 0, also extend edges with one bin to the left + fill_zeros = np.zeros(shape=binned_pdf.shape[:-1])[..., np.newaxis] + binned_pdf = np.concatenate((fill_zeros, binned_pdf), axis=-1) + bin_edges = np.append(bin_edges[0] - np.diff(bin_edges)[0], bin_edges) + + # compute quantiles from quantile_resolution + self.quantiles = np.linspace( + 0, 1, int(np.round(1 / quantile_resolution, decimals=0)) + ) + + super().__init__( + grid_points=grid_points, + bin_edges=bin_edges, + binned_pdf=binned_pdf, + normalization=normalization, + ) + + # Compute CDF values + self.cdfs = cdf_values(self.binned_pdf, self.bin_edges, self.normalization) + + # compute ppf values at quantiles, determine quantile step of [1] + self.ppfs = ppf_values(self.bin_mids, self.cdfs, self.quantiles) + +
+[docs] + def interpolate(self, target_point): + """ + Takes a grid of binned pdfs for a bunch of different parameters + and interpolates it to given value of those parameters. + This function provides an adapted version of the quantile interpolation introduced + in [1]. + Instead of following this method closely, it implements different approaches to the + steps shown in Fig. 5 of [1]. + + Parameters + ---------- + target_point: numpy.ndarray, shape=(1, n_dims) + Value for which the interpolation is performed (target point) + + Returns + ------- + f_new: numpy.ndarray, shape=(1, ..., n_bins) + Interpolated and binned pdf + + References + ---------- + .. [1] B. E. Hollister and A. T. Pang (2013). Interpolation of Non-Gaussian Probability Distributions + for Ensemble Visualization + https://engineering.ucsc.edu/sites/default/files/technical-reports/UCSC-SOE-13-13.pdf + """ + + # interpolate ppfs to target point, interpolate quantiles step of [1] + interpolated_ppfs = griddata(self.grid_points, self.ppfs, target_point) + + # compute pdf values for all bins, evaluate interpolant PDF values step of [1], drop the earlier + # introduced extra bin + interpolated_pdfs = pdf_from_ppf( + self.bin_edges, interpolated_ppfs, self.quantiles + )[..., 1:] + + # Renormalize pdf to sum of 1 + normed_interpolated_pdfs = norm_pdf( + interpolated_pdfs, self.bin_edges[1:], self.normalization + ) + + # Re-swap axes and set all nans to zero + return np.nan_to_num(normed_interpolated_pdfs).reshape(1, *self.input_shape[1:])
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/interpolation/visible_edges_extrapolator.html b/_modules/pyirf/interpolation/visible_edges_extrapolator.html new file mode 100644 index 000000000..e7d2720dc --- /dev/null +++ b/_modules/pyirf/interpolation/visible_edges_extrapolator.html @@ -0,0 +1,335 @@ + + + + + + pyirf.interpolation.visible_edges_extrapolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyirf.interpolation.visible_edges_extrapolator

+"""
+Extrapolators for Parametrized and DiscretePDF components that combine extrapolations 
+from all visible simplices (by blending over visible edges) to get a smooth extrapolation
+outside the grids convex hull.
+"""
+import numpy as np
+from scipy.spatial import Delaunay
+
+from .nearest_simplex_extrapolator import ParametrizedNearestSimplexExtrapolator
+from .utils import find_simplex_to_facet, point_facet_angle
+
+__all__ = ["ParametrizedVisibleEdgesExtrapolator"]
+
+
+def find_visible_facets(grid_points, target_point):
+    """
+    Find all facets of a convex hull visible from an outside point.
+    
+    To do so, this function constructs a triangulation containing
+    the target point and returns all facets that span a triangulation simplex with
+    it.
+
+    Parameters
+    ----------
+    grid_points: np.ndarray, shape=(N, M)
+        Grid points at which templates exist. May be one ot two dimensional.
+        Have to be sorted in accending order for 1D.
+    target_point: numpy.ndarray, shape=(1, M)
+        Value for which the extrapolation is performed (target point)
+
+    Returns
+    -------
+    visible_facets: np.ndarray, shape=(L, M)
+        L visible facets, spanned by a simplex in M-1 dimensions
+        (thus a line for M=2)
+    """
+    # Build a triangulation including the target point
+    full_set = np.vstack((grid_points, target_point))
+    triag = Delaunay(full_set)
+
+    # The target point is included in a simplex with all facets
+    # visible by it
+    simplices = triag.points[triag.simplices]
+    matches_target = np.all(simplices == target_point, axis=-1)
+    target_in_simplex = np.any(matches_target, axis=-1)
+
+    # The visible facets are spanned by those points in the matched
+    # simplices that are not the target
+    facet_point_mask = ~matches_target[target_in_simplex]
+    visible_facets = np.array(
+        [
+            triag.points[simplex[mask]]
+            for simplex, mask in zip(
+                triag.simplices[target_in_simplex], facet_point_mask
+            )
+        ]
+    )
+
+    return visible_facets
+
+
+def compute_extrapolation_weights(visible_facet_points, target_point, m):
+    """
+    Compute extrapolation weight according to [1].
+
+    Parameters
+    ----------
+    visible_facet_points: np.ndarray, shape=(L, M)
+        L facets visible from target_point
+    target_point: numpy.ndarray, shape=(1, M)
+        Value for which the extrapolation is performed (target point)
+
+    Returns
+    -------
+    extrapolation_weights: np.ndarray, shape=(L)
+        Weights for each visible facet, corresponding to the extrapolation
+        weight for the respective triangulation simplex. Weigths sum to unity.
+
+    References
+    ----------
+    .. [1] P. Alfred (1984). Triangular Extrapolation. Technical summary rept.,
+    Univ. of Wisconsin-Madison. https://apps.dtic.mil/sti/pdfs/ADA144660.pdf
+    """
+
+    angles = np.array(
+        [
+            point_facet_angle(line, target_point.squeeze())
+            for line in visible_facet_points
+        ]
+    )
+    weights = np.arccos(angles) ** (m + 1)
+
+    return weights / weights.sum()
+
+
+
+[docs] +class ParametrizedVisibleEdgesExtrapolator(ParametrizedNearestSimplexExtrapolator): + """ + Extrapolator using blending over visible edges. + + While the ParametrizedNearestSimplexExtrapolator does not result in a smooth + extrapolation outside of the grid due to using only the nearest available + simplex, this extrapolator blends over all visible edges as discussed in [1]. + For one grid-dimension this is equal to the ParametrizedNearestSimplexExtrapolator, + the same holds for grids consisting of only one simplex or constellations, + where only one simplex is visible from a target. + + Parameters + ---------- + grid_points: np.ndarray, shape=(N, ...) + Grid points at which templates exist. May be one ot two dimensional. + Have to be sorted in accending order for 1D. + params: np.ndarray, shape=(N, ...) + Array of corresponding parameter values at each point in grid_points. + First dimesion has to correspond to number of grid_points + m: non-zero int >= 1 + Degree of smoothness wanted in the extrapolation region. See [1] for + additional information. Defaults to 1. + + Raises + ------ + TypeError: + If m is not a number + ValueError: + If m is not a non-zero integer + + Note + ---- + Also calls pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.__init__. + + References + ---------- + .. [1] P. Alfred (1984). Triangular Extrapolation. Technical summary rept., + Univ. of Wisconsin-Madison. https://apps.dtic.mil/sti/pdfs/ADA144660.pdf + + """ + + def __init__(self, grid_points, params, m=1): + super().__init__(grid_points, params) + + # Test wether m is a number + try: + m > 0 + except TypeError: + raise TypeError(f"Only positive integers allowed for m, got {m}.") + + # Test wether m is a finite, positive integer + if (m <= 0) or ~np.isfinite(m) or (m != int(m)): + raise ValueError(f"Only positive integers allowed for m, got {m}.") + + self.m = m + +
+[docs] + def extrapolate(self, target_point): + if self.grid_dim == 1: + return super().extrapolate(target_point) + elif self.grid_dim == 2: + visible_facet_points = find_visible_facets(self.grid_points, target_point) + + if visible_facet_points.shape[0] == 1: + return super().extrapolate(target_point) + else: + simplices_points = self.triangulation.points[ + self.triangulation.simplices + ] + + visible_simplices_indices = np.array( + [ + find_simplex_to_facet(simplices_points, facet) + for facet in visible_facet_points + ] + ) + + extrapolation_weigths = compute_extrapolation_weights( + visible_facet_points, target_point, self.m + ) + + extrapolation_weigths = extrapolation_weigths.reshape( + extrapolation_weigths.shape[0], + *np.ones(self.params.ndim - 1, "int"), + ) + + # Function has to be copied outside list comprehention as the super() short-form + # cannot be used inside it (at least until python 3.11) + extrapolate2D = super()._extrapolate2D + + simplex_extrapolations = np.array( + [ + extrapolate2D( + self.triangulation.simplices[ind], target_point + ).squeeze() + for ind in visible_simplices_indices + ] + ) + + extrapolant = np.sum( + extrapolation_weigths * simplex_extrapolations, axis=0 + )[np.newaxis, :] + + return extrapolant
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/io/eventdisplay.html b/_modules/pyirf/io/eventdisplay.html new file mode 100644 index 000000000..800dafbb7 --- /dev/null +++ b/_modules/pyirf/io/eventdisplay.html @@ -0,0 +1,218 @@ + + + + + + pyirf.io.eventdisplay — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.io.eventdisplay

+import logging
+
+from astropy.table import QTable, unique
+import astropy.units as u
+
+from ..simulations import SimulatedEventsInfo
+
+
+log = logging.getLogger(__name__)
+
+
+COLUMN_MAP = {
+    "obs_id": "OBS_ID",
+    "event_id": "EVENT_ID",
+    "true_energy": "MC_ENERGY",
+    "reco_energy": "ENERGY",
+    "true_alt": "MC_ALT",
+    "true_az": "MC_AZ",
+    "pointing_alt": "PNT_ALT",
+    "pointing_az": "PNT_AZ",
+    "reco_alt": "ALT",
+    "reco_az": "AZ",
+    "gh_score": "GH_MVA",
+    "multiplicity": "MULTIP",
+}
+
+
+
+[docs] +def read_eventdisplay_fits(infile, use_histogram=True): + """ + Read a DL2 FITS file as produced by the EventDisplay DL2 converter + from ROOT files: + https://github.com/Eventdisplay/Converters/blob/master/DL2/generate_DL2_file.py + + Parameters + ---------- + infile : str or pathlib.Path + Path to the input fits file + use_histogram : bool + If True, use number of simulated events from histogram provided in fits file, + if False, estimate this number from the unique run_id, pointing direction + combinations and the number of events per run in the run header. + This will fail e.g. for protons with cuts already applied, since many + runs will have 0 events surviving cuts. + + Returns + ------- + events: astropy.QTable + Astropy Table object containing the reconstructed events information. + simulated_events: ``~pyirf.simulations.SimulatedEventsInfo`` + """ + log.debug(f"Reading {infile}") + events = QTable.read(infile, hdu="EVENTS") + sim_events = QTable.read(infile, hdu="SIMULATED EVENTS") + run_header = QTable.read(infile, hdu="RUNHEADER")[0] + + for new, old in COLUMN_MAP.items(): + events.rename_column(old, new) + + n_runs = len(unique(events[['obs_id', 'pointing_az', 'pointing_alt']])) + log.info(f"Estimated number of runs from obs ids and pointing position: {n_runs}") + + n_showers_guessed = n_runs * run_header["num_use"] * run_header["num_showers"] + n_showers_hist = int(sim_events["EVENTS"].sum()) + + if use_histogram: + n_showers = n_showers_hist + else: + n_showers = n_showers_guessed + + log.debug("Number of events histogram: %d", n_showers_hist) + log.debug("Number of events from n_runs and run header: %d", n_showers_guessed) + log.debug("Using number of events from %s", "histogram" if use_histogram else "guess") + + sim_info = SimulatedEventsInfo( + n_showers=n_showers, + energy_min=u.Quantity(run_header["E_range"][0], u.TeV), + energy_max=u.Quantity(run_header["E_range"][1], u.TeV), + max_impact=u.Quantity(run_header["core_range"][1], u.m), + spectral_index=run_header["spectral_index"], + viewcone_min=u.Quantity(run_header["viewcone"][0], u.deg), + viewcone_max=u.Quantity(run_header["viewcone"][1], u.deg), + ) + + return events, sim_info
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/io/gadf.html b/_modules/pyirf/io/gadf.html new file mode 100644 index 000000000..eb94fc714 --- /dev/null +++ b/_modules/pyirf/io/gadf.html @@ -0,0 +1,461 @@ + + + + + + pyirf.io.gadf — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.io.gadf

+from astropy.table import QTable
+import astropy.units as u
+from astropy.io.fits import Header, BinTableHDU
+import numpy as np
+from astropy.time import Time
+import pyirf.binning as binning
+
+from ..version import __version__
+
+
+__all__ = [
+    "create_aeff2d_hdu",
+    "create_energy_dispersion_hdu",
+    "create_psf_table_hdu",
+    "create_rad_max_hdu",
+]
+
+
+DEFAULT_HEADER = Header()
+DEFAULT_HEADER["CREATOR"] = f"pyirf v{__version__}"
+# fmt: off
+DEFAULT_HEADER["HDUDOC"] = "https://github.com/open-gamma-ray-astro/gamma-astro-data-formats"
+DEFAULT_HEADER["HDUVERS"] = "0.2"
+DEFAULT_HEADER["HDUCLASS"] = "GADF"
+
+
+def _add_header_cards(header, **header_cards):
+    for k, v in header_cards.items():
+        header[k] = v
+
+
+
+[docs] +@u.quantity_input( + effective_area=u.m ** 2, true_energy_bins=u.TeV, fov_offset_bins=u.deg +) +def create_aeff2d_hdu( + effective_area, + true_energy_bins, + fov_offset_bins, + extname="EFFECTIVE AREA", + point_like=True, + **header_cards, +): + """ + Create a fits binary table HDU in GADF format for effective area. + See the specification at + https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/aeff/index.html + + Parameters + ---------- + effective_area: astropy.units.Quantity[area] + Effective area array, must have shape (n_energy_bins, n_fov_offset_bins) + true_energy_bins: astropy.units.Quantity[energy] + Bin edges in true energy + fov_offset_bins: astropy.units.Quantity[angle] + Bin edges in the field of view offset. + For Point-Like IRFs, only giving a single bin is appropriate. + point_like: bool + If the provided effective area was calculated after applying a direction cut, + pass ``True``, else ``False`` for a full-enclosure effective area. + extname: str + Name for BinTableHDU + **header_cards + Additional metadata to add to the header, use this to set e.g. TELESCOP or + INSTRUME. + """ + aeff = QTable() + aeff["ENERG_LO"], aeff["ENERG_HI"] = binning.split_bin_lo_hi(true_energy_bins[np.newaxis, :].to(u.TeV)) + aeff["THETA_LO"], aeff["THETA_HI"] = binning.split_bin_lo_hi(fov_offset_bins[np.newaxis, :].to(u.deg)) + # transpose because FITS uses opposite dimension order than numpy + aeff["EFFAREA"] = effective_area.T[np.newaxis, ...].to(u.m ** 2) + + # required header keywords + header = DEFAULT_HEADER.copy() + header["HDUCLAS1"] = "RESPONSE" + header["HDUCLAS2"] = "EFF_AREA" + header["HDUCLAS3"] = "POINT-LIKE" if point_like else "FULL-ENCLOSURE" + header["HDUCLAS4"] = "AEFF_2D" + header["DATE"] = Time.now().utc.iso + idx = aeff.colnames.index("EFFAREA") + 1 + header[f"CREF{idx}"] = "(ENERG_LO:ENERG_HI,THETA_LO:THETA_HI)" + _add_header_cards(header, **header_cards) + + return BinTableHDU(aeff, header=header, name=extname)
+ + + +
+[docs] +@u.quantity_input( + psf=u.sr ** -1, + true_energy_bins=u.TeV, + source_offset_bins=u.deg, + fov_offset_bins=u.deg, +) +def create_psf_table_hdu( + psf, + true_energy_bins, + source_offset_bins, + fov_offset_bins, + extname="PSF", + **header_cards, +): + """ + Create a fits binary table HDU in GADF format for the PSF table. + See the specification at + https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/psf/psf_table/index.html + + Parameters + ---------- + psf: astropy.units.Quantity[(solid angle)^-1] + Point spread function array, must have shape + (n_energy_bins, n_fov_offset_bins, n_source_offset_bins) + true_energy_bins: astropy.units.Quantity[energy] + Bin edges in true energy + source_offset_bins: astropy.units.Quantity[angle] + Bin edges in the source offset. + fov_offset_bins: astropy.units.Quantity[angle] + Bin edges in the field of view offset. + For Point-Like IRFs, only giving a single bin is appropriate. + extname: str + Name for BinTableHDU + **header_cards + Additional metadata to add to the header, use this to set e.g. TELESCOP or + INSTRUME. + """ + + psf_ = QTable() + psf_["ENERG_LO"], psf_["ENERG_HI"] = binning.split_bin_lo_hi(true_energy_bins[np.newaxis, :].to(u.TeV)) + psf_["THETA_LO"], psf_["THETA_HI"] = binning.split_bin_lo_hi(fov_offset_bins[np.newaxis, :].to(u.deg)) + psf_["RAD_LO"], psf_["RAD_HI"] = binning.split_bin_lo_hi(source_offset_bins[np.newaxis, :].to(u.deg)) + # transpose as FITS uses opposite dimension order + psf_["RPSF"] = psf.T[np.newaxis, ...].to(1 / u.sr) + + # required header keywords + header = DEFAULT_HEADER.copy() + header["HDUCLAS1"] = "RESPONSE" + header["HDUCLAS2"] = "PSF" + header["HDUCLAS3"] = "FULL-ENCLOSURE" + header["HDUCLAS4"] = "PSF_TABLE" + header["DATE"] = Time.now().utc.iso + idx = psf_.colnames.index("RPSF") + 1 + header[f"CREF{idx}"] = "(ENERG_LO:ENERG_HI,THETA_LO:THETA_HI,RAD_LO:RAD_HI)" + _add_header_cards(header, **header_cards) + + return BinTableHDU(psf_, header=header, name=extname)
+ + + +
+[docs] +@u.quantity_input( + true_energy_bins=u.TeV, fov_offset_bins=u.deg, +) +def create_energy_dispersion_hdu( + energy_dispersion, + true_energy_bins, + migration_bins, + fov_offset_bins, + point_like=True, + extname="EDISP", + **header_cards, +): + """ + Create a fits binary table HDU in GADF format for the energy dispersion. + See the specification at + https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/edisp/index.html + + Parameters + ---------- + energy_dispersion: numpy.ndarray + Energy dispersion array, must have shape + (n_energy_bins, n_migra_bins, n_source_offset_bins) + true_energy_bins: astropy.units.Quantity[energy] + Bin edges in true energy + migration_bins: numpy.ndarray + Bin edges for the relative energy migration (``reco_energy / true_energy``) + fov_offset_bins: astropy.units.Quantity[angle] + Bin edges in the field of view offset. + For Point-Like IRFs, only giving a single bin is appropriate. + point_like: bool + If the provided effective area was calculated after applying a direction cut, + pass ``True``, else ``False`` for a full-enclosure effective area. + extname: str + Name for BinTableHDU + **header_cards + Additional metadata to add to the header, use this to set e.g. TELESCOP or + INSTRUME. + """ + + edisp = QTable() + edisp["ENERG_LO"], edisp["ENERG_HI"] = binning.split_bin_lo_hi(true_energy_bins[np.newaxis, :].to(u.TeV)) + edisp["MIGRA_LO"], edisp["MIGRA_HI"] = binning.split_bin_lo_hi(migration_bins[np.newaxis, :]) + edisp["THETA_LO"], edisp["THETA_HI"] = binning.split_bin_lo_hi(fov_offset_bins[np.newaxis, :].to(u.deg)) + # transpose as FITS uses opposite dimension order + edisp["MATRIX"] = u.Quantity(energy_dispersion.T[np.newaxis, ...]).to(u.one) + + # required header keywords + header = DEFAULT_HEADER.copy() + header["HDUCLAS1"] = "RESPONSE" + header["HDUCLAS2"] = "EDISP" + header["HDUCLAS3"] = "POINT-LIKE" if point_like else "FULL-ENCLOSURE" + header["HDUCLAS4"] = "EDISP_2D" + header["DATE"] = Time.now().utc.iso + idx = edisp.colnames.index("MATRIX") + 1 + header[f"CREF{idx}"] = "(ENERG_LO:ENERG_HI,MIGRA_LO:MIGRA_HI,THETA_LO:THETA_HI)" + _add_header_cards(header, **header_cards) + + return BinTableHDU(edisp, header=header, name=extname)
+ + + +#: Unit to store background rate in GADF format +#: +#: see https://github.com/open-gamma-ray-astro/gamma-astro-data-formats/issues/153 +#: for a discussion on why this is MeV not TeV as everywhere else +GADF_BACKGROUND_UNIT = u.Unit("MeV-1 s-1 sr-1") + + +
+[docs] +@u.quantity_input( + background=GADF_BACKGROUND_UNIT, reco_energy_bins=u.TeV, fov_offset_bins=u.deg, +) +def create_background_2d_hdu( + background_2d, + reco_energy_bins, + fov_offset_bins, + extname="BACKGROUND", + **header_cards, +): + """ + Create a fits binary table HDU in GADF format for the background 2d table. + See the specification at + https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/bkg/index.html#bkg-2d + + Parameters + ---------- + background_2d: astropy.units.Quantity[(MeV s sr)^-1] + Background rate, must have shape + (n_energy_bins, n_fov_offset_bins) + reco_energy_bins: astropy.units.Quantity[energy] + Bin edges in reconstructed energy + fov_offset_bins: astropy.units.Quantity[angle] + Bin edges in the field of view offset. + extname: str + Name for BinTableHDU + **header_cards + Additional metadata to add to the header, use this to set e.g. TELESCOP or + INSTRUME. + """ + + bkg = QTable() + bkg["ENERG_LO"], bkg["ENERG_HI"] = binning.split_bin_lo_hi(reco_energy_bins[np.newaxis, :].to(u.TeV)) + bkg["THETA_LO"], bkg["THETA_HI"] = binning.split_bin_lo_hi(fov_offset_bins[np.newaxis, :].to(u.deg)) + # transpose as FITS uses opposite dimension order + bkg["BKG"] = background_2d.T[np.newaxis, ...].to(GADF_BACKGROUND_UNIT) + + # required header keywords + header = DEFAULT_HEADER.copy() + header["HDUCLAS1"] = "RESPONSE" + header["HDUCLAS2"] = "BKG" + header["HDUCLAS3"] = "FULL-ENCLOSURE" + header["HDUCLAS4"] = "BKG_2D" + header["DATE"] = Time.now().utc.iso + idx = bkg.colnames.index("BKG") + 1 + header[f"CREF{idx}"] = "(ENERG_LO:ENERG_HI,THETA_LO:THETA_HI)" + _add_header_cards(header, **header_cards) + + return BinTableHDU(bkg, header=header, name=extname)
+ + + +
+[docs] +@u.quantity_input( + rad_max=u.deg, + reco_energy_bins=u.TeV, + fov_offset_bins=u.deg, +) +def create_rad_max_hdu( + rad_max, + reco_energy_bins, + fov_offset_bins, + point_like=True, + extname="RAD_MAX", + **header_cards, +): + """ + Create a fits binary table HDU in GADF format for the directional cut. + See the specification at + https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/point_like/index.html#rad-max + + Parameters + ---------- + rad_max: astropy.units.Quantity[angle] + Array of the directional (theta) cut. + Must have shape (n_reco_energy_bins, n_fov_offset_bins) + reco_energy_bins: astropy.units.Quantity[energy] + Bin edges in reconstructed energy + fov_offset_bins: astropy.units.Quantity[angle] + Bin edges in the field of view offset. + For Point-Like IRFs, only giving a single bin is appropriate. + extname: str + Name for BinTableHDU + **header_cards + Additional metadata to add to the header, use this to set e.g. TELESCOP or + INSTRUME. + """ + + rad_max_table = QTable() + rad_max_table["ENERG_LO"], rad_max_table["ENERG_HI"] = binning.split_bin_lo_hi(reco_energy_bins[np.newaxis, :].to(u.TeV)) + rad_max_table["THETA_LO"], rad_max_table["THETA_HI"] = binning.split_bin_lo_hi(fov_offset_bins[np.newaxis, :].to(u.deg)) + # transpose as FITS uses opposite dimension order + rad_max_table["RAD_MAX"] = rad_max.T[np.newaxis, ...].to(u.deg) + + # required header keywords + header = DEFAULT_HEADER.copy() + header["HDUCLAS1"] = "RESPONSE" + header["HDUCLAS2"] = "RAD_MAX" + header["HDUCLAS3"] = "POINT-LIKE" + header["HDUCLAS4"] = "RAD_MAX_2D" + header["DATE"] = Time.now().utc.iso + idx = rad_max_table.colnames.index("RAD_MAX") + 1 + header[f"CREF{idx}"] = "(ENERG_LO:ENERG_HI,THETA_LO:THETA_HI)" + _add_header_cards(header, **header_cards) + + return BinTableHDU(rad_max_table, header=header, name=extname)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/irf/background.html b/_modules/pyirf/irf/background.html new file mode 100644 index 000000000..9141ea100 --- /dev/null +++ b/_modules/pyirf/irf/background.html @@ -0,0 +1,192 @@ + + + + + + pyirf.irf.background — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.irf.background

+import astropy.units as u
+import numpy as np
+
+from ..utils import cone_solid_angle
+
+#: Unit of the background rate IRF
+BACKGROUND_UNIT = u.Unit('s-1 TeV-1 sr-1')
+
+
+
+[docs] +def background_2d(events, reco_energy_bins, fov_offset_bins, t_obs): + """ + Calculate background rates in radially symmetric bins in the field of view. + + GADF documentation here: + https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/bkg/index.html#bkg-2d + + Parameters + ---------- + events: astropy.table.QTable + DL2 events table of the selected background events. + Needed columns for this function: `reco_source_fov_offset`, `reco_energy`, `weight` + reco_energy: astropy.units.Quantity[energy] + The bins in reconstructed energy to be used for the IRF + fov_offset_bins: astropy.units.Quantity[angle] + The bins in the field of view offset to be used for the IRF + t_obs: astropy.units.Quantity[time] + Observation time. This must match with how the individual event + weights are calculated. + + Returns + ------- + bg_rate: astropy.units.Quantity + The background rate as particles per energy, time and solid angle + in the specified bins. + + Shape: (len(reco_energy_bins) - 1, len(fov_offset_bins) - 1) + """ + + hist, _, _ = np.histogram2d( + events["reco_energy"].to_value(u.TeV), + events["reco_source_fov_offset"].to_value(u.deg), + bins=[ + reco_energy_bins.to_value(u.TeV), + fov_offset_bins.to_value(u.deg), + ], + weights=events['weight'], + ) + + # divide all energy bins by their width + # hist has shape (n_energy, n_fov_offset) so we need to transpose and then back + bin_width_energy = np.diff(reco_energy_bins) + per_energy = (hist.T / bin_width_energy).T + + # divide by solid angle in each fov bin and the observation time + bin_solid_angle = np.diff(cone_solid_angle(fov_offset_bins)) + bg_rate = per_energy / t_obs / bin_solid_angle + + return bg_rate.to(BACKGROUND_UNIT)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/irf/effective_area.html b/_modules/pyirf/irf/effective_area.html new file mode 100644 index 000000000..42d20734a --- /dev/null +++ b/_modules/pyirf/irf/effective_area.html @@ -0,0 +1,227 @@ + + + + + + pyirf.irf.effective_area — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.irf.effective_area

+import numpy as np
+import astropy.units as u
+from ..binning import create_histogram_table
+
+
+__all__ = [
+    "effective_area",
+    "effective_area_per_energy",
+    "effective_area_per_energy_and_fov",
+]
+
+
+
+[docs] +@u.quantity_input(area=u.m ** 2) +def effective_area(n_selected, n_simulated, area): + """ + Calculate effective area for histograms of selected and total simulated events + + Parameters + ---------- + n_selected: int or numpy.ndarray[int] + The number of surviving (e.g. triggered, analysed, after cuts) + n_simulated: int or numpy.ndarray[int] + The total number of events simulated + area: astropy.units.Quantity[area] + Area in which particle's core position was simulated + """ + return (n_selected / n_simulated) * area
+ + + +
+[docs] +def effective_area_per_energy(selected_events, simulation_info, true_energy_bins): + """ + Calculate effective area in bins of true energy. + + Parameters + ---------- + selected_events: astropy.table.QTable + DL2 events table, required columns for this function: `true_energy`. + simulation_info: pyirf.simulations.SimulatedEventsInfo + The overall statistics of the simulated events + true_energy_bins: astropy.units.Quantity[energy] + The bin edges in which to calculate effective area. + """ + area = np.pi * simulation_info.max_impact ** 2 + + hist_selected = create_histogram_table( + selected_events, true_energy_bins, "true_energy" + ) + hist_simulated = simulation_info.calculate_n_showers_per_energy(true_energy_bins) + + return effective_area(hist_selected["n"], hist_simulated, area)
+ + + +
+[docs] +def effective_area_per_energy_and_fov( + selected_events, simulation_info, true_energy_bins, fov_offset_bins +): + """ + Calculate effective area in bins of true energy and field of view offset. + + Parameters + ---------- + selected_events: astropy.table.QTable + DL2 events table, required columns for this function: + - `true_energy` + - `true_source_fov_offset` + 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. + """ + area = np.pi * simulation_info.max_impact ** 2 + + hist_simulated = simulation_info.calculate_n_showers_per_energy_and_fov( + true_energy_bins, fov_offset_bins + ) + + hist_selected, _, _ = np.histogram2d( + selected_events["true_energy"].to_value(u.TeV), + selected_events["true_source_fov_offset"].to_value(u.deg), + bins=[ + true_energy_bins.to_value(u.TeV), + fov_offset_bins.to_value(u.deg), + ], + ) + + return effective_area(hist_selected, hist_simulated, area)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/irf/energy_dispersion.html b/_modules/pyirf/irf/energy_dispersion.html new file mode 100644 index 000000000..fd643292a --- /dev/null +++ b/_modules/pyirf/irf/energy_dispersion.html @@ -0,0 +1,292 @@ + + + + + + pyirf.irf.energy_dispersion — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.irf.energy_dispersion

+import warnings
+import numpy as np
+import astropy.units as u
+from ..binning import resample_histogram1d
+
+
+__all__ = [
+    "energy_dispersion",
+    "energy_dispersion_to_migration"
+]
+
+
+def _normalize_hist(hist, migration_bins):
+    # make sure we do not mutate the input array
+    hist = hist.copy()
+    bin_width = np.diff(migration_bins)
+
+    # calculate number of events along the N_MIGRA axis to get events
+    # per energy per fov
+    norm = hist.sum(axis=1)
+
+    with np.errstate(invalid="ignore"):
+        # hist shape is (N_E, N_MIGRA, N_FOV), norm shape is (N_E, N_FOV)
+        # so we need to add a new axis in the middle to get (N_E, 1, N_FOV)
+        # bin_width is 1d, so we need newaxis, use the values, newaxis
+        hist = hist / norm[:, np.newaxis, :] / bin_width[np.newaxis, :, np.newaxis]
+
+    return np.nan_to_num(hist)
+
+
+
+[docs] +def energy_dispersion( + selected_events, true_energy_bins, fov_offset_bins, migration_bins, +): + """ + Calculate energy dispersion for the given DL2 event list. + Energy dispersion is defined as the probability of finding an event + at a given relative deviation ``(reco_energy / true_energy)`` for a given + true energy. + + Parameters + ---------- + selected_events: astropy.table.QTable + Table of the DL2 events. + Required columns: ``reco_energy``, ``true_energy``, ``true_source_fov_offset``. + true_energy_bins: astropy.units.Quantity[energy] + Bin edges in true energy + migration_bins: astropy.units.Quantity[energy] + Bin edges in relative deviation, recommended range: [0.2, 5] + fov_offset_bins: astropy.units.Quantity[angle] + Bin edges in the field of view offset. + For Point-Like IRFs, only giving a single bin is appropriate. + + Returns + ------- + energy_dispersion: numpy.ndarray + Energy dispersion matrix + with shape (n_true_energy_bins, n_migration_bins, n_fov_ofset_bins) + """ + mu = (selected_events["reco_energy"] / selected_events["true_energy"]).to_value( + u.one + ) + + energy_dispersion, _ = np.histogramdd( + np.column_stack( + [ + selected_events["true_energy"].to_value(u.TeV), + mu, + selected_events["true_source_fov_offset"].to_value(u.deg), + ] + ), + bins=[ + true_energy_bins.to_value(u.TeV), + migration_bins, + fov_offset_bins.to_value(u.deg), + ], + ) + + energy_dispersion = _normalize_hist(energy_dispersion, migration_bins) + + return energy_dispersion
+ + + +def energy_dispersion_to_migration( + dispersion_matrix, + disp_true_energy_edges, + disp_migration_edges, + new_true_energy_edges, + new_reco_energy_edges, +): + """ + Construct a energy migration matrix from a energy + dispersion matrix. + Depending on the new energy ranges, the sum over the first axis + can be smaller than 1. + The new true energy bins need to be a subset of the old range, + extrapolation is not supported. + New reconstruction bins outside of the old migration range are filled with + zeros. + + Parameters + ---------- + dispersion_matrix: numpy.ndarray + Energy dispersion_matrix + disp_true_energy_edges: astropy.units.Quantity[energy] + True energy edges matching the first dimension of the dispersion matrix + disp_migration_edges: numpy.ndarray + Migration edges matching the second dimension of the dispersion matrix + new_true_energy_edges: astropy.units.Quantity[energy] + True energy edges matching the first dimension of the output + new_reco_energy_edges: astropy.units.Quantity[energy] + Reco energy edges matching the second dimension of the output + + Returns: + -------- + migration_matrix: numpy.ndarray + Three-dimensional energy migration matrix. The third dimension + equals the fov offset dimension of the energy dispersion matrix. + """ + + migration_matrix = np.zeros(( + len(new_true_energy_edges) - 1, + len(new_reco_energy_edges) - 1, + dispersion_matrix.shape[2], + )) + + true_energy_interpolation = resample_histogram1d( + dispersion_matrix, + disp_true_energy_edges, + new_true_energy_edges, + axis=0, + ) + + norm = np.sum(true_energy_interpolation, axis=1, keepdims=True) + norm[norm == 0] = 1 + true_energy_interpolation /= norm + + for idx, e_true in enumerate( + (new_true_energy_edges[1:] + new_true_energy_edges[:-1]) / 2 + ): + + # get migration for the new true energy bin + e_true_dispersion = true_energy_interpolation[idx] + + with warnings.catch_warnings(): + # silence inf/inf division warning + warnings.filterwarnings('ignore', 'invalid value encountered in true_divide') + interpolation_edges = new_reco_energy_edges / e_true + + y = resample_histogram1d( + e_true_dispersion, + disp_migration_edges, + interpolation_edges, + axis=0, + ) + + migration_matrix[idx, :, :] = y + + return migration_matrix +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/irf/psf.html b/_modules/pyirf/irf/psf.html new file mode 100644 index 000000000..1a44dc8d1 --- /dev/null +++ b/_modules/pyirf/irf/psf.html @@ -0,0 +1,179 @@ + + + + + + pyirf.irf.psf — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.irf.psf

+import numpy as np
+import astropy.units as u
+
+from ..utils import cone_solid_angle
+
+
+
+[docs] +def psf_table(events, true_energy_bins, source_offset_bins, fov_offset_bins): + """ + Calculate the table based PSF (radially symmetrical bins around the true source) + """ + + array = np.column_stack( + [ + events["true_energy"].to_value(u.TeV), + events["true_source_fov_offset"].to_value(u.deg), + events["theta"].to_value(u.deg), + ] + ) + + hist, _ = np.histogramdd( + array, + [ + true_energy_bins.to_value(u.TeV), + fov_offset_bins.to_value(u.deg), + source_offset_bins.to_value(u.deg), + ], + ) + + psf = _normalize_psf(hist, source_offset_bins) + return psf
+ + + +def _normalize_psf(hist, source_offset_bins): + """Normalize the psf histogram to a probability densitity over solid angle""" + solid_angle = np.diff(cone_solid_angle(source_offset_bins)) + + # ignore numpy zero division warning + with np.errstate(invalid="ignore"): + + # normalize over the theta axis + n_events = hist.sum(axis=2) + # normalize and replace nans with 0 + psf = np.nan_to_num(hist / n_events[:, :, np.newaxis]) + + return psf / solid_angle +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/sensitivity.html b/_modules/pyirf/sensitivity.html new file mode 100644 index 000000000..ca1b987a7 --- /dev/null +++ b/_modules/pyirf/sensitivity.html @@ -0,0 +1,481 @@ + + + + + + pyirf.sensitivity — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.sensitivity

+"""
+Functions to calculate sensitivity
+"""
+import numpy as np
+from scipy.optimize import brentq
+import logging
+
+from astropy.table import QTable
+import astropy.units as u
+
+from .statistics import li_ma_significance
+from .utils import check_histograms, cone_solid_angle
+from .binning import create_histogram_table, bin_center
+
+
+__all__ = [
+    "relative_sensitivity",
+    "calculate_sensitivity",
+    "estimate_background",
+]
+
+
+log = logging.getLogger(__name__)
+
+
+def _relative_sensitivity(
+    n_on,
+    n_off,
+    alpha,
+    min_significance=5,
+    min_signal_events=10,
+    min_excess_over_background=0.05,
+    significance_function=li_ma_significance,
+):
+    if np.isnan(n_on) or np.isnan(n_off):
+        return np.nan
+
+    if n_on < 0 or n_off < 0:
+        raise ValueError(f'n_on and n_off must be positive, got {n_on}, {n_off}')
+
+    n_background = n_off * alpha
+    n_signal = n_on - n_background
+
+    if n_signal <= 0:
+        return np.inf
+
+    def equation(relative_flux):
+        n_on = n_signal * relative_flux + n_background
+        s = significance_function(n_on, n_off, alpha)
+        return s - min_significance
+
+    try:
+        # brentq needs a lower and an upper bound
+        # we will use the simple, analytically  solvable significance formula and scale it
+        # with 10 to be sure it's above the Li and Ma solution
+        # so rel * n_signal / sqrt(n_background) = target_significance
+        if n_off > 1:
+            relative_flux_naive = min_significance * np.sqrt(n_background) / n_signal
+            upper_bound = 10 * relative_flux_naive
+            lower_bound = 0.01 * relative_flux_naive
+        else:
+            upper_bound = 100
+            lower_bound = 1e-4
+
+        relative_flux = brentq(equation, lower_bound, upper_bound)
+
+    except (RuntimeError, ValueError) as e:
+        log.warn(
+            "Could not calculate relative significance for"
+            f" n_signal={n_signal:.1f}, n_off={n_off:.1f}, returning nan {e}"
+        )
+        return np.nan
+
+    # scale to achieved flux level
+    n_signal = n_signal * relative_flux
+    min_excess = min_excess_over_background * n_background
+    min_signal = max(min_signal_events, min_excess)
+
+    # if we violate the min signal events condition,
+    # increase flux until we meet the requirement
+    if n_signal < min_signal:
+        scale = min_signal / n_signal
+    else:
+        scale = 1.0
+
+    return relative_flux * scale
+
+
+_relative_sensitivity_vectorized = np.vectorize(
+    _relative_sensitivity,
+    excluded=['significance_function']
+)
+
+
+
+[docs] +def relative_sensitivity( + n_on, + n_off, + alpha, + min_significance=5, + min_signal_events=10, + min_excess_over_background=0.05, + significance_function=li_ma_significance, +): + """ + Calculate the relative sensitivity defined as the flux + relative to the reference source that is detectable with + significance ``target_significance``. + + Given measured ``n_on`` and ``n_off``, + we estimate the number of gamma events ``n_signal`` as ``n_on - alpha * n_off``. + + The number of background events ``n_background` is estimated as ``n_off * alpha``. + + In the end, we find the relative sensitivity as the scaling factor for ``n_signal`` + that yields a significance of ``target_significance``. + + The reference time should be incorporated by appropriately weighting the events + before calculating ``n_on`` and ``n_off``. + + All input values with the exception of ``significance_function`` + must be broadcastable to a single, common shape. + + Parameters + ---------- + n_on: int or array-like + Number of signal-like events for the on observations + n_off: int or array-like + Number of signal-like events for the off observations + alpha: float or array-like + Scaling factor between on and off observations. + 1 / number of off regions for wobble observations. + min_significance: float or array-like + Significance necessary for a detection + min_signal_events: int or array-like + Minimum number of signal events required. + The relative flux will be scaled up from the one yielding ``min_significance`` + if this condition is violated. + min_excess_over_background: float or array-like + Minimum number of signal events expressed as the proportion of the + background. + So the required number of signal events will be + ``min_excess_over_background * alpha * n_off``. + The relative flux will be scaled up from the one yielding ``min_significance`` + if this condition is violated. + significance_function: function + A function f(n_on, n_off, alpha) -> significance in sigma + Used to calculate the significance, default is the Li&Ma + likelihood ratio test formula. + Li, T-P., and Y-Q. Ma. + "Analysis methods for results in gamma-ray astronomy." + The Astrophysical Journal 272 (1983): 317-324. + Formula (17) + """ + return _relative_sensitivity_vectorized( + n_on=n_on, + n_off=n_off, + alpha=alpha, + min_significance=min_significance, + min_signal_events=min_signal_events, + min_excess_over_background=min_excess_over_background, + significance_function=significance_function, + )
+ + + +
+[docs] +def calculate_sensitivity( + signal_hist, + background_hist, + alpha, + min_significance=5, + min_signal_events=10, + min_excess_over_background=0.05, + significance_function=li_ma_significance, +): + """ + Calculate sensitivity for DL2 event lists in bins of reconstructed energy. + + Sensitivity is defined as the minimum flux detectable with ``target_significance`` + sigma significance in a certain time. + + This time must be incorporated into the event weights. + + Two conditions are required for the sensitivity: + - At least ten weighted signal events + - The weighted signal must be larger than 5 % of the weighted background + - At least 5 sigma (so relative_sensitivity > 1) + + If the conditions are not met, the sensitivity will be set to nan. + + Parameters + ---------- + signal_hist: astropy.table.QTable + Histogram of detected signal events as a table. + Required columns: n and n_weighted. + See ``pyirf.binning.create_histogram_table`` + background_hist: astropy.table.QTable + Histogram of detected events as a table. + Required columns: n and n_weighted. + See ``pyirf.binning.create_histogram_table`` + alpha: float + Size ratio of signal region to background region + min_significance: float + Significance necessary for a detection + min_signal_events: int + Minimum number of signal events required. + The relative flux will be scaled up from the one yielding ``min_significance`` + if this condition is violated. + min_excess_over_background: float + Minimum number of signal events expressed as the proportion of the + background. + So the required number of signal events will be + ``min_excess_over_background * alpha * n_off``. + The relative flux will be scaled up from the one yielding ``min_significance`` + if this condition is violated. + significance_function: callable + A function with signature (n_on, n_off, alpha) -> significance. + Default is the Li & Ma likelihood ratio test. + + Returns + ------- + sensitivity_table: astropy.table.QTable + Table with sensitivity information. + Contains weighted and unweighted number of signal and background events + and the ``relative_sensitivity``, the scaling applied to the signal events + that yields ``target_significance`` sigma of significance according to + the ``significance_function`` + """ + check_histograms(signal_hist, background_hist) + + n_on = signal_hist["n_weighted"] + alpha * background_hist["n_weighted"] + + # convert any quantities to arrays, + # since quantitites don't work with vectorize + n_on = u.Quantity(n_on, copy=False).to_value(u.one) + n_off = u.Quantity(background_hist["n_weighted"], copy=False).to_value(u.one) + + # calculate sensitivity in each bin + rel_sens = relative_sensitivity( + n_on=n_on, + n_off=n_off, + alpha=alpha, + min_significance=min_significance, + min_signal_events=min_signal_events, + min_excess_over_background=min_excess_over_background, + significance_function=significance_function, + ) + + # fill output table + s = QTable() + for key in ("low", "high", "center"): + k = "reco_energy_" + key + s[k] = signal_hist[k] + + with np.errstate(invalid="ignore"): + s["n_signal"] = signal_hist["n"] * rel_sens + s["n_signal_weighted"] = signal_hist["n_weighted"] * rel_sens + s["n_background"] = background_hist["n"] + s["n_background_weighted"] = background_hist["n_weighted"] + + # copy also "n_proton" / "n_electron_weighted" etc. if available + for k in filter(lambda c: c.startswith('n_') and c != 'n_weighted', background_hist.colnames): + s[k] = background_hist[k] + + s["significance"] = significance_function( + n_on=s["n_signal_weighted"] + alpha * s["n_background_weighted"], + n_off=s["n_background_weighted"], + alpha=alpha, + ) + s["relative_sensitivity"] = rel_sens + + return s
+ + + +
+[docs] +def estimate_background( + events, reco_energy_bins, theta_cuts, alpha, + fov_offset_min, fov_offset_max, +): + ''' + Estimate the number of background events for a point-like sensitivity. + + Due to limited statistics, it is often not possible to just apply the same + theta cut to the background events as to the signal events around an assumed + source position. + + Here we calculate the expected number of background events for the off + regions by taking all background events between ``fov_offset_min`` and + ``fov_offset_max`` from the camera center and then scale these to the size + of the off region, which is scaled by 1 / alpha from the size of the on + region given by the theta cuts. + + + Parameters + ---------- + events: astropy.table.QTable + DL2 event list of background surviving event selection + and inside ``fov_offset_max`` from the center of the FOV + Required columns for this function: + - `reco_energy`, + - `reco_source_fov_offset`. + reco_energy_bins: astropy.units.Quantity[energy] + Desired bin edges in reconstructed energy for the background rate + theta_cuts: astropy.table.QTable + The cuts table for the theta cut, + e.g. as returned by ``~pyirf.cuts.calculate_percentile_cut``. + Columns `center` and `cut` are required for this function. + alpha: float + size of the on region divided by the size of the off region. + fov_offset_min: astropy.units.Quantity[angle] + Minimum distance from the fov center for background events to be taken into account + fov_offset_max: astropy.units.Quantity[angle] + Maximum distance from the fov center for background events to be taken into account + ''' + in_ring = ( + (events['reco_source_fov_offset'] >= fov_offset_min) + & (events['reco_source_fov_offset'] < fov_offset_max) + ) + + bg = create_histogram_table( + events[in_ring], + reco_energy_bins, + key='reco_energy', + ) + + # scale number of background events according to the on region size + # background radius and alpha + center = bin_center(reco_energy_bins) + # interpolate the theta cut to the bins used here + theta_cuts_bg_bins = np.interp( + center, + theta_cuts['center'], + theta_cuts['cut'] + ) + + solid_angle_on = cone_solid_angle(theta_cuts_bg_bins) + solid_angle_ring = cone_solid_angle(fov_offset_max) - cone_solid_angle(fov_offset_min) + size_ratio = (solid_angle_on / solid_angle_ring).to_value(u.one) + + for key in filter(lambda col: col.startswith('n'), bg.colnames): + # *= not possible due to upcast from int to float + bg[key] = bg[key] * size_ratio / alpha + + return bg
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/simulations.html b/_modules/pyirf/simulations.html new file mode 100644 index 000000000..3bf840478 --- /dev/null +++ b/_modules/pyirf/simulations.html @@ -0,0 +1,369 @@ + + + + + + pyirf.simulations — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.simulations

+import astropy.units as u
+import numpy as np
+
+__all__ = [
+    'SimulatedEventsInfo',
+]
+
+
+
+[docs] +class SimulatedEventsInfo: + """ + Information about all simulated events, for calculating event weights. + + Attributes + ---------- + n_showers: int + Total number of simulated showers. If reuse was used, this + should already include the reuse. + energy_min: u.Quantity[energy] + Lower limit of the simulated energy range + energy_max: u.Quantity[energy] + Upper limit of the simulated energy range + max_impact: u.Quantity[length] + Maximum simulated impact parameter + spectral_index: float + Spectral Index of the simulated power law with sign included. + viewcone_min: u.Quantity[angle] + Inner angle of the viewcone + viewcone_max: u.Quantity[angle] + Outer angle of the viewcone + """ + + __slots__ = ( + "n_showers", + "energy_min", + "energy_max", + "max_impact", + "spectral_index", + "viewcone_min", + "viewcone_max", + ) + + @u.quantity_input( + energy_min=u.TeV, energy_max=u.TeV, max_impact=u.m, viewcone_min=u.deg, viewcone_max=u.deg + ) + def __init__( + self, n_showers, energy_min, energy_max, max_impact, spectral_index, viewcone_min, viewcone_max, + ): + #: Total number of simulated showers, if reuse was used, this must + #: already include reuse + self.n_showers = n_showers + #: Lower limit of the simulated energy range + self.energy_min = energy_min + #: Upper limit of the simulated energy range + self.energy_max = energy_max + #: Maximum simualted impact radius + self.max_impact = max_impact + #: Spectral index of the simulated power law with sign included + self.spectral_index = spectral_index + #: Inner viewcone angle + self.viewcone_min = viewcone_min + #: Outer viewcone angle + self.viewcone_max = viewcone_max + + if spectral_index > -1: + raise ValueError("spectral index must be <= -1") + +
+[docs] + @u.quantity_input(energy_bins=u.TeV) + def calculate_n_showers_per_energy(self, energy_bins): + """ + Calculate number of showers that were simulated in the given energy intervals + + This assumes the events were generated and from a powerlaw + like CORSIKA simulates events. + + Parameters + ---------- + energy_bins: astropy.units.Quantity[energy] + The interval edges for which to calculate the number of simulated showers + + Returns + ------- + n_showers: numpy.ndarray + The expected number of events inside each of the ``energy_bins``. + This is a floating point number. + The actual numbers will follow a poissionian distribution around this + expected value. + """ + bins = energy_bins + e_low = bins[:-1] + e_high = bins[1:] + e_min = self.energy_min + e_max = self.energy_max + + integral = _powerlaw_pdf_integral( + self.spectral_index, e_low, e_high, e_min, e_max + ) + + integral[e_high <= e_min] = 0 + integral[e_low >= e_max] = 0 + + mask = (e_high > e_max) & (e_low < e_max) + integral[mask] = _powerlaw_pdf_integral( + self.spectral_index, e_low[mask], e_max, e_min, e_max + ) + + mask = (e_high > e_min) & (e_low < e_min) + integral[mask] = _powerlaw_pdf_integral( + self.spectral_index, e_min, e_high[mask], e_min, e_max + ) + + return self.n_showers * integral
+ + +
+[docs] + def calculate_n_showers_per_fov(self, fov_bins): + """ + Calculate number of showers that were simulated in the given fov bins. + + This assumes the events were generated uniformly distributed per solid angle, + like CORSIKA simulates events with the VIEWCONE option. + + Parameters + ---------- + fov_bins: astropy.units.Quantity[angle] + The FOV bin edges for which to calculate the number of simulated showers + + Returns + ------- + n_showers: numpy.ndarray(ndim=2) + The expected number of events inside each of the ``fov_bins``. + This is a floating point number. + The actual numbers will follow a poissionian distribution around this + expected value. + """ + fov_bins = fov_bins + fov_low = fov_bins[:-1] + fov_high = fov_bins[1:] + fov_integral = _viewcone_pdf_integral(self.viewcone_min, self.viewcone_max, fov_low, fov_high) + return self.n_showers * fov_integral
+ + +
+[docs] + @u.quantity_input(energy_bins=u.TeV, fov_bins=u.deg) + def calculate_n_showers_per_energy_and_fov(self, energy_bins, fov_bins): + """ + Calculate number of showers that were simulated in the given + energy and fov bins. + + 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_bins: astropy.units.Quantity[angle] + The FOV bin edges for which to calculate the number of simulated showers + + Returns + ------- + n_showers: numpy.ndarray(ndim=2) + The expected number of events inside each of the + ``energy_bins`` and ``fov_bins``. + Dimension (n_energy_bins, n_fov_bins) + This is a floating point number. + The actual numbers will follow a poissionian distribution around this + expected value. + """ + # energy distribution and fov distribution are independent in CORSIKA, + # so just multiply both distributions. + e_integral = self.calculate_n_showers_per_energy(energy_bins) + fov_integral = self.calculate_n_showers_per_fov(fov_bins) + return e_integral[:, np.newaxis] * fov_integral / self.n_showers
+ + + def __repr__(self): + return ( + f"{self.__class__.__name__}(" + f"n_showers={self.n_showers}, " + f"energy_min={self.energy_min:.3f}, " + f"energy_max={self.energy_max:.2f}, " + f"spectral_index={self.spectral_index:.1f}, " + f"max_impact={self.max_impact:.2f}, " + f"viewcone_min={self.viewcone_min}" + f"viewcone_max={self.viewcone_max}" + ")" + )
+ + + +def _powerlaw_pdf_integral(index, e_low, e_high, e_min, e_max): + # strip units, make sure all in the same unit + e_low = e_low.to_value(u.TeV) + e_high = e_high.to_value(u.TeV) + e_min = e_min.to_value(u.TeV) + e_max = e_max.to_value(u.TeV) + + int_index = index + 1 + normalization = 1 / (e_max ** int_index - e_min ** int_index) + e_term = e_high ** int_index - e_low ** int_index + return e_term * normalization + + +def _viewcone_pdf_integral(viewcone_min, viewcone_max, fov_low, fov_high): + """ + CORSIKA draws particles in the viewcone uniform per solid angle between + viewcone_min and viewcone_max, the associated pdf is: + + pdf(theta, theta_min, theta_max) = sin(theta) / (cos(theta_min) - cos(theta_max)) + """ + scalar = np.asanyarray(fov_low).ndim == 0 + + fov_low = np.atleast_1d(fov_low) + fov_high = np.atleast_1d(fov_high) + + if (viewcone_max - viewcone_min).value == 0: + raise ValueError("Only supported for diffuse simulations") + else: + norm = 1 / (np.cos(viewcone_min) - np.cos(viewcone_max)) + + inside = (fov_high >= viewcone_min) & (fov_low <= viewcone_max) + + integral = np.zeros(fov_low.shape) + lower = np.where(fov_low[inside] > viewcone_min, fov_low[inside], viewcone_min) + upper = np.where(fov_high[inside] < viewcone_max, fov_high[inside], viewcone_max) + + integral[inside] = np.cos(lower) - np.cos(upper) + integral *= norm + + if scalar: + return np.squeeze(integral) + return integral +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/spectral.html b/_modules/pyirf/spectral.html new file mode 100644 index 000000000..7446c9c9d --- /dev/null +++ b/_modules/pyirf/spectral.html @@ -0,0 +1,583 @@ + + + + + + pyirf.spectral — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.spectral

+"""
+Functions and classes for calculating spectral weights
+"""
+import sys
+from importlib.resources import as_file, files
+
+import astropy.units as u
+import numpy as np
+from astropy.table import QTable
+from scipy.interpolate import interp1d
+
+from .utils import cone_solid_angle
+
+#: Unit of a point source flux
+#:
+#: Number of particles per Energy, time and area
+POINT_SOURCE_FLUX_UNIT = (1 / u.TeV / u.s / u.m**2).unit
+
+#: Unit of a diffuse flux
+#:
+#: Number of particles per Energy, time, area and solid_angle
+DIFFUSE_FLUX_UNIT = POINT_SOURCE_FLUX_UNIT / u.sr
+
+
+__all__ = [
+    "POINT_SOURCE_FLUX_UNIT",
+    "DIFFUSE_FLUX_UNIT",
+    "calculate_event_weights",
+    "PowerLaw",
+    "LogParabola",
+    "PowerLawWithExponentialGaussian",
+    "CRAB_HEGRA",
+    "CRAB_MAGIC_JHEAP2015",
+    "PDG_ALL_PARTICLE",
+    "IRFDOC_PROTON_SPECTRUM",
+    "IRFDOC_ELECTRON_SPECTRUM",
+    "TableInterpolationSpectrum",
+    "DAMPE_P_He_SPECTRUM",
+]
+
+
+
+[docs] +@u.quantity_input(true_energy=u.TeV) +def calculate_event_weights(true_energy, target_spectrum, simulated_spectrum): + r""" + Calculate event weights + + Events with a certain ``simulated_spectrum`` are reweighted to ``target_spectrum``. + + .. math:: + w_i = \frac{\Phi_\text{Target}(E_i)}{\Phi_\text{Simulation}(E_i)} + + Parameters + ---------- + true_energy: astropy.units.Quantity[energy] + True energy of the event + target_spectrum: callable + The target spectrum. Must be a allable with signature (energy) -> flux + simulated_spectrum: callable + The simulated spectrum. Must be a callable with signature (energy) -> flux + + Returns + ------- + weights: numpy.ndarray + Weights for each event + """ + return (target_spectrum(true_energy) / simulated_spectrum(true_energy)).to_value( + u.one + )
+ + + +
+[docs] +class PowerLaw: + r""" + A power law with normalization, reference energy and index. + Index includes the sign: + + .. math:: + + \Phi(E, \Phi_0, \gamma, E_\text{ref}) = + \Phi_0 \left(\frac{E}{E_\text{ref}}\right)^{\gamma} + + Attributes + ---------- + normalization: astropy.units.Quantity[flux] + :math:`\Phi_0`, + index: float + :math:`\gamma` + e_ref: astropy.units.Quantity[energy] + :math:`E_\text{ref}` + """ + + @u.quantity_input( + normalization=[DIFFUSE_FLUX_UNIT, POINT_SOURCE_FLUX_UNIT], e_ref=u.TeV + ) + def __init__(self, normalization, index, e_ref=1 * u.TeV): + """Create a new PowerLaw spectrum""" + if index > 0: + raise ValueError(f"Index must be < 0, got {index}") + + self.normalization = normalization + self.index = index + self.e_ref = e_ref + +
+[docs] + @u.quantity_input(energy=u.TeV) + def __call__(self, energy): + e = (energy / self.e_ref).to_value(u.one) + return self.normalization * e**self.index
+ + +
+[docs] + @classmethod + @u.quantity_input(obstime=u.hour, e_ref=u.TeV) + def from_simulation(cls, simulated_event_info, obstime, e_ref=1 * u.TeV): + """ + Calculate the flux normalization for simulated events drawn + from a power law for a certain observation time. + """ + e_min = simulated_event_info.energy_min + e_max = simulated_event_info.energy_max + index = simulated_event_info.spectral_index + n_showers = simulated_event_info.n_showers + viewcone_min = simulated_event_info.viewcone_min + viewcone_max = simulated_event_info.viewcone_max + + if (viewcone_max - viewcone_min).value > 0: + solid_angle = cone_solid_angle(viewcone_max) - cone_solid_angle(viewcone_min) + unit = DIFFUSE_FLUX_UNIT + else: + solid_angle = 1 + unit = POINT_SOURCE_FLUX_UNIT + + A = np.pi * simulated_event_info.max_impact**2 + + delta = e_max ** (index + 1) - e_min ** (index + 1) + nom = (index + 1) * e_ref**index * n_showers + denom = (A * obstime * solid_angle) * delta + + return cls( + normalization=(nom / denom).to(unit), + index=index, + e_ref=e_ref, + )
+ + + def __repr__(self): + return f"{self.__class__.__name__}({self.normalization} * (E / {self.e_ref})**{self.index})" + +
+[docs] + @u.quantity_input(inner=u.rad, outer=u.rad) + def integrate_cone(self, inner, outer): + """Integrate this powerlaw over solid angle in the given cone + + Parameters + ---------- + inner : astropy.units.Quantity[angle] + inner opening angle of cone + outer : astropy.units.Quantity[angle] + outer opening angle of cone + + Returns + ------- + integrated : PowerLaw + A new powerlaw instance with new normalization with the integration + result. + """ + if not self.normalization.unit.is_equivalent(DIFFUSE_FLUX_UNIT): + raise ValueError("Can only integrate a diffuse flux over solid angle") + + solid_angle = cone_solid_angle(outer) - cone_solid_angle(inner) + + return PowerLaw( + normalization=self.normalization * solid_angle, + index=self.index, + e_ref=self.e_ref, + )
+
+ + + +
+[docs] +class LogParabola: + r""" + A log parabola flux parameterization. + + .. math:: + + \Phi(E, \Phi_0, \alpha, \beta, E_\text{ref}) = + \Phi_0 \left( + \frac{E}{E_\text{ref}} + \right)^{\alpha + \beta \cdot \log_{10}(E / E_\text{ref})} + + Attributes + ---------- + normalization: astropy.units.Quantity[flux] + :math:`\Phi_0`, + a: float + :math:`\alpha` + b: float + :math:`\beta` + e_ref: astropy.units.Quantity[energy] + :math:`E_\text{ref}` + """ + + @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): + """Create a new LogParabola spectrum""" + self.normalization = normalization + self.a = a + self.b = b + self.e_ref = e_ref + +
+[docs] + @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))
+ + + def __repr__(self): + return f"{self.__class__.__name__}({self.normalization} * (E / {self.e_ref})**({self.a} + {self.b} * log10(E / {self.e_ref}))"
+ + + +
+[docs] +class PowerLawWithExponentialGaussian(PowerLaw): + r""" + A power law with an additional Gaussian bump. + Beware that the Gaussian is not normalized! + + .. math:: + + \Phi(E, \Phi_0, \gamma, f, \mu, \sigma, E_\text{ref}) = + \Phi_0 \left( + \frac{E}{E_\text{ref}} + \right)^{\gamma} + \cdot \left( + 1 + f \cdot + \left( + \exp\left( + \operatorname{Gauss}(\log_{10}(E / E_\text{ref}), \mu, \sigma) + \right) - 1 + \right) + \right) + + Where :math:`\operatorname{Gauss}` is the unnormalized Gaussian distribution: + + .. math:: + \operatorname{Gauss}(x, \mu, \sigma) = \exp\left( + -\frac{1}{2} \left(\frac{x - \mu}{\sigma}\right)^2 + \right) + + Attributes + ---------- + normalization: astropy.units.Quantity[flux] + :math:`\Phi_0`, + a: float + :math:`\alpha` + b: float + :math:`\beta` + e_ref: astropy.units.Quantity[energy] + :math:`E_\text{ref}` + """ + + @u.quantity_input( + normalization=[DIFFUSE_FLUX_UNIT, POINT_SOURCE_FLUX_UNIT], e_ref=u.TeV + ) + def __init__(self, normalization, index, e_ref, f, mu, sigma): + """Create a new PowerLawWithExponentialGaussian spectrum""" + super().__init__(normalization=normalization, index=index, e_ref=e_ref) + self.f = f + self.mu = mu + self.sigma = sigma + +
+[docs] + @u.quantity_input(energy=u.TeV) + def __call__(self, energy): + power = super().__call__(energy) + log10_e = np.log10(energy / self.e_ref) + # ROOT's TMath::Gauss does not add the normalization + # this is missing from the IRFDocs + # the code used for the plot can be found here: + # https://gitlab.cta-observatory.org/cta-consortium/aswg/irfs-macros/cosmic-rays-spectra/-/blob/master/electron_spectrum.C#L508 + gauss = np.exp(-0.5 * ((log10_e - self.mu) / self.sigma) ** 2) + return power * (1 + self.f * (np.exp(gauss) - 1))
+ + + def __repr__(self): + s = super().__repr__() + gauss = f"Gauss(log10(E / {self.e_ref}), {self.mu}, {self.sigma})" + return s[:-1] + f" * (1 + {self.f} * (exp({gauss}) - 1))"
+ + + +
+[docs] +class TableInterpolationSpectrum: + """ + Interpolate flux points to obtain a spectrum. + + By default, flux is interpolated linearly in log-log space. + """ + + def __init__( + self, energy, flux, log_energy=True, log_flux=True, reference_energy=1 * u.TeV + ): + """Create a new TableInterpolationSpectrum spectrum""" + self.energy = energy + self.flux = flux + self.flux_unit = flux.unit + self.log_energy = log_energy + self.log_flux = log_flux + self.reference_energy = reference_energy + + x = (energy / reference_energy).to_value(u.one) + y = flux.to_value(self.flux_unit) + + if log_energy: + x = np.log10(x) + + if log_flux: + y = np.log10(y) + + self.interp = interp1d(x, y, bounds_error=False, fill_value="extrapolate") + +
+[docs] + def __call__(self, energy): + + x = (energy / self.reference_energy).to_value(u.one) + + if self.log_energy: + x = np.log10(x) + + y = self.interp(x) + + if self.log_flux: + y = 10**y + + return u.Quantity(y, self.flux_unit, copy=False)
+ + +
+[docs] + @classmethod + def from_table( + cls, table: QTable, log_energy=True, log_flux=True, reference_energy=1 * u.TeV + ): + return cls( + table["energy"], + table["flux"], + log_energy=log_energy, + log_flux=log_flux, + reference_energy=reference_energy, + )
+ + +
+[docs] + @classmethod + def from_file( + cls, path, log_energy=True, log_flux=True, reference_energy=1 * u.TeV + ): + return cls.from_table( + QTable.read(path), + log_energy=log_energy, + log_flux=log_flux, + reference_energy=reference_energy, + )
+
+ + + +#: Power Law parametrization of the Crab Nebula spectrum as published by HEGRA +#: +#: From "The Crab Nebula and Pulsar between 500 GeV and 80 TeV: Observations with the HEGRA stereoscopic air Cherenkov telescopes", +#: Aharonian et al, 2004, ApJ 614.2 +#: doi.org/10.1086/423931 +CRAB_HEGRA = PowerLaw( + normalization=2.83e-11 / (u.TeV * u.cm**2 * u.s), + index=-2.62, + e_ref=1 * u.TeV, +) + +#: Log-Parabola parametrization of the Crab Nebula spectrum as published by MAGIC +#: +#: From "Measurement of the Crab Nebula spectrum over three decades in energy with the MAGIC telescopes", +#: Aleksìc et al., 2015, JHEAP +#: https://doi.org/10.1016/j.jheap.2015.01.002 +CRAB_MAGIC_JHEAP2015 = LogParabola( + normalization=3.23e-11 / (u.TeV * u.cm**2 * u.s), + a=-2.47, + b=-0.24, +) + + +#: All particle spectrum +#: +#: (30.2) from "The Review of Particle Physics (2020)" +#: https://pdg.lbl.gov/2020/reviews/rpp2020-rev-cosmic-rays.pdf +PDG_ALL_PARTICLE = PowerLaw( + normalization=1.8e4 / (u.GeV * u.m**2 * u.s * u.sr), + index=-2.7, + e_ref=1 * u.GeV, +) + +#: Proton spectrum definition defined in the CTA Prod3b IRF Document +#: +#: From "Description of CTA Instrument Response Functions (Production 3b Simulation)", section 4.3.1 +#: https://gitlab.cta-observatory.org/cta-consortium/aswg/documentation/internal_reports/irfs-reports/prod3b-irf-description +IRFDOC_PROTON_SPECTRUM = PowerLaw( + normalization=9.8e-6 / (u.cm**2 * u.s * u.TeV * u.sr), + index=-2.62, + e_ref=1 * u.TeV, +) + +#: Electron spectrum definition defined in the CTA Prod3b IRF Document +#: +#: From "Description of CTA Instrument Response Functions (Production 3b Simulation)", section 4.3.1 +#: https://gitlab.cta-observatory.org/cta-consortium/aswg/documentation/internal_reports/irfs-reports/prod3b-irf-description +IRFDOC_ELECTRON_SPECTRUM = PowerLawWithExponentialGaussian( + normalization=2.385e-9 / (u.TeV * u.cm**2 * u.s * u.sr), + index=-3.43, + e_ref=1 * u.TeV, + mu=-0.101, + sigma=0.741, + f=1.950, +) + +#: Proton + Helium interpolated from DAMPE measurements +#: +#: Datapoints obtained from obtained from: +#: https://inspirehep.net/files/62efc8374ffced58ea7e3a333bfa1217 +#: Points are from DAMPE, up to 8 TeV. +#: For higher energies we assume a +#: flattening of the dF/dE*E^2.7 more or less in the middle of the large +#: spread of the available data reported on the same proceeding. +with as_file(files("pyirf") / "resources/dampe_p+he.ecsv") as _path: + DAMPE_P_He_SPECTRUM = TableInterpolationSpectrum.from_file(_path) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/statistics.html b/_modules/pyirf/statistics.html new file mode 100644 index 000000000..ef8902e15 --- /dev/null +++ b/_modules/pyirf/statistics.html @@ -0,0 +1,195 @@ + + + + + + pyirf.statistics — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.statistics

+import numpy as np
+
+from .utils import is_scalar
+
+__all__ = ["li_ma_significance"]
+
+
+
+[docs] +def li_ma_significance(n_on, n_off, alpha=0.2): + """ + Calculate the Li & Ma significance. + + Formula (17) in https://doi.org/10.1086/161295 + + This functions returns 0 significance when n_on < alpha * n_off + instead of the negative sensitivities that would result from naively + evaluating the formula. + + Parameters + ---------- + n_on: integer or array like + Number of events for the on observations + n_off: integer or array like + Number of events for the off observations + alpha: float + Ratio between the on region and the off region size or obstime. + + Returns + ------- + s_lima: float or array + The calculated significance + """ + + scalar = is_scalar(n_on) + + # Cast everything into float64 to avoid numeric instabilties + # when multiplying very small and very big numbers to get t1 and t2 + n_on = np.array(n_on, copy=False, ndmin=1, dtype=np.float64) + n_off = np.array(n_off, copy=False, ndmin=1, dtype=np.float64) + alpha = np.float64(alpha) + + with np.errstate(divide="ignore", invalid="ignore"): + p_on = n_on / (n_on + n_off) + p_off = n_off / (n_on + n_off) + + t1 = n_on * np.log(((1 + alpha) / alpha) * p_on) + t2 = n_off * np.log((1 + alpha) * p_off) + + # lim x+->0 (x log(x)) = 0 + t1[n_on == 0] = 0 + t2[n_off == 0] = 0 + + ts = t1 + t2 + + significance = np.sqrt(ts * 2) + + significance[n_on < (alpha * n_off)] = 0 + + if scalar: + return significance[0] + + return significance
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyirf/utils.html b/_modules/pyirf/utils.html new file mode 100644 index 000000000..873a140c1 --- /dev/null +++ b/_modules/pyirf/utils.html @@ -0,0 +1,308 @@ + + + + + + pyirf.utils — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyirf.utils

+import numpy as np
+import astropy.units as u
+from astropy.coordinates import angular_separation
+
+from .exceptions import MissingColumns, WrongColumnUnit
+
+
+__all__ = [
+    "is_scalar",
+    "calculate_theta",
+    "calculate_source_fov_offset",
+    "check_histograms",
+    "cone_solid_angle",
+]
+
+
+
+[docs] +def is_scalar(val): + """Workaround that also supports astropy quantities + + Parameters + ---------- + val : object + Any object (value, list, etc...) + + Returns + ------- + result: bool + True is if input object is a scalar, False otherwise. + """ + result = np.array(val, copy=False).shape == tuple() + return result
+ + + +
+[docs] +@u.quantity_input(assumed_source_az=u.deg, assumed_source_alt=u.deg) +def calculate_theta(events, assumed_source_az, assumed_source_alt): + """Calculate sky separation between assumed and reconstructed positions. + + Parameters + ---------- + events : astropy.QTable + Astropy Table object containing the reconstructed events information. + assumed_source_az: astropy.units.Quantity + Assumed Azimuth angle of the source. + assumed_source_alt: astropy.units.Quantity + Assumed Altitude angle of the source. + + Returns + ------- + theta: astropy.units.Quantity + Angular separation between the assumed and reconstructed positions + in the sky. + """ + theta = angular_separation( + assumed_source_az, + assumed_source_alt, + events["reco_az"], + events["reco_alt"], + ) + + return theta.to(u.deg)
+ + + +
+[docs] +def calculate_source_fov_offset(events, prefix="true"): + """Calculate angular separation between true and 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 offset. + + Returns + ------- + theta: astropy.units.Quantity + Angular separation between the true and pointing positions + in the sky. + """ + theta = angular_separation( + events[f"{prefix}_az"], + events[f"{prefix}_alt"], + events["pointing_az"], + events["pointing_alt"], + ) + + return theta.to(u.deg)
+ + + +
+[docs] +def check_histograms(hist1, hist2, key="reco_energy"): + """ + Check if two histogram tables have the same binning + + Parameters + ---------- + hist1: ``~astropy.table.Table`` + First histogram table, as created by + ``~pyirf.binning.create_histogram_table`` + hist2: ``~astropy.table.Table`` + Second histogram table + """ + + # check binning information and add to output + for k in ("low", "center", "high"): + k = key + "_" + k + if not np.all(hist1[k] == hist2[k]): + raise ValueError( + "Binning for signal_hist and background_hist must be equal" + )
+ + + +
+[docs] +def cone_solid_angle(angle): + """Calculate the solid angle of a view cone. + + Parameters + ---------- + angle: astropy.units.Quantity or astropy.coordinates.Angle + Opening angle of the view cone. + + Returns + ------- + solid_angle: astropy.units.Quantity + Solid angle of a view cone with opening angle ``angle``. + + """ + solid_angle = 2 * np.pi * (1 - np.cos(angle)) * u.sr + return solid_angle
+ + + +def check_table(table, required_columns=None, required_units=None): + """Check a table for required columns and units. + + Parameters + ---------- + table: astropy.table.QTable + Table to check + required_columns: iterable[str] + Column names that are required to be present + required_units: Mapping[str->astropy.units.Unit] + Required units for columns as a Mapping from column names to units. + Checks if the units are convertible, not if they are identical + + Raises + ------ + MissingColumns: If any of the columns specified in ``required_columns`` or + as keys in ``required_units are`` not present in the table. + WrongColumnUnit: if any column has the wrong unit + """ + if required_columns is not None: + missing = set(required_columns) - set(table.colnames) + if missing: + raise MissingColumns(missing) + + if required_units is not None: + for col, expected in required_units.items(): + if col not in table.colnames: + raise MissingColumns(col) + + unit = table[col].unit + if not expected.is_equivalent(unit): + raise WrongColumnUnit(col, unit, expected) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_sources/AUTHORS.rst.txt b/_sources/AUTHORS.rst.txt new file mode 100644 index 000000000..39c519851 --- /dev/null +++ b/_sources/AUTHORS.rst.txt @@ -0,0 +1,18 @@ +.. _authors: + +Authors +======= + +To see who contributed to ``pyirf``, please visit the +`GitHub contributors page `__ +or run + +.. code-block:: bash + + git shortlog -sne + + +``pyirf`` started as part of `protopipe `__ by Julien Lefaucher, +but was largely rewritten in September 2020, making use of code from the +previous version, the `pyfact `__ module and the +`FACT irf `__ package. diff --git a/_sources/api/pyirf.benchmarks.angular_resolution.rst.txt b/_sources/api/pyirf.benchmarks.angular_resolution.rst.txt new file mode 100644 index 000000000..d4ae2a44b --- /dev/null +++ b/_sources/api/pyirf.benchmarks.angular_resolution.rst.txt @@ -0,0 +1,6 @@ +angular_resolution +================== + +.. currentmodule:: pyirf.benchmarks + +.. autofunction:: angular_resolution diff --git a/_sources/api/pyirf.benchmarks.energy_bias_resolution.rst.txt b/_sources/api/pyirf.benchmarks.energy_bias_resolution.rst.txt new file mode 100644 index 000000000..fd2516fb6 --- /dev/null +++ b/_sources/api/pyirf.benchmarks.energy_bias_resolution.rst.txt @@ -0,0 +1,6 @@ +energy_bias_resolution +====================== + +.. currentmodule:: pyirf.benchmarks + +.. autofunction:: energy_bias_resolution diff --git a/_sources/api/pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion.rst.txt b/_sources/api/pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion.rst.txt new file mode 100644 index 000000000..7315f58ce --- /dev/null +++ b/_sources/api/pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion.rst.txt @@ -0,0 +1,6 @@ +energy_bias_resolution_from_energy_dispersion +============================================= + +.. currentmodule:: pyirf.benchmarks + +.. autofunction:: energy_bias_resolution_from_energy_dispersion diff --git a/_sources/api/pyirf.binning.add_overflow_bins.rst.txt b/_sources/api/pyirf.binning.add_overflow_bins.rst.txt new file mode 100644 index 000000000..5f7ac809f --- /dev/null +++ b/_sources/api/pyirf.binning.add_overflow_bins.rst.txt @@ -0,0 +1,6 @@ +add_overflow_bins +================= + +.. currentmodule:: pyirf.binning + +.. autofunction:: add_overflow_bins diff --git a/_sources/api/pyirf.binning.bin_center.rst.txt b/_sources/api/pyirf.binning.bin_center.rst.txt new file mode 100644 index 000000000..916be12f0 --- /dev/null +++ b/_sources/api/pyirf.binning.bin_center.rst.txt @@ -0,0 +1,6 @@ +bin_center +========== + +.. currentmodule:: pyirf.binning + +.. autofunction:: bin_center diff --git a/_sources/api/pyirf.binning.calculate_bin_indices.rst.txt b/_sources/api/pyirf.binning.calculate_bin_indices.rst.txt new file mode 100644 index 000000000..f0bd70f8d --- /dev/null +++ b/_sources/api/pyirf.binning.calculate_bin_indices.rst.txt @@ -0,0 +1,6 @@ +calculate_bin_indices +===================== + +.. currentmodule:: pyirf.binning + +.. autofunction:: calculate_bin_indices diff --git a/_sources/api/pyirf.binning.create_bins_per_decade.rst.txt b/_sources/api/pyirf.binning.create_bins_per_decade.rst.txt new file mode 100644 index 000000000..a41cc7798 --- /dev/null +++ b/_sources/api/pyirf.binning.create_bins_per_decade.rst.txt @@ -0,0 +1,6 @@ +create_bins_per_decade +====================== + +.. currentmodule:: pyirf.binning + +.. autofunction:: create_bins_per_decade diff --git a/_sources/api/pyirf.binning.create_histogram_table.rst.txt b/_sources/api/pyirf.binning.create_histogram_table.rst.txt new file mode 100644 index 000000000..d95e8b1ce --- /dev/null +++ b/_sources/api/pyirf.binning.create_histogram_table.rst.txt @@ -0,0 +1,6 @@ +create_histogram_table +====================== + +.. currentmodule:: pyirf.binning + +.. autofunction:: create_histogram_table diff --git a/_sources/api/pyirf.binning.join_bin_lo_hi.rst.txt b/_sources/api/pyirf.binning.join_bin_lo_hi.rst.txt new file mode 100644 index 000000000..40fec327c --- /dev/null +++ b/_sources/api/pyirf.binning.join_bin_lo_hi.rst.txt @@ -0,0 +1,6 @@ +join_bin_lo_hi +============== + +.. currentmodule:: pyirf.binning + +.. autofunction:: join_bin_lo_hi diff --git a/_sources/api/pyirf.binning.resample_histogram1d.rst.txt b/_sources/api/pyirf.binning.resample_histogram1d.rst.txt new file mode 100644 index 000000000..6bc78ee2f --- /dev/null +++ b/_sources/api/pyirf.binning.resample_histogram1d.rst.txt @@ -0,0 +1,6 @@ +resample_histogram1d +==================== + +.. currentmodule:: pyirf.binning + +.. autofunction:: resample_histogram1d diff --git a/_sources/api/pyirf.binning.split_bin_lo_hi.rst.txt b/_sources/api/pyirf.binning.split_bin_lo_hi.rst.txt new file mode 100644 index 000000000..e216cf7fb --- /dev/null +++ b/_sources/api/pyirf.binning.split_bin_lo_hi.rst.txt @@ -0,0 +1,6 @@ +split_bin_lo_hi +=============== + +.. currentmodule:: pyirf.binning + +.. autofunction:: split_bin_lo_hi diff --git a/_sources/api/pyirf.cut_optimization.optimize_gh_cut.rst.txt b/_sources/api/pyirf.cut_optimization.optimize_gh_cut.rst.txt new file mode 100644 index 000000000..a65ac48bb --- /dev/null +++ b/_sources/api/pyirf.cut_optimization.optimize_gh_cut.rst.txt @@ -0,0 +1,6 @@ +optimize_gh_cut +=============== + +.. currentmodule:: pyirf.cut_optimization + +.. autofunction:: optimize_gh_cut diff --git a/_sources/api/pyirf.cuts.calculate_percentile_cut.rst.txt b/_sources/api/pyirf.cuts.calculate_percentile_cut.rst.txt new file mode 100644 index 000000000..627ed0c39 --- /dev/null +++ b/_sources/api/pyirf.cuts.calculate_percentile_cut.rst.txt @@ -0,0 +1,6 @@ +calculate_percentile_cut +======================== + +.. currentmodule:: pyirf.cuts + +.. autofunction:: calculate_percentile_cut diff --git a/_sources/api/pyirf.cuts.compare_irf_cuts.rst.txt b/_sources/api/pyirf.cuts.compare_irf_cuts.rst.txt new file mode 100644 index 000000000..d5ef121fa --- /dev/null +++ b/_sources/api/pyirf.cuts.compare_irf_cuts.rst.txt @@ -0,0 +1,6 @@ +compare_irf_cuts +================ + +.. currentmodule:: pyirf.cuts + +.. autofunction:: compare_irf_cuts diff --git a/_sources/api/pyirf.cuts.evaluate_binned_cut.rst.txt b/_sources/api/pyirf.cuts.evaluate_binned_cut.rst.txt new file mode 100644 index 000000000..1887be904 --- /dev/null +++ b/_sources/api/pyirf.cuts.evaluate_binned_cut.rst.txt @@ -0,0 +1,6 @@ +evaluate_binned_cut +=================== + +.. currentmodule:: pyirf.cuts + +.. autofunction:: evaluate_binned_cut diff --git a/_sources/api/pyirf.gammapy.create_effective_area_table_2d.rst.txt b/_sources/api/pyirf.gammapy.create_effective_area_table_2d.rst.txt new file mode 100644 index 000000000..9a053c6f3 --- /dev/null +++ b/_sources/api/pyirf.gammapy.create_effective_area_table_2d.rst.txt @@ -0,0 +1,6 @@ +create_effective_area_table_2d +============================== + +.. currentmodule:: pyirf.gammapy + +.. autofunction:: create_effective_area_table_2d diff --git a/_sources/api/pyirf.gammapy.create_energy_dispersion_2d.rst.txt b/_sources/api/pyirf.gammapy.create_energy_dispersion_2d.rst.txt new file mode 100644 index 000000000..6bc37bb46 --- /dev/null +++ b/_sources/api/pyirf.gammapy.create_energy_dispersion_2d.rst.txt @@ -0,0 +1,6 @@ +create_energy_dispersion_2d +=========================== + +.. currentmodule:: pyirf.gammapy + +.. autofunction:: create_energy_dispersion_2d diff --git a/_sources/api/pyirf.gammapy.create_psf_3d.rst.txt b/_sources/api/pyirf.gammapy.create_psf_3d.rst.txt new file mode 100644 index 000000000..ffabae486 --- /dev/null +++ b/_sources/api/pyirf.gammapy.create_psf_3d.rst.txt @@ -0,0 +1,6 @@ +create_psf_3d +============= + +.. currentmodule:: pyirf.gammapy + +.. autofunction:: create_psf_3d diff --git a/_sources/api/pyirf.interpolation.BaseComponentEstimator.rst.txt b/_sources/api/pyirf.interpolation.BaseComponentEstimator.rst.txt new file mode 100644 index 000000000..b675e92b5 --- /dev/null +++ b/_sources/api/pyirf.interpolation.BaseComponentEstimator.rst.txt @@ -0,0 +1,17 @@ +BaseComponentEstimator +====================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: BaseComponentEstimator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~BaseComponentEstimator.__call__ + + .. rubric:: Methods Documentation + + .. automethod:: __call__ diff --git a/_sources/api/pyirf.interpolation.BaseExtrapolator.rst.txt b/_sources/api/pyirf.interpolation.BaseExtrapolator.rst.txt new file mode 100644 index 000000000..c89aa5f40 --- /dev/null +++ b/_sources/api/pyirf.interpolation.BaseExtrapolator.rst.txt @@ -0,0 +1,19 @@ +BaseExtrapolator +================ + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: BaseExtrapolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~BaseExtrapolator.__call__ + ~BaseExtrapolator.extrapolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: extrapolate diff --git a/_sources/api/pyirf.interpolation.BaseInterpolator.rst.txt b/_sources/api/pyirf.interpolation.BaseInterpolator.rst.txt new file mode 100644 index 000000000..4decf33ba --- /dev/null +++ b/_sources/api/pyirf.interpolation.BaseInterpolator.rst.txt @@ -0,0 +1,19 @@ +BaseInterpolator +================ + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: BaseInterpolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~BaseInterpolator.__call__ + ~BaseInterpolator.interpolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: interpolate diff --git a/_sources/api/pyirf.interpolation.BaseNearestNeighborSearcher.rst.txt b/_sources/api/pyirf.interpolation.BaseNearestNeighborSearcher.rst.txt new file mode 100644 index 000000000..9924f2b98 --- /dev/null +++ b/_sources/api/pyirf.interpolation.BaseNearestNeighborSearcher.rst.txt @@ -0,0 +1,19 @@ +BaseNearestNeighborSearcher +=========================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: BaseNearestNeighborSearcher + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~BaseNearestNeighborSearcher.__call__ + ~BaseNearestNeighborSearcher.interpolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: interpolate diff --git a/_sources/api/pyirf.interpolation.DiscretePDFComponentEstimator.rst.txt b/_sources/api/pyirf.interpolation.DiscretePDFComponentEstimator.rst.txt new file mode 100644 index 000000000..4513e25fe --- /dev/null +++ b/_sources/api/pyirf.interpolation.DiscretePDFComponentEstimator.rst.txt @@ -0,0 +1,17 @@ +DiscretePDFComponentEstimator +============================= + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: DiscretePDFComponentEstimator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~DiscretePDFComponentEstimator.__call__ + + .. rubric:: Methods Documentation + + .. automethod:: __call__ diff --git a/_sources/api/pyirf.interpolation.DiscretePDFExtrapolator.rst.txt b/_sources/api/pyirf.interpolation.DiscretePDFExtrapolator.rst.txt new file mode 100644 index 000000000..eb63dabfe --- /dev/null +++ b/_sources/api/pyirf.interpolation.DiscretePDFExtrapolator.rst.txt @@ -0,0 +1,19 @@ +DiscretePDFExtrapolator +======================= + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: DiscretePDFExtrapolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~DiscretePDFExtrapolator.__call__ + ~DiscretePDFExtrapolator.extrapolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: extrapolate diff --git a/_sources/api/pyirf.interpolation.DiscretePDFInterpolator.rst.txt b/_sources/api/pyirf.interpolation.DiscretePDFInterpolator.rst.txt new file mode 100644 index 000000000..66cb12982 --- /dev/null +++ b/_sources/api/pyirf.interpolation.DiscretePDFInterpolator.rst.txt @@ -0,0 +1,19 @@ +DiscretePDFInterpolator +======================= + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: DiscretePDFInterpolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~DiscretePDFInterpolator.__call__ + ~DiscretePDFInterpolator.interpolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: interpolate diff --git a/_sources/api/pyirf.interpolation.DiscretePDFNearestNeighborSearcher.rst.txt b/_sources/api/pyirf.interpolation.DiscretePDFNearestNeighborSearcher.rst.txt new file mode 100644 index 000000000..2a2bc9ed3 --- /dev/null +++ b/_sources/api/pyirf.interpolation.DiscretePDFNearestNeighborSearcher.rst.txt @@ -0,0 +1,19 @@ +DiscretePDFNearestNeighborSearcher +================================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: DiscretePDFNearestNeighborSearcher + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~DiscretePDFNearestNeighborSearcher.__call__ + ~DiscretePDFNearestNeighborSearcher.interpolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: interpolate diff --git a/_sources/api/pyirf.interpolation.EffectiveAreaEstimator.rst.txt b/_sources/api/pyirf.interpolation.EffectiveAreaEstimator.rst.txt new file mode 100644 index 000000000..c05159b99 --- /dev/null +++ b/_sources/api/pyirf.interpolation.EffectiveAreaEstimator.rst.txt @@ -0,0 +1,17 @@ +EffectiveAreaEstimator +====================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: EffectiveAreaEstimator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~EffectiveAreaEstimator.__call__ + + .. rubric:: Methods Documentation + + .. automethod:: __call__ diff --git a/_sources/api/pyirf.interpolation.EnergyDispersionEstimator.rst.txt b/_sources/api/pyirf.interpolation.EnergyDispersionEstimator.rst.txt new file mode 100644 index 000000000..11dfa9904 --- /dev/null +++ b/_sources/api/pyirf.interpolation.EnergyDispersionEstimator.rst.txt @@ -0,0 +1,17 @@ +EnergyDispersionEstimator +========================= + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: EnergyDispersionEstimator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~EnergyDispersionEstimator.__call__ + + .. rubric:: Methods Documentation + + .. automethod:: __call__ diff --git a/_sources/api/pyirf.interpolation.GridDataInterpolator.rst.txt b/_sources/api/pyirf.interpolation.GridDataInterpolator.rst.txt new file mode 100644 index 000000000..e33de77c4 --- /dev/null +++ b/_sources/api/pyirf.interpolation.GridDataInterpolator.rst.txt @@ -0,0 +1,19 @@ +GridDataInterpolator +==================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: GridDataInterpolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~GridDataInterpolator.__call__ + ~GridDataInterpolator.interpolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: interpolate diff --git a/_sources/api/pyirf.interpolation.MomentMorphInterpolator.rst.txt b/_sources/api/pyirf.interpolation.MomentMorphInterpolator.rst.txt new file mode 100644 index 000000000..d48ca1572 --- /dev/null +++ b/_sources/api/pyirf.interpolation.MomentMorphInterpolator.rst.txt @@ -0,0 +1,19 @@ +MomentMorphInterpolator +======================= + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: MomentMorphInterpolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~MomentMorphInterpolator.__call__ + ~MomentMorphInterpolator.interpolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: interpolate diff --git a/_sources/api/pyirf.interpolation.MomentMorphNearestSimplexExtrapolator.rst.txt b/_sources/api/pyirf.interpolation.MomentMorphNearestSimplexExtrapolator.rst.txt new file mode 100644 index 000000000..14da35cbe --- /dev/null +++ b/_sources/api/pyirf.interpolation.MomentMorphNearestSimplexExtrapolator.rst.txt @@ -0,0 +1,19 @@ +MomentMorphNearestSimplexExtrapolator +===================================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: MomentMorphNearestSimplexExtrapolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~MomentMorphNearestSimplexExtrapolator.__call__ + ~MomentMorphNearestSimplexExtrapolator.extrapolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: extrapolate diff --git a/_sources/api/pyirf.interpolation.PDFNormalization.rst.txt b/_sources/api/pyirf.interpolation.PDFNormalization.rst.txt new file mode 100644 index 000000000..4fa6bcd95 --- /dev/null +++ b/_sources/api/pyirf.interpolation.PDFNormalization.rst.txt @@ -0,0 +1,19 @@ +PDFNormalization +================ + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: PDFNormalization + :show-inheritance: + + .. rubric:: Attributes Summary + + .. autosummary:: + + ~PDFNormalization.AREA + ~PDFNormalization.CONE_SOLID_ANGLE + + .. rubric:: Attributes Documentation + + .. autoattribute:: AREA + .. autoattribute:: CONE_SOLID_ANGLE diff --git a/_sources/api/pyirf.interpolation.PSFTableEstimator.rst.txt b/_sources/api/pyirf.interpolation.PSFTableEstimator.rst.txt new file mode 100644 index 000000000..a802248ff --- /dev/null +++ b/_sources/api/pyirf.interpolation.PSFTableEstimator.rst.txt @@ -0,0 +1,17 @@ +PSFTableEstimator +================= + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: PSFTableEstimator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~PSFTableEstimator.__call__ + + .. rubric:: Methods Documentation + + .. automethod:: __call__ diff --git a/_sources/api/pyirf.interpolation.ParametrizedComponentEstimator.rst.txt b/_sources/api/pyirf.interpolation.ParametrizedComponentEstimator.rst.txt new file mode 100644 index 000000000..46f5bd72e --- /dev/null +++ b/_sources/api/pyirf.interpolation.ParametrizedComponentEstimator.rst.txt @@ -0,0 +1,17 @@ +ParametrizedComponentEstimator +============================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: ParametrizedComponentEstimator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~ParametrizedComponentEstimator.__call__ + + .. rubric:: Methods Documentation + + .. automethod:: __call__ diff --git a/_sources/api/pyirf.interpolation.ParametrizedExtrapolator.rst.txt b/_sources/api/pyirf.interpolation.ParametrizedExtrapolator.rst.txt new file mode 100644 index 000000000..ba2592916 --- /dev/null +++ b/_sources/api/pyirf.interpolation.ParametrizedExtrapolator.rst.txt @@ -0,0 +1,19 @@ +ParametrizedExtrapolator +======================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: ParametrizedExtrapolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~ParametrizedExtrapolator.__call__ + ~ParametrizedExtrapolator.extrapolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: extrapolate diff --git a/_sources/api/pyirf.interpolation.ParametrizedInterpolator.rst.txt b/_sources/api/pyirf.interpolation.ParametrizedInterpolator.rst.txt new file mode 100644 index 000000000..f42a57a6b --- /dev/null +++ b/_sources/api/pyirf.interpolation.ParametrizedInterpolator.rst.txt @@ -0,0 +1,19 @@ +ParametrizedInterpolator +======================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: ParametrizedInterpolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~ParametrizedInterpolator.__call__ + ~ParametrizedInterpolator.interpolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: interpolate diff --git a/_sources/api/pyirf.interpolation.ParametrizedNearestNeighborSearcher.rst.txt b/_sources/api/pyirf.interpolation.ParametrizedNearestNeighborSearcher.rst.txt new file mode 100644 index 000000000..bd6c20ba0 --- /dev/null +++ b/_sources/api/pyirf.interpolation.ParametrizedNearestNeighborSearcher.rst.txt @@ -0,0 +1,19 @@ +ParametrizedNearestNeighborSearcher +=================================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: ParametrizedNearestNeighborSearcher + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~ParametrizedNearestNeighborSearcher.__call__ + ~ParametrizedNearestNeighborSearcher.interpolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: interpolate diff --git a/_sources/api/pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.rst.txt b/_sources/api/pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.rst.txt new file mode 100644 index 000000000..5b8b88151 --- /dev/null +++ b/_sources/api/pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.rst.txt @@ -0,0 +1,19 @@ +ParametrizedNearestSimplexExtrapolator +====================================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: ParametrizedNearestSimplexExtrapolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~ParametrizedNearestSimplexExtrapolator.__call__ + ~ParametrizedNearestSimplexExtrapolator.extrapolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: extrapolate diff --git a/_sources/api/pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator.rst.txt b/_sources/api/pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator.rst.txt new file mode 100644 index 000000000..fd3e79e42 --- /dev/null +++ b/_sources/api/pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator.rst.txt @@ -0,0 +1,19 @@ +ParametrizedVisibleEdgesExtrapolator +==================================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: ParametrizedVisibleEdgesExtrapolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~ParametrizedVisibleEdgesExtrapolator.__call__ + ~ParametrizedVisibleEdgesExtrapolator.extrapolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: extrapolate diff --git a/_sources/api/pyirf.interpolation.QuantileInterpolator.rst.txt b/_sources/api/pyirf.interpolation.QuantileInterpolator.rst.txt new file mode 100644 index 000000000..8c5dbb171 --- /dev/null +++ b/_sources/api/pyirf.interpolation.QuantileInterpolator.rst.txt @@ -0,0 +1,19 @@ +QuantileInterpolator +==================== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: QuantileInterpolator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~QuantileInterpolator.__call__ + ~QuantileInterpolator.interpolate + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: interpolate diff --git a/_sources/api/pyirf.interpolation.RadMaxEstimator.rst.txt b/_sources/api/pyirf.interpolation.RadMaxEstimator.rst.txt new file mode 100644 index 000000000..884c3ce58 --- /dev/null +++ b/_sources/api/pyirf.interpolation.RadMaxEstimator.rst.txt @@ -0,0 +1,17 @@ +RadMaxEstimator +=============== + +.. currentmodule:: pyirf.interpolation + +.. autoclass:: RadMaxEstimator + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~RadMaxEstimator.__call__ + + .. rubric:: Methods Documentation + + .. automethod:: __call__ diff --git a/_sources/api/pyirf.io.create_aeff2d_hdu.rst.txt b/_sources/api/pyirf.io.create_aeff2d_hdu.rst.txt new file mode 100644 index 000000000..99afafb6a --- /dev/null +++ b/_sources/api/pyirf.io.create_aeff2d_hdu.rst.txt @@ -0,0 +1,6 @@ +create_aeff2d_hdu +================= + +.. currentmodule:: pyirf.io + +.. autofunction:: create_aeff2d_hdu diff --git a/_sources/api/pyirf.io.create_background_2d_hdu.rst.txt b/_sources/api/pyirf.io.create_background_2d_hdu.rst.txt new file mode 100644 index 000000000..aa9d2e413 --- /dev/null +++ b/_sources/api/pyirf.io.create_background_2d_hdu.rst.txt @@ -0,0 +1,6 @@ +create_background_2d_hdu +======================== + +.. currentmodule:: pyirf.io + +.. autofunction:: create_background_2d_hdu diff --git a/_sources/api/pyirf.io.create_energy_dispersion_hdu.rst.txt b/_sources/api/pyirf.io.create_energy_dispersion_hdu.rst.txt new file mode 100644 index 000000000..0700b7dee --- /dev/null +++ b/_sources/api/pyirf.io.create_energy_dispersion_hdu.rst.txt @@ -0,0 +1,6 @@ +create_energy_dispersion_hdu +============================ + +.. currentmodule:: pyirf.io + +.. autofunction:: create_energy_dispersion_hdu diff --git a/_sources/api/pyirf.io.create_psf_table_hdu.rst.txt b/_sources/api/pyirf.io.create_psf_table_hdu.rst.txt new file mode 100644 index 000000000..30d6aa837 --- /dev/null +++ b/_sources/api/pyirf.io.create_psf_table_hdu.rst.txt @@ -0,0 +1,6 @@ +create_psf_table_hdu +==================== + +.. currentmodule:: pyirf.io + +.. autofunction:: create_psf_table_hdu diff --git a/_sources/api/pyirf.io.create_rad_max_hdu.rst.txt b/_sources/api/pyirf.io.create_rad_max_hdu.rst.txt new file mode 100644 index 000000000..bf31f20b5 --- /dev/null +++ b/_sources/api/pyirf.io.create_rad_max_hdu.rst.txt @@ -0,0 +1,6 @@ +create_rad_max_hdu +================== + +.. currentmodule:: pyirf.io + +.. autofunction:: create_rad_max_hdu diff --git a/_sources/api/pyirf.io.read_eventdisplay_fits.rst.txt b/_sources/api/pyirf.io.read_eventdisplay_fits.rst.txt new file mode 100644 index 000000000..4a930ef2b --- /dev/null +++ b/_sources/api/pyirf.io.read_eventdisplay_fits.rst.txt @@ -0,0 +1,6 @@ +read_eventdisplay_fits +====================== + +.. currentmodule:: pyirf.io + +.. autofunction:: read_eventdisplay_fits diff --git a/_sources/api/pyirf.irf.background_2d.rst.txt b/_sources/api/pyirf.irf.background_2d.rst.txt new file mode 100644 index 000000000..e7fe4831d --- /dev/null +++ b/_sources/api/pyirf.irf.background_2d.rst.txt @@ -0,0 +1,6 @@ +background_2d +============= + +.. currentmodule:: pyirf.irf + +.. autofunction:: background_2d diff --git a/_sources/api/pyirf.irf.effective_area.rst.txt b/_sources/api/pyirf.irf.effective_area.rst.txt new file mode 100644 index 000000000..775c4b66c --- /dev/null +++ b/_sources/api/pyirf.irf.effective_area.rst.txt @@ -0,0 +1,6 @@ +effective_area +============== + +.. currentmodule:: pyirf.irf + +.. autofunction:: effective_area diff --git a/_sources/api/pyirf.irf.effective_area_per_energy.rst.txt b/_sources/api/pyirf.irf.effective_area_per_energy.rst.txt new file mode 100644 index 000000000..ebf22a910 --- /dev/null +++ b/_sources/api/pyirf.irf.effective_area_per_energy.rst.txt @@ -0,0 +1,6 @@ +effective_area_per_energy +========================= + +.. currentmodule:: pyirf.irf + +.. autofunction:: effective_area_per_energy diff --git a/_sources/api/pyirf.irf.effective_area_per_energy_and_fov.rst.txt b/_sources/api/pyirf.irf.effective_area_per_energy_and_fov.rst.txt new file mode 100644 index 000000000..2e71d01e6 --- /dev/null +++ b/_sources/api/pyirf.irf.effective_area_per_energy_and_fov.rst.txt @@ -0,0 +1,6 @@ +effective_area_per_energy_and_fov +================================= + +.. currentmodule:: pyirf.irf + +.. autofunction:: effective_area_per_energy_and_fov diff --git a/_sources/api/pyirf.irf.energy_dispersion.rst.txt b/_sources/api/pyirf.irf.energy_dispersion.rst.txt new file mode 100644 index 000000000..3c024a701 --- /dev/null +++ b/_sources/api/pyirf.irf.energy_dispersion.rst.txt @@ -0,0 +1,6 @@ +energy_dispersion +================= + +.. currentmodule:: pyirf.irf + +.. autofunction:: energy_dispersion diff --git a/_sources/api/pyirf.irf.psf_table.rst.txt b/_sources/api/pyirf.irf.psf_table.rst.txt new file mode 100644 index 000000000..de9d1751a --- /dev/null +++ b/_sources/api/pyirf.irf.psf_table.rst.txt @@ -0,0 +1,6 @@ +psf_table +========= + +.. currentmodule:: pyirf.irf + +.. autofunction:: psf_table diff --git a/_sources/api/pyirf.sensitivity.calculate_sensitivity.rst.txt b/_sources/api/pyirf.sensitivity.calculate_sensitivity.rst.txt new file mode 100644 index 000000000..1ae75702b --- /dev/null +++ b/_sources/api/pyirf.sensitivity.calculate_sensitivity.rst.txt @@ -0,0 +1,6 @@ +calculate_sensitivity +===================== + +.. currentmodule:: pyirf.sensitivity + +.. autofunction:: calculate_sensitivity diff --git a/_sources/api/pyirf.sensitivity.estimate_background.rst.txt b/_sources/api/pyirf.sensitivity.estimate_background.rst.txt new file mode 100644 index 000000000..bf9a35a73 --- /dev/null +++ b/_sources/api/pyirf.sensitivity.estimate_background.rst.txt @@ -0,0 +1,6 @@ +estimate_background +=================== + +.. currentmodule:: pyirf.sensitivity + +.. autofunction:: estimate_background diff --git a/_sources/api/pyirf.sensitivity.relative_sensitivity.rst.txt b/_sources/api/pyirf.sensitivity.relative_sensitivity.rst.txt new file mode 100644 index 000000000..6412f601d --- /dev/null +++ b/_sources/api/pyirf.sensitivity.relative_sensitivity.rst.txt @@ -0,0 +1,6 @@ +relative_sensitivity +==================== + +.. currentmodule:: pyirf.sensitivity + +.. autofunction:: relative_sensitivity diff --git a/_sources/api/pyirf.simulations.SimulatedEventsInfo.rst.txt b/_sources/api/pyirf.simulations.SimulatedEventsInfo.rst.txt new file mode 100644 index 000000000..b702fbc3b --- /dev/null +++ b/_sources/api/pyirf.simulations.SimulatedEventsInfo.rst.txt @@ -0,0 +1,43 @@ +SimulatedEventsInfo +=================== + +.. currentmodule:: pyirf.simulations + +.. autoclass:: SimulatedEventsInfo + :show-inheritance: + + .. rubric:: Attributes Summary + + .. autosummary:: + + ~SimulatedEventsInfo.energy_max + ~SimulatedEventsInfo.energy_min + ~SimulatedEventsInfo.max_impact + ~SimulatedEventsInfo.n_showers + ~SimulatedEventsInfo.spectral_index + ~SimulatedEventsInfo.viewcone_max + ~SimulatedEventsInfo.viewcone_min + + .. rubric:: Methods Summary + + .. autosummary:: + + ~SimulatedEventsInfo.calculate_n_showers_per_energy + ~SimulatedEventsInfo.calculate_n_showers_per_energy_and_fov + ~SimulatedEventsInfo.calculate_n_showers_per_fov + + .. rubric:: Attributes Documentation + + .. autoattribute:: energy_max + .. autoattribute:: energy_min + .. autoattribute:: max_impact + .. autoattribute:: n_showers + .. autoattribute:: spectral_index + .. autoattribute:: viewcone_max + .. autoattribute:: viewcone_min + + .. rubric:: Methods Documentation + + .. automethod:: calculate_n_showers_per_energy + .. automethod:: calculate_n_showers_per_energy_and_fov + .. automethod:: calculate_n_showers_per_fov diff --git a/_sources/api/pyirf.spectral.CRAB_HEGRA.rst.txt b/_sources/api/pyirf.spectral.CRAB_HEGRA.rst.txt new file mode 100644 index 000000000..01654c0a9 --- /dev/null +++ b/_sources/api/pyirf.spectral.CRAB_HEGRA.rst.txt @@ -0,0 +1,6 @@ +CRAB_HEGRA +========== + +.. currentmodule:: pyirf.spectral + +.. autodata:: CRAB_HEGRA diff --git a/_sources/api/pyirf.spectral.CRAB_MAGIC_JHEAP2015.rst.txt b/_sources/api/pyirf.spectral.CRAB_MAGIC_JHEAP2015.rst.txt new file mode 100644 index 000000000..8ddb00858 --- /dev/null +++ b/_sources/api/pyirf.spectral.CRAB_MAGIC_JHEAP2015.rst.txt @@ -0,0 +1,6 @@ +CRAB_MAGIC_JHEAP2015 +==================== + +.. currentmodule:: pyirf.spectral + +.. autodata:: CRAB_MAGIC_JHEAP2015 diff --git a/_sources/api/pyirf.spectral.DAMPE_P_He_SPECTRUM.rst.txt b/_sources/api/pyirf.spectral.DAMPE_P_He_SPECTRUM.rst.txt new file mode 100644 index 000000000..25d659664 --- /dev/null +++ b/_sources/api/pyirf.spectral.DAMPE_P_He_SPECTRUM.rst.txt @@ -0,0 +1,6 @@ +DAMPE_P_He_SPECTRUM +=================== + +.. currentmodule:: pyirf.spectral + +.. autodata:: DAMPE_P_He_SPECTRUM diff --git a/_sources/api/pyirf.spectral.DIFFUSE_FLUX_UNIT.rst.txt b/_sources/api/pyirf.spectral.DIFFUSE_FLUX_UNIT.rst.txt new file mode 100644 index 000000000..00a5e7b00 --- /dev/null +++ b/_sources/api/pyirf.spectral.DIFFUSE_FLUX_UNIT.rst.txt @@ -0,0 +1,6 @@ +DIFFUSE_FLUX_UNIT +================= + +.. currentmodule:: pyirf.spectral + +.. autodata:: DIFFUSE_FLUX_UNIT diff --git a/_sources/api/pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM.rst.txt b/_sources/api/pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM.rst.txt new file mode 100644 index 000000000..ecd335bed --- /dev/null +++ b/_sources/api/pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM.rst.txt @@ -0,0 +1,6 @@ +IRFDOC_ELECTRON_SPECTRUM +======================== + +.. currentmodule:: pyirf.spectral + +.. autodata:: IRFDOC_ELECTRON_SPECTRUM diff --git a/_sources/api/pyirf.spectral.IRFDOC_PROTON_SPECTRUM.rst.txt b/_sources/api/pyirf.spectral.IRFDOC_PROTON_SPECTRUM.rst.txt new file mode 100644 index 000000000..d08bb3286 --- /dev/null +++ b/_sources/api/pyirf.spectral.IRFDOC_PROTON_SPECTRUM.rst.txt @@ -0,0 +1,6 @@ +IRFDOC_PROTON_SPECTRUM +====================== + +.. currentmodule:: pyirf.spectral + +.. autodata:: IRFDOC_PROTON_SPECTRUM diff --git a/_sources/api/pyirf.spectral.LogParabola.rst.txt b/_sources/api/pyirf.spectral.LogParabola.rst.txt new file mode 100644 index 000000000..c657e4405 --- /dev/null +++ b/_sources/api/pyirf.spectral.LogParabola.rst.txt @@ -0,0 +1,17 @@ +LogParabola +=========== + +.. currentmodule:: pyirf.spectral + +.. autoclass:: LogParabola + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~LogParabola.__call__ + + .. rubric:: Methods Documentation + + .. automethod:: __call__ diff --git a/_sources/api/pyirf.spectral.PDG_ALL_PARTICLE.rst.txt b/_sources/api/pyirf.spectral.PDG_ALL_PARTICLE.rst.txt new file mode 100644 index 000000000..7ba60b1aa --- /dev/null +++ b/_sources/api/pyirf.spectral.PDG_ALL_PARTICLE.rst.txt @@ -0,0 +1,6 @@ +PDG_ALL_PARTICLE +================ + +.. currentmodule:: pyirf.spectral + +.. autodata:: PDG_ALL_PARTICLE diff --git a/_sources/api/pyirf.spectral.POINT_SOURCE_FLUX_UNIT.rst.txt b/_sources/api/pyirf.spectral.POINT_SOURCE_FLUX_UNIT.rst.txt new file mode 100644 index 000000000..dd6ae397e --- /dev/null +++ b/_sources/api/pyirf.spectral.POINT_SOURCE_FLUX_UNIT.rst.txt @@ -0,0 +1,6 @@ +POINT_SOURCE_FLUX_UNIT +====================== + +.. currentmodule:: pyirf.spectral + +.. autodata:: POINT_SOURCE_FLUX_UNIT diff --git a/_sources/api/pyirf.spectral.PowerLaw.rst.txt b/_sources/api/pyirf.spectral.PowerLaw.rst.txt new file mode 100644 index 000000000..943470780 --- /dev/null +++ b/_sources/api/pyirf.spectral.PowerLaw.rst.txt @@ -0,0 +1,21 @@ +PowerLaw +======== + +.. currentmodule:: pyirf.spectral + +.. autoclass:: PowerLaw + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~PowerLaw.__call__ + ~PowerLaw.from_simulation + ~PowerLaw.integrate_cone + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: from_simulation + .. automethod:: integrate_cone diff --git a/_sources/api/pyirf.spectral.PowerLawWithExponentialGaussian.rst.txt b/_sources/api/pyirf.spectral.PowerLawWithExponentialGaussian.rst.txt new file mode 100644 index 000000000..381e401ad --- /dev/null +++ b/_sources/api/pyirf.spectral.PowerLawWithExponentialGaussian.rst.txt @@ -0,0 +1,17 @@ +PowerLawWithExponentialGaussian +=============================== + +.. currentmodule:: pyirf.spectral + +.. autoclass:: PowerLawWithExponentialGaussian + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~PowerLawWithExponentialGaussian.__call__ + + .. rubric:: Methods Documentation + + .. automethod:: __call__ diff --git a/_sources/api/pyirf.spectral.TableInterpolationSpectrum.rst.txt b/_sources/api/pyirf.spectral.TableInterpolationSpectrum.rst.txt new file mode 100644 index 000000000..701089bea --- /dev/null +++ b/_sources/api/pyirf.spectral.TableInterpolationSpectrum.rst.txt @@ -0,0 +1,21 @@ +TableInterpolationSpectrum +========================== + +.. currentmodule:: pyirf.spectral + +.. autoclass:: TableInterpolationSpectrum + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~TableInterpolationSpectrum.__call__ + ~TableInterpolationSpectrum.from_file + ~TableInterpolationSpectrum.from_table + + .. rubric:: Methods Documentation + + .. automethod:: __call__ + .. automethod:: from_file + .. automethod:: from_table diff --git a/_sources/api/pyirf.spectral.calculate_event_weights.rst.txt b/_sources/api/pyirf.spectral.calculate_event_weights.rst.txt new file mode 100644 index 000000000..525cb1508 --- /dev/null +++ b/_sources/api/pyirf.spectral.calculate_event_weights.rst.txt @@ -0,0 +1,6 @@ +calculate_event_weights +======================= + +.. currentmodule:: pyirf.spectral + +.. autofunction:: calculate_event_weights diff --git a/_sources/api/pyirf.statistics.li_ma_significance.rst.txt b/_sources/api/pyirf.statistics.li_ma_significance.rst.txt new file mode 100644 index 000000000..964a5c5ed --- /dev/null +++ b/_sources/api/pyirf.statistics.li_ma_significance.rst.txt @@ -0,0 +1,6 @@ +li_ma_significance +================== + +.. currentmodule:: pyirf.statistics + +.. autofunction:: li_ma_significance diff --git a/_sources/api/pyirf.utils.calculate_source_fov_offset.rst.txt b/_sources/api/pyirf.utils.calculate_source_fov_offset.rst.txt new file mode 100644 index 000000000..2e63dff42 --- /dev/null +++ b/_sources/api/pyirf.utils.calculate_source_fov_offset.rst.txt @@ -0,0 +1,6 @@ +calculate_source_fov_offset +=========================== + +.. currentmodule:: pyirf.utils + +.. autofunction:: calculate_source_fov_offset diff --git a/_sources/api/pyirf.utils.calculate_theta.rst.txt b/_sources/api/pyirf.utils.calculate_theta.rst.txt new file mode 100644 index 000000000..8766065e3 --- /dev/null +++ b/_sources/api/pyirf.utils.calculate_theta.rst.txt @@ -0,0 +1,6 @@ +calculate_theta +=============== + +.. currentmodule:: pyirf.utils + +.. autofunction:: calculate_theta diff --git a/_sources/api/pyirf.utils.check_histograms.rst.txt b/_sources/api/pyirf.utils.check_histograms.rst.txt new file mode 100644 index 000000000..194dca33a --- /dev/null +++ b/_sources/api/pyirf.utils.check_histograms.rst.txt @@ -0,0 +1,6 @@ +check_histograms +================ + +.. currentmodule:: pyirf.utils + +.. autofunction:: check_histograms diff --git a/_sources/api/pyirf.utils.cone_solid_angle.rst.txt b/_sources/api/pyirf.utils.cone_solid_angle.rst.txt new file mode 100644 index 000000000..2ca4a1925 --- /dev/null +++ b/_sources/api/pyirf.utils.cone_solid_angle.rst.txt @@ -0,0 +1,6 @@ +cone_solid_angle +================ + +.. currentmodule:: pyirf.utils + +.. autofunction:: cone_solid_angle diff --git a/_sources/api/pyirf.utils.is_scalar.rst.txt b/_sources/api/pyirf.utils.is_scalar.rst.txt new file mode 100644 index 000000000..cf693503f --- /dev/null +++ b/_sources/api/pyirf.utils.is_scalar.rst.txt @@ -0,0 +1,6 @@ +is_scalar +========= + +.. currentmodule:: pyirf.utils + +.. autofunction:: is_scalar diff --git a/_sources/benchmarks/index.rst.txt b/_sources/benchmarks/index.rst.txt new file mode 100644 index 000000000..53badad58 --- /dev/null +++ b/_sources/benchmarks/index.rst.txt @@ -0,0 +1,11 @@ +.. _benchmarks: + +Benchmarks +========== + +Functions to calculate benchmarks. + +------------- + +.. automodapi:: pyirf.benchmarks + :no-inheritance-diagram: diff --git a/_sources/binning.rst.txt b/_sources/binning.rst.txt new file mode 100644 index 000000000..f91b493e8 --- /dev/null +++ b/_sources/binning.rst.txt @@ -0,0 +1,11 @@ +.. _binning: + +Binning and Histogram Utilities +=============================== + + +Reference/API +------------- + +.. automodapi:: pyirf.binning + :no-inheritance-diagram: diff --git a/_sources/changelog.rst.txt b/_sources/changelog.rst.txt new file mode 100644 index 000000000..31c6f9c84 --- /dev/null +++ b/_sources/changelog.rst.txt @@ -0,0 +1,7 @@ +.. _changelog: + +========= +Changelog +========= + +.. include:: ../CHANGES.rst diff --git a/_sources/contribute.rst.txt b/_sources/contribute.rst.txt new file mode 100644 index 000000000..c35121715 --- /dev/null +++ b/_sources/contribute.rst.txt @@ -0,0 +1,144 @@ +.. _contribute: + +How to contribute +================= + +.. contents:: Current projects + :local: + :depth: 2 + +Issue Tracker +------------- + +We use the `GitHub issue tracker `__ +for individual issues and the `GitHub Projects page `_ can give you a quick overview. + +If you found a bug or you are missing a feature, please check the existing +issues and then open a new one or contribute to the existing issue. + +Development procedure +--------------------- + + +We use the standard `GitHub workflow `__. + +If you are not part of the ``cta-observatory`` organization, +you need to fork the repository to contribute. +See the `GitHub tutorial on forks `__ if you are unsure how to do this. + +#. When you find something that is wrong or missing + + - Go to the issue tracker and check if an issue already exists for your bug or feature + - In general it is always better to anticipate a PR with a new issue and link the two + +#. To work on a bug fix or new feature, create a new branch, add commits and open your pull request + + - If you think your pull request is good to go and ready to be reviewed, + you can directly open it as normal pull request. + + - You can also open it as a “Draft Pull Request”, if you are not yet finished + but want to get early feedback on your ideas. + + - Especially when working on a bug, it makes sense to first add a new + test that fails due to the bug and in a later commit add the fix showing + that the test is then passing. + This helps understanding the bug and will prevent it from reappearing later. + + - Create a changelog entry in ``docs/changes``, please note the ``README.md`` there. + Minor changes (on the magnitude of fixing a broken link or renaming a variable) can receive the ``no-changelog-needed`` label. + This should, however, be a rare exception. + +#. Wait for review comments and then implement or discuss requested changes. + + +We use `Github Actions `__ to +run the unit tests and documentation building automatically for every pull request. +Passing unit tests and coverage of the changed code are required for all pull requests. + + +Running the tests and looking at coverage +----------------------------------------- + +For more immediate feedback, you should run the tests locally before pushing, +as builds on travis take quite long. + +To run the tests locally, make sure you have the `tests` extras installed and then +run + +.. code:: bash + + $ pytest -v + + +To also inspect the coverage, run + +.. code:: bash + + $ pytest --cov=pyirf --cov-report=html -v + +This will create a coverage report in html form in the ``htmlcov`` directory, +which you can serve locally using + +.. code:: bash + + $ python -m http.server -d htmlcov + +After this, you can view the report in your browser by visiting the url printed +to the terminal. + + +Building the documentation +-------------------------- + +This documentation uses sphinx and restructured text. +For an Introduction, see the `Sphinx documentation `_. + +To build the docs locally, enter the ``docs`` directory and call: + +.. code:: bash + + make html + +Some changes require a full remake of the documentation, for that call + +.. code:: bash + + make clean html + +If you created or deleted file or submodule, you also need to remove the +``api`` directory, it will be regenerated automatically. + +Make sure the docs are built without warnings from sphinx, as these +will be treated as errors in the build in the CI system as they most often +result in broken styling. + +To look at the docs, use + +.. code:: bash + + $ python -m http.server _build/html + +and visit the printed URL in your browser. + +Making your contribution visible +-------------------------------- + +Together with the changes that will come with you PR, you should check that the +following maintenance files are up-to-date: + +- ``.mailmap`` +- ``CODEOWNERS`` +- ``.zenodo.json`` + +Further details +--------------- + +Please also have a look at the + +- ``ctapipe`` `development guidelines `__ +- The `Open Gamma-Ray Astronomy data formats `__ + which also describe the IRF formats and their definitions. +- ``ctools`` `documentation page on IRFs `__ +- `CTA IRF working group wiki (internal) `__ + +- `CTA IRF Description Document for Prod3b (internal) `__ diff --git a/_sources/cut_optimization.rst.txt b/_sources/cut_optimization.rst.txt new file mode 100644 index 000000000..b35363b37 --- /dev/null +++ b/_sources/cut_optimization.rst.txt @@ -0,0 +1,11 @@ +.. _cut_optimization: + +Cut Optimization +================ + + +Reference/API +------------- + +.. automodapi:: pyirf.cut_optimization + :no-inheritance-diagram: diff --git a/_sources/cuts.rst.txt b/_sources/cuts.rst.txt new file mode 100644 index 000000000..b86adb71d --- /dev/null +++ b/_sources/cuts.rst.txt @@ -0,0 +1,11 @@ +.. _cuts: + +Calculating and Applying Cuts +============================= + + +Reference/API +------------- + +.. automodapi:: pyirf.cuts + :no-inheritance-diagram: diff --git a/_sources/examples.rst.txt b/_sources/examples.rst.txt new file mode 100644 index 000000000..2efe6e61b --- /dev/null +++ b/_sources/examples.rst.txt @@ -0,0 +1,44 @@ +.. _examples: + +Examples +======== + +Calculating Sensitivity and IRFs for EventDisplay DL2 data +---------------------------------------------------------- + +The ``examples/calculate_eventdisplay_irfs.py`` file is +using ``pyirf`` to optimize cuts, calculate sensitivity and IRFs +and then store these to FITS files for DL2 event lists from EventDisplay. + +The ROOT files were provided by Gernot Maier and converted to FITS format +using `the EventDisplay DL2 converter script `_. +The resulting FITS files are the input to the example and can be downloaded using: + +.. code:: bash + + ./download_private_data.sh + +This requires ``curl`` and ``unzip`` to be installed. +The download is password protected, please ask one of the maintainers for the +password. + +A detailed explanation of the contents of such DL2 files can be found +`here (internal) `_. + +The example can then be run from the root of the repository after installing pyirf +by running: + +.. code:: bash + + python examples/calculate_eventdisplay_irfs.py + + +A jupyter notebook plotting the results and comparing them to the EventDisplay output +is available in ``examples/comparison_with_EventDisplay.ipynb`` + + +Visualization of the included Flux Models +----------------------------------------- + +The ``examples/plot_spectra.py`` visualizes the Flux models included +in ``pyirf`` for Crab Nebula, cosmic ray and electron flux. diff --git a/_sources/gammapy.rst.txt b/_sources/gammapy.rst.txt new file mode 100644 index 000000000..da632d2ed --- /dev/null +++ b/_sources/gammapy.rst.txt @@ -0,0 +1,13 @@ +.. _gammapy: + +Gammapy Interoperability +======================== + +This module provides functions to convert the ``pyirf`` quantities +for IRFs and the binning to the corresponding ``gammapy`` classes. + +Reference/API +------------- + +.. automodapi:: pyirf.gammapy + :no-inheritance-diagram: diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 000000000..00d92469a --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,93 @@ +.. meta:: + :github_url: https://github.com/cta-observatory/pyirf + +Welcome to pyirf's documentation! +================================= + +`pyirf` is a prototype for the generation of Instrument Response Functions (IRFs) +for the `Cherenkov Telescope Array `__ +(CTA). +The package is being developed and tested by members of the CTA consortium and +is a spin-off of the analog sub-process of the +`pipeline protopype `_. + +Its main features are currently to + + * find the best cutoff in gammaness/score, to discriminate between signal + and background, as well as the angular cut to obtain the best sensitivity + for a given amount of observation time and a given template for the + source of interest (:ref:`cut_optimization`) + * compute the instrument response functions, effective area, + point spread function and energy resolution (:ref:`irf`) + * estimate the sensitivity of the array (:ref:`sensitivity`), + +with plans to extend its capabilities to reach the requirements of the +future observatory. + +.. Should we add the following or is it too soon? ---> +.. Event though the initial efforts are focused on CTA, it is potentially possible +.. to extend the capabilities of `pyirf` to other IACTs instruments as well. + +The source code is hosted on a `GitHub repository `__, to +which this documentation is linked. + +.. warning:: + This is not yet stable code, so expect large and rapid changes. + +Citing this software +-------------------- + +.. |doilatest| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4740755.svg + :target: https://doi.org/10.5281/zenodo.4740755 +.. |doi_v0.5.0| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4748994.svg + :target: https://doi.org/10.5281/zenodo.4748994 +.. |doi_v0.4.0| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4304466.svg + :target: https://doi.org/10.5281/zenodo.4304466 + +If you use a released version of this software for a publication, +please cite it by using the corresponding DOI. + +- latest : |doilatest| +- v0.5.0 : |doi_v0.5.0| +- v0.4.0 : |doi_v0.4.0| + +.. toctree:: + :maxdepth: 1 + :caption: Overview + :name: _pyirf_intro + + install + introduction + examples + notebooks/index + contribute + changelog + AUTHORS + + +.. toctree:: + :maxdepth: 1 + :caption: API Documentation + :name: _pyirf_api_docs + + irf/index + sensitivity + benchmarks/index + cuts + cut_optimization + simulation + spectral + statistics + binning + io/index + interpolation + gammapy + utils + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/_sources/install.rst.txt b/_sources/install.rst.txt new file mode 100644 index 000000000..a90f8a648 --- /dev/null +++ b/_sources/install.rst.txt @@ -0,0 +1,67 @@ +.. _install: + +Installation +============ + +``pyirf`` requires Python ≥3.7 and pip, plus the packages defined in +the ``setup.py``. + +Core dependencies are + +* ``numpy`` +* ``astropy`` +* ``scipy`` + +We provide an environment file for Anaconda or Miniconda users. + +Installing a released version +----------------------------- + +To install a released version, just install the ``pyirf`` package using + +.. code-block:: bash + + $ pip install pyirf + +or add it to the dependencies of your project. + +Installing for development +-------------------------- + +If you want to work on pyirf itself, clone the repository and install the local +copy of pyirf in development mode. + +The dependencies required to perform unit-testing and to build the documentation +are defined in ``extras`` under ``tests`` and ``docs`` respectively. + +These requirements can also be enabled by installing the ``all`` extra: + +.. code-block:: bash + + $ pip install -e '.[all]' # or [docs,tests] to install them separately + + +You should isolate your pyirf development environment from the rest of your system. +Either by using a virtual environment or by using ``conda`` environments. +``pyirf`` provides a conda ``environment.yml``, that includes all dependencies: + +.. code-block:: bash + + $ conda env create -f environment.yml + $ conda activate pyirf + $ pip install -e '.[all]' + +In order to have passing unit-tests you have to download some CTA IRFs +from `zenodo `. Simply run + +.. code-block:: bash + + $ python download_irfs.py + +which will download and unpack three IRF files to ``irfs/``. + +Run the tests to make sure everything is OK: + +.. code-block:: bash + + $ pytest diff --git a/_sources/interpolation.rst.txt b/_sources/interpolation.rst.txt new file mode 100644 index 000000000..5ed66dfbc --- /dev/null +++ b/_sources/interpolation.rst.txt @@ -0,0 +1,262 @@ +.. _interpolation: + +Interpolation and Extrapolation of IRFs +======================================= + +.. currentmodule:: pyirf.interpolation + +This module contains functions to inter- or extrapolate from a set of IRFs for different +conditions to a new IRF. Implementations of interpolation and extrapolation algorithms +exist as interpolator and extrapolator classes and are applied by top-level estimator +classes to IRF components. +Direct usage of the inter- and extrapolator classes is discouraged, as only the estimator classes +check the data for consistency. + +Most methods support an arbitrary number of interpolation dimensions although it +is strongly advised to limit those for reasonable results. +The herein provided functionalities can e.g. be used to interpolate the IRF +for a zenith angle of 30° from available IRFs at 20° and 40°. + + +IRF Component Estimator Classes +------------------------------- + +.. autosummary:: + :nosignatures: + + EffectiveAreaEstimator Estimate AEFF tables. + RadMaxEstimator Estimate RadMax tables. + EnergyDispersionEstimator Estimate 2D EDISPs. + PSFTableEstimator Estimate PSF tables. + + + +Inter- and Extrapolation Classes +-------------------------------- + +This module provides inter- and extrapolation classes that can be +plugged into the estimator classes. +Not all of these classes support arbitrary grid-dimensions where the grid +in this context is the grid of e.g. observation parameters like zenith angle and +magnetic field inclination (this would be a 2D grid) on which template IRFs exist +and are meant to be inter- or extrapolated. + +For parametrized components (Effective Areas and Rad-Max tables) these classes are: + +============================================= ================== ============ ================================================================================================== +**Name** **Type** **Grid-Dim** **Note** +============================================= ================== ============ ================================================================================================== +:any:`GridDataInterpolator` Interpolation Arbitrary See also :any:`scipy.interpolate.griddata`. +:any:`ParametrizedNearestSimplexExtrapolator` Extrapolation 1D or 2D Linear (1D) or baryzentric (2D) extension outside the grid's convex hull from the nearest simplex. +:any:`ParametrizedVisibleEdgesExtrapolator` Extrapolation 1D or 2D Like :any:`ParametrizedNearestSimplexExtrapolator` but blends over all visible simplices [Alf84]_ and is thus smooth outside the convex hull. +:any:`ParametrizedNearestNeighborSearcher` Nearest Neighbor Arbitrary Nearest neighbor finder usable instead of inter- and/or extrapolation. +============================================= ================== ============ ================================================================================================== + +For components represented by discretized PDFs (PSF and EDISP tables) these classes are: + +============================================= ================== ============ ============================================================================== +**Name** **Type** **Grid-Dim** **Note** +============================================= ================== ============ ============================================================================== +:any:`QuantileInterpolator` Interpolation Arbitrary Adaption of [Hol+13]_ and [Rea99]_ to discretized PDFs. +:any:`MomentMorphInterpolator` Interpolation 1D or 2D Adaption of [Baa+15]_ to discretized PDFs. +:any:`MomentMorphNearestSimplexExtrapolator` Extrapolation 1D or 2D Extension of [Baa+15]_ beyond the grid's convex hull from the nearest simplex. +:any:`DiscretePDFNearestNeighborSearcher` Nearest Neighbor Arbitrary Nearest neighbor finder usable instead of inter- and/or extrapolation. +============================================= ================== ============ ============================================================================== + +.. [Alf84] P. Alfred (1984). Triangular Extrapolation. + Technical summary rept., Univ. of Wisconsin-Madison. https://apps.dtic.mil/sti/pdfs/ADA144660.pdf +.. [Hol+13] B. E. Hollister and A. T. Pang (2013). Interpolation of Non-Gaussian Probability Distributions for Ensemble Visualization. + https://engineering.ucsc.edu/sites/default/files/technical-reports/UCSC-SOE-13-13.pdf +.. [Rea99] A. L. Read (1999). Linear Interpolation of Histograms. + Nucl. Instrum. Methods Phys. Res. A 425, 357-360. https://doi.org/10.1016/S0168-9002(98)01347-3 +.. [Baa+15] M. Baak, S. Gadatsch, R. Harrington and W. Verkerke (2015). Interpolation between + multi-dimensional histograms using a new non-linear moment morphing method + Nucl. Instrum. Methods Phys. Res. A 771, 39-48. https://doi.org/10.1016/j.nima.2014.10.033 + + +Using Estimator Classes +----------------------- + +Usage of the estimator classes is simple. +As an example, consider CTA's Prod5 IRFs [CTA+21]_, they can be downloaded manually or by executing +``download_irfs.py`` in ``pyirf's`` root directory, which downloads them to ``.../pyirf/irfs/``. +The estimator classes can simply be used by first creating an instance of the respective class with all +relevant information and then using the object's ``__call__`` interface the obtain results for a specific +target point. +As the energy dispersion represents one of the discretized PDF IRF components, one can use the +``MomentMorphInterpolator`` for interpolation and the ``DiscretePDFNearestNeighborSearcher`` +for extrapolation. + +.. code-block:: python + + import numpy as np + + from gammapy.irf import load_irf_dict_from_file + from glob import glob + from pyirf.interpolation import ( + EnergyDispersionEstimator, + MomentMorphInterpolator, + DiscretePDFNearestNeighborSearcher + ) + + # Load IRF data, replace path with actual path + PROD5_IRF_PATH = "pyirf/irfs/*.fits.gz" + + irfs = [load_irf_dict_from_file(path) for path in sorted(glob(PROD5_IRF_PATH))] + + edisps = np.array([irf["edisp"].quantity for irf in irfs]) + bin_edges = irfs[0]["edisp"].axes["migra"].edges + # IRFs are for zenith distances of 20, 40 and 60 deg + zen_pnt = np.array([[20], [40], [60]]) + + # Create estimator instance + edisp_estimator = EnergyDispersionEstimator( + grid_points=zen_pnt, + migra_bins=bin_edges, + energy_dispersion=edisps, + interpolator_cls=MomentMorphInterpolator, + interpolator_kwargs=None, + extrapolator_cls=DiscretePDFNearestNeighborSearcher, + extrapolator_kwargs=None, + ) + + # Estimate energy dispersions + interpolated_edisp = edisp_estimator(np.array([[30]])) + extrapolated_edisp = edisp_estimator(np.array([[10]])) + + +.. [CTA+21] Cherenkov Telescope Array Observatory & Cherenkov Telescope Array Consortium. (2021). + CTAO Instrument Response Functions - prod5 version v0.1 (v0.1) [Data set]. Zenodo. + https://doi.org/10.5281/zenodo.5499840 + + +Creating new Estimator Classes +------------------------------ + +To create a estimator class for an IRF component not yet implemented, one can simply +inherit from respective base class. +There are two, tailored to either parametrized or discrete PDF components. + +.. autosummary:: + :nosignatures: + + ParametrizedComponentEstimator Parametrized components + DiscretePDFComponentEstimator Discrete PDF components + +Consider an example, where one is interested in an estimator for simple Gaussians. +As this is already the scope of the ``DiscretePDFComponentEstimator`` base class and +for the sake of this demonstration, let the Gaussians come with some +units attached that need handling: + +.. code-block:: python + + import astropy.units as u + from pyirf.interpolation import (DiscretePDFComponentEstimator, + MomentMorphInterpolator) + + class GaussianEstimatior(DiscretePDFComponentEstimator): + @u.quantity_input(gaussians=u.m) + def __init__( + self, + grid_points, + bin_edges, + gaussians, + interpolator_cls=MomentMorphInterpolator, + interpolator_kwargs=None, + extrapolator_cls=None, + extrapolator_kwargs=None, + ): + if interpolator_kwargs is None: + interpolator_kwargs = {} + + if extrapolator_kwargs is None: + extrapolator_kwargs = {} + + self.unit = gaussians.unit + + super().__init__( + grid_points=grid_points, + bin_edges=bin_edges, + binned_pdf=gaussians.to_value(u.m), + interpolator_cls=interpolator_cls, + interpolator_kwargs=interpolator_kwargs, + extrapolator_cls=extrapolator_cls, + extrapolator_kwargs=extrapolator_kwargs, + ) + + def __call__(self, target_point): + res = super().__call__(target_point) + + # Return result with correct unit + return u.Quantity(res, u.m, copy=False).to(self.unit) + +This new estimator class can now be used just like any other estimator class already +implemented in ``pyirf.interpolation``. +While the ``extrapolator_cls`` argument can be empty when creating an instance of +``GaussianEstimator``, effectively disabling extrapolation and raising an error in +case it would be needed regardless, assume the desired extrapolation method to be +``MomentMorphNearestSimplexExtrapolator``: + +.. code-block:: python + + import numpy as np + from pyirf.interpolation import MomentMorphNearestSimplexExtrapolator + from scipy.stats import norm + + bins = np.linspace(-10, 10, 51) + grid = np.array([[1], [2], [3]]) + + gaussians = np.array([np.diff(norm(loc=x, scale=1/x).cdf(bins))/np.diff(bins) for x in grid]) + + estimator = GaussianEstimatior( + grid_points = grid, + bin_edges = bins, + gaussians = gaussians * u.m, + interpolator_cls = MomentMorphInterpolator, + extrapolator_cls = MomentMorphNearestSimplexExtrapolator + ) + +This estimator object can now easily be used to estimate Gaussians at arbitrary target points: + +.. code-block:: python + + targets = np.array([[0.9], [1.5]]) + + results = u.Quantity([estimator(target).squeeze() for target in targets]) + + +Helper Classes +-------------- + +.. autosummary:: + :nosignatures: + + PDFNormalization + + +Base Classes +------------ + +.. autosummary:: + :nosignatures: + + BaseComponentEstimator + ParametrizedComponentEstimator + DiscretePDFComponentEstimator + BaseInterpolator + ParametrizedInterpolator + DiscretePDFInterpolator + BaseExtrapolator + ParametrizedExtrapolator + DiscretePDFExtrapolator + BaseNearestNeighborSearcher + + +Full API +-------- + +.. automodapi:: pyirf.interpolation + :no-heading: + :no-main-docstr: + :inherited-members: + :no-inheritance-diagram: diff --git a/_sources/introduction.rst.txt b/_sources/introduction.rst.txt new file mode 100644 index 000000000..e13a50753 --- /dev/null +++ b/_sources/introduction.rst.txt @@ -0,0 +1,92 @@ +.. _introduction: + +Introduction to ``pyirf`` +========================= + + +``pyirf`` aims to provide functions to calculate the Instrument Response Functions (IRFs) +and sensitivity for Imaging Air Cherenkov Telescopes. + +To support a wide range of use cases, ``pyirf`` opts for a library approach of +composable building blocks with well-defined inputs and outputs. + +For more information on IRFs, have a look at the `Specification of the Data Formats for Gamma-Ray Astronomy`_ +or the `ctools documentation on IRFs `_. + + +Currently, ``pyirf`` allows calculation of the usual factorization of the IRFs into: + +* Effective area +* Energy migration +* Point spread function + +Additionally, functions for calculating point-source flux sensitivity are provided. +Flux sensitivity is defined as the smallest flux an IACT can detect with a certain significance, +usually 5 σ according to the Li&Ma likelihood ratio test, in a specified amount of time. + +``pyirf`` also provides functions to calculate event weights, that are needed +to translate a set of simulations to a physical flux for calculating sensitivity +and expected event counts. + +Event selection with energy dependent cuts is also supported, +but at the moment, only rudimentary functions to find optimal cuts are provided. + + +Input formats +------------- + +``pyirf`` does not rely on specific input file formats. +All functions take ``numpy`` arrays, astropy quantities or astropy tables for the +required data and also return the results as these objects. + +``~pyirf.io`` provides functions to export the internal IRF representation +to FITS files following the `Specification of the Data Formats for Gamma-Ray Astronomy`_ + + +DL2 event lists +^^^^^^^^^^^^^^^ + +Most functions for calculating IRFs need DL2 event lists as input. +We use ``~astropy.table.QTable`` instances for this. +``QTable`` are very similar to the standard ``~astropy.table.Table``, +but offer better interoperability with ``astropy.units.Quantity``. + +We expect certain columns to be present in the tables with the appropriate units. +To learn which functions need which columns to be present, have a look at the :ref:`_pyirf_api_docs` + +Most functions only need a small subgroup of these columns. + +.. table:: Column definitions for DL2 event lists + + +------------------------+--------+----------------------------------------------------+ + | Column | Unit | Explanation | + +========================+========+====================================================+ + | true_energy | TeV | True energy of the simulated shower | + +------------------------+--------+----------------------------------------------------+ + | weight | | Event weight | + +------------------------+--------+----------------------------------------------------+ + | true_source_fov_offset | deg | Distance of the true origin to the FOV center | + +------------------------+--------+----------------------------------------------------+ + | reco_source_fov_offset | deg | Distance of the reco origin to the FOV center | + +------------------------+--------+----------------------------------------------------+ + | true_alt | deg | True altitude of the shower origin | + +------------------------+--------+----------------------------------------------------+ + | true_az | deg | True azimuth of the shower origin | + +------------------------+--------+----------------------------------------------------+ + | pointing_alt | deg | Altitude of the field of view center | + +------------------------+--------+----------------------------------------------------+ + | pointing_az | deg | Azimuth of the field of view center | + +------------------------+--------+----------------------------------------------------+ + | reco_energy | TeV | Reconstructed energy of the simulated shower | + +------------------------+--------+----------------------------------------------------+ + | reco_alt | deg | Reconstructed altitude of shower origin | + +------------------------+--------+----------------------------------------------------+ + | reco_az | deg | Reconstructed azimuth of shower origin | + +------------------------+--------+----------------------------------------------------+ + | gh_score | | Gamma/Hadron classification output | + +------------------------+--------+----------------------------------------------------+ + | multiplicity | | Number of telescopes used in the reconstruction | + +------------------------+--------+----------------------------------------------------+ + + +.. _Specification of the Data Formats for Gamma-Ray Astronomy: https://gamma-astro-data-formats.readthedocs.io diff --git a/_sources/io/index.rst.txt b/_sources/io/index.rst.txt new file mode 100644 index 000000000..3094fd16d --- /dev/null +++ b/_sources/io/index.rst.txt @@ -0,0 +1,19 @@ +.. _io: + +Input / Output +============== + +Introduction +------------ + +This module contains functions to read input data and write IRFs in GADF format. + +Currently there is only support for reading EventDisplay DL2 FITS files, +which were converted from the ROOT files by using `EventDisplay DL2 conversion scripts `_. + + +Reference/API +------------- + +.. automodapi:: pyirf.io + :no-inheritance-diagram: diff --git a/_sources/irf/index.rst.txt b/_sources/irf/index.rst.txt new file mode 100644 index 000000000..0aeb5e6df --- /dev/null +++ b/_sources/irf/index.rst.txt @@ -0,0 +1,42 @@ +.. _irf: + +Instrument Response Functions +============================= + + +Effective Area +-------------- + +The collection area, which is proportional to the gamma-ray efficiency +of detection, is computed as a function of the true energy. The events which +are considered are the ones passing the threshold of the best cutoff plus +the angular cuts. + +Energy Dispersion Matrix +------------------------ + +The energy dispersion matrix, ratio of the reconstructed energy over the true energy +as a function of the true energy, is computed with the events passing the +threshold of the best cutoff plus the angular cuts. + +The corresponding energy migration matrix can be build from the dispersion matrix. + + +Point Spread Function +--------------------- + +The PSF describes the probability of measuring a gamma ray +of a given true energy and true position at a reconstructed position. + +Background rate +--------------- + +The background rate is calculated as the number of background-like events per +second, reconstructed energy and solid angle. +The current version is computed in radially symmetric bins in the Field Of View. + +Reference/API +------------- + +.. automodapi:: pyirf.irf + :no-inheritance-diagram: diff --git a/_sources/notebooks/fact_example.ipynb.txt b/_sources/notebooks/fact_example.ipynb.txt new file mode 100644 index 000000000..f1ed1f245 --- /dev/null +++ b/_sources/notebooks/fact_example.ipynb.txt @@ -0,0 +1,506 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "plastic-system", + "metadata": {}, + "source": [ + "# Using `pyirf` to calculate IRFs from the FACT Open Data\n", + "\n", + "\n", + "**Note** In FACT, we used a different terminology, partly because of being a monoscopic telescope or out of confusion witht the CTA terms, in this context DL3 are reconstructed events, but not necessarily already with the IRF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "alike-dover", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import astropy.units as u\n", + "import matplotlib.pyplot as plt\n", + "import subprocess as sp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "german-carroll", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "id": "analyzed-canberra", + "metadata": {}, + "source": [ + "## Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "joined-experiment", + "metadata": {}, + "outputs": [], + "source": [ + "path = \"gamma_test_dl3.hdf5\"\n", + "url = f\"https://factdata.app.tu-dortmund.de/dl3/FACT-Tools/v1.1.2/{path}\"\n", + "ret = sp.run([\"curl\", \"-z\", path, \"-fsSLO\", url], stdout=sp.PIPE, stderr=sp.PIPE, encoding='utf-8')\n", + "if ret.returncode != 0:\n", + " raise IOError(ret.stderr)" + ] + }, + { + "cell_type": "markdown", + "id": "accredited-count", + "metadata": {}, + "source": [ + "## Read in the data\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "italian-redhead", + "metadata": {}, + "outputs": [], + "source": [ + "from astropy.table import QTable\n", + "import astropy.units as u\n", + "import tables" + ] + }, + { + "cell_type": "markdown", + "id": "healthy-wrapping", + "metadata": {}, + "source": [ + "### Simulated Event Info\n", + "\n", + "Currently, pyirf only works with powerlaw simulated events, like CORSIKA does it.\n", + "We want to also support arbitrary histograms / event distributions, but that is not yet implemented.\n", + "\n", + "This can be created from a file with that information, but I will just create it here." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "micro-anniversary", + "metadata": {}, + "outputs": [], + "source": [ + "from pyirf.simulations import SimulatedEventsInfo\n", + "\n", + "simulation_info = SimulatedEventsInfo(\n", + " energy_min=200 * u.GeV,\n", + " energy_max=50 * u.TeV,\n", + " spectral_index=-2.7,\n", + " n_showers=12600000,\n", + " max_impact=300 * u.m,\n", + " viewcone_min=0 * u.deg,\n", + " viewcone_max=0 * u.deg,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "interior-richards", + "metadata": {}, + "source": [ + "### DL2 Event List\n", + "\n", + "`pyirf` does not prescribe or use a specific DL2 *file* format.\n", + "You need to read the data into an `astropy.table.QTable` following our conventions, detailed in the docs here: \n", + "\n", + "\n", + "\n", + "The FACT-Tools / aict-tools analysis chain uses a column-oriented hdf5 file written using h5py. \n", + "Unfortunately, units have to be known and are not in the metadata." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "southeast-reform", + "metadata": {}, + "outputs": [], + "source": [ + "gammas = QTable()\n", + "\n", + "# mapping of : ()\n", + "columns = {\n", + " 'obs_id': ('run_id', None),\n", + " 'event_id': ('event_num', None),\n", + " 'reco_energy': ('gamma_energy_prediction', u.GeV),\n", + " 'true_energy': ('corsika_event_header_total_energy', u.GeV),\n", + " 'true_az': ('source_position_az', u.deg),\n", + " 'pointing_az': ('pointing_position_az', u.deg),\n", + " 'theta': ('theta_deg', u.deg),\n", + " 'gh_score': ('gamma_prediction', None),\n", + "}\n", + "\n", + "with tables.open_file('gamma_test_dl3.hdf5', mode='r') as f:\n", + " events = f.root.events\n", + " \n", + " for col, (name, unit) in columns.items():\n", + " if unit is not None:\n", + " gammas[col] = u.Quantity(events[name][:], unit, copy=False)\n", + " else:\n", + " gammas[col] = events[name][:]\n", + " \n", + " gammas['true_alt'] = u.Quantity(90 - events['source_position_zd'][:], u.deg, copy=False)\n", + " gammas['pointing_alt'] = u.Quantity(90 - events['pointing_position_zd'][:], u.deg, copy=False)\n", + "\n", + " \n", + "# make it display nice\n", + "for col in gammas.colnames:\n", + " if gammas[col].dtype == float:\n", + " gammas[col].info.format = '.2f'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "optional-crawford", + "metadata": {}, + "outputs": [], + "source": [ + "gammas[:10]" + ] + }, + { + "cell_type": "markdown", + "id": "virgin-source", + "metadata": {}, + "source": [ + "### Apply Event Selection\n", + "\n", + "We remove likely hadronic events by requiring a minimal `gh_score`.\n", + "\n", + "We will calculate point-like IRFs, that means selecting events in a radius around the \n", + "assumed source position." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "proved-store", + "metadata": {}, + "outputs": [], + "source": [ + "gammas['selected_gh'] = gammas['gh_score'] > 0.8\n", + "gammas['selected_theta'] = gammas['theta'] < 0.16 * u.deg\n", + "\n", + "gammas['selected'] = gammas['selected_gh'] & gammas['selected_theta']\n", + "\n", + "np.count_nonzero(gammas['selected']) / len(gammas)" + ] + }, + { + "cell_type": "markdown", + "id": "universal-potential", + "metadata": {}, + "source": [ + "## Calculate IRFs\n", + "\n", + "### Effective area\n", + "\n", + "We only have point-like simulations at a specific wobble offset (0.6° for FACT),\n", + "so we calculate the effective area for all events at once, equivalent to a single fov offset bin.\n", + "\n", + "\n", + "#### Create the binning" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "failing-exchange", + "metadata": {}, + "outputs": [], + "source": [ + "from pyirf.binning import create_bins_per_decade, bin_center" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "compact-complaint", + "metadata": {}, + "outputs": [], + "source": [ + "true_energy_bins = create_bins_per_decade(simulation_info.energy_min, simulation_info.energy_max, 5)\n", + "\n", + "# single offset bin around the wobble distance\n", + "# since we are dealing with point-like simulations \n", + "wobble_offset = 0.6 * u.deg\n", + "fov_offset_bins = [0.59, 0.61] * u.deg" + ] + }, + { + "cell_type": "markdown", + "id": "blocked-japan", + "metadata": {}, + "source": [ + "### Calculate effective area\n", + "\n", + "\n", + "Effective area is calculated before and after cuts, for the IRF, we only need after the event selection\n", + "has been applied.\n", + "\n", + "The difference between point-like IRFs and Full-Enclosure IRFs is if a theta cut has been applied or not." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "frequent-concert", + "metadata": {}, + "outputs": [], + "source": [ + "from pyirf.irf import effective_area_per_energy\n", + "\n", + "aeff_all = effective_area_per_energy(gammas, simulation_info, true_energy_bins)\n", + "aeff_selected = effective_area_per_energy(gammas[gammas['selected']], simulation_info, true_energy_bins)" + ] + }, + { + "cell_type": "markdown", + "id": "armed-street", + "metadata": {}, + "source": [ + "Let's use gammapy to plot the IRF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "norman-personal", + "metadata": {}, + "outputs": [], + "source": [ + "# utility function to converet pyirf Quantities to the gammapy classes\n", + "from pyirf.gammapy import create_effective_area_table_2d\n", + "\n", + "plt.figure()\n", + "\n", + "for aeff, label in zip((aeff_all, aeff_selected), ('All Events', 'Selected Events')):\n", + " aeff_gammapy = create_effective_area_table_2d(\n", + " # add a new dimension for the single fov offset bin\n", + " effective_area=aeff[..., np.newaxis],\n", + " true_energy_bins=true_energy_bins,\n", + " fov_offset_bins=fov_offset_bins,\n", + " )\n", + "\n", + "\n", + " aeff_gammapy.plot_energy_dependence(label=label, offset=[wobble_offset])\n", + "\n", + "plt.xlim(true_energy_bins.min().to_value(u.GeV), true_energy_bins.max().to_value(u.GeV)) \n", + "plt.yscale('log')\n", + "plt.xscale('log')\n", + "plt.legend()\n", + "\n", + "print(aeff_gammapy)" + ] + }, + { + "cell_type": "markdown", + "id": "eleven-sessions", + "metadata": {}, + "source": [ + "### Point Spread Function\n", + "\n", + "The point spread function describes how well the direction of the gamma rays is estimated." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "spiritual-attention", + "metadata": {}, + "outputs": [], + "source": [ + "from pyirf.irf import psf_table\n", + "from pyirf.utils import calculate_source_fov_offset\n", + "\n", + "\n", + "gammas['true_source_fov_offset'] = calculate_source_fov_offset(gammas)\n", + "\n", + "\n", + "source_offset_bins = np.linspace(0, 3, 100) * u.deg\n", + "\n", + "# calculate this only for the events after the gamma/hadron separation\n", + "psf = psf_table(gammas[gammas['selected_gh']], true_energy_bins, source_offset_bins, fov_offset_bins)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "animated-prescription", + "metadata": {}, + "outputs": [], + "source": [ + "psf.shape" + ] + }, + { + "cell_type": "markdown", + "id": "opposed-coordinator", + "metadata": {}, + "source": [ + "Again, let's use gammapy to plot:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "spoken-shock", + "metadata": {}, + "outputs": [], + "source": [ + "from pyirf.gammapy import create_psf_3d\n", + "\n", + "psf_gammapy = create_psf_3d(psf, true_energy_bins, source_offset_bins, fov_offset_bins)\n", + "\n", + "plt.figure()\n", + "psf_gammapy.plot_psf_vs_rad(offset=[wobble_offset], energy_true=[1., 10.]*u.TeV)\n", + "plt.legend(plt.gca().lines, ['1 TeV', '10 TeV'])" + ] + }, + { + "cell_type": "markdown", + "id": "floral-aquarium", + "metadata": {}, + "source": [ + "### Energy Dispersion\n", + "\n", + "Describes how well the energy is estimated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "north-compatibility", + "metadata": {}, + "outputs": [], + "source": [ + "from pyirf.irf import energy_dispersion\n", + "\n", + "# logarithmic space, is \"symmetric\" in terms of ratios 0.1 is a factor of 10 from 1 is a factor of 10 from 10\n", + "migration_bins = np.geomspace(0.1, 10, 100)\n", + "\n", + "edisp = energy_dispersion(\n", + " gammas[gammas['selected']],\n", + " true_energy_bins=true_energy_bins,\n", + " fov_offset_bins=fov_offset_bins,\n", + " migration_bins=migration_bins,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "copyrighted-oakland", + "metadata": {}, + "source": [ + "Plot edisp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "heard-plate", + "metadata": {}, + "outputs": [], + "source": [ + "from gammapy.irf import EnergyDispersion2D\n", + "\n", + "plt.figure()\n", + "plt.pcolormesh(\n", + " true_energy_bins.to_value(u.GeV),\n", + " migration_bins,\n", + " edisp[:, :, 0].T,\n", + " cmap='inferno'\n", + ")\n", + "\n", + "plt.xlabel('$E_\\mathrm{true} / \\mathrm{GeV}$')\n", + "plt.ylabel('$\\mu$')\n", + "plt.yscale('log')\n", + "plt.xscale('log')" + ] + }, + { + "cell_type": "markdown", + "id": "medical-dominican", + "metadata": {}, + "source": [ + "## Export to GADF FITS files\n", + "\n", + "We use the classes and methods from `astropy.io.fits` and `pyirf.io.gadf` to write files following the GADF \n", + "specification, which can be found here:\n", + "\n", + "https://gamma-astro-data-formats.readthedocs.io/en/latest/" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "twenty-equity", + "metadata": {}, + "outputs": [], + "source": [ + "from pyirf.io.gadf import create_aeff2d_hdu, create_energy_dispersion_hdu, create_psf_table_hdu\n", + "from astropy.io import fits\n", + "from astropy.time import Time\n", + "from pyirf import __version__\n", + "\n", + "# set some common meta data for all hdus\n", + "meta = dict(\n", + " CREATOR='pyirf-v' + __version__,\n", + " TELESCOP='FACT',\n", + " INSTRUME='FACT',\n", + " DATE=Time.now().iso,\n", + ")\n", + "\n", + "hdus = []\n", + "\n", + "# every fits file has to have an Image HDU as first HDU.\n", + "# GADF only uses Binary Table HDUs, so we need to add an empty HDU in front\n", + "hdus.append(fits.PrimaryHDU(header=fits.Header(meta)))\n", + "\n", + "hdus.append(create_aeff2d_hdu(aeff_selected, true_energy_bins, fov_offset_bins, **meta))\n", + "hdus.append(create_energy_dispersion_hdu(edisp, true_energy_bins, migration_bins, fov_offset_bins, **meta))\n", + "hdus.append(create_psf_table_hdu(psf, true_energy_bins, source_offset_bins, fov_offset_bins, **meta))\n", + "\n", + "fits.HDUList(hdus).writeto('fact_irf.fits.gz', overwrite=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/_sources/notebooks/index.rst.txt b/_sources/notebooks/index.rst.txt new file mode 100644 index 000000000..cf07b82d5 --- /dev/null +++ b/_sources/notebooks/index.rst.txt @@ -0,0 +1,10 @@ +.. _notebooks: + +================= +Example Notebooks +================= + +.. toctree:: + :maxdepth: 1 + + fact_example diff --git a/_sources/sensitivity.rst.txt b/_sources/sensitivity.rst.txt new file mode 100644 index 000000000..d1575ff57 --- /dev/null +++ b/_sources/sensitivity.rst.txt @@ -0,0 +1,11 @@ +.. _sensitivity: + +Sensitivity +=========== + + +Reference/API +------------- + +.. automodapi:: pyirf.sensitivity + :no-inheritance-diagram: diff --git a/_sources/simulation.rst.txt b/_sources/simulation.rst.txt new file mode 100644 index 000000000..70496415e --- /dev/null +++ b/_sources/simulation.rst.txt @@ -0,0 +1,11 @@ +.. _simulation: + +Simulation Information +====================== + + +Reference/API +------------- + +.. automodapi:: pyirf.simulations + :no-inheritance-diagram: diff --git a/_sources/spectral.rst.txt b/_sources/spectral.rst.txt new file mode 100644 index 000000000..1b8189382 --- /dev/null +++ b/_sources/spectral.rst.txt @@ -0,0 +1,13 @@ +.. _spectral: + +Event Weighting and Spectrum Definitions +======================================== + + +Reference/API +------------- + + +.. automodapi:: pyirf.spectral + :no-inheritance-diagram: + :include-all-objects: diff --git a/_sources/statistics.rst.txt b/_sources/statistics.rst.txt new file mode 100644 index 000000000..c527ff3f8 --- /dev/null +++ b/_sources/statistics.rst.txt @@ -0,0 +1,11 @@ +.. _statistics: + +Statistics +========== + + +Reference/API +------------- + +.. automodapi:: pyirf.statistics + :no-inheritance-diagram: diff --git a/_sources/utils.rst.txt b/_sources/utils.rst.txt new file mode 100644 index 000000000..f0b6b6cb8 --- /dev/null +++ b/_sources/utils.rst.txt @@ -0,0 +1,11 @@ +.. _utils: + +Utility functions +================= + + +Reference/API +------------- + +.. automodapi:: pyirf.utils + :no-inheritance-diagram: diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 000000000..81415803e --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 000000000..f316efcb4 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 000000000..c718cee44 --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 000000000..6cb600001 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 000000000..7059e2314 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 000000000..f815f63f9 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 000000000..f2c76e5bd Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 000000000..e9f60ca95 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 000000000..855c845e5 --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..35acda2fa Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..400014a4b Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 000000000..4d13fc604 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 000000000..88ad05b9f Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 000000000..c4e3d804b Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 000000000..c6dff51f0 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 000000000..bb195043c Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 000000000..76114bc03 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 000000000..3404f37e2 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 000000000..ae1307ff5 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 000000000..3bf984332 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 000000000..19a446a0e --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 000000000..4d67807d1 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 000000000..7e4c114f2 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 000000000..a858a410e Binary files /dev/null and b/_static/file.png differ diff --git a/_static/graphviz.css b/_static/graphviz.css new file mode 100644 index 000000000..027576e34 --- /dev/null +++ b/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 000000000..c4c6022f2 --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/html5shiv.min.js b/_static/js/html5shiv.min.js new file mode 100644 index 000000000..cd1c674f5 --- /dev/null +++ b/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/theme.js b/_static/js/theme.js new file mode 100644 index 000000000..1fddb6ee4 --- /dev/null +++ b/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 000000000..d96755fda Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/nbsphinx-broken-thumbnail.svg b/_static/nbsphinx-broken-thumbnail.svg new file mode 100644 index 000000000..4919ca882 --- /dev/null +++ b/_static/nbsphinx-broken-thumbnail.svg @@ -0,0 +1,9 @@ + + + + diff --git a/_static/nbsphinx-code-cells.css b/_static/nbsphinx-code-cells.css new file mode 100644 index 000000000..a3fb27c30 --- /dev/null +++ b/_static/nbsphinx-code-cells.css @@ -0,0 +1,259 @@ +/* remove conflicting styling from Sphinx themes */ +div.nbinput.container div.prompt *, +div.nboutput.container div.prompt *, +div.nbinput.container div.input_area pre, +div.nboutput.container div.output_area pre, +div.nbinput.container div.input_area .highlight, +div.nboutput.container div.output_area .highlight { + border: none; + padding: 0; + margin: 0; + box-shadow: none; +} + +div.nbinput.container > div[class*=highlight], +div.nboutput.container > div[class*=highlight] { + margin: 0; +} + +div.nbinput.container div.prompt *, +div.nboutput.container div.prompt * { + background: none; +} + +div.nboutput.container div.output_area .highlight, +div.nboutput.container div.output_area pre { + background: unset; +} + +div.nboutput.container div.output_area div.highlight { + color: unset; /* override Pygments text color */ +} + +/* avoid gaps between output lines */ +div.nboutput.container div[class*=highlight] pre { + line-height: normal; +} + +/* input/output containers */ +div.nbinput.container, +div.nboutput.container { + display: -webkit-flex; + display: flex; + align-items: flex-start; + margin: 0; + width: 100%; +} +@media (max-width: 540px) { + div.nbinput.container, + div.nboutput.container { + flex-direction: column; + } +} + +/* input container */ +div.nbinput.container { + padding-top: 5px; +} + +/* last container */ +div.nblast.container { + padding-bottom: 5px; +} + +/* input prompt */ +div.nbinput.container div.prompt pre, +/* for sphinx_immaterial theme: */ +div.nbinput.container div.prompt pre > code { + color: #307FC1; +} + +/* output prompt */ +div.nboutput.container div.prompt pre, +/* for sphinx_immaterial theme: */ +div.nboutput.container div.prompt pre > code { + color: #BF5B3D; +} + +/* all prompts */ +div.nbinput.container div.prompt, +div.nboutput.container div.prompt { + width: 4.5ex; + padding-top: 5px; + position: relative; + user-select: none; +} + +div.nbinput.container div.prompt > div, +div.nboutput.container div.prompt > div { + position: absolute; + right: 0; + margin-right: 0.3ex; +} + +@media (max-width: 540px) { + div.nbinput.container div.prompt, + div.nboutput.container div.prompt { + width: unset; + text-align: left; + padding: 0.4em; + } + div.nboutput.container div.prompt.empty { + padding: 0; + } + + div.nbinput.container div.prompt > div, + div.nboutput.container div.prompt > div { + position: unset; + } +} + +/* disable scrollbars and line breaks on prompts */ +div.nbinput.container div.prompt pre, +div.nboutput.container div.prompt pre { + overflow: hidden; + white-space: pre; +} + +/* input/output area */ +div.nbinput.container div.input_area, +div.nboutput.container div.output_area { + -webkit-flex: 1; + flex: 1; + overflow: auto; +} +@media (max-width: 540px) { + div.nbinput.container div.input_area, + div.nboutput.container div.output_area { + width: 100%; + } +} + +/* input area */ +div.nbinput.container div.input_area { + border: 1px solid #e0e0e0; + border-radius: 2px; + /*background: #f5f5f5;*/ +} + +/* override MathJax center alignment in output cells */ +div.nboutput.container div[class*=MathJax] { + text-align: left !important; +} + +/* override sphinx.ext.imgmath center alignment in output cells */ +div.nboutput.container div.math p { + text-align: left; +} + +/* standard error */ +div.nboutput.container div.output_area.stderr { + background: #fdd; +} + +/* ANSI colors */ +.ansi-black-fg { color: #3E424D; } +.ansi-black-bg { background-color: #3E424D; } +.ansi-black-intense-fg { color: #282C36; } +.ansi-black-intense-bg { background-color: #282C36; } +.ansi-red-fg { color: #E75C58; } +.ansi-red-bg { background-color: #E75C58; } +.ansi-red-intense-fg { color: #B22B31; } +.ansi-red-intense-bg { background-color: #B22B31; } +.ansi-green-fg { color: #00A250; } +.ansi-green-bg { background-color: #00A250; } +.ansi-green-intense-fg { color: #007427; } +.ansi-green-intense-bg { background-color: #007427; } +.ansi-yellow-fg { color: #DDB62B; } +.ansi-yellow-bg { background-color: #DDB62B; } +.ansi-yellow-intense-fg { color: #B27D12; } +.ansi-yellow-intense-bg { background-color: #B27D12; } +.ansi-blue-fg { color: #208FFB; } +.ansi-blue-bg { background-color: #208FFB; } +.ansi-blue-intense-fg { color: #0065CA; } +.ansi-blue-intense-bg { background-color: #0065CA; } +.ansi-magenta-fg { color: #D160C4; } +.ansi-magenta-bg { background-color: #D160C4; } +.ansi-magenta-intense-fg { color: #A03196; } +.ansi-magenta-intense-bg { background-color: #A03196; } +.ansi-cyan-fg { color: #60C6C8; } +.ansi-cyan-bg { background-color: #60C6C8; } +.ansi-cyan-intense-fg { color: #258F8F; } +.ansi-cyan-intense-bg { background-color: #258F8F; } +.ansi-white-fg { color: #C5C1B4; } +.ansi-white-bg { background-color: #C5C1B4; } +.ansi-white-intense-fg { color: #A1A6B2; } +.ansi-white-intense-bg { background-color: #A1A6B2; } + +.ansi-default-inverse-fg { color: #FFFFFF; } +.ansi-default-inverse-bg { background-color: #000000; } + +.ansi-bold { font-weight: bold; } +.ansi-underline { text-decoration: underline; } + + +div.nbinput.container div.input_area div[class*=highlight] > pre, +div.nboutput.container div.output_area div[class*=highlight] > pre, +div.nboutput.container div.output_area div[class*=highlight].math, +div.nboutput.container div.output_area.rendered_html, +div.nboutput.container div.output_area > div.output_javascript, +div.nboutput.container div.output_area:not(.rendered_html) > img{ + padding: 5px; + margin: 0; +} + +/* fix copybtn overflow problem in chromium (needed for 'sphinx_copybutton') */ +div.nbinput.container div.input_area > div[class^='highlight'], +div.nboutput.container div.output_area > div[class^='highlight']{ + overflow-y: hidden; +} + +/* hide copy button on prompts for 'sphinx_copybutton' extension ... */ +.prompt .copybtn, +/* ... and 'sphinx_immaterial' theme */ +.prompt .md-clipboard.md-icon { + display: none; +} + +/* Some additional styling taken form the Jupyter notebook CSS */ +.jp-RenderedHTMLCommon table, +div.rendered_html table { + border: none; + border-collapse: collapse; + border-spacing: 0; + color: black; + font-size: 12px; + table-layout: fixed; +} +.jp-RenderedHTMLCommon thead, +div.rendered_html thead { + border-bottom: 1px solid black; + vertical-align: bottom; +} +.jp-RenderedHTMLCommon tr, +.jp-RenderedHTMLCommon th, +.jp-RenderedHTMLCommon td, +div.rendered_html tr, +div.rendered_html th, +div.rendered_html td { + text-align: right; + vertical-align: middle; + padding: 0.5em 0.5em; + line-height: normal; + white-space: normal; + max-width: none; + border: none; +} +.jp-RenderedHTMLCommon th, +div.rendered_html th { + font-weight: bold; +} +.jp-RenderedHTMLCommon tbody tr:nth-child(odd), +div.rendered_html tbody tr:nth-child(odd) { + background: #f5f5f5; +} +.jp-RenderedHTMLCommon tbody tr:hover, +div.rendered_html tbody tr:hover { + background: rgba(66, 165, 245, 0.2); +} + diff --git a/_static/nbsphinx-gallery.css b/_static/nbsphinx-gallery.css new file mode 100644 index 000000000..365c27a96 --- /dev/null +++ b/_static/nbsphinx-gallery.css @@ -0,0 +1,31 @@ +.nbsphinx-gallery { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 5px; + margin-top: 1em; + margin-bottom: 1em; +} + +.nbsphinx-gallery > a { + padding: 5px; + border: 1px dotted currentColor; + border-radius: 2px; + text-align: center; +} + +.nbsphinx-gallery > a:hover { + border-style: solid; +} + +.nbsphinx-gallery img { + max-width: 100%; + max-height: 100%; +} + +.nbsphinx-gallery > a > div:first-child { + display: flex; + align-items: start; + justify-content: center; + height: 120px; + margin-bottom: 5px; +} diff --git a/_static/nbsphinx-no-thumbnail.svg b/_static/nbsphinx-no-thumbnail.svg new file mode 100644 index 000000000..9dca7588f --- /dev/null +++ b/_static/nbsphinx-no-thumbnail.svg @@ -0,0 +1,9 @@ + + + + diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 000000000..7107cec93 Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 000000000..84ab3030a --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 000000000..92da3f8b2 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,619 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlinks", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 000000000..8a96c69a1 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/api/pyirf.benchmarks.angular_resolution.html b/api/pyirf.benchmarks.angular_resolution.html new file mode 100644 index 000000000..d79c91c10 --- /dev/null +++ b/api/pyirf.benchmarks.angular_resolution.html @@ -0,0 +1,183 @@ + + + + + + + angular_resolution — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

angular_resolution

+
+
+pyirf.benchmarks.angular_resolution(events, energy_bins, energy_type='true', quantile=0.6826894921370859)[source]
+

Calculate the angular resolution.

+

This implementation corresponds to the 68% containment of the angular +distance distribution.

+
+
Parameters:
+
+
eventsastropy.table.QTable

Astropy Table object containing the reconstructed events information.

+
+
energy_bins: numpy.ndarray(dtype=float, ndim=1)

Bin edges in energy.

+
+
energy_type: str

Either “true” or “reco” energy. +Default is “true”.

+
+
quantilefloat

Which quantile to use for the angular resolution, +by default, the containment of the 1-sigma region +of the normal distribution (~68%) is used.

+
+
+
+
Returns:
+
+
resultastropy.table.QTable

QTable containing the 68% containment of the angular +distance distribution per each reconstructed energy bin.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.benchmarks.energy_bias_resolution.html b/api/pyirf.benchmarks.energy_bias_resolution.html new file mode 100644 index 000000000..9e7179e13 --- /dev/null +++ b/api/pyirf.benchmarks.energy_bias_resolution.html @@ -0,0 +1,181 @@ + + + + + + + energy_bias_resolution — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

energy_bias_resolution

+
+
+pyirf.benchmarks.energy_bias_resolution(events, energy_bins, energy_type='true', bias_function=<function nanmedian>, resolution_function=<function inter_quantile_distance>)[source]
+

Calculate bias and energy resolution.

+
+
Parameters:
+
+
events: astropy.table.QTable

Astropy Table object containing the reconstructed events information.

+
+
energy_bins: numpy.ndarray(dtype=float, ndim=1)

Bin edges in energy.

+
+
energy_type: str

Either “true” or “reco” energy. +Default is “true”.

+
+
bias_function: callable

Function used to calculate the energy bias

+
+
resolution_function: callable

Function used to calculate the energy resolution

+
+
+
+
Returns:
+
+
resultastropy.table.QTable

QTable containing the energy bias and resolution +per each bin in true energy.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion.html b/api/pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion.html new file mode 100644 index 000000000..16f91e028 --- /dev/null +++ b/api/pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion.html @@ -0,0 +1,168 @@ + + + + + + + energy_bias_resolution_from_energy_dispersion — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

energy_bias_resolution_from_energy_dispersion

+
+
+pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion(energy_dispersion, migration_bins)[source]
+

Calculate bias and energy resolution.

+
+
Parameters:
+
+
edisp:

Energy dispersion matrix of shape +(n_energy_bins, n_migra_bins, n_source_offset_bins)

+
+
migration_bins: numpy.ndarray

Bin edges for the relative energy migration (reco_energy / true_energy)

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.binning.add_overflow_bins.html b/api/pyirf.binning.add_overflow_bins.html new file mode 100644 index 000000000..47a663591 --- /dev/null +++ b/api/pyirf.binning.add_overflow_bins.html @@ -0,0 +1,173 @@ + + + + + + + add_overflow_bins — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

add_overflow_bins

+
+
+pyirf.binning.add_overflow_bins(bins, positive=True)[source]
+

Add under and overflow bins to a bin array.

+
+
Parameters:
+
+
bins: np.array or u.Quantity

Bin edges array

+
+
positive: bool

If True, the underflow array will start at 0, if not at -np.inf

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.binning.bin_center.html b/api/pyirf.binning.bin_center.html new file mode 100644 index 000000000..8e3fc5152 --- /dev/null +++ b/api/pyirf.binning.bin_center.html @@ -0,0 +1,162 @@ + + + + + + + bin_center — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api/pyirf.binning.calculate_bin_indices.html b/api/pyirf.binning.calculate_bin_indices.html new file mode 100644 index 000000000..9d979252b --- /dev/null +++ b/api/pyirf.binning.calculate_bin_indices.html @@ -0,0 +1,189 @@ + + + + + + + calculate_bin_indices — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

calculate_bin_indices

+
+
+pyirf.binning.calculate_bin_indices(data, bins)[source]
+

Calculate bin indices of inidividula entries of the given data array using +the supplied binning. Underflow will be indicated by UNDERFLOW_INDEX and +overflow by OVERFLOW_INDEX.

+

If the bins already include underflow / overflow bins, e.g. +bins[0] = -np.inf and bins[-1] = np.inf, using the result of this +function will always be a valid index into the resulting histogram.

+
+
Parameters:
+
+
data: ``~np.ndarray`` or ``~astropy.units.Quantity``

Array with the data

+
+
bins: ``~np.ndarray`` or ``~astropy.units.Quantity``

Array or Quantity of bin edges. Must have the same unit as data if a Quantity.

+
+
+
+
Returns:
+
+
bin_index: np.ndarray[int]

Indices of the histogram bin the values in data belong to. +Under- and overflown values will have values of UNDERFLOW_INDEX +and OVERFLOW_INDEX respectively.

+
+
valid: np.ndarray[bool]

Boolean mask indicating if a given value belongs into one of the defined bins. +False indicates that an entry fell into the over- or underflow bins.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.binning.create_bins_per_decade.html b/api/pyirf.binning.create_bins_per_decade.html new file mode 100644 index 000000000..6b80c6a5a --- /dev/null +++ b/api/pyirf.binning.create_bins_per_decade.html @@ -0,0 +1,184 @@ + + + + + + + create_bins_per_decade — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_bins_per_decade

+
+
+pyirf.binning.create_bins_per_decade(e_min, e_max, bins_per_decade=5)[source]
+

Create a bin array with bins equally spaced in logarithmic energy +with bins_per_decade bins per decade.

+
+
Parameters:
+
+
e_min: u.Quantity[energy]

Minimum energy, inclusive

+
+
e_max: u.Quantity[energy]

Maximum energy, non-inclusive +If the endpoint exactly matches the n_bins_per_decade requirement, +it will be included.

+
+
n_bins_per_decade: int

number of bins per decade

+
+
+
+
Returns:
+
+
bins: u.Quantity[energy]

The created bin array, will have units of e_min

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.binning.create_histogram_table.html b/api/pyirf.binning.create_histogram_table.html new file mode 100644 index 000000000..938d4a80b --- /dev/null +++ b/api/pyirf.binning.create_histogram_table.html @@ -0,0 +1,182 @@ + + + + + + + create_histogram_table — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_histogram_table

+
+
+pyirf.binning.create_histogram_table(events, bins, key='reco_energy')[source]
+

Histogram a variable from events data into an astropy table.

+
+
Parameters:
+
+
eventsastropy.QTable

Astropy Table object containing the reconstructed events information.

+
+
bins: ``~np.ndarray`` or ``~astropy.units.Quantity``

Array or Quantity of bin edges. +It must have the same units as data if a Quantity.

+
+
keystring

Variable to histogram from the events table.

+
+
+
+
Returns:
+
+
hist: astropy.QTable

Astropy table containg the histogram.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.binning.join_bin_lo_hi.html b/api/pyirf.binning.join_bin_lo_hi.html new file mode 100644 index 000000000..a6c680b38 --- /dev/null +++ b/api/pyirf.binning.join_bin_lo_hi.html @@ -0,0 +1,181 @@ + + + + + + + join_bin_lo_hi — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

join_bin_lo_hi

+
+
+pyirf.binning.join_bin_lo_hi(bin_lo, bin_hi)[source]
+

Function joins bins into lo and hi part, +e.g. [0, 1, 2] and [1, 2, 4] into [0, 1, 2, 4] +It works on multidimentional arrays as long as the binning is in the last axis

+
+
Parameters:
+
+
bin_lo: np.array or u.Quantity

Lo bin edges array

+
+
bin_hi: np.array or u.Quantity

Hi bin edges array

+
+
+
+
Returns:
+
+
bins: np.array of u.Quantity

The joint bins

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.binning.resample_histogram1d.html b/api/pyirf.binning.resample_histogram1d.html new file mode 100644 index 000000000..76dc95de8 --- /dev/null +++ b/api/pyirf.binning.resample_histogram1d.html @@ -0,0 +1,188 @@ + + + + + + + resample_histogram1d — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

resample_histogram1d

+
+
+pyirf.binning.resample_histogram1d(data, old_edges, new_edges, axis=0)[source]
+

Rebinning of a histogram by interpolation along a given axis.

+
+
Parameters:
+
+
datanumpy.ndarray or astropy.units.Quantity

Histogram.

+
+
old_edgesnumpy.array or astropy.units.Quantity

Binning used to calculate data. +len(old_edges) - 1 needs to equal the length of data +along interpolation axis (axis). +If quantity, needs to be compatible to new_edges.

+
+
new_edgesnumpy.array or astropy.units.Quantity

Binning of new histogram. +If quantity, needs to be compatible to old_edges.

+
+
axisint

Interpolation axis.

+
+
+
+
Returns:
+
+
numpy.ndarray or astropy.units.Quantity

Interpolated histogram with dimension according to data and new_edges. +If data is a quantity, this has the same unit.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.binning.split_bin_lo_hi.html b/api/pyirf.binning.split_bin_lo_hi.html new file mode 100644 index 000000000..9d077d7e2 --- /dev/null +++ b/api/pyirf.binning.split_bin_lo_hi.html @@ -0,0 +1,180 @@ + + + + + + + split_bin_lo_hi — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

split_bin_lo_hi

+
+
+pyirf.binning.split_bin_lo_hi(bins)[source]
+

Inverted function to join_bin_hi_lo, +e.g. it splits [0, 1, 2, 4] into [0, 1, 2] and [1, 2, 4]

+
+
Parameters:
+
+
bins: np.array of u.Quantity

The joint bins

+
+
+
+
Returns:
+
+
bin_lo: np.array or u.Quantity

Lo bin edges array

+
+
bin_hi: np.array or u.Quantity

Hi bin edges array

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.cut_optimization.optimize_gh_cut.html b/api/pyirf.cut_optimization.optimize_gh_cut.html new file mode 100644 index 000000000..492e64ade --- /dev/null +++ b/api/pyirf.cut_optimization.optimize_gh_cut.html @@ -0,0 +1,197 @@ + + + + + + + optimize_gh_cut — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

optimize_gh_cut

+
+
+pyirf.cut_optimization.optimize_gh_cut(signal, background, reco_energy_bins, gh_cut_efficiencies, theta_cuts, op=<built-in function ge>, fov_offset_min=<Quantity 0. deg>, fov_offset_max=<Quantity 1. deg>, alpha=1.0, progress=True, **kwargs)[source]
+

Optimize the gh-score cut in every energy bin of reconstructed energy +for best sensitivity.

+

This procedure is EventDisplay-like, since it only applies a +pre-computed theta cut and then optimizes only the gamma/hadron separation +cut.

+
+
Parameters:
+
+
signal: astropy.table.QTable

event list of simulated signal events. +Required columns are theta, reco_energy, ‘weight’, gh_score +No directional (theta) or gamma/hadron cut should already be applied.

+
+
background: astropy.table.QTable

event list of simulated background events. +Required columns are reco_source_fov_offset, reco_energy, +‘weight’, gh_score. +No directional (theta) or gamma/hadron cut should already be applied.

+
+
reco_energy_bins: astropy.units.Quantity[energy]

Bins in reconstructed energy to use for sensitivity computation

+
+
gh_cut_efficiencies: np.ndarray[float, ndim=1]

The cut efficiencies to scan for best sensitivity.

+
+
theta_cuts: astropy.table.QTable

cut definition of the energy dependent theta cut, +e.g. as created by calculate_percentile_cut

+
+
op: comparison function with signature f(a, b) -> bool

The comparison function to use for the gamma hadron score. +Returning true means an event passes the cut, so is not discarded. +E.g. for gammaness-like score, use operator.ge (>=) and for a +hadroness-like score use operator.le (<=).

+
+
fov_offset_min: astropy.units.Quantity[angle]

Minimum distance from the fov center for background events to be taken into account

+
+
fov_offset_max: astropy.units.Quantity[angle]

Maximum distance from the fov center for background events to be taken into account

+
+
alpha: float

Size ratio of off region / on region. Will be used to +scale the background rate.

+
+
progress: bool

If True, show a progress bar during cut optimization

+
+
**kwargs are passed to ``calculate_sensitivity``
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.cuts.calculate_percentile_cut.html b/api/pyirf.cuts.calculate_percentile_cut.html new file mode 100644 index 000000000..9f5e2e683 --- /dev/null +++ b/api/pyirf.cuts.calculate_percentile_cut.html @@ -0,0 +1,186 @@ + + + + + + + calculate_percentile_cut — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

calculate_percentile_cut

+
+
+pyirf.cuts.calculate_percentile_cut(values, bin_values, bins, fill_value, percentile=68, min_value=None, max_value=None, smoothing=None, min_events=10)[source]
+

Calculate cuts as the percentile of a given quantity in bins of another +quantity.

+
+
Parameters:
+
+
values: ``~numpy.ndarray`` or ``~astropy.units.Quantity``

The values for which the cut should be calculated

+
+
bin_values: ``~numpy.ndarray`` or ``~astropy.units.Quantity``

The values used to sort the values into bins

+
+
edges: ``~numpy.ndarray`` or ``~astropy.units.Quantity``

Bin edges

+
+
fill_value: float or quantity

Value for bins with less than min_events, +must have same unit as values

+
+
percentile: float

The percentile to calculate in each bin as a percentage, +i.e. 0 <= percentile <= 100.

+
+
min_value: float or quantity or None

If given, cuts smaller than this value are replaced with min_value

+
+
max_value: float or quantity or None

If given, cuts larger than this value are replaced with max_value

+
+
smoothing: float or None

If given, apply a gaussian filter of width sigma in terms +of bins.

+
+
min_events: int

Bins with less events than this number are replaced with fill_value

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.cuts.compare_irf_cuts.html b/api/pyirf.cuts.compare_irf_cuts.html new file mode 100644 index 000000000..bbc58cdbe --- /dev/null +++ b/api/pyirf.cuts.compare_irf_cuts.html @@ -0,0 +1,170 @@ + + + + + + + compare_irf_cuts — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

compare_irf_cuts

+
+
+pyirf.cuts.compare_irf_cuts(cuts)[source]
+

checks if the same cuts have been applied in all of them

+
+
Parameters:
+
+
cuts: list of QTables

list of cuts each entry in the list correspond to one set of IRFs

+
+
Returns
+
——-
+
match: Boolean

if the cuts are the same in all the files

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.cuts.evaluate_binned_cut.html b/api/pyirf.cuts.evaluate_binned_cut.html new file mode 100644 index 000000000..a99ad5a42 --- /dev/null +++ b/api/pyirf.cuts.evaluate_binned_cut.html @@ -0,0 +1,188 @@ + + + + + + + evaluate_binned_cut — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

evaluate_binned_cut

+
+
+pyirf.cuts.evaluate_binned_cut(values, bin_values, cut_table, op)[source]
+

Evaluate a binned cut as defined in cut_table on given events.

+

Events with bin_values outside the bin edges defined in cut table +will be set to False.

+
+
Parameters:
+
+
values: ``~numpy.ndarray`` or ``~astropy.units.Quantity``

The values on which the cut should be evaluated

+
+
bin_values: ``~numpy.ndarray`` or ``~astropy.units.Quantity``

The values used to sort the values into bins

+
+
cut_table: ``~astropy.table.Table``

A table describing the binned cuts, e.g. as created by +~pyirf.cuts.calculate_percentile_cut. +Required columns: +- low: lower edges of the bins +- high: upper edges of the bins, +- cut: cut value

+
+
op: callable(a, b) -> bool

A function taking two arguments, comparing element-wise and +returning an array of booleans. +Must support vectorized application.

+
+
+
+
Returns:
+
+
result: np.ndarray[bool]

A mask for each entry in values indicating if the event +passes the bin specific cut given in cut table.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.gammapy.create_effective_area_table_2d.html b/api/pyirf.gammapy.create_effective_area_table_2d.html new file mode 100644 index 000000000..bce19e0e6 --- /dev/null +++ b/api/pyirf.gammapy.create_effective_area_table_2d.html @@ -0,0 +1,177 @@ + + + + + + + create_effective_area_table_2d — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_effective_area_table_2d

+
+
+pyirf.gammapy.create_effective_area_table_2d(effective_area, true_energy_bins, fov_offset_bins)[source]
+

Create a gammapy.irf.EffectiveAreaTable2D from pyirf outputs.

+
+
Parameters:
+
+
effective_area: astropy.units.Quantity[area]

Effective area array, must have shape (n_energy_bins, n_fov_offset_bins)

+
+
true_energy_bins: astropy.units.Quantity[energy]

Bin edges in true energy

+
+
fov_offset_bins: astropy.units.Quantity[angle]

Bin edges in the field of view offset. +For Point-Like IRFs, only giving a single bin is appropriate.

+
+
+
+
Returns:
+
+
gammapy.irf.EffectiveAreaTable2D
+
aeff2d: gammapy.irf.EffectiveAreaTable2D
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.gammapy.create_energy_dispersion_2d.html b/api/pyirf.gammapy.create_energy_dispersion_2d.html new file mode 100644 index 000000000..ae0993c9d --- /dev/null +++ b/api/pyirf.gammapy.create_energy_dispersion_2d.html @@ -0,0 +1,179 @@ + + + + + + + create_energy_dispersion_2d — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_energy_dispersion_2d

+
+
+pyirf.gammapy.create_energy_dispersion_2d(energy_dispersion, true_energy_bins, migration_bins, fov_offset_bins)[source]
+

Create a gammapy.irf.EnergyDispersion2D from pyirf outputs.

+
+
Parameters:
+
+
energy_dispersion: numpy.ndarray

Energy dispersion array, must have shape +(n_energy_bins, n_migra_bins, n_source_offset_bins)

+
+
true_energy_bins: astropy.units.Quantity[energy]

Bin edges in true energy

+
+
migration_bins: numpy.ndarray

Bin edges for the relative energy migration (reco_energy / true_energy)

+
+
fov_offset_bins: astropy.units.Quantity[angle]

Bin edges in the field of view offset. +For Point-Like IRFs, only giving a single bin is appropriate.

+
+
+
+
Returns:
+
+
edisp: gammapy.irf.EnergyDispersion2D
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.gammapy.create_psf_3d.html b/api/pyirf.gammapy.create_psf_3d.html new file mode 100644 index 000000000..8f237bd5b --- /dev/null +++ b/api/pyirf.gammapy.create_psf_3d.html @@ -0,0 +1,179 @@ + + + + + + + create_psf_3d — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_psf_3d

+
+
+pyirf.gammapy.create_psf_3d(psf, true_energy_bins, source_offset_bins, fov_offset_bins)[source]
+

Create a gammapy.irf.PSF3D from pyirf outputs.

+
+
Parameters:
+
+
psf: astropy.units.Quantity[(solid angle)^-1]

Point spread function array, must have shape +(n_energy_bins, n_fov_offset_bins, n_source_offset_bins)

+
+
true_energy_bins: astropy.units.Quantity[energy]

Bin edges in true energy

+
+
source_offset_bins: astropy.units.Quantity[angle]

Bin edges in the source offset.

+
+
fov_offset_bins: astropy.units.Quantity[angle]

Bin edges in the field of view offset. +For Point-Like IRFs, only giving a single bin is appropriate.

+
+
+
+
Returns:
+
+
psf: gammapy.irf.PSF3D
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.BaseComponentEstimator.html b/api/pyirf.interpolation.BaseComponentEstimator.html new file mode 100644 index 000000000..2d129f7ae --- /dev/null +++ b/api/pyirf.interpolation.BaseComponentEstimator.html @@ -0,0 +1,229 @@ + + + + + + + BaseComponentEstimator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

BaseComponentEstimator

+
+
+class pyirf.interpolation.BaseComponentEstimator(grid_points)[source]
+

Bases: object

+

Base class for all Estimators working on specific IRF components.

+

While usable, it is encouraged to use the actual class for the respective IRF +component as it ensures further checks and if necessary e.g. unit handling.

+

Methods Summary

+ + + + + + +

__call__(target_point)

Inter-/ Extrapolation as needed and sanity checking of the target point

+

Methods Documentation

+
+
+__call__(target_point)[source]
+

Inter-/ Extrapolation as needed and sanity checking of +the target point

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation

+
+
+
+
Returns:
+
+
Interpolated or, if necessary extrapolated, result.
+
+
+
Raises:
+
+
TypeError:

When target_point is not an np.ndarray

+
+
ValueError:

When more then one target_point is given

+
+
ValueError:

When target_point and grid_points have miss-matching dimensions

+
+
ValueError:

When target_point is outside of the grids convex hull but extrapolator is None

+
+
Warning:

When target_points need extrapolation

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.BaseExtrapolator.html b/api/pyirf.interpolation.BaseExtrapolator.html new file mode 100644 index 000000000..fdd647521 --- /dev/null +++ b/api/pyirf.interpolation.BaseExtrapolator.html @@ -0,0 +1,222 @@ + + + + + + + BaseExtrapolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

BaseExtrapolator

+
+
+class pyirf.interpolation.BaseExtrapolator(grid_points)[source]
+

Bases: object

+

Base class for all extrapolators, only knowing grid-points, +providing a common __call__-interface.

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

extrapolate(target_point)

Overridable function for the actual extrapolation code

+

Methods Documentation

+
+
+__call__(target_point)[source]
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for extrapolation

+
+
+
+
Returns:
+
+
Extrapolated result.
+
+
+
+
+ +
+
+abstract extrapolate(target_point)[source]
+

Overridable function for the actual extrapolation code

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.BaseInterpolator.html b/api/pyirf.interpolation.BaseInterpolator.html new file mode 100644 index 000000000..5a198d7c7 --- /dev/null +++ b/api/pyirf.interpolation.BaseInterpolator.html @@ -0,0 +1,223 @@ + + + + + + + BaseInterpolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

BaseInterpolator

+
+
+class pyirf.interpolation.BaseInterpolator(grid_points)[source]
+

Bases: object

+

Base class for all interpolators, only knowing grid-points, +providing a common __call__-interface.

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

interpolate(target_point)

Overridable function for the actual interpolation code

+

Methods Documentation

+
+
+__call__(target_point)[source]
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation +When target_point is outside of the grids convex hull but extrapolator is None

+
+
+
+
Returns:
+
+
Interpolated result.
+
+
+
+
+ +
+
+abstract interpolate(target_point)[source]
+

Overridable function for the actual interpolation code

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.BaseNearestNeighborSearcher.html b/api/pyirf.interpolation.BaseNearestNeighborSearcher.html new file mode 100644 index 000000000..503aa5cf2 --- /dev/null +++ b/api/pyirf.interpolation.BaseNearestNeighborSearcher.html @@ -0,0 +1,241 @@ + + + + + + + BaseNearestNeighborSearcher — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

BaseNearestNeighborSearcher

+
+
+class pyirf.interpolation.BaseNearestNeighborSearcher(grid_points, values, norm_ord=2)[source]
+

Bases: BaseInterpolator

+

Dummy NearestNeighbor approach usable instead of +actual Interpolation/Extrapolation

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

interpolate(target_point)

Takes a grid of IRF values for a bunch of different parameters and returns the values at the nearest grid point as seen from the target point.

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation +When target_point is outside of the grids convex hull but extrapolator is None

+
+
+
+
Returns:
+
+
Interpolated result.
+
+
+
+
+ +
+
+interpolate(target_point)[source]
+

Takes a grid of IRF values for a bunch of different parameters and returns +the values at the nearest grid point as seen from the target point.

+
+
Parameters:
+
+
target_point: numpy.ndarray, shape=(1, n_dims)

Value for which the nearest neighbor should be found (target point)

+
+
+
+
Returns:
+
+
content_new: numpy.ndarray, shape=(1, …)

values at nearest neighbor

+
+
+
+
+

Notes

+

In case of multiple nearest neighbors, the values corresponding +to the first one are returned.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.DiscretePDFComponentEstimator.html b/api/pyirf.interpolation.DiscretePDFComponentEstimator.html new file mode 100644 index 000000000..36299c7db --- /dev/null +++ b/api/pyirf.interpolation.DiscretePDFComponentEstimator.html @@ -0,0 +1,229 @@ + + + + + + + DiscretePDFComponentEstimator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

DiscretePDFComponentEstimator

+
+
+class pyirf.interpolation.DiscretePDFComponentEstimator(grid_points, bin_edges, binned_pdf, interpolator_cls=<class 'pyirf.interpolation.quantile_interpolator.QuantileInterpolator'>, interpolator_kwargs=None, extrapolator_cls=None, extrapolator_kwargs=None)[source]
+

Bases: BaseComponentEstimator

+

Base class for all Estimators working on IRF components that represent discretized PDFs.

+

While usable, it is encouraged to use the actual class for the respective IRF +component as it ensures further checks and if necessary e.g. unit handling.

+

Methods Summary

+ + + + + + +

__call__(target_point)

Inter-/ Extrapolation as needed and sanity checking of the target point

+

Methods Documentation

+
+
+__call__(target_point)
+

Inter-/ Extrapolation as needed and sanity checking of +the target point

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation

+
+
+
+
Returns:
+
+
Interpolated or, if necessary extrapolated, result.
+
+
+
Raises:
+
+
TypeError:

When target_point is not an np.ndarray

+
+
ValueError:

When more then one target_point is given

+
+
ValueError:

When target_point and grid_points have miss-matching dimensions

+
+
ValueError:

When target_point is outside of the grids convex hull but extrapolator is None

+
+
Warning:

When target_points need extrapolation

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.DiscretePDFExtrapolator.html b/api/pyirf.interpolation.DiscretePDFExtrapolator.html new file mode 100644 index 000000000..336e026b3 --- /dev/null +++ b/api/pyirf.interpolation.DiscretePDFExtrapolator.html @@ -0,0 +1,222 @@ + + + + + + + DiscretePDFExtrapolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

DiscretePDFExtrapolator

+
+
+class pyirf.interpolation.DiscretePDFExtrapolator(grid_points, bin_edges, binned_pdf, normalization=PDFNormalization.AREA)[source]
+

Bases: BaseExtrapolator

+

Base class for all extrapolators used with binned IRF components like EDisp. +Derived from pyirf.interpolation.BaseExtrapolator

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

extrapolate(target_point)

Overridable function for the actual extrapolation code

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for extrapolation

+
+
+
+
Returns:
+
+
Extrapolated result.
+
+
+
+
+ +
+
+abstract extrapolate(target_point)
+

Overridable function for the actual extrapolation code

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.DiscretePDFInterpolator.html b/api/pyirf.interpolation.DiscretePDFInterpolator.html new file mode 100644 index 000000000..7a1737f6e --- /dev/null +++ b/api/pyirf.interpolation.DiscretePDFInterpolator.html @@ -0,0 +1,223 @@ + + + + + + + DiscretePDFInterpolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

DiscretePDFInterpolator

+
+
+class pyirf.interpolation.DiscretePDFInterpolator(grid_points, bin_edges, binned_pdf, normalization=PDFNormalization.AREA)[source]
+

Bases: BaseInterpolator

+

Base class for all interpolators used with binned IRF components like EDisp. +Derived from pyirf.interpolation.BaseInterpolator

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

interpolate(target_point)

Overridable function for the actual interpolation code

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation +When target_point is outside of the grids convex hull but extrapolator is None

+
+
+
+
Returns:
+
+
Interpolated result.
+
+
+
+
+ +
+
+abstract interpolate(target_point)
+

Overridable function for the actual interpolation code

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.DiscretePDFNearestNeighborSearcher.html b/api/pyirf.interpolation.DiscretePDFNearestNeighborSearcher.html new file mode 100644 index 000000000..75e07e231 --- /dev/null +++ b/api/pyirf.interpolation.DiscretePDFNearestNeighborSearcher.html @@ -0,0 +1,242 @@ + + + + + + + DiscretePDFNearestNeighborSearcher — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

DiscretePDFNearestNeighborSearcher

+
+
+class pyirf.interpolation.DiscretePDFNearestNeighborSearcher(grid_points, bin_edges, binned_pdf, norm_ord=2)[source]
+

Bases: BaseNearestNeighborSearcher

+

Dummy NearestNeighbor approach usable instead of +actual interpolation/extrapolation. +Compatible with discretized PDF IRF component API.

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

interpolate(target_point)

Takes a grid of IRF values for a bunch of different parameters and returns the values at the nearest grid point as seen from the target point.

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation +When target_point is outside of the grids convex hull but extrapolator is None

+
+
+
+
Returns:
+
+
Interpolated result.
+
+
+
+
+ +
+
+interpolate(target_point)
+

Takes a grid of IRF values for a bunch of different parameters and returns +the values at the nearest grid point as seen from the target point.

+
+
Parameters:
+
+
target_point: numpy.ndarray, shape=(1, n_dims)

Value for which the nearest neighbor should be found (target point)

+
+
+
+
Returns:
+
+
content_new: numpy.ndarray, shape=(1, …)

values at nearest neighbor

+
+
+
+
+

Notes

+

In case of multiple nearest neighbors, the values corresponding +to the first one are returned.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.EffectiveAreaEstimator.html b/api/pyirf.interpolation.EffectiveAreaEstimator.html new file mode 100644 index 000000000..9d16be07b --- /dev/null +++ b/api/pyirf.interpolation.EffectiveAreaEstimator.html @@ -0,0 +1,217 @@ + + + + + + + EffectiveAreaEstimator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

EffectiveAreaEstimator

+
+
+class pyirf.interpolation.EffectiveAreaEstimator(grid_points, effective_area, interpolator_cls=<class 'pyirf.interpolation.griddata_interpolator.GridDataInterpolator'>, interpolator_kwargs=None, extrapolator_cls=None, extrapolator_kwargs=None, min_effective_area=<Quantity 1. m2>)[source]
+

Bases: ParametrizedComponentEstimator

+

Estimator class for effective area tables (AEFF_2D).

+

Methods Summary

+ + + + + + +

__call__(target_point)

Estimating effective area at target_point, inter-/extrapolates as needed and specified in __init__.

+

Methods Documentation

+
+
+__call__(target_point)[source]
+

Estimating effective area at target_point, inter-/extrapolates as needed and +specified in __init__.

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation

+
+
+
+
Returns:
+
+
aeff_interp: np.ndarray of (astropy.units.m)**2, shape=(n_points, …)

Interpolated Effective area array with same shape as input +effective areas. For AEFF2D of shape (n_energy_bins, n_fov_offset_bins). +Values lower or equal to __init__’s min_effective_area are set +to zero.

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.EnergyDispersionEstimator.html b/api/pyirf.interpolation.EnergyDispersionEstimator.html new file mode 100644 index 000000000..f9d6958eb --- /dev/null +++ b/api/pyirf.interpolation.EnergyDispersionEstimator.html @@ -0,0 +1,215 @@ + + + + + + + EnergyDispersionEstimator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

EnergyDispersionEstimator

+
+
+class pyirf.interpolation.EnergyDispersionEstimator(grid_points, migra_bins, energy_dispersion, interpolator_cls=<class 'pyirf.interpolation.quantile_interpolator.QuantileInterpolator'>, interpolator_kwargs=None, extrapolator_cls=None, extrapolator_kwargs=None, axis=-2)[source]
+

Bases: DiscretePDFComponentEstimator

+

Estimator class for energy dispersions (EDISP_2D).

+

Methods Summary

+ + + + + + +

__call__(target_point)

Estimating energy dispersions at target_point, inter-/extrapolates as needed and specified in __init__.

+

Methods Documentation

+
+
+__call__(target_point)[source]
+

Estimating energy dispersions at target_point, inter-/extrapolates as needed and +specified in __init__.

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation

+
+
+
+
Returns:
+
+
edisp_interp: np.ndarray, shape=(n_points, …, n_migration_bins, …)

Interpolated EDISP matrix with same shape as input matrices. For EDISP_2D +of shape (n_points, n_energy_bins, n_migration_bins, n_fov_offset_bins)

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.GridDataInterpolator.html b/api/pyirf.interpolation.GridDataInterpolator.html new file mode 100644 index 000000000..95a95961b --- /dev/null +++ b/api/pyirf.interpolation.GridDataInterpolator.html @@ -0,0 +1,244 @@ + + + + + + + GridDataInterpolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

GridDataInterpolator

+
+
+class pyirf.interpolation.GridDataInterpolator(grid_points, params, **griddata_kwargs)[source]
+

Bases: ParametrizedInterpolator

+

“Wrapper arounf scipy.interpolate.griddata.

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

interpolate(target_point)

Wrapper around scipy.interpolate.griddata [1]

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation +When target_point is outside of the grids convex hull but extrapolator is None

+
+
+
+
Returns:
+
+
Interpolated result.
+
+
+
+
+ +
+
+interpolate(target_point)[source]
+

Wrapper around scipy.interpolate.griddata [1]

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target point for interpolation

+
+
+
+
Returns:
+
+
interpolant: np.ndarray, shape=(1, …, n_params)

Interpolated parameter values

+
+
+
+
+

References

+
+
+[1] +

Scipy Documentation, scipy.interpolate.griddata +https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.griddata.html

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.MomentMorphInterpolator.html b/api/pyirf.interpolation.MomentMorphInterpolator.html new file mode 100644 index 000000000..b43b51826 --- /dev/null +++ b/api/pyirf.interpolation.MomentMorphInterpolator.html @@ -0,0 +1,248 @@ + + + + + + + MomentMorphInterpolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

MomentMorphInterpolator

+
+
+class pyirf.interpolation.MomentMorphInterpolator(grid_points, bin_edges, binned_pdf, normalization=PDFNormalization.AREA)[source]
+

Bases: DiscretePDFInterpolator

+

Interpolator class providing Moment Morphing to interpolate discretized PDFs.

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

interpolate(target_point)

Takes a grid of binned pdfs for a bunch of different parameters and interpolates it to given value of those parameters.

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation +When target_point is outside of the grids convex hull but extrapolator is None

+
+
+
+
Returns:
+
+
Interpolated result.
+
+
+
+
+ +
+
+interpolate(target_point)[source]
+

Takes a grid of binned pdfs for a bunch of different parameters +and interpolates it to given value of those parameters. +This function calls implementations of the moment morphing interpolation +pocedure introduced in [1].

+
+
Parameters:
+
+
target_point: numpy.ndarray, shape=(1, n_dims)

Value for which the interpolation is performed (target point)

+
+
+
+
Returns:
+
+
f_new: numpy.ndarray, shape=(1,…,n_bins)

Interpolated and binned pdf

+
+
+
+
+

References

+
+
+[1] +

M. Baak, S. Gadatsch, R. Harrington and W. Verkerke (2015). Interpolation between +multi-dimensional histograms using a new non-linear moment morphing method +Nucl. Instrum. Methods Phys. Res. A 771, 39-48. https://doi.org/10.1016/j.nima.2014.10.033

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.MomentMorphNearestSimplexExtrapolator.html b/api/pyirf.interpolation.MomentMorphNearestSimplexExtrapolator.html new file mode 100644 index 000000000..4896f871d --- /dev/null +++ b/api/pyirf.interpolation.MomentMorphNearestSimplexExtrapolator.html @@ -0,0 +1,236 @@ + + + + + + + MomentMorphNearestSimplexExtrapolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

MomentMorphNearestSimplexExtrapolator

+
+
+class pyirf.interpolation.MomentMorphNearestSimplexExtrapolator(grid_points, bin_edges, binned_pdf, normalization=PDFNormalization.AREA)[source]
+

Bases: DiscretePDFExtrapolator

+

Extrapolator class extending moment morphing interpolation outside a grid’s convex hull.

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

extrapolate(target_point)

Takes a grid of discretized PDFs for a bunch of different parameters and extrapolates it to given value of those parameters.

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for extrapolation

+
+
+
+
Returns:
+
+
Extrapolated result.
+
+
+
+
+ +
+
+extrapolate(target_point)[source]
+

Takes a grid of discretized PDFs for a bunch of different parameters +and extrapolates it to given value of those parameters.

+
+
Parameters:
+
+
target_point: numpy.ndarray, shape=(1, n_dims)

Value for which the extrapolation is performed (target point)

+
+
+
+
Returns:
+
+
values: numpy.ndarray, shape=(1, …, n_bins)

Extrapolated discretized PDFs

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.PDFNormalization.html b/api/pyirf.interpolation.PDFNormalization.html new file mode 100644 index 000000000..ac8a77770 --- /dev/null +++ b/api/pyirf.interpolation.PDFNormalization.html @@ -0,0 +1,209 @@ + + + + + + + PDFNormalization — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

PDFNormalization

+
+
+class pyirf.interpolation.PDFNormalization(value)[source]
+

Bases: Enum

+

How a discrete PDF is normalized

+

Attributes Summary

+ + + + + + + + + +

AREA

PDF is normalized to a "normal" area integral of 1

CONE_SOLID_ANGLE

PDF is normalized to 1 over the solid angle integral where the bin edges represent the opening angles of cones in radian.

+

Attributes Documentation

+
+
+AREA = 1
+

PDF is normalized to a “normal” area integral of 1

+
+ +
+
+CONE_SOLID_ANGLE = 2
+

PDF is normalized to 1 over the solid angle integral where the bin +edges represent the opening angles of cones in radian.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.PSFTableEstimator.html b/api/pyirf.interpolation.PSFTableEstimator.html new file mode 100644 index 000000000..527e56daa --- /dev/null +++ b/api/pyirf.interpolation.PSFTableEstimator.html @@ -0,0 +1,215 @@ + + + + + + + PSFTableEstimator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

PSFTableEstimator

+
+
+class pyirf.interpolation.PSFTableEstimator(grid_points, source_offset_bins, psf, interpolator_cls=<class 'pyirf.interpolation.quantile_interpolator.QuantileInterpolator'>, interpolator_kwargs=None, extrapolator_cls=None, extrapolator_kwargs=None, axis=-1)[source]
+

Bases: DiscretePDFComponentEstimator

+

Estimator class for point spread function tables (PSF_TABLE).

+

Methods Summary

+ + + + + + +

__call__(target_point)

Estimating psf tables at target_point, inter-/extrapolates as needed and specified in __init__.

+

Methods Documentation

+
+
+__call__(target_point)[source]
+

Estimating psf tables at target_point, inter-/extrapolates as needed and +specified in __init__.

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation

+
+
+
+
Returns:
+
+
psf_interp: u.Quantity[sr-1], shape=(n_points, …, n_source_offset_bins)

Interpolated psf table with same shape as input matrices. For PSF_TABLE +of shape (n_points, n_energy_bins, n_fov_offset_bins, n_source_offset_bins)

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.ParametrizedComponentEstimator.html b/api/pyirf.interpolation.ParametrizedComponentEstimator.html new file mode 100644 index 000000000..3d5e6c1e3 --- /dev/null +++ b/api/pyirf.interpolation.ParametrizedComponentEstimator.html @@ -0,0 +1,230 @@ + + + + + + + ParametrizedComponentEstimator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ParametrizedComponentEstimator

+
+
+class pyirf.interpolation.ParametrizedComponentEstimator(grid_points, params, interpolator_cls=<class 'pyirf.interpolation.griddata_interpolator.GridDataInterpolator'>, interpolator_kwargs=None, extrapolator_cls=None, extrapolator_kwargs=None)[source]
+

Bases: BaseComponentEstimator

+

Base class for all Estimators working on IRF components that represent parametrized +or scalar quantities.

+

While usable, it is encouraged to use the actual class for the respective IRF +component as it ensures further checks and if necessary e.g. unit handling.

+

Methods Summary

+ + + + + + +

__call__(target_point)

Inter-/ Extrapolation as needed and sanity checking of the target point

+

Methods Documentation

+
+
+__call__(target_point)
+

Inter-/ Extrapolation as needed and sanity checking of +the target point

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation

+
+
+
+
Returns:
+
+
Interpolated or, if necessary extrapolated, result.
+
+
+
Raises:
+
+
TypeError:

When target_point is not an np.ndarray

+
+
ValueError:

When more then one target_point is given

+
+
ValueError:

When target_point and grid_points have miss-matching dimensions

+
+
ValueError:

When target_point is outside of the grids convex hull but extrapolator is None

+
+
Warning:

When target_points need extrapolation

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.ParametrizedExtrapolator.html b/api/pyirf.interpolation.ParametrizedExtrapolator.html new file mode 100644 index 000000000..fce04b16e --- /dev/null +++ b/api/pyirf.interpolation.ParametrizedExtrapolator.html @@ -0,0 +1,223 @@ + + + + + + + ParametrizedExtrapolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ParametrizedExtrapolator

+
+
+class pyirf.interpolation.ParametrizedExtrapolator(grid_points, params)[source]
+

Bases: BaseExtrapolator

+

Base class for all extrapolators used with IRF components that can be +treated independently, e.g. parametrized ones like 3Gauss +but also AEff. Derived from pyirf.interpolation.BaseExtrapolator

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

extrapolate(target_point)

Overridable function for the actual extrapolation code

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for extrapolation

+
+
+
+
Returns:
+
+
Extrapolated result.
+
+
+
+
+ +
+
+abstract extrapolate(target_point)
+

Overridable function for the actual extrapolation code

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.ParametrizedInterpolator.html b/api/pyirf.interpolation.ParametrizedInterpolator.html new file mode 100644 index 000000000..98394b450 --- /dev/null +++ b/api/pyirf.interpolation.ParametrizedInterpolator.html @@ -0,0 +1,224 @@ + + + + + + + ParametrizedInterpolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ParametrizedInterpolator

+
+
+class pyirf.interpolation.ParametrizedInterpolator(grid_points, params)[source]
+

Bases: BaseInterpolator

+

Base class for all interpolators used with IRF components that can be +independently interpolated, e.g. parametrized ones like 3Gauss +but also AEff. Derived from pyirf.interpolation.BaseInterpolator

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

interpolate(target_point)

Overridable function for the actual interpolation code

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation +When target_point is outside of the grids convex hull but extrapolator is None

+
+
+
+
Returns:
+
+
Interpolated result.
+
+
+
+
+ +
+
+abstract interpolate(target_point)
+

Overridable function for the actual interpolation code

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.ParametrizedNearestNeighborSearcher.html b/api/pyirf.interpolation.ParametrizedNearestNeighborSearcher.html new file mode 100644 index 000000000..cdf7fb9b9 --- /dev/null +++ b/api/pyirf.interpolation.ParametrizedNearestNeighborSearcher.html @@ -0,0 +1,242 @@ + + + + + + + ParametrizedNearestNeighborSearcher — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ParametrizedNearestNeighborSearcher

+
+
+class pyirf.interpolation.ParametrizedNearestNeighborSearcher(grid_points, params, norm_ord=2)[source]
+

Bases: BaseNearestNeighborSearcher

+

Dummy NearestNeighbor approach usable instead of +actual interpolation/extrapolation +Compatible with parametrized IRF component API.

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

interpolate(target_point)

Takes a grid of IRF values for a bunch of different parameters and returns the values at the nearest grid point as seen from the target point.

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation +When target_point is outside of the grids convex hull but extrapolator is None

+
+
+
+
Returns:
+
+
Interpolated result.
+
+
+
+
+ +
+
+interpolate(target_point)
+

Takes a grid of IRF values for a bunch of different parameters and returns +the values at the nearest grid point as seen from the target point.

+
+
Parameters:
+
+
target_point: numpy.ndarray, shape=(1, n_dims)

Value for which the nearest neighbor should be found (target point)

+
+
+
+
Returns:
+
+
content_new: numpy.ndarray, shape=(1, …)

values at nearest neighbor

+
+
+
+
+

Notes

+

In case of multiple nearest neighbors, the values corresponding +to the first one are returned.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.html b/api/pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.html new file mode 100644 index 000000000..53ea47709 --- /dev/null +++ b/api/pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.html @@ -0,0 +1,236 @@ + + + + + + + ParametrizedNearestSimplexExtrapolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ParametrizedNearestSimplexExtrapolator

+
+
+class pyirf.interpolation.ParametrizedNearestSimplexExtrapolator(grid_points, params)[source]
+

Bases: ParametrizedExtrapolator

+

Extrapolator class extending linear or baryzentric interpolation outside a grid’s convex hull.

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

extrapolate(target_point)

Takes a grid of scalar values for a bunch of different parameters and extrapolates it to given value of those parameters.

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for extrapolation

+
+
+
+
Returns:
+
+
Extrapolated result.
+
+
+
+
+ +
+
+extrapolate(target_point)[source]
+

Takes a grid of scalar values for a bunch of different parameters +and extrapolates it to given value of those parameters.

+
+
Parameters:
+
+
target_point: numpy.ndarray, shape=(1, n_dims)

Value for which the extrapolation is performed (target point)

+
+
+
+
Returns:
+
+
values: numpy.ndarray, shape=(1,…)

Extrapolated values

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator.html b/api/pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator.html new file mode 100644 index 000000000..d8ff058b2 --- /dev/null +++ b/api/pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator.html @@ -0,0 +1,273 @@ + + + + + + + ParametrizedVisibleEdgesExtrapolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ParametrizedVisibleEdgesExtrapolator

+
+
+class pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator(grid_points, params, m=1)[source]
+

Bases: ParametrizedNearestSimplexExtrapolator

+

Extrapolator using blending over visible edges.

+

While the ParametrizedNearestSimplexExtrapolator does not result in a smooth +extrapolation outside of the grid due to using only the nearest available +simplex, this extrapolator blends over all visible edges as discussed in [1]. +For one grid-dimension this is equal to the ParametrizedNearestSimplexExtrapolator, +the same holds for grids consisting of only one simplex or constellations, +where only one simplex is visible from a target.

+
+
Parameters:
+
+
grid_points: np.ndarray, shape=(N, …)

Grid points at which templates exist. May be one ot two dimensional. +Have to be sorted in accending order for 1D.

+
+
params: np.ndarray, shape=(N, …)

Array of corresponding parameter values at each point in grid_points. +First dimesion has to correspond to number of grid_points

+
+
m: non-zero int >= 1

Degree of smoothness wanted in the extrapolation region. See [1] for +additional information. Defaults to 1.

+
+
+
+
Raises:
+
+
TypeError:

If m is not a number

+
+
ValueError:

If m is not a non-zero integer

+
+
+
+
+

References

+
+
+[1] +

P. Alfred (1984). Triangular Extrapolation. Technical summary rept., +Univ. of Wisconsin-Madison. https://apps.dtic.mil/sti/pdfs/ADA144660.pdf

+
+
+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

extrapolate(target_point)

Takes a grid of scalar values for a bunch of different parameters and extrapolates it to given value of those parameters.

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for extrapolation

+
+
+
+
Returns:
+
+
Extrapolated result.
+
+
+
+
+ +
+
+extrapolate(target_point)[source]
+

Takes a grid of scalar values for a bunch of different parameters +and extrapolates it to given value of those parameters.

+
+
Parameters:
+
+
target_point: numpy.ndarray, shape=(1, n_dims)

Value for which the extrapolation is performed (target point)

+
+
+
+
Returns:
+
+
values: numpy.ndarray, shape=(1,…)

Extrapolated values

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.QuantileInterpolator.html b/api/pyirf.interpolation.QuantileInterpolator.html new file mode 100644 index 000000000..683a9e332 --- /dev/null +++ b/api/pyirf.interpolation.QuantileInterpolator.html @@ -0,0 +1,250 @@ + + + + + + + QuantileInterpolator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

QuantileInterpolator

+
+
+class pyirf.interpolation.QuantileInterpolator(grid_points, bin_edges, binned_pdf, quantile_resolution=0.001, normalization=PDFNormalization.AREA)[source]
+

Bases: DiscretePDFInterpolator

+

Interpolator class providing quantile interpoalation.

+

Methods Summary

+ + + + + + + + + +

__call__(target_point)

Providing a common __call__ interface

interpolate(target_point)

Takes a grid of binned pdfs for a bunch of different parameters and interpolates it to given value of those parameters.

+

Methods Documentation

+
+
+__call__(target_point)
+

Providing a common __call__ interface

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation +When target_point is outside of the grids convex hull but extrapolator is None

+
+
+
+
Returns:
+
+
Interpolated result.
+
+
+
+
+ +
+
+interpolate(target_point)[source]
+

Takes a grid of binned pdfs for a bunch of different parameters +and interpolates it to given value of those parameters. +This function provides an adapted version of the quantile interpolation introduced +in [1]. +Instead of following this method closely, it implements different approaches to the +steps shown in Fig. 5 of [1].

+
+
Parameters:
+
+
target_point: numpy.ndarray, shape=(1, n_dims)

Value for which the interpolation is performed (target point)

+
+
+
+
Returns:
+
+
f_new: numpy.ndarray, shape=(1, …, n_bins)

Interpolated and binned pdf

+
+
+
+
+

References

+
+
+[1] +

B. E. Hollister and A. T. Pang (2013). Interpolation of Non-Gaussian Probability Distributions +for Ensemble Visualization +https://engineering.ucsc.edu/sites/default/files/technical-reports/UCSC-SOE-13-13.pdf

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.interpolation.RadMaxEstimator.html b/api/pyirf.interpolation.RadMaxEstimator.html new file mode 100644 index 000000000..edf652910 --- /dev/null +++ b/api/pyirf.interpolation.RadMaxEstimator.html @@ -0,0 +1,215 @@ + + + + + + + RadMaxEstimator — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+ +
+

RadMaxEstimator

+
+
+class pyirf.interpolation.RadMaxEstimator(grid_points, rad_max, fill_value=None, interpolator_cls=<class 'pyirf.interpolation.griddata_interpolator.GridDataInterpolator'>, interpolator_kwargs=None, extrapolator_cls=None, extrapolator_kwargs=None)[source]
+

Bases: ParametrizedComponentEstimator

+

Estimator class for rad-max tables (RAD_MAX, RAD_MAX_2D).

+

Methods Summary

+ + + + + + +

__call__(target_point)

Estimating rad max table at target_point, inter-/extrapolates as needed and specified in __init__.

+

Methods Documentation

+
+
+__call__(target_point)[source]
+

Estimating rad max table at target_point, inter-/extrapolates as needed and +specified in __init__.

+
+
Parameters:
+
+
target_point: np.ndarray, shape=(1, n_dims)

Target for inter-/extrapolation

+
+
+
+
Returns:
+
+
rad_max_interp: np.ndarray, shape=(n_points, …)

Interpolated RAD_MAX table with same shape as input +effective areas. For RAD_MAX_2D of shape (n_energy_bins, n_fov_offset_bins)

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.io.create_aeff2d_hdu.html b/api/pyirf.io.create_aeff2d_hdu.html new file mode 100644 index 000000000..c128a51cf --- /dev/null +++ b/api/pyirf.io.create_aeff2d_hdu.html @@ -0,0 +1,186 @@ + + + + + + + create_aeff2d_hdu — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_aeff2d_hdu

+
+
+pyirf.io.create_aeff2d_hdu(effective_area, true_energy_bins, fov_offset_bins, extname='EFFECTIVE AREA', point_like=True, **header_cards)[source]
+

Create a fits binary table HDU in GADF format for effective area. +See the specification at +https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/aeff/index.html

+
+
Parameters:
+
+
effective_area: astropy.units.Quantity[area]

Effective area array, must have shape (n_energy_bins, n_fov_offset_bins)

+
+
true_energy_bins: astropy.units.Quantity[energy]

Bin edges in true energy

+
+
fov_offset_bins: astropy.units.Quantity[angle]

Bin edges in the field of view offset. +For Point-Like IRFs, only giving a single bin is appropriate.

+
+
point_like: bool

If the provided effective area was calculated after applying a direction cut, +pass True, else False for a full-enclosure effective area.

+
+
extname: str

Name for BinTableHDU

+
+
**header_cards

Additional metadata to add to the header, use this to set e.g. TELESCOP or +INSTRUME.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.io.create_background_2d_hdu.html b/api/pyirf.io.create_background_2d_hdu.html new file mode 100644 index 000000000..2f67ab015 --- /dev/null +++ b/api/pyirf.io.create_background_2d_hdu.html @@ -0,0 +1,183 @@ + + + + + + + create_background_2d_hdu — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_background_2d_hdu

+
+
+pyirf.io.create_background_2d_hdu(background_2d, reco_energy_bins, fov_offset_bins, extname='BACKGROUND', **header_cards)[source]
+

Create a fits binary table HDU in GADF format for the background 2d table. +See the specification at +https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/bkg/index.html#bkg-2d

+
+
Parameters:
+
+
background_2d: astropy.units.Quantity[(MeV s sr)^-1]

Background rate, must have shape +(n_energy_bins, n_fov_offset_bins)

+
+
reco_energy_bins: astropy.units.Quantity[energy]

Bin edges in reconstructed energy

+
+
fov_offset_bins: astropy.units.Quantity[angle]

Bin edges in the field of view offset.

+
+
extname: str

Name for BinTableHDU

+
+
**header_cards

Additional metadata to add to the header, use this to set e.g. TELESCOP or +INSTRUME.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.io.create_energy_dispersion_hdu.html b/api/pyirf.io.create_energy_dispersion_hdu.html new file mode 100644 index 000000000..5145abba8 --- /dev/null +++ b/api/pyirf.io.create_energy_dispersion_hdu.html @@ -0,0 +1,189 @@ + + + + + + + create_energy_dispersion_hdu — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_energy_dispersion_hdu

+
+
+pyirf.io.create_energy_dispersion_hdu(energy_dispersion, true_energy_bins, migration_bins, fov_offset_bins, point_like=True, extname='EDISP', **header_cards)[source]
+

Create a fits binary table HDU in GADF format for the energy dispersion. +See the specification at +https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/edisp/index.html

+
+
Parameters:
+
+
energy_dispersion: numpy.ndarray

Energy dispersion array, must have shape +(n_energy_bins, n_migra_bins, n_source_offset_bins)

+
+
true_energy_bins: astropy.units.Quantity[energy]

Bin edges in true energy

+
+
migration_bins: numpy.ndarray

Bin edges for the relative energy migration (reco_energy / true_energy)

+
+
fov_offset_bins: astropy.units.Quantity[angle]

Bin edges in the field of view offset. +For Point-Like IRFs, only giving a single bin is appropriate.

+
+
point_like: bool

If the provided effective area was calculated after applying a direction cut, +pass True, else False for a full-enclosure effective area.

+
+
extname: str

Name for BinTableHDU

+
+
**header_cards

Additional metadata to add to the header, use this to set e.g. TELESCOP or +INSTRUME.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.io.create_psf_table_hdu.html b/api/pyirf.io.create_psf_table_hdu.html new file mode 100644 index 000000000..7dee966a7 --- /dev/null +++ b/api/pyirf.io.create_psf_table_hdu.html @@ -0,0 +1,186 @@ + + + + + + + create_psf_table_hdu — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_psf_table_hdu

+
+
+pyirf.io.create_psf_table_hdu(psf, true_energy_bins, source_offset_bins, fov_offset_bins, extname='PSF', **header_cards)[source]
+

Create a fits binary table HDU in GADF format for the PSF table. +See the specification at +https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/psf/psf_table/index.html

+
+
Parameters:
+
+
psf: astropy.units.Quantity[(solid angle)^-1]

Point spread function array, must have shape +(n_energy_bins, n_fov_offset_bins, n_source_offset_bins)

+
+
true_energy_bins: astropy.units.Quantity[energy]

Bin edges in true energy

+
+
source_offset_bins: astropy.units.Quantity[angle]

Bin edges in the source offset.

+
+
fov_offset_bins: astropy.units.Quantity[angle]

Bin edges in the field of view offset. +For Point-Like IRFs, only giving a single bin is appropriate.

+
+
extname: str

Name for BinTableHDU

+
+
**header_cards

Additional metadata to add to the header, use this to set e.g. TELESCOP or +INSTRUME.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.io.create_rad_max_hdu.html b/api/pyirf.io.create_rad_max_hdu.html new file mode 100644 index 000000000..840be498d --- /dev/null +++ b/api/pyirf.io.create_rad_max_hdu.html @@ -0,0 +1,184 @@ + + + + + + + create_rad_max_hdu — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create_rad_max_hdu

+
+
+pyirf.io.create_rad_max_hdu(rad_max, reco_energy_bins, fov_offset_bins, point_like=True, extname='RAD_MAX', **header_cards)[source]
+

Create a fits binary table HDU in GADF format for the directional cut. +See the specification at +https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/point_like/index.html#rad-max

+
+
Parameters:
+
+
rad_max: astropy.units.Quantity[angle]

Array of the directional (theta) cut. +Must have shape (n_reco_energy_bins, n_fov_offset_bins)

+
+
reco_energy_bins: astropy.units.Quantity[energy]

Bin edges in reconstructed energy

+
+
fov_offset_bins: astropy.units.Quantity[angle]

Bin edges in the field of view offset. +For Point-Like IRFs, only giving a single bin is appropriate.

+
+
extname: str

Name for BinTableHDU

+
+
**header_cards

Additional metadata to add to the header, use this to set e.g. TELESCOP or +INSTRUME.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.io.read_eventdisplay_fits.html b/api/pyirf.io.read_eventdisplay_fits.html new file mode 100644 index 000000000..0f2245a65 --- /dev/null +++ b/api/pyirf.io.read_eventdisplay_fits.html @@ -0,0 +1,186 @@ + + + + + + + read_eventdisplay_fits — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

read_eventdisplay_fits

+
+
+pyirf.io.read_eventdisplay_fits(infile, use_histogram=True)[source]
+

Read a DL2 FITS file as produced by the EventDisplay DL2 converter +from ROOT files: +https://github.com/Eventdisplay/Converters/blob/master/DL2/generate_DL2_file.py

+
+
Parameters:
+
+
infilestr or pathlib.Path

Path to the input fits file

+
+
use_histogrambool

If True, use number of simulated events from histogram provided in fits file, +if False, estimate this number from the unique run_id, pointing direction +combinations and the number of events per run in the run header. +This will fail e.g. for protons with cuts already applied, since many +runs will have 0 events surviving cuts.

+
+
+
+
Returns:
+
+
events: astropy.QTable

Astropy Table object containing the reconstructed events information.

+
+
simulated_events: ~pyirf.simulations.SimulatedEventsInfo
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.irf.background_2d.html b/api/pyirf.irf.background_2d.html new file mode 100644 index 000000000..46d91c973 --- /dev/null +++ b/api/pyirf.irf.background_2d.html @@ -0,0 +1,191 @@ + + + + + + + background_2d — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

background_2d

+
+
+pyirf.irf.background_2d(events, reco_energy_bins, fov_offset_bins, t_obs)[source]
+

Calculate background rates in radially symmetric bins in the field of view.

+

GADF documentation here: +https://gamma-astro-data-formats.readthedocs.io/en/latest/irfs/full_enclosure/bkg/index.html#bkg-2d

+
+
Parameters:
+
+
events: astropy.table.QTable

DL2 events table of the selected background events. +Needed columns for this function: reco_source_fov_offset, reco_energy, weight

+
+
reco_energy: astropy.units.Quantity[energy]

The bins in reconstructed energy to be used for the IRF

+
+
fov_offset_bins: astropy.units.Quantity[angle]

The bins in the field of view offset to be used for the IRF

+
+
t_obs: astropy.units.Quantity[time]

Observation time. This must match with how the individual event +weights are calculated.

+
+
+
+
Returns:
+
+
bg_rate: astropy.units.Quantity

The background rate as particles per energy, time and solid angle +in the specified bins.

+

Shape: (len(reco_energy_bins) - 1, len(fov_offset_bins) - 1)

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.irf.effective_area.html b/api/pyirf.irf.effective_area.html new file mode 100644 index 000000000..0897774d8 --- /dev/null +++ b/api/pyirf.irf.effective_area.html @@ -0,0 +1,177 @@ + + + + + + + effective_area — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

effective_area

+
+
+pyirf.irf.effective_area(n_selected, n_simulated, area)[source]
+

Calculate effective area for histograms of selected and total simulated events

+
+
Parameters:
+
+
n_selected: int or numpy.ndarray[int]

The number of surviving (e.g. triggered, analysed, after cuts)

+
+
n_simulated: int or numpy.ndarray[int]

The total number of events simulated

+
+
area: astropy.units.Quantity[area]

Area in which particle’s core position was simulated

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.irf.effective_area_per_energy.html b/api/pyirf.irf.effective_area_per_energy.html new file mode 100644 index 000000000..f0e2efc00 --- /dev/null +++ b/api/pyirf.irf.effective_area_per_energy.html @@ -0,0 +1,177 @@ + + + + + + + effective_area_per_energy — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

effective_area_per_energy

+
+
+pyirf.irf.effective_area_per_energy(selected_events, simulation_info, true_energy_bins)[source]
+

Calculate effective area in bins of true energy.

+
+
Parameters:
+
+
selected_events: astropy.table.QTable

DL2 events table, required columns for this function: true_energy.

+
+
simulation_info: pyirf.simulations.SimulatedEventsInfo

The overall statistics of the simulated events

+
+
true_energy_bins: astropy.units.Quantity[energy]

The bin edges in which to calculate effective area.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.irf.effective_area_per_energy_and_fov.html b/api/pyirf.irf.effective_area_per_energy_and_fov.html new file mode 100644 index 000000000..12e66c295 --- /dev/null +++ b/api/pyirf.irf.effective_area_per_energy_and_fov.html @@ -0,0 +1,181 @@ + + + + + + + effective_area_per_energy_and_fov — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

effective_area_per_energy_and_fov

+
+
+pyirf.irf.effective_area_per_energy_and_fov(selected_events, simulation_info, true_energy_bins, fov_offset_bins)[source]
+

Calculate effective area in bins of true energy and field of view offset.

+
+
Parameters:
+
+
selected_events: astropy.table.QTable

DL2 events table, required columns for this function: +- true_energy +- true_source_fov_offset

+
+
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.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.irf.energy_dispersion.html b/api/pyirf.irf.energy_dispersion.html new file mode 100644 index 000000000..24e5b5061 --- /dev/null +++ b/api/pyirf.irf.energy_dispersion.html @@ -0,0 +1,191 @@ + + + + + + + energy_dispersion — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

energy_dispersion

+
+
+pyirf.irf.energy_dispersion(selected_events, true_energy_bins, fov_offset_bins, migration_bins)[source]
+

Calculate energy dispersion for the given DL2 event list. +Energy dispersion is defined as the probability of finding an event +at a given relative deviation (reco_energy / true_energy) for a given +true energy.

+
+
Parameters:
+
+
selected_events: astropy.table.QTable

Table of the DL2 events. +Required columns: reco_energy, true_energy, true_source_fov_offset.

+
+
true_energy_bins: astropy.units.Quantity[energy]

Bin edges in true energy

+
+
migration_bins: astropy.units.Quantity[energy]

Bin edges in relative deviation, recommended range: [0.2, 5]

+
+
fov_offset_bins: astropy.units.Quantity[angle]

Bin edges in the field of view offset. +For Point-Like IRFs, only giving a single bin is appropriate.

+
+
+
+
Returns:
+
+
energy_dispersion: numpy.ndarray

Energy dispersion matrix +with shape (n_true_energy_bins, n_migration_bins, n_fov_ofset_bins)

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.irf.psf_table.html b/api/pyirf.irf.psf_table.html new file mode 100644 index 000000000..c2aebc0b9 --- /dev/null +++ b/api/pyirf.irf.psf_table.html @@ -0,0 +1,165 @@ + + + + + + + psf_table — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

psf_table

+
+
+pyirf.irf.psf_table(events, true_energy_bins, source_offset_bins, fov_offset_bins)[source]
+

Calculate the table based PSF (radially symmetrical bins around the true source)

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.sensitivity.calculate_sensitivity.html b/api/pyirf.sensitivity.calculate_sensitivity.html new file mode 100644 index 000000000..2de1ac4ba --- /dev/null +++ b/api/pyirf.sensitivity.calculate_sensitivity.html @@ -0,0 +1,208 @@ + + + + + + + calculate_sensitivity — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

calculate_sensitivity

+
+
+pyirf.sensitivity.calculate_sensitivity(signal_hist, background_hist, alpha, min_significance=5, min_signal_events=10, min_excess_over_background=0.05, significance_function=<function li_ma_significance>)[source]
+

Calculate sensitivity for DL2 event lists in bins of reconstructed energy.

+

Sensitivity is defined as the minimum flux detectable with target_significance +sigma significance in a certain time.

+

This time must be incorporated into the event weights.

+

Two conditions are required for the sensitivity: +- At least ten weighted signal events +- The weighted signal must be larger than 5 % of the weighted background +- At least 5 sigma (so relative_sensitivity > 1)

+

If the conditions are not met, the sensitivity will be set to nan.

+
+
Parameters:
+
+
signal_hist: astropy.table.QTable

Histogram of detected signal events as a table. +Required columns: n and n_weighted. +See pyirf.binning.create_histogram_table

+
+
background_hist: astropy.table.QTable

Histogram of detected events as a table. +Required columns: n and n_weighted. +See pyirf.binning.create_histogram_table

+
+
alpha: float

Size ratio of signal region to background region

+
+
min_significance: float

Significance necessary for a detection

+
+
min_signal_events: int

Minimum number of signal events required. +The relative flux will be scaled up from the one yielding min_significance +if this condition is violated.

+
+
min_excess_over_background: float

Minimum number of signal events expressed as the proportion of the +background. +So the required number of signal events will be +min_excess_over_background * alpha * n_off. +The relative flux will be scaled up from the one yielding min_significance +if this condition is violated.

+
+
significance_function: callable

A function with signature (n_on, n_off, alpha) -> significance. +Default is the Li & Ma likelihood ratio test.

+
+
+
+
Returns:
+
+
sensitivity_table: astropy.table.QTable

Table with sensitivity information. +Contains weighted and unweighted number of signal and background events +and the relative_sensitivity, the scaling applied to the signal events +that yields target_significance sigma of significance according to +the significance_function

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.sensitivity.estimate_background.html b/api/pyirf.sensitivity.estimate_background.html new file mode 100644 index 000000000..f4dece1de --- /dev/null +++ b/api/pyirf.sensitivity.estimate_background.html @@ -0,0 +1,190 @@ + + + + + + + estimate_background — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

estimate_background

+
+
+pyirf.sensitivity.estimate_background(events, reco_energy_bins, theta_cuts, alpha, fov_offset_min, fov_offset_max)[source]
+

Estimate the number of background events for a point-like sensitivity.

+

Due to limited statistics, it is often not possible to just apply the same +theta cut to the background events as to the signal events around an assumed +source position.

+

Here we calculate the expected number of background events for the off +regions by taking all background events between fov_offset_min and +fov_offset_max from the camera center and then scale these to the size +of the off region, which is scaled by 1 / alpha from the size of the on +region given by the theta cuts.

+
+
Parameters:
+
+
events: astropy.table.QTable

DL2 event list of background surviving event selection +and inside fov_offset_max from the center of the FOV +Required columns for this function: +- reco_energy, +- reco_source_fov_offset.

+
+
reco_energy_bins: astropy.units.Quantity[energy]

Desired bin edges in reconstructed energy for the background rate

+
+
theta_cuts: astropy.table.QTable

The cuts table for the theta cut, +e.g. as returned by ~pyirf.cuts.calculate_percentile_cut. +Columns center and cut are required for this function.

+
+
alpha: float

size of the on region divided by the size of the off region.

+
+
fov_offset_min: astropy.units.Quantity[angle]

Minimum distance from the fov center for background events to be taken into account

+
+
fov_offset_max: astropy.units.Quantity[angle]

Maximum distance from the fov center for background events to be taken into account

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.sensitivity.relative_sensitivity.html b/api/pyirf.sensitivity.relative_sensitivity.html new file mode 100644 index 000000000..a09868b8c --- /dev/null +++ b/api/pyirf.sensitivity.relative_sensitivity.html @@ -0,0 +1,203 @@ + + + + + + + relative_sensitivity — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

relative_sensitivity

+
+
+pyirf.sensitivity.relative_sensitivity(n_on, n_off, alpha, min_significance=5, min_signal_events=10, min_excess_over_background=0.05, significance_function=<function li_ma_significance>)[source]
+

Calculate the relative sensitivity defined as the flux +relative to the reference source that is detectable with +significance target_significance.

+

Given measured n_on and n_off, +we estimate the number of gamma events n_signal as n_on - alpha * n_off.

+

The number of background events n_background` is estimated as ``n_off * alpha.

+

In the end, we find the relative sensitivity as the scaling factor for n_signal +that yields a significance of target_significance.

+

The reference time should be incorporated by appropriately weighting the events +before calculating n_on and n_off.

+

All input values with the exception of significance_function +must be broadcastable to a single, common shape.

+
+
Parameters:
+
+
n_on: int or array-like

Number of signal-like events for the on observations

+
+
n_off: int or array-like

Number of signal-like events for the off observations

+
+
alpha: float or array-like

Scaling factor between on and off observations. +1 / number of off regions for wobble observations.

+
+
min_significance: float or array-like

Significance necessary for a detection

+
+
min_signal_events: int or array-like

Minimum number of signal events required. +The relative flux will be scaled up from the one yielding min_significance +if this condition is violated.

+
+
min_excess_over_background: float or array-like

Minimum number of signal events expressed as the proportion of the +background. +So the required number of signal events will be +min_excess_over_background * alpha * n_off. +The relative flux will be scaled up from the one yielding min_significance +if this condition is violated.

+
+
significance_function: function

A function f(n_on, n_off, alpha) -> significance in sigma +Used to calculate the significance, default is the Li&Ma +likelihood ratio test formula. +Li, T-P., and Y-Q. Ma. +“Analysis methods for results in gamma-ray astronomy.” +The Astrophysical Journal 272 (1983): 317-324. +Formula (17)

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.simulations.SimulatedEventsInfo.html b/api/pyirf.simulations.SimulatedEventsInfo.html new file mode 100644 index 000000000..9ab0419e4 --- /dev/null +++ b/api/pyirf.simulations.SimulatedEventsInfo.html @@ -0,0 +1,343 @@ + + + + + + + SimulatedEventsInfo — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

SimulatedEventsInfo

+
+
+class pyirf.simulations.SimulatedEventsInfo(n_showers, energy_min, energy_max, max_impact, spectral_index, viewcone_min, viewcone_max)[source]
+

Bases: object

+

Information about all simulated events, for calculating event weights.

+
+
Attributes:
+
+
n_showers: int

Total number of simulated showers. If reuse was used, this +should already include the reuse.

+
+
energy_min: u.Quantity[energy]

Lower limit of the simulated energy range

+
+
energy_max: u.Quantity[energy]

Upper limit of the simulated energy range

+
+
max_impact: u.Quantity[length]

Maximum simulated impact parameter

+
+
spectral_index: float

Spectral Index of the simulated power law with sign included.

+
+
viewcone_min: u.Quantity[angle]

Inner angle of the viewcone

+
+
viewcone_max: u.Quantity[angle]

Outer angle of the viewcone

+
+
+
+
+

Attributes Summary

+ + + + + + + + + + + + + + + + + + + + + + + + +

energy_max

Upper limit of the simulated energy range

energy_min

Lower limit of the simulated energy range

max_impact

Maximum simualted impact radius

n_showers

Total number of simulated showers, if reuse was used, this must already include reuse

spectral_index

Spectral index of the simulated power law with sign included

viewcone_max

Outer viewcone angle

viewcone_min

Inner viewcone angle

+

Methods Summary

+ + + + + + + + + + + + +

calculate_n_showers_per_energy(energy_bins)

Calculate number of showers that were simulated in the given energy intervals

calculate_n_showers_per_energy_and_fov(...)

Calculate number of showers that were simulated in the given energy and fov bins.

calculate_n_showers_per_fov(fov_bins)

Calculate number of showers that were simulated in the given fov bins.

+

Attributes Documentation

+
+
+energy_max
+

Upper limit of the simulated energy range

+
+ +
+
+energy_min
+

Lower limit of the simulated energy range

+
+ +
+
+max_impact
+

Maximum simualted impact radius

+
+ +
+
+n_showers
+

Total number of simulated showers, if reuse was used, this must +already include reuse

+
+ +
+
+spectral_index
+

Spectral index of the simulated power law with sign included

+
+ +
+
+viewcone_max
+

Outer viewcone angle

+
+ +
+
+viewcone_min
+

Inner viewcone angle

+
+ +

Methods Documentation

+
+
+calculate_n_showers_per_energy(energy_bins)[source]
+

Calculate number of showers that were simulated in the given energy intervals

+

This assumes the events were generated and from a powerlaw +like CORSIKA simulates events.

+
+
Parameters:
+
+
energy_bins: astropy.units.Quantity[energy]

The interval edges for which to calculate the number of simulated showers

+
+
+
+
Returns:
+
+
n_showers: numpy.ndarray

The expected number of events inside each of the energy_bins. +This is a floating point number. +The actual numbers will follow a poissionian distribution around this +expected value.

+
+
+
+
+
+ +
+
+calculate_n_showers_per_energy_and_fov(energy_bins, fov_bins)[source]
+

Calculate number of showers that were simulated in the given +energy and fov bins.

+

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_bins: astropy.units.Quantity[angle]

The FOV bin edges for which to calculate the number of simulated showers

+
+
+
+
Returns:
+
+
n_showers: numpy.ndarray(ndim=2)

The expected number of events inside each of the +energy_bins and fov_bins. +Dimension (n_energy_bins, n_fov_bins) +This is a floating point number. +The actual numbers will follow a poissionian distribution around this +expected value.

+
+
+
+
+
+ +
+
+calculate_n_showers_per_fov(fov_bins)[source]
+

Calculate number of showers that were simulated in the given fov bins.

+

This assumes the events were generated uniformly distributed per solid angle, +like CORSIKA simulates events with the VIEWCONE option.

+
+
Parameters:
+
+
fov_bins: astropy.units.Quantity[angle]

The FOV bin edges for which to calculate the number of simulated showers

+
+
+
+
Returns:
+
+
n_showers: numpy.ndarray(ndim=2)

The expected number of events inside each of the fov_bins. +This is a floating point number. +The actual numbers will follow a poissionian distribution around this +expected value.

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.CRAB_HEGRA.html b/api/pyirf.spectral.CRAB_HEGRA.html new file mode 100644 index 000000000..b3b25610d --- /dev/null +++ b/api/pyirf.spectral.CRAB_HEGRA.html @@ -0,0 +1,168 @@ + + + + + + + CRAB_HEGRA — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+ +
+

CRAB_HEGRA

+
+
+pyirf.spectral.CRAB_HEGRA = PowerLaw(2.83e-11 1 / (TeV s cm2) * (E / 1.0 TeV)**-2.62)
+

Power Law parametrization of the Crab Nebula spectrum as published by HEGRA

+

From “The Crab Nebula and Pulsar between 500 GeV and 80 TeV: Observations with the HEGRA stereoscopic air Cherenkov telescopes”, +Aharonian et al, 2004, ApJ 614.2 +doi.org/10.1086/423931

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.CRAB_MAGIC_JHEAP2015.html b/api/pyirf.spectral.CRAB_MAGIC_JHEAP2015.html new file mode 100644 index 000000000..d56175fa4 --- /dev/null +++ b/api/pyirf.spectral.CRAB_MAGIC_JHEAP2015.html @@ -0,0 +1,168 @@ + + + + + + + CRAB_MAGIC_JHEAP2015 — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

CRAB_MAGIC_JHEAP2015

+
+
+pyirf.spectral.CRAB_MAGIC_JHEAP2015 = LogParabola(3.23e-11 1 / (TeV s cm2) * (E / 1.0 TeV)**(-2.47 + -0.24 * log10(E / 1.0 TeV))
+

Log-Parabola parametrization of the Crab Nebula spectrum as published by MAGIC

+

From “Measurement of the Crab Nebula spectrum over three decades in energy with the MAGIC telescopes”, +Aleksìc et al., 2015, JHEAP +https://doi.org/10.1016/j.jheap.2015.01.002

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.DAMPE_P_He_SPECTRUM.html b/api/pyirf.spectral.DAMPE_P_He_SPECTRUM.html new file mode 100644 index 000000000..aafcf40cb --- /dev/null +++ b/api/pyirf.spectral.DAMPE_P_He_SPECTRUM.html @@ -0,0 +1,166 @@ + + + + + + + DAMPE_P_He_SPECTRUM — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

DAMPE_P_He_SPECTRUM

+
+
+pyirf.spectral.DAMPE_P_He_SPECTRUM = <pyirf.spectral.TableInterpolationSpectrum object>
+

Interpolate flux points to obtain a spectrum.

+

By default, flux is interpolated linearly in log-log space.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.DIFFUSE_FLUX_UNIT.html b/api/pyirf.spectral.DIFFUSE_FLUX_UNIT.html new file mode 100644 index 000000000..453411aad --- /dev/null +++ b/api/pyirf.spectral.DIFFUSE_FLUX_UNIT.html @@ -0,0 +1,166 @@ + + + + + + + DIFFUSE_FLUX_UNIT — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

DIFFUSE_FLUX_UNIT

+
+
+pyirf.spectral.DIFFUSE_FLUX_UNIT = Unit("1 / (TeV s sr m2)")
+

Unit of a diffuse flux

+

Number of particles per Energy, time, area and solid_angle

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM.html b/api/pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM.html new file mode 100644 index 000000000..b043beb9c --- /dev/null +++ b/api/pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM.html @@ -0,0 +1,167 @@ + + + + + + + IRFDOC_ELECTRON_SPECTRUM — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

IRFDOC_ELECTRON_SPECTRUM

+
+
+pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM = PowerLawWithExponentialGaussian(2.385e-09 1 / (TeV s sr cm2) * (E / 1.0 TeV)**-3.43 * (1 + 1.95 * (exp(Gauss(log10(E / 1.0 TeV), -0.101, 0.741)) - 1))
+

Electron spectrum definition defined in the CTA Prod3b IRF Document

+

From “Description of CTA Instrument Response Functions (Production 3b Simulation)”, section 4.3.1 +https://gitlab.cta-observatory.org/cta-consortium/aswg/documentation/internal_reports/irfs-reports/prod3b-irf-description

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.IRFDOC_PROTON_SPECTRUM.html b/api/pyirf.spectral.IRFDOC_PROTON_SPECTRUM.html new file mode 100644 index 000000000..ccce5829e --- /dev/null +++ b/api/pyirf.spectral.IRFDOC_PROTON_SPECTRUM.html @@ -0,0 +1,167 @@ + + + + + + + IRFDOC_PROTON_SPECTRUM — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

IRFDOC_PROTON_SPECTRUM

+
+
+pyirf.spectral.IRFDOC_PROTON_SPECTRUM = PowerLaw(9.8e-06 1 / (TeV s sr cm2) * (E / 1.0 TeV)**-2.62)
+

Proton spectrum definition defined in the CTA Prod3b IRF Document

+

From “Description of CTA Instrument Response Functions (Production 3b Simulation)”, section 4.3.1 +https://gitlab.cta-observatory.org/cta-consortium/aswg/documentation/internal_reports/irfs-reports/prod3b-irf-description

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.LogParabola.html b/api/pyirf.spectral.LogParabola.html new file mode 100644 index 000000000..4f719622e --- /dev/null +++ b/api/pyirf.spectral.LogParabola.html @@ -0,0 +1,198 @@ + + + + + + + LogParabola — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+ +
+

LogParabola

+
+
+class pyirf.spectral.LogParabola(normalization, a, b, e_ref=<Quantity 1. TeV>)[source]
+

Bases: object

+

A log parabola flux parameterization.

+
+\[\Phi(E, \Phi_0, \alpha, \beta, E_\text{ref}) = +\Phi_0 \left( + \frac{E}{E_\text{ref}} +\right)^{\alpha + \beta \cdot \log_{10}(E / E_\text{ref})}\]
+
+
Attributes:
+
+
normalization: astropy.units.Quantity[flux]

\(\Phi_0\),

+
+
a: float

\(\alpha\)

+
+
b: float

\(\beta\)

+
+
e_ref: astropy.units.Quantity[energy]

\(E_\text{ref}\)

+
+
+
+
+

Methods Summary

+ + + + + + +

__call__(energy)

Call self as a function.

+

Methods Documentation

+
+
+__call__(energy)[source]
+

Call self as a function.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.PDG_ALL_PARTICLE.html b/api/pyirf.spectral.PDG_ALL_PARTICLE.html new file mode 100644 index 000000000..75a1eb00e --- /dev/null +++ b/api/pyirf.spectral.PDG_ALL_PARTICLE.html @@ -0,0 +1,167 @@ + + + + + + + PDG_ALL_PARTICLE — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

PDG_ALL_PARTICLE

+
+
+pyirf.spectral.PDG_ALL_PARTICLE = PowerLaw(18000.0 1 / (GeV s sr m2) * (E / 1.0 GeV)**-2.7)
+

All particle spectrum

+

(30.2) from “The Review of Particle Physics (2020)” +https://pdg.lbl.gov/2020/reviews/rpp2020-rev-cosmic-rays.pdf

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.POINT_SOURCE_FLUX_UNIT.html b/api/pyirf.spectral.POINT_SOURCE_FLUX_UNIT.html new file mode 100644 index 000000000..55d1e7d96 --- /dev/null +++ b/api/pyirf.spectral.POINT_SOURCE_FLUX_UNIT.html @@ -0,0 +1,166 @@ + + + + + + + POINT_SOURCE_FLUX_UNIT — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

POINT_SOURCE_FLUX_UNIT

+
+
+pyirf.spectral.POINT_SOURCE_FLUX_UNIT = Unit("1 / (TeV s m2)")
+

Unit of a point source flux

+

Number of particles per Energy, time and area

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.PowerLaw.html b/api/pyirf.spectral.PowerLaw.html new file mode 100644 index 000000000..53469e036 --- /dev/null +++ b/api/pyirf.spectral.PowerLaw.html @@ -0,0 +1,231 @@ + + + + + + + PowerLaw — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+ +
+

PowerLaw

+
+
+class pyirf.spectral.PowerLaw(normalization, index, e_ref=<Quantity 1. TeV>)[source]
+

Bases: object

+

A power law with normalization, reference energy and index. +Index includes the sign:

+
+\[\Phi(E, \Phi_0, \gamma, E_\text{ref}) = +\Phi_0 \left(\frac{E}{E_\text{ref}}\right)^{\gamma}\]
+
+
Attributes:
+
+
normalization: astropy.units.Quantity[flux]

\(\Phi_0\),

+
+
index: float

\(\gamma\)

+
+
e_ref: astropy.units.Quantity[energy]

\(E_\text{ref}\)

+
+
+
+
+

Methods Summary

+ + + + + + + + + + + + +

__call__(energy)

Call self as a function.

from_simulation(simulated_event_info, obstime)

Calculate the flux normalization for simulated events drawn from a power law for a certain observation time.

integrate_cone(inner, outer)

Integrate this powerlaw over solid angle in the given cone

+

Methods Documentation

+
+
+__call__(energy)[source]
+

Call self as a function.

+
+ +
+
+classmethod from_simulation(simulated_event_info, obstime, e_ref=<Quantity 1. TeV>)[source]
+

Calculate the flux normalization for simulated events drawn +from a power law for a certain observation time.

+
+ +
+
+integrate_cone(inner, outer)[source]
+

Integrate this powerlaw over solid angle in the given cone

+
+
Parameters:
+
+
innerastropy.units.Quantity[angle]

inner opening angle of cone

+
+
outerastropy.units.Quantity[angle]

outer opening angle of cone

+
+
+
+
Returns:
+
+
integratedPowerLaw

A new powerlaw instance with new normalization with the integration +result.

+
+
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.PowerLawWithExponentialGaussian.html b/api/pyirf.spectral.PowerLawWithExponentialGaussian.html new file mode 100644 index 000000000..225bc50e0 --- /dev/null +++ b/api/pyirf.spectral.PowerLawWithExponentialGaussian.html @@ -0,0 +1,212 @@ + + + + + + + PowerLawWithExponentialGaussian — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

PowerLawWithExponentialGaussian

+
+
+class pyirf.spectral.PowerLawWithExponentialGaussian(normalization, index, e_ref, f, mu, sigma)[source]
+

Bases: PowerLaw

+

A power law with an additional Gaussian bump. +Beware that the Gaussian is not normalized!

+
+\[\Phi(E, \Phi_0, \gamma, f, \mu, \sigma, E_\text{ref}) = +\Phi_0 \left( + \frac{E}{E_\text{ref}} +\right)^{\gamma} +\cdot \left( + 1 + f \cdot + \left( + \exp\left( + \operatorname{Gauss}(\log_{10}(E / E_\text{ref}), \mu, \sigma) + \right) - 1 + \right) +\right)\]
+

Where \(\operatorname{Gauss}\) is the unnormalized Gaussian distribution:

+
+\[\operatorname{Gauss}(x, \mu, \sigma) = \exp\left( + -\frac{1}{2} \left(\frac{x - \mu}{\sigma}\right)^2 +\right)\]
+
+
Attributes:
+
+
normalization: astropy.units.Quantity[flux]

\(\Phi_0\),

+
+
a: float

\(\alpha\)

+
+
b: float

\(\beta\)

+
+
e_ref: astropy.units.Quantity[energy]

\(E_\text{ref}\)

+
+
+
+
+

Methods Summary

+ + + + + + +

__call__(energy)

Call self as a function.

+

Methods Documentation

+
+
+__call__(energy)[source]
+

Call self as a function.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.TableInterpolationSpectrum.html b/api/pyirf.spectral.TableInterpolationSpectrum.html new file mode 100644 index 000000000..14e3bb098 --- /dev/null +++ b/api/pyirf.spectral.TableInterpolationSpectrum.html @@ -0,0 +1,194 @@ + + + + + + + TableInterpolationSpectrum — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

TableInterpolationSpectrum

+
+
+class pyirf.spectral.TableInterpolationSpectrum(energy, flux, log_energy=True, log_flux=True, reference_energy=<Quantity 1. TeV>)[source]
+

Bases: object

+

Interpolate flux points to obtain a spectrum.

+

By default, flux is interpolated linearly in log-log space.

+

Methods Summary

+ + + + + + + + + + + + +

__call__(energy)

Call self as a function.

from_file(path[, log_energy, log_flux, ...])

from_table(table[, log_energy, log_flux, ...])

+

Methods Documentation

+
+
+__call__(energy)[source]
+

Call self as a function.

+
+ +
+
+classmethod from_file(path, log_energy=True, log_flux=True, reference_energy=<Quantity 1. TeV>)[source]
+
+ +
+
+classmethod from_table(table: ~astropy.table.table.QTable, log_energy=True, log_flux=True, reference_energy=<Quantity 1. TeV>)[source]
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.spectral.calculate_event_weights.html b/api/pyirf.spectral.calculate_event_weights.html new file mode 100644 index 000000000..498c3e153 --- /dev/null +++ b/api/pyirf.spectral.calculate_event_weights.html @@ -0,0 +1,181 @@ + + + + + + + calculate_event_weights — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

calculate_event_weights

+
+
+pyirf.spectral.calculate_event_weights(true_energy, target_spectrum, simulated_spectrum)[source]
+

Calculate event weights

+

Events with a certain simulated_spectrum are reweighted to target_spectrum.

+
+\[w_i = \frac{\Phi_\text{Target}(E_i)}{\Phi_\text{Simulation}(E_i)}\]
+
+
Parameters:
+
+
true_energy: astropy.units.Quantity[energy]

True energy of the event

+
+
target_spectrum: callable

The target spectrum. Must be a allable with signature (energy) -> flux

+
+
simulated_spectrum: callable

The simulated spectrum. Must be a callable with signature (energy) -> flux

+
+
+
+
Returns:
+
+
weights: numpy.ndarray

Weights for each event

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.statistics.li_ma_significance.html b/api/pyirf.statistics.li_ma_significance.html new file mode 100644 index 000000000..e0a48e314 --- /dev/null +++ b/api/pyirf.statistics.li_ma_significance.html @@ -0,0 +1,178 @@ + + + + + + + li_ma_significance — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

li_ma_significance

+
+
+pyirf.statistics.li_ma_significance(n_on, n_off, alpha=0.2)[source]
+

Calculate the Li & Ma significance.

+

Formula (17) in https://doi.org/10.1086/161295

+

This functions returns 0 significance when n_on < alpha * n_off +instead of the negative sensitivities that would result from naively +evaluating the formula.

+
+
Parameters:
+
+
n_on: integer or array like

Number of events for the on observations

+
+
n_off: integer or array like

Number of events for the off observations

+
+
alpha: float

Ratio between the on region and the off region size or obstime.

+
+
+
+
Returns:
+
+
s_lima: float or array

The calculated significance

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.utils.calculate_source_fov_offset.html b/api/pyirf.utils.calculate_source_fov_offset.html new file mode 100644 index 000000000..e66e7165c --- /dev/null +++ b/api/pyirf.utils.calculate_source_fov_offset.html @@ -0,0 +1,178 @@ + + + + + + + calculate_source_fov_offset — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

calculate_source_fov_offset

+
+
+pyirf.utils.calculate_source_fov_offset(events, prefix='true')[source]
+

Calculate angular separation between true and pointing positions.

+
+
Parameters:
+
+
eventsastropy.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 offset.

+
+
+
+
Returns:
+
+
theta: astropy.units.Quantity

Angular separation between the true and pointing positions +in the sky.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.utils.calculate_theta.html b/api/pyirf.utils.calculate_theta.html new file mode 100644 index 000000000..d465c8a66 --- /dev/null +++ b/api/pyirf.utils.calculate_theta.html @@ -0,0 +1,179 @@ + + + + + + + calculate_theta — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

calculate_theta

+
+
+pyirf.utils.calculate_theta(events, assumed_source_az, assumed_source_alt)[source]
+

Calculate sky separation between assumed and reconstructed positions.

+
+
Parameters:
+
+
eventsastropy.QTable

Astropy Table object containing the reconstructed events information.

+
+
assumed_source_az: astropy.units.Quantity

Assumed Azimuth angle of the source.

+
+
assumed_source_alt: astropy.units.Quantity

Assumed Altitude angle of the source.

+
+
+
+
Returns:
+
+
theta: astropy.units.Quantity

Angular separation between the assumed and reconstructed positions +in the sky.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.utils.check_histograms.html b/api/pyirf.utils.check_histograms.html new file mode 100644 index 000000000..54a5ec7ca --- /dev/null +++ b/api/pyirf.utils.check_histograms.html @@ -0,0 +1,171 @@ + + + + + + + check_histograms — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

check_histograms

+
+
+pyirf.utils.check_histograms(hist1, hist2, key='reco_energy')[source]
+

Check if two histogram tables have the same binning

+
+
Parameters:
+
+
hist1: ``~astropy.table.Table``

First histogram table, as created by +~pyirf.binning.create_histogram_table

+
+
hist2: ``~astropy.table.Table``

Second histogram table

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.utils.cone_solid_angle.html b/api/pyirf.utils.cone_solid_angle.html new file mode 100644 index 000000000..a1ac02932 --- /dev/null +++ b/api/pyirf.utils.cone_solid_angle.html @@ -0,0 +1,172 @@ + + + + + + + cone_solid_angle — pyirf documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

cone_solid_angle

+
+
+pyirf.utils.cone_solid_angle(angle)[source]
+

Calculate the solid angle of a view cone.

+
+
Parameters:
+
+
angle: astropy.units.Quantity or astropy.coordinates.Angle

Opening angle of the view cone.

+
+
+
+
Returns:
+
+
solid_angle: astropy.units.Quantity

Solid angle of a view cone with opening angle angle.

+
+
+
+
+
+ +
+ + +
+
+
+ +
+ +
+

© Copyright 2020, Maximilian Nöthe, Michele Peresano, Thomas Vuillaume.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/api/pyirf.utils.is_scalar.html b/api/pyirf.utils.is_scalar.html new file mode 100644 index 000000000..b1df17556 --- /dev/null +++ b/api/pyirf.utils.is_scalar.html @@ -0,0 +1,174 @@ + + + + + + + is_scalar — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

is_scalar

+
+
+pyirf.utils.is_scalar(val)[source]
+

Workaround that also supports astropy quantities

+
+
Parameters:
+
+
valobject

Any object (value, list, etc…)

+
+
+
+
Returns:
+
+
result: bool

True is if input object is a scalar, False otherwise.

+
+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/benchmarks/index.html b/benchmarks/index.html new file mode 100644 index 000000000..649aa1cdd --- /dev/null +++ b/benchmarks/index.html @@ -0,0 +1,171 @@ + + + + + + + Benchmarks — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Benchmarks

+

Functions to calculate benchmarks.

+
+
+

pyirf.benchmarks Package

+
+

Functions

+ + + + + + + + + + + + +

energy_bias_resolution(events, energy_bins)

Calculate bias and energy resolution.

energy_bias_resolution_from_energy_dispersion(...)

Calculate bias and energy resolution.

angular_resolution(events, energy_bins[, ...])

Calculate the angular resolution.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/binning.html b/binning.html new file mode 100644 index 000000000..1b8d4b22b --- /dev/null +++ b/binning.html @@ -0,0 +1,194 @@ + + + + + + + Binning and Histogram Utilities — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Binning and Histogram Utilities

+
+

Reference/API

+
+
+

pyirf.binning Module

+

Utility functions for binning

+
+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

add_overflow_bins(bins[, positive])

Add under and overflow bins to a bin array.

bin_center(edges)

calculate_bin_indices(data, bins)

Calculate bin indices of inidividula entries of the given data array using the supplied binning.

create_bins_per_decade(e_min, e_max[, ...])

Create a bin array with bins equally spaced in logarithmic energy with bins_per_decade bins per decade.

create_histogram_table(events, bins[, key])

Histogram a variable from events data into an astropy table.

join_bin_lo_hi(bin_lo, bin_hi)

Function joins bins into lo and hi part, e.g. [0, 1, 2] and [1, 2, 4] into [0, 1, 2, 4] It works on multidimentional arrays as long as the binning is in the last axis.

resample_histogram1d(data, old_edges, new_edges)

Rebinning of a histogram by interpolation along a given axis.

split_bin_lo_hi(bins)

Inverted function to join_bin_hi_lo, e.g. it splits [0, 1, 2, 4] into [0, 1, 2] and [1, 2, 4].

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/changelog.html b/changelog.html new file mode 100644 index 000000000..77b941553 --- /dev/null +++ b/changelog.html @@ -0,0 +1,623 @@ + + + + + + + Changelog — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Changelog

+
+

Pyirf 0.10.2.dev55+g0fefb93 (2024-05-14)

+
+

API Changes

+
+
+

Bug Fixes

+
+
+

New Features

+
+
+

Maintenance

+
+
+

Refactoring and Optimization

+
+
+
+

pyirf v0.11.0 (2024-05-14)

+
+

Bug Fixes

+
    +
  • Fix pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion. +This function was not adapted to the now correct normalization of the +energy dispersion matrix, resulting in wrong results on the now correct +matrices. [#268]

  • +
+
+
+

New Features

+
    +
  • Adds an extrapolator for parametrized compontents utilizing blending over visible edges, resulting +in a smooth extrapolation compared to the NearestSimplexExtrapolator. [#253]

  • +
  • Ignore warnings about invalid floating point operations when calculating n_signal and n_signal_weigthed because the relative sensitivty is frequently NaN. [#264]

  • +
  • Add basic combinatoric fill-value handling for RAD_MAX estimation. [#282]

  • +
+
+
+

Maintenance

+
    +
  • Clarified some documentation. [#266]

  • +
  • Add support for astropy 6.0. [#271]

  • +
  • Added filling of CREF keyword (IRF axis order) in the output files [#275]

  • +
+
+
+

Refactoring and Optimization

+
+
+
+

pyirf v0.10.1 (2023-09-15)

+
+

Bug Fixes

+
    +
  • Fix PowerLaw.from_simulation for the new format of SimulatedEventsInformation, +it was broken since splitting the single viewcone into viewcone_min and viewcone_max. [#258]

  • +
+
+
+
+

Pyirf v0.10.0 (2023-08-23)

+

This release contains an important bug fix for the energy dispersion computation, +it was wrongly normalized before.

+
+

API Changes

+
    +
  • In prior versions of pyirf, the energy dispersion matrix was normalized to a +sum of 1 over the migration axis. +This is wrong, the correct normalization is to an integral of 1, which is fixed now.

    +

    The internal API of the interpolation functions had to be adapted to take in additional +keywords, mainly the bin edges and the kind of normalization (standard or solid angle cone sections). [#250]

    +
  • +
  • Replace single viewcone argument of SimulationInfo with +viewcone_min and viewcone_max, e.g. to correctly enable +ring wobble simulations. [#239]

  • +
+
+
+

Bug Fixes

+
    +
  • See above on the energy dispersion change.

  • +
+
+
+

New Features

+
    +
  • Add option to specify which containment to use for angular resolution. [#234]

  • +
+
+
+
+

pyirf 0.9.0 (2023-07-19)

+
+

API Changes

+
    +
  • Change the interpolation API to top-level estimator classes that instantiate +inter- and extrapolator objects. Drops the interpolate_xyz functions +originally used to interpolate a xyz IRF component in favour of a XYZEstimator +class. Moves data checks from intepolator to estimator classes.

    +

    Direct usage of interpolator objects is now discuraged, use estimator objects instead. [#228]

    +
  • +
+
+
+

Bug Fixes

+
    +
  • Correctly fill n_events in angular_resolution, was always 0 before. [#231]

  • +
  • Remove condition that relative sensitivity must be > 1. +This condition was added by error and resulted in returning +nan if the flux needed to fulfill the conditions is larger than +the reference flux used to weight the events. [#241]

  • +
+
+
+

New Features

+
    +
  • Add moment morphing as second interpolation method able to handle discretized PDF +components of IRFs. [#229]

  • +
  • Add a base structure for extrapolators similar to the interpolation case +as well as a first extrapolator for parametrized components, extrapolating from the +nearest simplex in one or two dimensions. [#236]

  • +
  • Add an extrapolator for discretized PDF components, extrapolating from the +nearest simplex in one or two dimensions utilizing the same approach moment morphing +interpolation uses. [#237]

  • +
  • Add a DiscretePDFNearestNeighborSearcher and a ParametrizedNearestNeighborSearcher to support nearest neighbor approaches +as alternatives to inter-/ and extrapolation [#232]

  • +
+
+
+

Maintenance

+
    +
  • Drop python 3.8 support in accordance with NEP 29 [#243]

  • +
+
+
+
+

pyirf 0.8.1 (2023-03-16)

+
+

New Features

+
    +
  • Migrating the interpolation methods from pyirf.interpolation to interpolator +objects, allowing for later inheritance for new algorithms and reusability. [#210]

  • +
+
+
+

Maintenance

+
    +
  • Add and enable towncrier in CI. [#207]

  • +
  • Add a fixture containing three IRFs from the prod5 IRF data-release +for unit testing. Specifically the fixture contains the contents of:

    +
    +
      +
    • Prod5-North-20deg-AverageAz-4LSTs.180000s-v0.1.fits.gz.

    • +
    • Prod5-North-40deg-AverageAz-4LSTs.180000s-v0.1.fits.gz

    • +
    • Prod5-North-60deg-AverageAz-4LSTs.180000s-v0.1.fits.gz

    • +
    +

    The user has to download these irfs to irfs/ using download_irfs.py, +github’s CI does so automatically and caches them for convenience. [#211]

    +
    +
  • +
+
+
+
+

Older releases

+

For releases between v0.4.1 and v0.8.1, please refer to the GitHub releases page.

+
+

0.4.1 (2021-03-22)

+
+

Summary

+
    +
  • Released March 22nd, 2021

  • +
  • 1 Contributors

  • +
+
+
+

Contributors

+
    +
  • Maximilian Nöthe

  • +
+
+
+

Merged Pull Requests

+
    +
  • #135 Add functions to convert pyirf results to the corresponding gammapy classes

  • +
  • #137 Add example notebook for calculating point-lile IRFs from the FACT open data

  • +
+
+
+
+

0.4.0 (2020-11-09)

+
+

Summary

+
    +
  • Released November 11th, 2020

  • +
  • 2 Contributors

  • +
+
+
+

Contributors

+

In order of number of commits:

+
    +
  • Maximilian Nöthe

  • +
  • Michele Peresano

  • +
+
+
+

Description

+

This release is an important update that introduces three +changes in the cut optimization, background estimation and sensitivity calculation.

+

Together, these changes bring the calculated sensitivities much closer to the ones calculated by +EventDisplay.

+
    +
  • Scale the relative flux calculated to reach the target sensitivity +up if the requirements on the minimum number of signal events are not met. +Essentially, instead of always calculating the flux that +yields target_sensitivity and then checking if the two other conditions are met, +we increase the required flux to meet the other requirements. +This can result in new sensitivities where before pyirf would report no sensitivities, +and report better sensitivities everywhere where the event number conditions where not +met before at the target significance. +The best sensitivity now is the lowest flux that just barely satisfies all +requirements (so is at the minimum requirement of one of the three).

  • +
  • Differentiate between reco_source_fov_offset and true_source_fov_offset, +using the former for background rates and the latter for everything concerning +signal events.

  • +
  • Change optimize_gh_cut to do the optimization in terms of efficiency and +limit this efficiency to max. 80 % in the EventDisplay comparison.

  • +
+

Smaller improvements also include:

+
    +
  • It is now possible to include a particle_type column in the event lists, +which will result in additionally reporting all event counts also per particle_type. +E.g. if particle_type is included in the background table consisting of both +electrons and protons, estimate_background will not only report n_background(_weighted) +but also n_electron(_weighted) and n_proton(_weighted)

  • +
  • relative_sensitivity now supports vectorized application and broadcasting +of inputs, as previously wrongly advertized in the docstring.

  • +
+
+ +
+

Merged Pull Requests

+
+
Feature changes
+
    +
  • #110 Optimize cuts in efficiency steps with maximum efficiency of 80% for EventDisplay comparison

  • +
  • #104 Scale flux for conditions, differenatiate reco and true source_fov_offset

  • +
  • #108 Add counts / weighted counts per particle type

  • +
  • #107 Small update to installation instructions

  • +
  • #106 Use vectorize for relative_sensitivity

  • +
+
+
+
Project maintenance
+
    +
  • #102 Require astropy >= 4.0.2

  • +
  • #100 Fix deploy condition in travis yml

  • +
+
+
+
+
+

0.3.0 (2020-10-05)

+
+

Summary

+
    +
  • Released October 5th, 2020

  • +
  • 5 Contributors

  • +
+
+
+

Contributors

+

In order of number of commits:

+
    +
  • Maximilian Nöthe

  • +
  • Michele Peresano

  • +
  • Noah Biederbeck

  • +
  • Lukas Nickel

  • +
  • Gaia Verna

  • +
+
+
+

Description

+

This release is the result of the IRF sprint week in September 2020. +Many bug fixes and improvements were made to the code.

+

As the target for the sprint week was to reproduce the approach of EventDisplay and +the resulting IRFs, one scheme of cut optimization is implemented. +The examples/calculate_eventdisplay_irfs.py should follow the approach +of EventDisplay closely and shows what is currently implemented in pyirf. +In the central and upper energy range, pyirf now reproduces the EventDisplay sensitivity +exactly, the lower energy bins still show some disagreement. +The cut optimization seems not yet to be the same as EventDisplay’s and will be further investigated. +This example could be used as a starting point if you also want to do cut optimization for best sensitivity.

+

At least one version of each IRF is now implemented and can be stored in the GADF format. +Computation of full-enclosure IRFs should be possible but is of now not yet tested +on a reference dataset.

+
+
+

Merged Pull Requests

+
    +
  • #97 Store correct signal amount, store information on which checks failed for sensitivity bins (Maximilian Nöthe)

  • +
  • #96 Add integration test (Michele Peresano)

  • +
  • #98 Remove option point_like for psf (Maximilian Nöthe)

  • +
  • #95 Cut updates (Maximilian Nöthe)

  • +
  • #91 Fix conditions to take relative sensitivity into account, fixes #90 (Maximilian Nöthe)

  • +
  • #89 Fix brentq returning the lower bound of 0 for flat li ma function (Maximilian Nöthe)

  • +
  • #85 Improve comparison to EventDisplay (Maximilian Nöthe)

  • +
  • #75 Add a function to check a table for required cols / units (Maximilian Nöthe)

  • +
  • #86 Fix Li & Ma significance for n_off = 0 (Maximilian Nöthe)

  • +
  • #76 Feature resample histogram (Noah Biederbeck, Lukas Nickel)

  • +
  • #79 Fix integration of power law pdf in simulations.py (Gaia Verna)

  • +
  • #80 Estimate unique runs taking pointing pos into account (Maximilian Nöthe)

  • +
  • #71 Background estimation (Maximilian Nöthe)

  • +
  • #78 Change argument order in create_rad_max_hdu (Lukas Nickel)

  • +
  • #77 Calculate optimized cut on only the events surviving gh separation (Maximilian Nöthe)

  • +
  • #68 Effective area 2d (Maximilian Nöthe)

  • +
  • #67 Add method integrating sim. events in FOV bins (Maximilian Nöthe)

  • +
  • #63 Verify hdus using ogadf-schema (Maximilian Nöthe)

  • +
  • #58 Implement Background2d (Maximilian Nöthe)

  • +
  • #52 Add sections about tests, coverage and building docs to docs (Maximilian Nöthe)

  • +
  • #46 Add PyPI deploy and metadata (Maximilian Nöthe)

  • +
+
+
+
+

0.2.0 (2020-09-27)

+
+

Summary

+
    +
  • Released September 27th, 2020

  • +
  • 4 Contributors

  • +
+
+
+

Contributors

+

In order of number of commits:

+
    +
  • Maximilian Nöthe

  • +
  • Michele Peresano

  • +
  • Lukas Nickel

  • +
  • Hugo van Kemenade

  • +
+
+
+

Description

+

For this version, pyirf’s API was completely rewritten from scratch, +merging code from several projects (pyirf, pyfact, fact-project/irf) to provide a library to compute IACT +IRFs and sensitivity and store them in the GADF data format.

+

The class based API using a configuration file was replaced by a finer grained +function based API.

+

Implemented are point-like IRFs and sensitivity.

+

This release was the starting point for the IRF sprint week in September 2020, +where the refactoring continued.

+
+
+

Merged Pull Requests

+
    +
  • #36 Start refactoring pyirf (Maximilian Nöthe, Michele Peresano, Lukas Nickel)

  • +
  • #35 Cleanup example notebook (Maximilian Nöthe, Michele Peresano, Lukas Nickel)

  • +
  • #37 Move to python >= 3.6 (Hugo van Kemenade)

  • +
+
+
+
+

0.1.0 (2020-09-16)

+

This is a pre-release.

+
    +
  • Released September 16th, 2020

  • +
+
+
+

0.1.0-alpha (2020-05-27)

+
+

Summary

+

This is a pre-release.

+
    +
  • Released May 27th, 2020

  • +
  • 3 contributors

  • +
+
+
+

Description

+
    +
  • Started basic maintenance

  • +
  • Started refactoring

  • +
  • First tests with CTA-LST data

  • +
+
+
+

Contributors

+

In alphabetical order by last name:

+
    +
  • Lea Jouvin

  • +
  • Michele Peresano

  • +
  • Thomas Vuillaume

  • +
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/contribute.html b/contribute.html new file mode 100644 index 000000000..7a675add8 --- /dev/null +++ b/contribute.html @@ -0,0 +1,267 @@ + + + + + + + How to contribute — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

How to contribute

+ +
+

Issue Tracker

+

We use the GitHub issue tracker +for individual issues and the GitHub Projects page can give you a quick overview.

+

If you found a bug or you are missing a feature, please check the existing +issues and then open a new one or contribute to the existing issue.

+
+
+

Development procedure

+

We use the standard GitHub workflow.

+

If you are not part of the cta-observatory organization, +you need to fork the repository to contribute. +See the GitHub tutorial on forks if you are unsure how to do this.

+
    +
  1. When you find something that is wrong or missing

    +
    +
      +
    • Go to the issue tracker and check if an issue already exists for your bug or feature

    • +
    • In general it is always better to anticipate a PR with a new issue and link the two

    • +
    +
    +
  2. +
  3. To work on a bug fix or new feature, create a new branch, add commits and open your pull request

    +
      +
    • If you think your pull request is good to go and ready to be reviewed, +you can directly open it as normal pull request.

    • +
    • You can also open it as a “Draft Pull Request”, if you are not yet finished +but want to get early feedback on your ideas.

    • +
    • Especially when working on a bug, it makes sense to first add a new +test that fails due to the bug and in a later commit add the fix showing +that the test is then passing. +This helps understanding the bug and will prevent it from reappearing later.

    • +
    • Create a changelog entry in docs/changes, please note the README.md there. +Minor changes (on the magnitude of fixing a broken link or renaming a variable) can receive the no-changelog-needed label. +This should, however, be a rare exception.

    • +
    +
  4. +
  5. Wait for review comments and then implement or discuss requested changes.

  6. +
+

We use Github Actions to +run the unit tests and documentation building automatically for every pull request. +Passing unit tests and coverage of the changed code are required for all pull requests.

+
+
+

Running the tests and looking at coverage

+

For more immediate feedback, you should run the tests locally before pushing, +as builds on travis take quite long.

+

To run the tests locally, make sure you have the tests extras installed and then +run

+
$ pytest -v
+
+
+

To also inspect the coverage, run

+
$ pytest --cov=pyirf --cov-report=html -v
+
+
+

This will create a coverage report in html form in the htmlcov directory, +which you can serve locally using

+
$ python -m http.server -d htmlcov
+
+
+

After this, you can view the report in your browser by visiting the url printed +to the terminal.

+
+
+

Building the documentation

+

This documentation uses sphinx and restructured text. +For an Introduction, see the Sphinx documentation.

+

To build the docs locally, enter the docs directory and call:

+
make html
+
+
+

Some changes require a full remake of the documentation, for that call

+
make clean html
+
+
+

If you created or deleted file or submodule, you also need to remove the +api directory, it will be regenerated automatically.

+

Make sure the docs are built without warnings from sphinx, as these +will be treated as errors in the build in the CI system as they most often +result in broken styling.

+

To look at the docs, use

+
$ python -m http.server _build/html
+
+
+

and visit the printed URL in your browser.

+
+
+

Making your contribution visible

+

Together with the changes that will come with you PR, you should check that the +following maintenance files are up-to-date:

+
    +
  • .mailmap

  • +
  • CODEOWNERS

  • +
  • .zenodo.json

  • +
+
+
+

Further details

+

Please also have a look at the

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/cut_optimization.html b/cut_optimization.html new file mode 100644 index 000000000..87b744684 --- /dev/null +++ b/cut_optimization.html @@ -0,0 +1,165 @@ + + + + + + + Cut Optimization — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Cut Optimization

+
+

Reference/API

+
+
+

pyirf.cut_optimization Module

+
+

Functions

+ + + + + + +

optimize_gh_cut(signal, background, ...[, ...])

Optimize the gh-score cut in every energy bin of reconstructed energy for best sensitivity.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/cuts.html b/cuts.html new file mode 100644 index 000000000..f3307d535 --- /dev/null +++ b/cuts.html @@ -0,0 +1,173 @@ + + + + + + + Calculating and Applying Cuts — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Calculating and Applying Cuts

+
+

Reference/API

+
+
+

pyirf.cuts Module

+
+

Functions

+ + + + + + + + + + + + +

calculate_percentile_cut(values, bin_values, ...)

Calculate cuts as the percentile of a given quantity in bins of another quantity.

evaluate_binned_cut(values, bin_values, ...)

Evaluate a binned cut as defined in cut_table on given events.

compare_irf_cuts(cuts)

checks if the same cuts have been applied in all of them

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/examples.html b/examples.html new file mode 100644 index 000000000..ec0c74d0f --- /dev/null +++ b/examples.html @@ -0,0 +1,172 @@ + + + + + + + Examples — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Examples

+
+

Calculating Sensitivity and IRFs for EventDisplay DL2 data

+

The examples/calculate_eventdisplay_irfs.py file is +using pyirf to optimize cuts, calculate sensitivity and IRFs +and then store these to FITS files for DL2 event lists from EventDisplay.

+

The ROOT files were provided by Gernot Maier and converted to FITS format +using the EventDisplay DL2 converter script. +The resulting FITS files are the input to the example and can be downloaded using:

+
./download_private_data.sh
+
+
+

This requires curl and unzip to be installed. +The download is password protected, please ask one of the maintainers for the +password.

+

A detailed explanation of the contents of such DL2 files can be found +here (internal).

+

The example can then be run from the root of the repository after installing pyirf +by running:

+
python examples/calculate_eventdisplay_irfs.py
+
+
+

A jupyter notebook plotting the results and comparing them to the EventDisplay output +is available in examples/comparison_with_EventDisplay.ipynb

+
+
+

Visualization of the included Flux Models

+

The examples/plot_spectra.py visualizes the Flux models included +in pyirf for Crab Nebula, cosmic ray and electron flux.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/gammapy.html b/gammapy.html new file mode 100644 index 000000000..8af20f39c --- /dev/null +++ b/gammapy.html @@ -0,0 +1,175 @@ + + + + + + + Gammapy Interoperability — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Gammapy Interoperability

+

This module provides functions to convert the pyirf quantities +for IRFs and the binning to the corresponding gammapy classes.

+
+

Reference/API

+
+
+

pyirf.gammapy Module

+
+

Functions

+ + + + + + + + + + + + +

create_effective_area_table_2d(...)

Create a gammapy.irf.EffectiveAreaTable2D from pyirf outputs.

create_energy_dispersion_2d(...)

Create a gammapy.irf.EnergyDispersion2D from pyirf outputs.

create_psf_3d(psf, true_energy_bins, ...)

Create a gammapy.irf.PSF3D from pyirf outputs.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 000000000..dcf553341 --- /dev/null +++ b/genindex.html @@ -0,0 +1,686 @@ + + + + + + Index — pyirf documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ _ + | A + | B + | C + | D + | E + | F + | G + | I + | J + | L + | M + | N + | O + | P + | Q + | R + | S + | T + | V + +
+

_

+ + +
+ +

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + +
+ +

I

+ + + +
+ +

J

+ + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + +
+ +

O

+ + +
+ +

P

+ + + +
    +
  • + pyirf.cuts + +
  • +
  • + pyirf.gammapy + +
  • +
  • + pyirf.io + +
  • +
  • + pyirf.irf + +
  • +
  • + pyirf.sensitivity + +
  • +
  • + pyirf.simulations + +
  • +
  • + pyirf.spectral + +
  • +
  • + pyirf.statistics + +
  • +
  • + pyirf.utils + +
  • +
+ +

Q

+ + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + +
+ +

V

+ + + +
+ + + +
+
+
+ +
+ +
+

© Copyright 2020, Maximilian Nöthe, Michele Peresano, Thomas Vuillaume.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 000000000..9d9d937fb --- /dev/null +++ b/index.html @@ -0,0 +1,214 @@ + + + + + + + + Welcome to pyirf’s documentation! — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Welcome to pyirf’s documentation!

+

pyirf is a prototype for the generation of Instrument Response Functions (IRFs) +for the Cherenkov Telescope Array +(CTA). +The package is being developed and tested by members of the CTA consortium and +is a spin-off of the analog sub-process of the +pipeline protopype.

+

Its main features are currently to

+
+
    +
  • find the best cutoff in gammaness/score, to discriminate between signal +and background, as well as the angular cut to obtain the best sensitivity +for a given amount of observation time and a given template for the +source of interest (Cut Optimization)

  • +
  • compute the instrument response functions, effective area, +point spread function and energy resolution (Instrument Response Functions)

  • +
  • estimate the sensitivity of the array (Sensitivity),

  • +
+
+

with plans to extend its capabilities to reach the requirements of the +future observatory.

+

The source code is hosted on a GitHub repository, to +which this documentation is linked.

+
+

Warning

+

This is not yet stable code, so expect large and rapid changes.

+
+
+

Citing this software

+

If you use a released version of this software for a publication, +please cite it by using the corresponding DOI.

+
    +
  • latest : doilatest

  • +
  • v0.5.0 : doi_v0.5.0

  • +
  • v0.4.0 : doi_v0.4.0

  • +
+ + +
+
+
+

Indices and tables

+ +
+ + +
+
+
+ +
+ +
+

© Copyright 2020, Maximilian Nöthe, Michele Peresano, Thomas Vuillaume.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/install.html b/install.html new file mode 100644 index 000000000..7de4c33de --- /dev/null +++ b/install.html @@ -0,0 +1,189 @@ + + + + + + + Installation — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Installation

+

pyirf requires Python ≥3.7 and pip, plus the packages defined in +the setup.py.

+

Core dependencies are

+
    +
  • numpy

  • +
  • astropy

  • +
  • scipy

  • +
+

We provide an environment file for Anaconda or Miniconda users.

+
+

Installing a released version

+

To install a released version, just install the pyirf package using

+
$ pip install pyirf
+
+
+

or add it to the dependencies of your project.

+
+
+

Installing for development

+

If you want to work on pyirf itself, clone the repository and install the local +copy of pyirf in development mode.

+

The dependencies required to perform unit-testing and to build the documentation +are defined in extras under tests and docs respectively.

+

These requirements can also be enabled by installing the all extra:

+
$ pip install -e '.[all]'  # or [docs,tests] to install them separately
+
+
+

You should isolate your pyirf development environment from the rest of your system. +Either by using a virtual environment or by using conda environments. +pyirf provides a conda environment.yml, that includes all dependencies:

+
$ conda env create -f environment.yml
+$ conda activate pyirf
+$ pip install -e '.[all]'
+
+
+

In order to have passing unit-tests you have to download some CTA IRFs +from zenodo <https://zenodo.org/record/5499840>. Simply run

+
$ python download_irfs.py
+
+
+

which will download and unpack three IRF files to irfs/.

+

Run the tests to make sure everything is OK:

+
$ pytest
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/interpolation.html b/interpolation.html new file mode 100644 index 000000000..8163c0184 --- /dev/null +++ b/interpolation.html @@ -0,0 +1,575 @@ + + + + + + + Interpolation and Extrapolation of IRFs — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Interpolation and Extrapolation of IRFs

+

This module contains functions to inter- or extrapolate from a set of IRFs for different +conditions to a new IRF. Implementations of interpolation and extrapolation algorithms +exist as interpolator and extrapolator classes and are applied by top-level estimator +classes to IRF components. +Direct usage of the inter- and extrapolator classes is discouraged, as only the estimator classes +check the data for consistency.

+

Most methods support an arbitrary number of interpolation dimensions although it +is strongly advised to limit those for reasonable results. +The herein provided functionalities can e.g. be used to interpolate the IRF +for a zenith angle of 30° from available IRFs at 20° and 40°.

+
+

IRF Component Estimator Classes

+ + + + + + + + + + + + + + + +

EffectiveAreaEstimator

Estimator class for effective area tables (AEFF_2D).

RadMaxEstimator

Estimator class for rad-max tables (RAD_MAX, RAD_MAX_2D).

EnergyDispersionEstimator

Estimator class for energy dispersions (EDISP_2D).

PSFTableEstimator

Estimator class for point spread function tables (PSF_TABLE).

+
+
+

Inter- and Extrapolation Classes

+

This module provides inter- and extrapolation classes that can be +plugged into the estimator classes. +Not all of these classes support arbitrary grid-dimensions where the grid +in this context is the grid of e.g. observation parameters like zenith angle and +magnetic field inclination (this would be a 2D grid) on which template IRFs exist +and are meant to be inter- or extrapolated.

+

For parametrized components (Effective Areas and Rad-Max tables) these classes are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Type

Grid-Dim

Note

GridDataInterpolator

Interpolation

Arbitrary

See also scipy.interpolate.griddata.

ParametrizedNearestSimplexExtrapolator

Extrapolation

1D or 2D

Linear (1D) or baryzentric (2D) extension outside the grid’s convex hull from the nearest simplex.

ParametrizedVisibleEdgesExtrapolator

Extrapolation

1D or 2D

Like ParametrizedNearestSimplexExtrapolator but blends over all visible simplices [Alf84] and is thus smooth outside the convex hull.

ParametrizedNearestNeighborSearcher

Nearest Neighbor

Arbitrary

Nearest neighbor finder usable instead of inter- and/or extrapolation.

+

For components represented by discretized PDFs (PSF and EDISP tables) these classes are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Type

Grid-Dim

Note

QuantileInterpolator

Interpolation

Arbitrary

Adaption of [Hol+13] and [Rea99] to discretized PDFs.

MomentMorphInterpolator

Interpolation

1D or 2D

Adaption of [Baa+15] to discretized PDFs.

MomentMorphNearestSimplexExtrapolator

Extrapolation

1D or 2D

Extension of [Baa+15] beyond the grid’s convex hull from the nearest simplex.

DiscretePDFNearestNeighborSearcher

Nearest Neighbor

Arbitrary

Nearest neighbor finder usable instead of inter- and/or extrapolation.

+
+
+[Alf84] +

P. Alfred (1984). Triangular Extrapolation. +Technical summary rept., Univ. of Wisconsin-Madison. https://apps.dtic.mil/sti/pdfs/ADA144660.pdf

+
+
+[Hol+13] +

B. E. Hollister and A. T. Pang (2013). Interpolation of Non-Gaussian Probability Distributions for Ensemble Visualization. +https://engineering.ucsc.edu/sites/default/files/technical-reports/UCSC-SOE-13-13.pdf

+
+
+[Rea99] +

A. L. Read (1999). Linear Interpolation of Histograms. +Nucl. Instrum. Methods Phys. Res. A 425, 357-360. https://doi.org/10.1016/S0168-9002(98)01347-3

+
+
+[Baa+15] +(1,2) +

M. Baak, S. Gadatsch, R. Harrington and W. Verkerke (2015). Interpolation between +multi-dimensional histograms using a new non-linear moment morphing method +Nucl. Instrum. Methods Phys. Res. A 771, 39-48. https://doi.org/10.1016/j.nima.2014.10.033

+
+
+
+
+

Using Estimator Classes

+

Usage of the estimator classes is simple. +As an example, consider CTA’s Prod5 IRFs [CTA+21], they can be downloaded manually or by executing +download_irfs.py in pyirf's root directory, which downloads them to .../pyirf/irfs/. +The estimator classes can simply be used by first creating an instance of the respective class with all +relevant information and then using the object’s __call__ interface the obtain results for a specific +target point. +As the energy dispersion represents one of the discretized PDF IRF components, one can use the +MomentMorphInterpolator for interpolation and the DiscretePDFNearestNeighborSearcher +for extrapolation.

+
import numpy as np
+
+from gammapy.irf import load_irf_dict_from_file
+from glob import glob
+from pyirf.interpolation import (
+   EnergyDispersionEstimator,
+   MomentMorphInterpolator,
+   DiscretePDFNearestNeighborSearcher
+)
+
+# Load IRF data, replace path with actual path
+PROD5_IRF_PATH = "pyirf/irfs/*.fits.gz"
+
+irfs = [load_irf_dict_from_file(path) for path in sorted(glob(PROD5_IRF_PATH))]
+
+edisps = np.array([irf["edisp"].quantity for irf in irfs])
+bin_edges = irfs[0]["edisp"].axes["migra"].edges
+# IRFs are for zenith distances of 20, 40 and 60 deg
+zen_pnt = np.array([[20], [40], [60]])
+
+# Create estimator instance
+edisp_estimator = EnergyDispersionEstimator(
+     grid_points=zen_pnt,
+     migra_bins=bin_edges,
+     energy_dispersion=edisps,
+     interpolator_cls=MomentMorphInterpolator,
+     interpolator_kwargs=None,
+     extrapolator_cls=DiscretePDFNearestNeighborSearcher,
+     extrapolator_kwargs=None,
+ )
+
+ # Estimate energy dispersions
+ interpolated_edisp = edisp_estimator(np.array([[30]]))
+ extrapolated_edisp = edisp_estimator(np.array([[10]]))
+
+
+
+
+[CTA+21] +

Cherenkov Telescope Array Observatory & Cherenkov Telescope Array Consortium. (2021). +CTAO Instrument Response Functions - prod5 version v0.1 (v0.1) [Data set]. Zenodo. +https://doi.org/10.5281/zenodo.5499840

+
+
+
+
+

Creating new Estimator Classes

+

To create a estimator class for an IRF component not yet implemented, one can simply +inherit from respective base class. +There are two, tailored to either parametrized or discrete PDF components.

+ + + + + + + + + +

ParametrizedComponentEstimator

Base class for all Estimators working on IRF components that represent parametrized or scalar quantities.

DiscretePDFComponentEstimator

Base class for all Estimators working on IRF components that represent discretized PDFs.

+

Consider an example, where one is interested in an estimator for simple Gaussians. +As this is already the scope of the DiscretePDFComponentEstimator base class and +for the sake of this demonstration, let the Gaussians come with some +units attached that need handling:

+
import astropy.units as u
+from pyirf.interpolation import (DiscretePDFComponentEstimator,
+                                 MomentMorphInterpolator)
+
+class GaussianEstimatior(DiscretePDFComponentEstimator):
+   @u.quantity_input(gaussians=u.m)
+   def __init__(
+      self,
+      grid_points,
+      bin_edges,
+      gaussians,
+      interpolator_cls=MomentMorphInterpolator,
+      interpolator_kwargs=None,
+      extrapolator_cls=None,
+      extrapolator_kwargs=None,
+   ):
+      if interpolator_kwargs is None:
+         interpolator_kwargs = {}
+
+      if extrapolator_kwargs is None:
+         extrapolator_kwargs = {}
+
+      self.unit = gaussians.unit
+
+      super().__init__(
+         grid_points=grid_points,
+         bin_edges=bin_edges,
+         binned_pdf=gaussians.to_value(u.m),
+         interpolator_cls=interpolator_cls,
+         interpolator_kwargs=interpolator_kwargs,
+         extrapolator_cls=extrapolator_cls,
+         extrapolator_kwargs=extrapolator_kwargs,
+      )
+
+   def __call__(self, target_point):
+      res = super().__call__(target_point)
+
+      # Return result with correct unit
+      return u.Quantity(res, u.m, copy=False).to(self.unit)
+
+
+

This new estimator class can now be used just like any other estimator class already +implemented in pyirf.interpolation. +While the extrapolator_cls argument can be empty when creating an instance of +GaussianEstimator, effectively disabling extrapolation and raising an error in +case it would be needed regardless, assume the desired extrapolation method to be +MomentMorphNearestSimplexExtrapolator:

+
import numpy as np
+from pyirf.interpolation import MomentMorphNearestSimplexExtrapolator
+from scipy.stats import norm
+
+bins = np.linspace(-10, 10, 51)
+grid = np.array([[1], [2], [3]])
+
+gaussians = np.array([np.diff(norm(loc=x, scale=1/x).cdf(bins))/np.diff(bins) for x in grid])
+
+estimator = GaussianEstimatior(
+   grid_points = grid,
+   bin_edges = bins,
+   gaussians = gaussians * u.m,
+   interpolator_cls = MomentMorphInterpolator,
+   extrapolator_cls = MomentMorphNearestSimplexExtrapolator
+)
+
+
+

This estimator object can now easily be used to estimate Gaussians at arbitrary target points:

+
targets = np.array([[0.9], [1.5]])
+
+results = u.Quantity([estimator(target).squeeze() for target in targets])
+
+
+
+
+

Helper Classes

+ + + + + + +

PDFNormalization

How a discrete PDF is normalized

+
+
+

Base Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

BaseComponentEstimator

Base class for all Estimators working on specific IRF components.

ParametrizedComponentEstimator

Base class for all Estimators working on IRF components that represent parametrized or scalar quantities.

DiscretePDFComponentEstimator

Base class for all Estimators working on IRF components that represent discretized PDFs.

BaseInterpolator

Base class for all interpolators, only knowing grid-points, providing a common __call__-interface.

ParametrizedInterpolator

Base class for all interpolators used with IRF components that can be independently interpolated, e.g. parametrized ones like 3Gauss but also AEff.

DiscretePDFInterpolator

Base class for all interpolators used with binned IRF components like EDisp.

BaseExtrapolator

Base class for all extrapolators, only knowing grid-points, providing a common __call__-interface.

ParametrizedExtrapolator

Base class for all extrapolators used with IRF components that can be treated independently, e.g. parametrized ones like 3Gauss but also AEff.

DiscretePDFExtrapolator

Base class for all extrapolators used with binned IRF components like EDisp.

BaseNearestNeighborSearcher

Dummy NearestNeighbor approach usable instead of actual Interpolation/Extrapolation

+
+
+

Full API

+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

BaseComponentEstimator(grid_points)

Base class for all Estimators working on specific IRF components.

BaseInterpolator(grid_points)

Base class for all interpolators, only knowing grid-points, providing a common __call__-interface.

BaseNearestNeighborSearcher(grid_points, values)

Dummy NearestNeighbor approach usable instead of actual Interpolation/Extrapolation

BaseExtrapolator(grid_points)

Base class for all extrapolators, only knowing grid-points, providing a common __call__-interface.

PDFNormalization(value)

How a discrete PDF is normalized

DiscretePDFExtrapolator(grid_points, ...[, ...])

Base class for all extrapolators used with binned IRF components like EDisp.

ParametrizedExtrapolator(grid_points, params)

Base class for all extrapolators used with IRF components that can be treated independently, e.g. parametrized ones like 3Gauss but also AEff.

DiscretePDFComponentEstimator(grid_points, ...)

Base class for all Estimators working on IRF components that represent discretized PDFs.

DiscretePDFInterpolator(grid_points, ...[, ...])

Base class for all interpolators used with binned IRF components like EDisp.

DiscretePDFNearestNeighborSearcher(...[, ...])

Dummy NearestNeighbor approach usable instead of actual interpolation/extrapolation.

GridDataInterpolator(grid_points, params, ...)

"Wrapper arounf scipy.interpolate.griddata.

MomentMorphInterpolator(grid_points, ...[, ...])

Interpolator class providing Moment Morphing to interpolate discretized PDFs.

MomentMorphNearestSimplexExtrapolator(...[, ...])

Extrapolator class extending moment morphing interpolation outside a grid's convex hull.

ParametrizedComponentEstimator(grid_points, ...)

Base class for all Estimators working on IRF components that represent parametrized or scalar quantities.

ParametrizedInterpolator(grid_points, params)

Base class for all interpolators used with IRF components that can be independently interpolated, e.g. parametrized ones like 3Gauss but also AEff.

ParametrizedNearestNeighborSearcher(...[, ...])

Dummy NearestNeighbor approach usable instead of actual interpolation/extrapolation Compatible with parametrized IRF component API.

ParametrizedNearestSimplexExtrapolator(...)

Extrapolator class extending linear or baryzentric interpolation outside a grid's convex hull.

ParametrizedVisibleEdgesExtrapolator(...[, m])

Extrapolator using blending over visible edges.

QuantileInterpolator(grid_points, bin_edges, ...)

Interpolator class providing quantile interpoalation.

EffectiveAreaEstimator(grid_points, ...[, ...])

Estimator class for effective area tables (AEFF_2D).

RadMaxEstimator(grid_points, rad_max[, ...])

Estimator class for rad-max tables (RAD_MAX, RAD_MAX_2D).

EnergyDispersionEstimator(grid_points, ...)

Estimator class for energy dispersions (EDISP_2D).

PSFTableEstimator(grid_points, ...[, ...])

Estimator class for point spread function tables (PSF_TABLE).

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/introduction.html b/introduction.html new file mode 100644 index 000000000..6203a1060 --- /dev/null +++ b/introduction.html @@ -0,0 +1,246 @@ + + + + + + + Introduction to pyirf — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Introduction to pyirf

+

pyirf aims to provide functions to calculate the Instrument Response Functions (IRFs) +and sensitivity for Imaging Air Cherenkov Telescopes.

+

To support a wide range of use cases, pyirf opts for a library approach of +composable building blocks with well-defined inputs and outputs.

+

For more information on IRFs, have a look at the Specification of the Data Formats for Gamma-Ray Astronomy +or the ctools documentation on IRFs.

+

Currently, pyirf allows calculation of the usual factorization of the IRFs into:

+
    +
  • Effective area

  • +
  • Energy migration

  • +
  • Point spread function

  • +
+

Additionally, functions for calculating point-source flux sensitivity are provided. +Flux sensitivity is defined as the smallest flux an IACT can detect with a certain significance, +usually 5 σ according to the Li&Ma likelihood ratio test, in a specified amount of time.

+

pyirf also provides functions to calculate event weights, that are needed +to translate a set of simulations to a physical flux for calculating sensitivity +and expected event counts.

+

Event selection with energy dependent cuts is also supported, +but at the moment, only rudimentary functions to find optimal cuts are provided.

+
+

Input formats

+

pyirf does not rely on specific input file formats. +All functions take numpy arrays, astropy quantities or astropy tables for the +required data and also return the results as these objects.

+

~pyirf.io provides functions to export the internal IRF representation +to FITS files following the Specification of the Data Formats for Gamma-Ray Astronomy

+
+

DL2 event lists

+

Most functions for calculating IRFs need DL2 event lists as input. +We use ~astropy.table.QTable instances for this. +QTable are very similar to the standard ~astropy.table.Table, +but offer better interoperability with astropy.units.Quantity.

+

We expect certain columns to be present in the tables with the appropriate units. +To learn which functions need which columns to be present, have a look at the API Documentation

+

Most functions only need a small subgroup of these columns.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Column definitions for DL2 event lists

Column

Unit

Explanation

true_energy

TeV

True energy of the simulated shower

weight

Event weight

true_source_fov_offset

deg

Distance of the true origin to the FOV center

reco_source_fov_offset

deg

Distance of the reco origin to the FOV center

true_alt

deg

True altitude of the shower origin

true_az

deg

True azimuth of the shower origin

pointing_alt

deg

Altitude of the field of view center

pointing_az

deg

Azimuth of the field of view center

reco_energy

TeV

Reconstructed energy of the simulated shower

reco_alt

deg

Reconstructed altitude of shower origin

reco_az

deg

Reconstructed azimuth of shower origin

gh_score

Gamma/Hadron classification output

multiplicity

Number of telescopes used in the reconstruction

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/io/index.html b/io/index.html new file mode 100644 index 000000000..f7804e362 --- /dev/null +++ b/io/index.html @@ -0,0 +1,196 @@ + + + + + + + Input / Output — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Input / Output

+
+

Introduction

+

This module contains functions to read input data and write IRFs in GADF format.

+

Currently there is only support for reading EventDisplay DL2 FITS files, +which were converted from the ROOT files by using EventDisplay DL2 conversion scripts.

+
+
+

Reference/API

+
+
+

pyirf.io Package

+
+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + +

read_eventdisplay_fits(infile[, use_histogram])

Read a DL2 FITS file as produced by the EventDisplay DL2 converter from ROOT files: https://github.com/Eventdisplay/Converters/blob/master/DL2/generate_DL2_file.py

create_psf_table_hdu(psf, true_energy_bins, ...)

Create a fits binary table HDU in GADF format for the PSF table.

create_aeff2d_hdu(effective_area, ...[, ...])

Create a fits binary table HDU in GADF format for effective area.

create_energy_dispersion_hdu(...[, ...])

Create a fits binary table HDU in GADF format for the energy dispersion.

create_psf_table_hdu(psf, true_energy_bins, ...)

Create a fits binary table HDU in GADF format for the PSF table.

create_rad_max_hdu(rad_max, ...[, ...])

Create a fits binary table HDU in GADF format for the directional cut.

create_background_2d_hdu(background_2d, ...)

Create a fits binary table HDU in GADF format for the background 2d table.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/irf/index.html b/irf/index.html new file mode 100644 index 000000000..1e270e15c --- /dev/null +++ b/irf/index.html @@ -0,0 +1,214 @@ + + + + + + + Instrument Response Functions — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Instrument Response Functions

+
+

Effective Area

+

The collection area, which is proportional to the gamma-ray efficiency +of detection, is computed as a function of the true energy. The events which +are considered are the ones passing the threshold of the best cutoff plus +the angular cuts.

+
+
+

Energy Dispersion Matrix

+

The energy dispersion matrix, ratio of the reconstructed energy over the true energy +as a function of the true energy, is computed with the events passing the +threshold of the best cutoff plus the angular cuts.

+

The corresponding energy migration matrix can be build from the dispersion matrix.

+
+
+

Point Spread Function

+

The PSF describes the probability of measuring a gamma ray +of a given true energy and true position at a reconstructed position.

+
+
+

Background rate

+

The background rate is calculated as the number of background-like events per +second, reconstructed energy and solid angle. +The current version is computed in radially symmetric bins in the Field Of View.

+
+
+

Reference/API

+
+
+

pyirf.irf Package

+
+

Functions

+ + + + + + + + + + + + + + + + + + + + + +

effective_area(n_selected, n_simulated, area)

Calculate effective area for histograms of selected and total simulated events

effective_area_per_energy(selected_events, ...)

Calculate effective area in bins of true energy.

effective_area_per_energy_and_fov(...)

Calculate effective area in bins of true energy and field of view offset.

energy_dispersion(selected_events, ...)

Calculate energy dispersion for the given DL2 event list.

psf_table(events, true_energy_bins, ...)

Calculate the table based PSF (radially symmetrical bins around the true source)

background_2d(events, reco_energy_bins, ...)

Calculate background rates in radially symmetric bins in the field of view.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/notebooks/fact_example.html b/notebooks/fact_example.html new file mode 100644 index 000000000..8eba84320 --- /dev/null +++ b/notebooks/fact_example.html @@ -0,0 +1,604 @@ + + + + + + + Using pyirf to calculate IRFs from the FACT Open Data — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Using pyirf to calculate IRFs from the FACT Open Data

+

Note In FACT, we used a different terminology, partly because of being a monoscopic telescope or out of confusion witht the CTA terms, in this context DL3 are reconstructed events, but not necessarily already with the IRF

+
+
[1]:
+
+
+
import numpy as np
+import astropy.units as u
+import matplotlib.pyplot as plt
+import subprocess as sp
+
+
+
+
+
[2]:
+
+
+
%matplotlib inline
+
+
+
+
+

Download Data

+
+
[3]:
+
+
+
path = "gamma_test_dl3.hdf5"
+url = f"https://factdata.app.tu-dortmund.de/dl3/FACT-Tools/v1.1.2/{path}"
+ret = sp.run(["curl", "-z", path, "-fsSLO", url], stdout=sp.PIPE, stderr=sp.PIPE, encoding='utf-8')
+if ret.returncode != 0:
+    raise IOError(ret.stderr)
+
+
+
+
+
+

Read in the data

+
+
[4]:
+
+
+
from astropy.table import QTable
+import astropy.units as u
+import tables
+
+
+
+
+

Simulated Event Info

+

Currently, pyirf only works with powerlaw simulated events, like CORSIKA does it. We want to also support arbitrary histograms / event distributions, but that is not yet implemented.

+

This can be created from a file with that information, but I will just create it here.

+
+
[5]:
+
+
+
from pyirf.simulations import SimulatedEventsInfo
+
+simulation_info = SimulatedEventsInfo(
+    energy_min=200 * u.GeV,
+    energy_max=50 * u.TeV,
+    spectral_index=-2.7,
+    n_showers=12600000,
+    max_impact=300 * u.m,
+    viewcone_min=0 * u.deg,
+    viewcone_max=0 * u.deg,
+)
+
+
+
+
+
+

DL2 Event List

+

pyirf does not prescribe or use a specific DL2 file format. You need to read the data into an astropy.table.QTable following our conventions, detailed in the docs here:

+

https://cta-observatory.github.io/pyirf/introduction.html#dl2-event-lists

+
+
The FACT-Tools / aict-tools analysis chain uses a column-oriented hdf5 file written using h5py.
+
Unfortunately, units have to be known and are not in the metadata.
+
+
+
[6]:
+
+
+
gammas = QTable()
+
+# mapping of <target column name>: (<column in the file, unit>)
+columns = {
+    'obs_id': ('run_id', None),
+    'event_id': ('event_num', None),
+    'reco_energy': ('gamma_energy_prediction', u.GeV),
+    'true_energy': ('corsika_event_header_total_energy', u.GeV),
+    'true_az': ('source_position_az', u.deg),
+    'pointing_az': ('pointing_position_az', u.deg),
+    'theta': ('theta_deg', u.deg),
+    'gh_score': ('gamma_prediction', None),
+}
+
+with tables.open_file('gamma_test_dl3.hdf5', mode='r') as f:
+    events = f.root.events
+
+    for col, (name, unit) in columns.items():
+        if unit is not None:
+            gammas[col] = u.Quantity(events[name][:], unit, copy=False)
+        else:
+            gammas[col] = events[name][:]
+
+    gammas['true_alt'] = u.Quantity(90 - events['source_position_zd'][:], u.deg, copy=False)
+    gammas['pointing_alt'] = u.Quantity(90 - events['pointing_position_zd'][:], u.deg, copy=False)
+
+
+# make it display nice
+for col in gammas.colnames:
+    if gammas[col].dtype == float:
+        gammas[col].info.format = '.2f'
+
+
+
+
+
[7]:
+
+
+
gammas[:10]
+
+
+
+
+
[7]:
+
+
+
+
QTable length=10 + + + + + + + + + + + + + + +
obs_idevent_idreco_energytrue_energytrue_azpointing_azthetagh_scoretrue_altpointing_alt
GeVGeVdegdegdegdegdeg
int64int64float64float64float64float64float64float64float64float64
11583403638.00712.87353.00349.761.490.4579.3479.33
106806237815.28885.04353.0042.350.020.8289.3789.23
11325166838.62928.05353.00-4.950.260.7583.3783.92
13486165680.72420.22353.00348.770.130.7881.9282.02
142281221896.682458.86353.00351.270.080.8870.5170.69
1083122214430.695347.98353.00-5.940.050.9766.5566.12
133273103649.234550.83353.00347.980.040.9183.5383.75
1072962371723.472109.52353.00350.670.050.2082.5682.05
14269217956.90953.94353.00-5.980.170.7969.5370.02
108669183804.27626.66353.00-6.380.120.8461.0660.54
+
+
+
+

Apply Event Selection

+

We remove likely hadronic events by requiring a minimal gh_score.

+

We will calculate point-like IRFs, that means selecting events in a radius around the assumed source position.

+
+
[8]:
+
+
+
gammas['selected_gh'] = gammas['gh_score'] > 0.8
+gammas['selected_theta'] = gammas['theta'] < 0.16 * u.deg
+
+gammas['selected'] = gammas['selected_gh'] & gammas['selected_theta']
+
+np.count_nonzero(gammas['selected']) / len(gammas)
+
+
+
+
+
[8]:
+
+
+
+
+0.18115575805640652
+
+
+
+
+
+

Calculate IRFs

+
+

Effective area

+

We only have point-like simulations at a specific wobble offset (0.6° for FACT), so we calculate the effective area for all events at once, equivalent to a single fov offset bin.

+
+

Create the binning

+
+
[9]:
+
+
+
from pyirf.binning import create_bins_per_decade, bin_center
+
+
+
+
+
[10]:
+
+
+
true_energy_bins = create_bins_per_decade(simulation_info.energy_min, simulation_info.energy_max, 5)
+
+# single offset bin around the wobble distance
+# since we are dealing with point-like simulations
+wobble_offset = 0.6 * u.deg
+fov_offset_bins = [0.59, 0.61] * u.deg
+
+
+
+
+
+
+

Calculate effective area

+

Effective area is calculated before and after cuts, for the IRF, we only need after the event selection has been applied.

+

The difference between point-like IRFs and Full-Enclosure IRFs is if a theta cut has been applied or not.

+
+
[11]:
+
+
+
from pyirf.irf import effective_area_per_energy
+
+aeff_all = effective_area_per_energy(gammas, simulation_info, true_energy_bins)
+aeff_selected = effective_area_per_energy(gammas[gammas['selected']], simulation_info, true_energy_bins)
+
+
+
+

Let’s use gammapy to plot the IRF

+
+
[12]:
+
+
+
# utility function to converet pyirf Quantities to the gammapy classes
+from pyirf.gammapy import create_effective_area_table_2d
+
+plt.figure()
+
+for aeff, label in zip((aeff_all, aeff_selected), ('All Events', 'Selected Events')):
+    aeff_gammapy = create_effective_area_table_2d(
+        # add a new dimension for the single fov offset bin
+        effective_area=aeff[..., np.newaxis],
+        true_energy_bins=true_energy_bins,
+        fov_offset_bins=fov_offset_bins,
+    )
+
+
+    aeff_gammapy.plot_energy_dependence(label=label, offset=[wobble_offset])
+
+plt.xlim(true_energy_bins.min().to_value(u.GeV), true_energy_bins.max().to_value(u.GeV))
+plt.yscale('log')
+plt.xscale('log')
+plt.legend()
+
+print(aeff_gammapy)
+
+
+
+
+
+
+
+
+/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from .autonotebook import tqdm as notebook_tqdm
+
+
+
+
+
+
+
+EffectiveAreaTable2D
+--------------------
+
+  axes  : ['energy_true', 'offset']
+  shape : (11, 1)
+  ndim  : 2
+  unit  : m2
+  dtype : float64
+
+
+
+
+
+
+
+../_images/notebooks_fact_example_20_2.png +
+
+
+
+

Point Spread Function

+

The point spread function describes how well the direction of the gamma rays is estimated.

+
+
[13]:
+
+
+
from pyirf.irf import psf_table
+from pyirf.utils import calculate_source_fov_offset
+
+
+gammas['true_source_fov_offset'] = calculate_source_fov_offset(gammas)
+
+
+source_offset_bins = np.linspace(0, 3, 100) * u.deg
+
+# calculate this only for the events after the gamma/hadron separation
+psf = psf_table(gammas[gammas['selected_gh']], true_energy_bins, source_offset_bins, fov_offset_bins)
+
+
+
+
+
[14]:
+
+
+
psf.shape
+
+
+
+
+
[14]:
+
+
+
+
+(11, 1, 99)
+
+
+

Again, let’s use gammapy to plot:

+
+
[15]:
+
+
+
from pyirf.gammapy import create_psf_3d
+
+psf_gammapy = create_psf_3d(psf, true_energy_bins, source_offset_bins,  fov_offset_bins)
+
+plt.figure()
+psf_gammapy.plot_psf_vs_rad(offset=[wobble_offset], energy_true=[1., 10.]*u.TeV)
+plt.legend(plt.gca().lines, ['1 TeV', '10 TeV'])
+
+
+
+
+
[15]:
+
+
+
+
+<matplotlib.legend.Legend at 0x7f233171e0d0>
+
+
+
+
+
+
+../_images/notebooks_fact_example_25_1.png +
+
+
+
+

Energy Dispersion

+

Describes how well the energy is estimated

+
+
[16]:
+
+
+
from pyirf.irf import energy_dispersion
+
+# logarithmic space, is "symmetric" in terms of ratios 0.1 is a factor of 10 from 1 is a factor of 10 from 10
+migration_bins = np.geomspace(0.1, 10, 100)
+
+edisp = energy_dispersion(
+    gammas[gammas['selected']],
+    true_energy_bins=true_energy_bins,
+    fov_offset_bins=fov_offset_bins,
+    migration_bins=migration_bins,
+)
+
+
+
+

Plot edisp

+
+
[17]:
+
+
+
from gammapy.irf import EnergyDispersion2D
+
+plt.figure()
+plt.pcolormesh(
+    true_energy_bins.to_value(u.GeV),
+    migration_bins,
+    edisp[:, :, 0].T,
+    cmap='inferno'
+)
+
+plt.xlabel('$E_\mathrm{true} / \mathrm{GeV}$')
+plt.ylabel('$\mu$')
+plt.yscale('log')
+plt.xscale('log')
+
+
+
+
+
+
+
+../_images/notebooks_fact_example_29_0.png +
+
+
+
+
+

Export to GADF FITS files

+

We use the classes and methods from astropy.io.fits and pyirf.io.gadf to write files following the GADF specification, which can be found here:

+

https://gamma-astro-data-formats.readthedocs.io/en/latest/

+
+
[18]:
+
+
+
from pyirf.io.gadf import create_aeff2d_hdu, create_energy_dispersion_hdu, create_psf_table_hdu
+from astropy.io import fits
+from astropy.time import Time
+from pyirf import __version__
+
+# set some common meta data for all hdus
+meta = dict(
+    CREATOR='pyirf-v' + __version__,
+    TELESCOP='FACT',
+    INSTRUME='FACT',
+    DATE=Time.now().iso,
+)
+
+hdus = []
+
+# every fits file has to have an Image HDU as first HDU.
+# GADF only uses Binary Table HDUs, so we need to add an empty HDU in front
+hdus.append(fits.PrimaryHDU(header=fits.Header(meta)))
+
+hdus.append(create_aeff2d_hdu(aeff_selected, true_energy_bins, fov_offset_bins, **meta))
+hdus.append(create_energy_dispersion_hdu(edisp, true_energy_bins, migration_bins, fov_offset_bins, **meta))
+hdus.append(create_psf_table_hdu(psf, true_energy_bins,    source_offset_bins, fov_offset_bins, **meta))
+
+fits.HDUList(hdus).writeto('fact_irf.fits.gz', overwrite=True)
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/notebooks/fact_example.ipynb b/notebooks/fact_example.ipynb new file mode 100644 index 000000000..14f9ff553 --- /dev/null +++ b/notebooks/fact_example.ipynb @@ -0,0 +1,763 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "plastic-system", + "metadata": {}, + "source": [ + "# Using `pyirf` to calculate IRFs from the FACT Open Data\n", + "\n", + "\n", + "**Note** In FACT, we used a different terminology, partly because of being a monoscopic telescope or out of confusion witht the CTA terms, in this context DL3 are reconstructed events, but not necessarily already with the IRF" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "alike-dover", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:02.200961Z", + "iopub.status.busy": "2024-05-14T10:12:02.200421Z", + "iopub.status.idle": "2024-05-14T10:12:02.794490Z", + "shell.execute_reply": "2024-05-14T10:12:02.793748Z" + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import astropy.units as u\n", + "import matplotlib.pyplot as plt\n", + "import subprocess as sp" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "german-carroll", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:02.797647Z", + "iopub.status.busy": "2024-05-14T10:12:02.797372Z", + "iopub.status.idle": "2024-05-14T10:12:02.806910Z", + "shell.execute_reply": "2024-05-14T10:12:02.806244Z" + } + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "id": "analyzed-canberra", + "metadata": {}, + "source": [ + "## Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "joined-experiment", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:02.809580Z", + "iopub.status.busy": "2024-05-14T10:12:02.809367Z", + "iopub.status.idle": "2024-05-14T10:12:09.072481Z", + "shell.execute_reply": "2024-05-14T10:12:09.071697Z" + } + }, + "outputs": [], + "source": [ + "path = \"gamma_test_dl3.hdf5\"\n", + "url = f\"https://factdata.app.tu-dortmund.de/dl3/FACT-Tools/v1.1.2/{path}\"\n", + "ret = sp.run([\"curl\", \"-z\", path, \"-fsSLO\", url], stdout=sp.PIPE, stderr=sp.PIPE, encoding='utf-8')\n", + "if ret.returncode != 0:\n", + " raise IOError(ret.stderr)" + ] + }, + { + "cell_type": "markdown", + "id": "accredited-count", + "metadata": {}, + "source": [ + "## Read in the data\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "italian-redhead", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:09.075791Z", + "iopub.status.busy": "2024-05-14T10:12:09.075326Z", + "iopub.status.idle": "2024-05-14T10:12:11.324316Z", + "shell.execute_reply": "2024-05-14T10:12:11.323584Z" + } + }, + "outputs": [], + "source": [ + "from astropy.table import QTable\n", + "import astropy.units as u\n", + "import tables" + ] + }, + { + "cell_type": "markdown", + "id": "healthy-wrapping", + "metadata": {}, + "source": [ + "### Simulated Event Info\n", + "\n", + "Currently, pyirf only works with powerlaw simulated events, like CORSIKA does it.\n", + "We want to also support arbitrary histograms / event distributions, but that is not yet implemented.\n", + "\n", + "This can be created from a file with that information, but I will just create it here." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "micro-anniversary", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:11.327881Z", + "iopub.status.busy": "2024-05-14T10:12:11.327319Z", + "iopub.status.idle": "2024-05-14T10:12:11.334104Z", + "shell.execute_reply": "2024-05-14T10:12:11.333455Z" + } + }, + "outputs": [], + "source": [ + "from pyirf.simulations import SimulatedEventsInfo\n", + "\n", + "simulation_info = SimulatedEventsInfo(\n", + " energy_min=200 * u.GeV,\n", + " energy_max=50 * u.TeV,\n", + " spectral_index=-2.7,\n", + " n_showers=12600000,\n", + " max_impact=300 * u.m,\n", + " viewcone_min=0 * u.deg,\n", + " viewcone_max=0 * u.deg,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "interior-richards", + "metadata": {}, + "source": [ + "### DL2 Event List\n", + "\n", + "`pyirf` does not prescribe or use a specific DL2 *file* format.\n", + "You need to read the data into an `astropy.table.QTable` following our conventions, detailed in the docs here: \n", + "\n", + "\n", + "\n", + "The FACT-Tools / aict-tools analysis chain uses a column-oriented hdf5 file written using h5py. \n", + "Unfortunately, units have to be known and are not in the metadata." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "southeast-reform", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:11.338184Z", + "iopub.status.busy": "2024-05-14T10:12:11.336820Z", + "iopub.status.idle": "2024-05-14T10:12:11.424446Z", + "shell.execute_reply": "2024-05-14T10:12:11.423818Z" + } + }, + "outputs": [], + "source": [ + "gammas = QTable()\n", + "\n", + "# mapping of : ()\n", + "columns = {\n", + " 'obs_id': ('run_id', None),\n", + " 'event_id': ('event_num', None),\n", + " 'reco_energy': ('gamma_energy_prediction', u.GeV),\n", + " 'true_energy': ('corsika_event_header_total_energy', u.GeV),\n", + " 'true_az': ('source_position_az', u.deg),\n", + " 'pointing_az': ('pointing_position_az', u.deg),\n", + " 'theta': ('theta_deg', u.deg),\n", + " 'gh_score': ('gamma_prediction', None),\n", + "}\n", + "\n", + "with tables.open_file('gamma_test_dl3.hdf5', mode='r') as f:\n", + " events = f.root.events\n", + " \n", + " for col, (name, unit) in columns.items():\n", + " if unit is not None:\n", + " gammas[col] = u.Quantity(events[name][:], unit, copy=False)\n", + " else:\n", + " gammas[col] = events[name][:]\n", + " \n", + " gammas['true_alt'] = u.Quantity(90 - events['source_position_zd'][:], u.deg, copy=False)\n", + " gammas['pointing_alt'] = u.Quantity(90 - events['pointing_position_zd'][:], u.deg, copy=False)\n", + "\n", + " \n", + "# make it display nice\n", + "for col in gammas.colnames:\n", + " if gammas[col].dtype == float:\n", + " gammas[col].info.format = '.2f'" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "optional-crawford", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:11.427376Z", + "iopub.status.busy": "2024-05-14T10:12:11.427168Z", + "iopub.status.idle": "2024-05-14T10:12:11.436063Z", + "shell.execute_reply": "2024-05-14T10:12:11.435404Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
QTable length=10\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
obs_idevent_idreco_energytrue_energytrue_azpointing_azthetagh_scoretrue_altpointing_alt
GeVGeVdegdegdegdegdeg
int64int64float64float64float64float64float64float64float64float64
11583403638.00712.87353.00349.761.490.4579.3479.33
106806237815.28885.04353.0042.350.020.8289.3789.23
11325166838.62928.05353.00-4.950.260.7583.3783.92
13486165680.72420.22353.00348.770.130.7881.9282.02
142281221896.682458.86353.00351.270.080.8870.5170.69
1083122214430.695347.98353.00-5.940.050.9766.5566.12
133273103649.234550.83353.00347.980.040.9183.5383.75
1072962371723.472109.52353.00350.670.050.2082.5682.05
14269217956.90953.94353.00-5.980.170.7969.5370.02
108669183804.27626.66353.00-6.380.120.8461.0660.54
" + ], + "text/plain": [ + "\n", + "obs_id event_id reco_energy true_energy ... gh_score true_alt pointing_alt\n", + " GeV GeV ... deg deg \n", + "int64 int64 float64 float64 ... float64 float64 float64 \n", + "------ -------- ----------- ----------- ... -------- -------- ------------\n", + " 11583 403 638.00 712.87 ... 0.45 79.34 79.33\n", + "106806 237 815.28 885.04 ... 0.82 89.37 89.23\n", + " 11325 166 838.62 928.05 ... 0.75 83.37 83.92\n", + " 13486 165 680.72 420.22 ... 0.78 81.92 82.02\n", + " 14228 122 1896.68 2458.86 ... 0.88 70.51 70.69\n", + "108312 221 4430.69 5347.98 ... 0.97 66.55 66.12\n", + " 13327 310 3649.23 4550.83 ... 0.91 83.53 83.75\n", + "107296 237 1723.47 2109.52 ... 0.20 82.56 82.05\n", + " 14269 217 956.90 953.94 ... 0.79 69.53 70.02\n", + "108669 183 804.27 626.66 ... 0.84 61.06 60.54" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gammas[:10]" + ] + }, + { + "cell_type": "markdown", + "id": "virgin-source", + "metadata": {}, + "source": [ + "### Apply Event Selection\n", + "\n", + "We remove likely hadronic events by requiring a minimal `gh_score`.\n", + "\n", + "We will calculate point-like IRFs, that means selecting events in a radius around the \n", + "assumed source position." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "proved-store", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:11.438525Z", + "iopub.status.busy": "2024-05-14T10:12:11.438178Z", + "iopub.status.idle": "2024-05-14T10:12:11.444442Z", + "shell.execute_reply": "2024-05-14T10:12:11.443774Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.18115575805640652" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gammas['selected_gh'] = gammas['gh_score'] > 0.8\n", + "gammas['selected_theta'] = gammas['theta'] < 0.16 * u.deg\n", + "\n", + "gammas['selected'] = gammas['selected_gh'] & gammas['selected_theta']\n", + "\n", + "np.count_nonzero(gammas['selected']) / len(gammas)" + ] + }, + { + "cell_type": "markdown", + "id": "universal-potential", + "metadata": {}, + "source": [ + "## Calculate IRFs\n", + "\n", + "### Effective area\n", + "\n", + "We only have point-like simulations at a specific wobble offset (0.6° for FACT),\n", + "so we calculate the effective area for all events at once, equivalent to a single fov offset bin.\n", + "\n", + "\n", + "#### Create the binning" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "failing-exchange", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:11.447276Z", + "iopub.status.busy": "2024-05-14T10:12:11.446710Z", + "iopub.status.idle": "2024-05-14T10:12:11.562998Z", + "shell.execute_reply": "2024-05-14T10:12:11.562310Z" + } + }, + "outputs": [], + "source": [ + "from pyirf.binning import create_bins_per_decade, bin_center" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "compact-complaint", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:11.567114Z", + "iopub.status.busy": "2024-05-14T10:12:11.566346Z", + "iopub.status.idle": "2024-05-14T10:12:11.571018Z", + "shell.execute_reply": "2024-05-14T10:12:11.570394Z" + } + }, + "outputs": [], + "source": [ + "true_energy_bins = create_bins_per_decade(simulation_info.energy_min, simulation_info.energy_max, 5)\n", + "\n", + "# single offset bin around the wobble distance\n", + "# since we are dealing with point-like simulations \n", + "wobble_offset = 0.6 * u.deg\n", + "fov_offset_bins = [0.59, 0.61] * u.deg" + ] + }, + { + "cell_type": "markdown", + "id": "blocked-japan", + "metadata": {}, + "source": [ + "### Calculate effective area\n", + "\n", + "\n", + "Effective area is calculated before and after cuts, for the IRF, we only need after the event selection\n", + "has been applied.\n", + "\n", + "The difference between point-like IRFs and Full-Enclosure IRFs is if a theta cut has been applied or not." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "frequent-concert", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:11.573413Z", + "iopub.status.busy": "2024-05-14T10:12:11.573189Z", + "iopub.status.idle": "2024-05-14T10:12:11.759659Z", + "shell.execute_reply": "2024-05-14T10:12:11.759071Z" + } + }, + "outputs": [], + "source": [ + "from pyirf.irf import effective_area_per_energy\n", + "\n", + "aeff_all = effective_area_per_energy(gammas, simulation_info, true_energy_bins)\n", + "aeff_selected = effective_area_per_energy(gammas[gammas['selected']], simulation_info, true_energy_bins)" + ] + }, + { + "cell_type": "markdown", + "id": "armed-street", + "metadata": {}, + "source": [ + "Let's use gammapy to plot the IRF" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "norman-personal", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:11.762775Z", + "iopub.status.busy": "2024-05-14T10:12:11.762348Z", + "iopub.status.idle": "2024-05-14T10:12:13.039571Z", + "shell.execute_reply": "2024-05-14T10:12:13.038804Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "EffectiveAreaTable2D\n", + "--------------------\n", + "\n", + " axes : ['energy_true', 'offset']\n", + " shape : (11, 1)\n", + " ndim : 2\n", + " unit : m2\n", + " dtype : float64\n", + "\n" + ] + }, + { + "data": { + "image/png": "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VaumO+6446zXBU7KPZSvf8xe5zcf/+tOio6KMNgICDLeYmnhi/ZZZA2p1+1m+wAAAAAAAAB+OGaI1aVLF9/jo0ePauHChbrwwgvPer3p06f7HkdEROj888+vUL/KsHTpUn300UdGPsvr9eq6667Txx9/XOJ6RESEmjVrpvj4eG3atEkHD/7vfil79uzRZZddpq+++koXXHBBmT9r7969viGknfj4eE2bNk0dOnQo/w8CnObJz7J1pKDINrssuaEuatfAcCMgyKz+WNq/yT47b7gUG1w7kwEAAAAAABC+zn4rk2Ft27ZVixYt5HK5JEnPP//8Wa916NAhTZgwQS6XSy6XSykpKYqLC+6jk2rWrFmp6z333HOlBlh33HGHtmzZoo0bN2rFihXKy8vThx9+qGbNmvlec/ToUV177bUlhltny+Vy6f7779fatWt1+eWXV3g9YNH6vfr4B/tdjDHVIvTw4I6GGwFBxrKkBRPsM3ek1Ptus30AAAAAAACAM3DMEEuShg8fLsuyZFmWZs6cqSlTppR7jeLiYg0fPlzbt2+XZVmSpNGjR1d21QqJi4vTRRddpD/+8Y+aPn26cnJyNGvWrEpbf9++fXryySdLXHv66af16quvljhW0e1268orr9SiRYuUlJTku75t27ZyDRHr1Kmjn376ST/99JN++OEHzZ07V0888YSaNm2qF198USNHjtT27dsr/HMhvBUWefXwx6v85vde3EaNa9cw2AgIQhu+lnb9aJ91vkaq3cw+AwAAAAAAAALAUUOsBx54QA0aNJDL5ZJlWRo1apSee+45FRcXl+n9a9as0YABAzRr1izfLqy2bdvqxhtvrOLmZTNkyBBlZWXpwIEDmjdvnp599ln95je/UfPmzSv1c5599lkdPnzY97xfv3568MEH/b6+SZMmmjRpUolrEyZM0L59+8r0eREREUpOTlZycrK6dOmiAQMG6K9//auysrI0cOBAffrpp+revbuys7PP7gcCJE1asFEb9nhss7aJNXVLnxaGGwFByN8uLEnqM8ZcDwAAAAAAAKAMHDXEio2N1aRJk+R2u+VyuVRcXKyxY8eqdevW+stf/qIPPvhAknw7rJYtW6b3339ff/vb33TppZcqOTlZCxYs8O3mio6O1rRp03xHFAZaq1at1LFjR7ndVfc/i9fr1eTJk0tce/TRR3/x38HA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W52NIUOG6NJLL1X79u3ldpfcKLdp06ZK+5xnn31Whw//79iufv366cEHH/T7+iZNmmjSpEm6+OKLfdcmTJige+65R/Xq1SvXZ1uWpd/97neaOXOmJKlXr16aNGlSOX8CACGjuEha9E/7LCpWShlltg8AAAAAAACAoBAUQ6zHHnvM93j48OFKSkoKXJkAa9WqVZV/htfr1eTJk0tce/TRR333FvNn4MCBuvDCCzV//nxJ0uHDh/Xee+/pzjvvLPNnW5al0aNH680335QkdevWTV988YViY2PL+VMACBlZH0kHtthnPW6RYuqa7QMAAAAAAAAgKATFEOvUAUrfvn39DrEYdlWORYsWac+e/x3b1bJlS1100UVleu/IkSN9QyxJmjFjRrmGWL///e/1r3/9S5J07rnnas6cOapdu3aZ3w8gxFiWtGCCfeaOks4fbbYPAAAAAAAAgKARFEMs6cQOnV/aCVTWYRfO7NNPPy3x/JJLLvnFf/envvZU33zzjTweT5l2Uv3+97/XxIkTJUmdO3fW3Llzy30UIYAQ8/NsKTfLPutynRTfxGwfAAAAAAAAAEHD/csvMaOsQxTLsqq4Sej74YcfSjzv3bt3md/buHHjEsPDwsJCrV69+hffd8899+jll1+WJHXq1Elz585VQkJCmT8XQIjytwtLLqnPvSabAAAAAAAAAAgyQTHEio6O9g2nCgoKzvjasg674F92dnaJ5x07dizX+09//enrnW7MmDF66aWXJJ0YYH399deqX79+uT4TQAja/J205Tv7rMMQKaGN2T4AAAAAAAAAgkpQDLFOPVLulwYiqJhjx45py5YtJa41bdq0XGuc/vq1a9f6fe19992nf/7zn5JODL++/vprNWjQoFyfByBELXzBf9b3XlMtAAAAAAAAAASpoLgnVpcuXbR9+3ZZlqVXXnlFt9xyi+rUqRPoWiFp7969JY5kjIqKKvdQqUmTkveoyc3NtX3dgw8+qBdeeEGSVL9+fb300kvKzc31+3pJqlOnTqn1AYSg3VnSui/ssxapUpPuZvsAAAAAAAAACDpBMcS64oor9Nlnn8nlcmnTpk1q166drrrqKnXq1Em1atWyPUJw9uzZ2rZtW6V1GD58eKWtFcyOHDlS4nlMTEy5j2iMjY0945onvfvuu77He/bs0cCBA39x7REjRig9Pb1cfU6Vm5ur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9cYbb+inn36y2u/QoUM6dOiQ3n//fXXr1k3/+te/yrQdI1BdkMQC4J4SlktZF623dR4n3bAcHgAAAABQOXl5eqhR3eJXe6DqKXzu0sGDB3XvvfeWaa79+/crNjZWnTp1ckRoVdKgQYP05z//WRkZGZKkzz//XO+++668vP53WzgtLU1r1661lCMiItSjR48S5/X39zds/7du3TrDnGVRv379co13F/fff7/uv/9+JSYmav369dq8ebN++OEHnTp1qkjfrVu3qlu3blq0aJEefvhhF0QLuD+SWADcj9ks7Zxjvc2nttR6sHPjAQAAAAAA5ZaYmKgffvjBoXMuWLCAJFYJatasqUGDBlmSh6mpqVq3bp369etn6fP5558rNzfXUh45cqThLC1rQkJCDEms9u3bKzQ01MHRV26RkZGaMGGCJkyYIEk6duyYvvvuOy1fvlzr16+3rDzLycnRyJEjdccdd6hx48auDBlwS5yJBcD9pOyWTu+13tbuMakGn9IDAAAAAKCyWbBggcPPJPr888+Vk5Pj0DmrmsJbAxbeOrDw6riRI0eWOmdkZKShfOTIkTJGV300adJE48aN07p16xQfH2/YHjMrK0sffvihC6MD3BdJLADuJ3Zu8W0dxzgvDgAAAAAA4BBms7lI8mTz5s0ym812f91zzz2WOc6fP681a9Y4++FUKr169VJ4eLilvGbNGl24cEGSdODAAcXFxVnaunfvrqZNm5Y6Z+/exrPsbjx3q7Ly8DDeKnd0wvVGrVu31kcffWSoK+4MLaC6I4kFwL1cOS/t+9J6W5PeUkgz58YDAAAAAADK7YcfflBiYqKlHBYWprvuuqtMcz3yyCOGckxMTHlCq/JMJpNhdVV2draWLFkiqegqrMKrtorzwAMPGMofffSRYUvCyqhmzZqG8pUrVyr0et26dTOUz507V6HXAyorklgA3MueT6T8bOttncY6NxYAAAAAAOAQhZMlQ4cOLbLyxVYDBw5UjRo1LOVvvvlGqamp5Yqvqiu8ReDChQtVUFCgTz/91FLn5+enIUOG2DRfhw4dDKuxTp48qZdfftkxwbpI3bp1DeUbk64VoXDSqk6dOhV6PaCyIokFwH0UFEix86y31WooNX/AehsAAAAAAHBbV65c0bJlywx1w4YNK/N8derU0f33328p5+XladGiRWWerzpo1qyZYeXP9u3bNXv2bCUnJ1vqBgwYoFq1atk85/Tp0w2JyHfeeUfTpk2zaxu+5ORkTZ48WbGxsTaPqSitWrUylFevXm3z6rJZs2bpww8/tGv11owZMwzlDh062DwWqE68XB0AAFgc/U66eNx6W4fRkie/sgAAAAAAqGyWLVum9PR0S7lp06bq3LlzueYcNmyY4SysBQsW6G9/+1u55nSVjRs3lnnsrbfeqkaNGtnUd9SoUdq6daul/MwzzxRpt0e3bt305ptv6oUXXrDUTZ06VatXr9azzz6r+++/v8jqovz8fB0+fFg//vijvvzyS23atEl5eXn64x//aNe1K0JoaKhuv/12xcfHS5J+++033XnnnRoxYoSaNm0qX19fQ//u3btb6hITEzVr1iy99NJL6t+/vwYMGKAuXbqoQYMGRa6zd+9evfXWW5YtHaVr53E9/vjjFfjogMqLO8KoNmJiYqzukZyZmen8YGBd7Fzr9R7eUvuR1tsAAAAAAIBbs7aVYHn1799f/v7+lpUvv/zyi/bs2aN27dqVe25nu/fee8s8dubMmZo0aZJNfYcMGaKnn35aWVlZkmT5r3TtjLI+ffrYff3nn39eqampmjlzpqVu165deuSRR+Th4aHGjRsrODhYknTx4kWdPn26ws+aKo9nnnnGsPXirl27tGvXLqt9ExMTFRERYai7dOmSFi5cqIULF0qS6tWrp9DQUAUGBiorK0tJSUm6ePFikbmee+45VmIBxSCJhWojKSlJW7ZscXUYKM6FJOnwt9bbov4kBd7k1HAAAAAAAED5nThxQt9//72h7pFHHin3vDVr1lTfvn31xRdfWOoWLFhQKZNYzlK7dm0NGDBAixcvLtI2fPhweXp6lmne9957T23bttXEiRMNCZqCggIlJSUpKSmpxPGBgYEKCgoq07UdbcSIEdq3b59mzJhh17aIxTl79qzOnj1bbLunp6deeuklvf766+W+FlBVcSYWqo2IiAj17NmzyFfHjh1dHRokKW6+pGL+OOg01qmhAAAAAAAAx1i4cKEhGdCqVSu1bt3aIXMXToZ99tlnNp9hVF0Vt2XgjauPymLkyJFKSkrS9OnT1bx581L716lTR4MHD9bChQv1+++/q23btuW6viO9/fbb2rt3r5599ll1795doaGhRbYSLGzatGlavHixhg8fbtP2jgEBARo+fLj27NlD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TzVo0EC9e/e2JLBq166tFStWqGXLluV7UAAAAKgwAQEBDp1vxowZRRJYTzzxhE6cOKFjx45pz549On/+vJYvX67GjRtb+ly5ckVDhgwxJLeKk5ycrGeeeUaSNG3aNDVt2tShjwEAcA1JLAAAgGquMt40KI3JZNLf/vY3HTp0SA8++GC55wMAAIBjBAYGqlevXpo8ebKWLl2qpKQkrVmzxmHzp6Wl6c033zTU/eMf/9B//vMfhYWFWeo8PDz00EMPadu2bYqIiLDUJycn67333iv1OuPGjdPly5fVqVMnTZo0yVHhAwAKMZnNZrOrgwAAAEDF+te//qW//vWvCgwMVIcOHdSpUyd17txZnTp1UmJionr37m3pW54zsdLS0hQZGWk4Y+sf//iHnn/+eav9U1JS1L17d8P1Xn31Vb3++us2XS8/P18HDhywfJ+Wlqbt27fro48+UkpKih544AH997//VcOGDcv0eAAAAOAYR48eVXZ2tlq0aCEPD+Pn6jdv3uywv0enTJmid955x1Lu0aOHNm/eLJPJVOyY7777Tn369LGUAwMDlZiYqODgYKv9P/74Y40ZM0be3t7atWuXbrvttiJ9rl/PkefNAkB1RBILAACgGqhKNw1skZGRoUGDBmn9+vW66aab9P3337OlIAAAgJty1N+jBQUFql+/vs6ePWup27Rpk2Hu4vTo0UM//vijpfzvf/9bTz75ZJF+KSkpatWqlS5duqSXX35Z06dPtzofSSwAcAwvVwcAAACAiueMPfoLCgo0f/58Q91rr71WYgJLku655x7dddddlpsG6enp+uKLL6zeNLBVQECAPvnkE0VEROjMmTN64okntGXLljLPBwAAAPe3bds2QwKrSZMm6tWrl01jx4wZY0hirVy50urfo++//74uXbokf39/NWvWTIsXLy5x3szMTEufmjVrql+/fjbFAwC4hiQWAAAAHMIZNw3sERoaqu7du2vDhg364YcfdPr0aTVo0KBccwIAAMB9rV271lC+9957S/1A1Y19b7R582ZlZmaqZs2ahvrs7GxJ185zjY6OLnXec+fO6ZFHHpF0bVUWSSwAsI9H6V0AAACA0lXETYPyCgkJsXzPNi4AAABV2969ew3lrl272jw2LCxMERERlnJOTo7279/voMgAAGVFEgsAAAAO4Y43DZKTky3fBwYGlns+AAAA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+++/q379+pY+c+fONax0c8dVWJJ05513ytPTU/n5+Za6uLg4RUREOC0GV73fhgwZoqefftqyrebu3buVkJBgGFv4rCxWYQEAAFQsklgAAAAAHMpkMpV57JUrV2zuGxISYihHRkaqT58+Zb42ileZnuvC2+GdOnVKbdu2tXn8qVOnynTdJ554wpLEysvL0/z58/XCCy9YyjExMZa+fn5+Gj58eJmuU9ECAgLUrl07xcXFWeq+/vprDR482GkxuOr9VqtWLT300EP67LPPLHULFizQO++8I0k6c+aMvv32W0ubh4eHRo4cWeFxAQAAVGdsJwgAAADAoQqfyXR9VYMtzp49a3PfyMhIQ/nIkSM2j4V9KtNzHRUVZSjv2bPHrvF79+4t03UfeughhYaGWso3rry6vjLruocfftjus7ecqX///obyF198UaYVamXlyvdbdHS0ofzpp59atiL87LPPlJeXZ2m7++67dfPNNzstNgAAgOqIJBYAAAAAh6pVq5ahfObMGZvHxsbG2ty3d+/ehvKmTZtsHgv7VKbnuvA5U1999ZVd41evXl2m63p7e+vxxx+3lI8dO2Z5ngqfkeWuWwle9+STTxq29MvMzNSsWbOcdn1Xvt/uueceQ2Lq1KlT2rhxo6SiWwkWTngBAADA8UhiAQAAAHCo8PBwQ9melTBLliyxuW/nzp1Vp04dS3nTpk3av3+/zeNhu8r0XHfq1Ek33XSTpfzzzz9r165dNo3duXOnXYnUwsaPH2/YTnPOnDlKTk42bEHXokULde/evczXcIbg4GBDQk6S3njjDf3yyy9Oub4r328eHh4aMWKEoW7BggX69ddfDav0rm89CAAAgIpFEgsAAACAQ7Vv395QXrFihWELruIsX77ccA5Paby9vTVp0iRL2Ww2a8KECcrNzbV5DtimMj3X3t7eGj16tKHuySefVFZWVonjrl69qieeeKJc146MjNR9991nKa9YsULvvPOO8vPzLXXuvgrrutdee02NGjWylHNycvSnP/1JBw8erPBru/r9VniF1cqVK/V///d/hrqHH37YsFoNAAAAFYMkFgAAAACHql+/vtq1a2cpnzx5Uu+8806JY2JjYzV27Fi7rzVx4kTDqpuffvpJgwcP1qVLl2yeIzMzU++//77mzZtn9/Wrk8r0XP/1r39V3bp1LeXY2Fj1799fqampVvufOXNG/fr10549ewwrqcrixkRYTk6OIflRo0YNjRw5slzzO0twcLCWLFkib29vS93x48fVrVs3LV682HLel60OHz5sV39Xvt+aN2+uLl26WMpXrlwpsiXkqFGj7JoTAAAAZePl6gAAAAAAVD3jxo3TU089ZSm//PLLSk9P13PPPWfYJuzUqVP673//qxkzZujq1atq2rSpjh49avN1ateuraVLl+qee+6xrNJYvXq1WrVqpb/+9a96+OGH1bhx4yLjTp48qR07dmjlypVas2aNLl++rKlTp5bjEZdu165dNq1IsyY0NFRt2rRxcET2qUzPdWhoqGbNmmXYFm79+vW69dZbNWzYMHXv3l1169ZVWlqafvrpJy1evNiSHJkwYYJmz55d5mv37dtXYWFhOnXqVJG2hx56SCEhIWWe29nuvPNOffTRRxozZowKCgokSefPn9cjjzyit956S2PGjFGfPn3UsmXLImPNZrOSkpL07bffatGiRdq6datd13b1+y06Olo///yz1bamTZvqrrvusntOAAAA2I8kFgAAAACHGzdunObMmWM5D8tsNuutt97Su+++q+bNmysgIEBnz55VYmKiZUz9+vU1b9489erVy65r3XXXXVq4cKFGjx5t2TIuJSVFzz77rJ599lk1aNBAoaGh8vHx0aVLl5SamqoLFy447LHa6tlnny3z2P79+2vlypWOC6aMKstzLUnDhw9XUlKSXnnlFUvdxYsXNXv27GKTVAMGDNDkyZMN7V5e9v1vs5eXl8aOHatp06YVaassWwneKDo6WnXq1FF0dLQuXrxoqY+Pj9fTTz8tSQoICFC9evUUEhIis9ms9PR0JScnKzMz0+qcISEhevXVV0u9tivfb0OHDtWkSZOsbkNZWVbTAQAAVAVsJwgAAADA4by8vLR8+XI1bdrUUJ+Xl6f9+/dr586dhgRW48aNtXHjRoWHh5fpesOGDdNPP/2k5s2bF2k7ffq04uPjtXPnTh06dMjqTW5PT0+FhYWV6drVTWV6rl9++WXNnz/fsPrPGpPJpD//+c/64osvdOXKFUNb7dq17b7u2LFj5enpaahr0qSJ7r77brvncgf9+/fXL7/8oqFDh1rdbjEjI0OJiYmKjY1VXFycDh06ZDWBVadOHT377LP67bff9Pjjj9t0bVe934KCgtS/f/8i9SaTiSQWAACAE5HEAgAAAFAhIiIitGPHDj311FPy8fGx2sfPz09/+ctfFB8fr1atWpXreh06dND+/fu1cOFCdenSpUgSoTAfHx/dfffdevfdd3Xy5EmNHz++XNevTirTcx0dHa3Dhw9r5syZ6tGjh8LCwuTt7a2AgAC1adNGTz/9tOLj4/XBBx/I29tb58+fN4wvSxKrUaNG6tOnj6Fu7Nix5T5vy5UaNWqkxYsX68CBA3rmmWfUpEkTm8bddNNNGjBggBYvXqzTp09rxowZCgoKsuvarnq/RUdHF6nr2bOnIiIiyjQfAAAA7Gcy23saKwAAAADYKTMzU1u2bFFiYqIuXrwof39/tWjRQj169FDNmjUr5JqXLl3Szz//rFOnTuncuXPKzc1VYGCgQkND1aJFC916663y9fWtkGtXN1Xpuf7ggw/0l7/8xVKOiYnRqFGj7JrDbDYrMjJSx48fl3RtZeLJkydVv359h8ZanJiYGI0ePdpSnj9/vtWETHmdPn1av/zyi44fP64LFy4oJydHgYGBqlOnjoKDg3XbbbeVeXVlSarS+02SkpKSFBkZaSmPGjVKMTExrgsIAADAjXAmFgAAAIAKV7NmTT344INOvWbt2rV1//33O/Wa1VVVeq6/++47Q7ljx452z7FhwwZLAkuS+vbt67QEljM1aNBADRo0cPp1q9L7DQAAACVjO0EAAAAAAHRtRcyaNWss5bp166ply5Z2z/Pf//7XUJ4wYUK5YyuP0aNHy2QyGb42b97s0piqs8KvxY2rsAAAAGBEEgsAAAAAUCXZs3t+bm6uRo0apfz8fEvdqFGj5OFh3/82Hz58WCtXrrSUmzVrxqohAAAAoIxIYgEAAAAAqqT27dvriy++UE5OTon9jh07pj59+uiHH36w1Pn4+Oipp56y63r5+fl66qmnVFBQYKmbNGmSTCaTfYEDAAAAkCSZzPZ8NA0AAAAAgErievIoKChI999/vzp16qTw8HAFBAQoPT1dJ06c0ObNm7Vu3TrDCixJmjFjhp599tkS59+1a5cuXLigvLw8JSYm6qOPPtLevXst7Y0bN9bhw4fl4+Pj8MdWktOnTyshIaHEPh06dFCdOnWcFBFutHHjxhLbw8LCFBUV5aRoAAAA3BtJLAAAAABAlVTWFVBPP/20Zs6cWepWgr169dKWLVuKbf/qq6/0xz/+sUwxAAAAAGA7QQAAAABAFdWwYUO7+jdq1Ejz58/XrFmz7D4L60Ymk0lvvfUWCSwAAACgnLxcHQAAAAAAABXh5MmT2r59u77//nvt3LlTR48e1alTp5SRkSEPDw/VqVNHoaGh6tKli+655x4NGDBANWrUKNO1vLy8dNNNN6lbt256+umn1a1bNwc/GgAAAKD6YTtBAAAAAAAAAAAAuB22EwQAAAAAAAAAAIDbIYkFAAAAAAAAAAAAt0MSCwAAAAAAAAAAAG6HJBYAAAAAAAAAAADcDkksAAAAAAAAAAAAuB2SWAAAAAAAAAAAAHA7JLEAAAAAAAAAAADgdkhiAQAAAAAAAAAAwO2QxAIAAAAAAAAAAIDbIYkFAAAAAAAAAAAAt0MSCwAAAAAAAAAAAG6HJBYAAAAAAAAAAADcDkksAAAAAAAAAAAAuB2SWAAAAAAAAAAAAHA7JLEAAAAAAAAAAADgdkhiAQAAAAAAAAAAwO2QxAIAAAAAAAAAAIDbIYkFAAAAAAAAAAAAt0MSCwAAAAAAAAAAAG6HJBYAAAAAAAAAAADcDkksAAAAAAAAAAAAuB2SWAAAAAAAAAAAAHA7JLEAAAAAAAAAAADgdkhiAQAAAAAAAAAAwO2QxAIAAAAAAAAAAIDbIYkFAAAAAAAAAAAAt0MSCwDw/7VnxwIAAAAAg/ytp7GjNAIAAAAA2JFYAAAAAAAA7EgsAAAAAAAAdiQWAAAAAAAAOxILAAAAAACAnQDNxG0svGOzlAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# utility function to converet pyirf Quantities to the gammapy classes\n", + "from pyirf.gammapy import create_effective_area_table_2d\n", + "\n", + "plt.figure()\n", + "\n", + "for aeff, label in zip((aeff_all, aeff_selected), ('All Events', 'Selected Events')):\n", + " aeff_gammapy = create_effective_area_table_2d(\n", + " # add a new dimension for the single fov offset bin\n", + " effective_area=aeff[..., np.newaxis],\n", + " true_energy_bins=true_energy_bins,\n", + " fov_offset_bins=fov_offset_bins,\n", + " )\n", + "\n", + "\n", + " aeff_gammapy.plot_energy_dependence(label=label, offset=[wobble_offset])\n", + "\n", + "plt.xlim(true_energy_bins.min().to_value(u.GeV), true_energy_bins.max().to_value(u.GeV)) \n", + "plt.yscale('log')\n", + "plt.xscale('log')\n", + "plt.legend()\n", + "\n", + "print(aeff_gammapy)" + ] + }, + { + "cell_type": "markdown", + "id": "eleven-sessions", + "metadata": {}, + "source": [ + "### Point Spread Function\n", + "\n", + "The point spread function describes how well the direction of the gamma rays is estimated." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "spiritual-attention", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:13.042567Z", + "iopub.status.busy": "2024-05-14T10:12:13.042082Z", + "iopub.status.idle": "2024-05-14T10:12:13.216584Z", + "shell.execute_reply": "2024-05-14T10:12:13.215976Z" + } + }, + "outputs": [], + "source": [ + "from pyirf.irf import psf_table\n", + "from pyirf.utils import calculate_source_fov_offset\n", + "\n", + "\n", + "gammas['true_source_fov_offset'] = calculate_source_fov_offset(gammas)\n", + "\n", + "\n", + "source_offset_bins = np.linspace(0, 3, 100) * u.deg\n", + "\n", + "# calculate this only for the events after the gamma/hadron separation\n", + "psf = psf_table(gammas[gammas['selected_gh']], true_energy_bins, source_offset_bins, fov_offset_bins)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "animated-prescription", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:13.219560Z", + "iopub.status.busy": "2024-05-14T10:12:13.219163Z", + "iopub.status.idle": "2024-05-14T10:12:13.223568Z", + "shell.execute_reply": "2024-05-14T10:12:13.222900Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(11, 1, 99)" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "psf.shape" + ] + }, + { + "cell_type": "markdown", + "id": "opposed-coordinator", + "metadata": {}, + "source": [ + "Again, let's use gammapy to plot:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "spoken-shock", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:13.226180Z", + "iopub.status.busy": "2024-05-14T10:12:13.225758Z", + "iopub.status.idle": "2024-05-14T10:12:13.737299Z", + "shell.execute_reply": "2024-05-14T10:12:13.736582Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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rV4vKPT/mfH8zjWSfb9/awEAAAAAAAAAAC1Gkw6mWblypcrLy13jvn37auzYsU169/rrr3cbz5s3r9X1dOnSRYmJiZKkPXv2tHq9oODtXjo5pS0L/VoKAAAAAAAAAABoOZp0MM2CBQvcxhMnTjzpLrqfPvtTS5Yskd1ub3VN9fX1kqTw8PBWrxUUEvtJXTKMY9uX+bcWAAAAAAAAAADQYjTpYJr169e7jUeMGNHkd5OTk5WWluYa19TUaOPGja2qZ9euXa677Xr16tWqtYKGxSL1m2gcsy33by0AAAAAAAAAAKDFQmR7EYLBpk2b3MZZWVnNej8rK0s2m81tveHDh7e4nqefftr15wkTJrR4naCTNkpa/YLnfEWJdGiH1DnV/zUBAAAAAAAAwP9XXV2tlStXavPmzTp06JAiIyPVq1cvnXXWWerbt2+gywOCBjvpYIpjx46ppKTEba53797NWqPh80VFRYbPzZ8/X1VVVY2uNXv2bD322GOSpIiICP3mN79pVi1BLXWk9xi76QAAAAAAAAD8RGlpqT744AP9/ve/1/jx49WxY0dZLBbX109POGut8vJy3XbbbUpMTNSECRN066236k9/+pPuuece/fznP1dGRoaGDRumDz/80LScP5WWlub2vfnia+zYsabV+8Ybb7it3bNnT9cVTq21du1at7UjIyNVXl5uytowDzvpYIr9+/fL6XS6xhEREUpKSmrWGikpKW7jsrIyw+cee+wxXX311Zo8ebJGjx6tAQMGqFOnTqqurtamTZv05ptv6vPPP3c9/8gjj6h///7NqiWoxXSRumdL+773jNmWSadf7f+aAAAAAAAAAASNFStW6PHHH9fq1au1e/duv+RcsmSJLr/8cu3fv7/R59auXaupU6fq2muv1csvv6zIyEi/1BeMLr30Ut1yyy2qrKyUJO3du1cLFy7UBRdc0Oq1Z82a5TaeNGmSunXr1up1YS6adDBFw51tMTExslgszVojNja20TV/6siRI5ozZ47mzJnj9Zm4uDg9+eST+uUvf9msOrwpKytr9m8aFBcXm5LbQ9ooL0265ZLTeeLuOgAAAAAAAADt0tdff60PPvjAb/mWL1+uSZMm6dixY27znTp1Unp6ug4dOqSdO3e67RKbPXu2qqqq9O677zb7s+RQERMTo8svv1yvvfaaa2727NmtbtLV1tbqzTffdJubMWNGq9aEb9CkgykaNtSio6ObvUaHDh0aXfNHjz/+uBYvXqw1a9Zo06ZNOnDggA4cOKCwsDB17dpVp556qiZOnKgZM2aoc+fOza7Dm+eee04PPPCAaeu1StpoL/fS7ZQO75A6p/m9JAAAAAAAAADBLy4u7qTXCTXHoUOHdMUVV7g16FJTU/Xkk0/q4osvdjXgdu3apYceekgvvvii67n3339f//jHP/S73/3OlFrmzJnj0Sg0sm/fPl1zzTVucz89na0xZn7mLJ1onv20STdv3jwdOXJEHTt2bPGan3zyiduOxsTERF100UWtqhO+QZMOpqiurnYbt2SLclRUlNvY2w/ToUOHaujQoc1eP6SkjpBkkeT0jNmW06QDAAAAAAAAoPj4eA0dOlTDhw/XmWeeqeHDh2v79u0aN26caTkeffRRtyM109PTtXz5ciUnJ7s916tXL73wwgvq06eP/vjHP7rm//znP+u6664zpfk1cuTIJj1ns9k85s4999xW52+J0aNHq2/fvtq2bZukE5+1v/3227rhhhtavObs2bPdxldddZUiIiJaVSd8wxroAhAaGu6cq6mpafYax48fb3RN/MSP99IZsS33by0AAAAAAAAAgsrkyZNVWFiow4cPa/HixXrkkUc0bdo0paammpqnvLxcTz/9tNvcyy+/7NGg+6l7771XY8aMcY0rKir02GOPmVpXW2KxWDyOomx4n1xzHDx4UPPnz3eby83NbfF68C120sEUcXFxbuOGO+uaouHOuYZrBtott9yiyy+/vFnvFBcXa+rUqb4pKG2UtO87z3nupQMAAAAAAADatYyMDL/keeutt9yOzhwzZowmTJjQ6DsWi0X33Xef23OvvfaaHnrooXZ7N921116r+++/X07niZPTVqxYoW3btqlv377NXuutt95y20Rzyimn6IwzzjCtVpiLJh1M0bChdvToUTmdzmb9ULXb7Y2uGWhJSUlKSkoKdBn/lTZKWv285zz30gEAAAAAAADwgw8//NBtfP311zfpvXHjxik9PV3bt2+XJO3du1erVq3SOeecY3qNvnT06FGtWLFCpaWlKisrU1hYmJKSkpSVlaUzzjijyZ+Pp6WlKScnR0uWLJEkOZ1OzZ49W/fff3+za2q4C6/hLj0EF467hCkSExPdfuDU1taqrKysWWuUlpa6jYOqIRaM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tlZCQoJmzpzpGjscDl111VX66KOPmrXO2rVrdcUVV7Qo/9SpU93mnnzySbcxR122XeGBLgAIBTabTfn5+YEu48S9dF894zlfufvEvXRdM/xfEwAAAAAAAAC/W7FihY4dO+Yxv2HDBrdxdXW1x/1wP0pOTlZWVlajee655x7NmjVLe/fulSRt375dI0aM0FNPPaXJkyfLYjlxRc+uXbv00EMP6cUXX3R7/49//KO6dOnS5O8rEO655x4tW7ZMn3zyiSSpsrJSU6dO1dSpU3Xbbbdp5MiRioqKcnunurpa3377rRYtWqT33ntP69atkyT9+9//bnb+3NxcvfXWW4ax+Ph4XXbZZc1eE8GBJh1ggrS0NOXk5HjM2+12FRQU+K+QPj/eS2ewa862nCYdAAAAAAAA0E5cffXV2rFjx0mf27dvnyZOnGgYmzFjhvLy8hp9v0uXLvr3v/+t888/X9XV1ZKkHTt2aMqUKerUqZPS09N1+PBhlZSUqL6+3u3dKVOm6K677mraNxRAVqtVc+fO1dSpU7VkyRJJJ47t/OCDD/TBBx8oKipKqamp6ty5s6qrq3X48GHt2rXL4/ttqYkTJyolJUWlpaUesWnTpikmJsaUPPA/mnSACXJzcw2PtiwsLFR2drb/CunQSep5qrRng2fMtlwayrZnAAAAAAAAAOYaM2aMFixYoMsvv1wHDx50zR8+fFjffPON4Ts///nP9dprr7l22gW7hIQEff7555o5c6aeeuop1dXVuWLHjx/XDz/8cNI1evfu3aLcVqtV11xzjR5++GGPmN+vXIKpuJMOCDVpo43nbcu4lw4AAAAAAACAT4wfP14bN27UzTff3OjOrtNPP13vvfee5syZ43FEZLALDw/X448/rqKiIt14441KSko66TtpaWm68cYbtXDhQtlsthbnNmrGpaena/RoL58Ho02wOJ18ag/4SsOddN9//70GDx7s26RFn0pvXmkc+806jrwEAAAAAABoY+rq6rRlyxa3uX79+ik8nIPSEJyOHTumlStXatOmTTp8+LAiIyOVkpKis846S5mZmYEuzzROp1OFhYUqLCzU/v37dfjwYUVFRSkhIUHp6enKyspScnJyoMtsNV/9DArI5+dBhp/iQKjpc45ksUpOh2fMtowmHQAAAAAAAACf6tChgyZMmKAJEyYEuhSfslgsys7O9u+V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AACANoUmHYCmyZxoPF9XLdmWu4YXn5astK4xho++t26Xdh486ovqAAAAAAAAAABoU2jSAWialDOkDl2MY8Wfu/4YHmbVreOMd9PVOZx6bgm76QAAAAAAAAAAoEkHoGmsYVLmucaxLQslp9M1nHp6inp36WD46Ltrd6r08DFfVAgAAAAAAAAAQJtBkw4hw2azKT4+XhaLRRaLRWlpaYEuKfT0O894/pBNOlDsGkaEWXWbl910tfVOPb+k2DAGAAAAAAAAAEB7QZMOIcHpdOr6669XVVVVoEsJbRnjJVmMY1s+dxtecnovpXQy3k339te7tKeC3XQAAAAAAAAAgPaLJh1CwgsvvKAvv/xS3bt3D3QpoS22q9RrmHFsy0K3YWS497vpauodeoG76QAAAAAAAAAA7RhNOrR5O3bs0D333CNJevrppwNcTTvg7cjLHSuk4+47GacN7aXkhGjDx9/8eqf2Hak2uzoAAAAAAAAAANoEmnRo83485nLq1Km6/PLLA11O6Ms813i+vkayLXObigy36mZvu+nqHHp56TazqwMAAAAAAAAAoE0ID3QBCH1bt27VmjVrtGvXLtXU1Khz584aOHCgRowYoeho411WTfXiiy/qiy++UKdOnfTcc8+ZVDEa1XOIFNtNspd7xrYslAZc4DY1fVgvPftlsfYa7Jqbu6ZEt47LVOfYSB8VCwAAAAAAAABAcKJJ186UlpZqzZo1Wr16tdasWaOCggJVVla64qmpqbLZbKbkmjdvnh588EGtW7fOMB4XF6fc3Fzdd999SkxMbPb6JSUluvvuuyVJjz76qHr27NmqetFEVquUOVHaMNcztuVzyemULBbXVFR4mG7K6av7P97o8fjRmnrlrbTpfyb292XFAAAAAAAAAAAEHY67bAdWrFihSy+9VCkpKerVq5cuvfRSPfzww1q8eLFbg84sx48f1zXXXKNLLrnEa4NOkqqqqvTMM88oKytLS5cubXaeX/3qV6qsrNT48eN1ww03tKZkNFc/L0deVuyUyos8pq88s48S44x3y+WttKnqeJ2Z1QEAAAAAAAAAEPRo0rUDX3/9tT744APt3r3b57kcDoeuuOIKzZkzx20+LCxM6enpGjJkiBISEtxi5eXluuCCC/TVV181Oc8rr7yihQsXKiYmRi+99JIptaMZMsZLFi8/PrYs9JiKjgjTL0el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8xMRERERERERERE1JSzSEWlBUlISwsPD9Z0GGQNrF6BlF+B6lGYsbi/Q40mdTj/vgXbYGn0dZRWae9D9eiAeU3q0gaO1mU5zICIiIiIiIiIiImoKWKQj0gJPT08MGDBA43hhYSEiIiL0kBE1ad5DxIt0iQeBilLAxFxnU7dysMSM3h74/VCiRiy/tAK/HIjDO6P8dTY/ERERERERERERUVPBIh2RFoSGhiI0NFTjeGxsLAIDA+VPiJo2n6HAoa81j5cXAinHAS/NgrE2zRnogzWnriK/RHMPumVHkxEa0hatHCx1mgMRERERERERERFRY6fQdwJERFRP7t0Bc3vxWNwenU/vaG2GZwZ4i8bKKlX4bvdlnedARERERERERERE1Ng16iLdxo0b4eXlBW9v8ZvFRERNktJEerVc/D5ZUpgZ0hautuJtNTeevoZLN/NlyYOIiIiIiIiIiIiosWrURbqCggIkJSUhKSlJ36kQEcnLZ6j48bQY4NYNnU9vaabE3KHtRGNqNfDVPxd1ngMRERERERERERFRY9aoi3REREbLZ4h0LH6vLCk8GuwOLxdr0dieC+k4npAlSx5EREREREREREREjRGLdEREjZG9O9DMTzwmw750AGCiVOC14e0l46+si0ZOYZksuRARERERERERERE1Nib6mDQlJUUr18nMzNTKdYiIGiWfoUCGSFvJ+P1AZUXV3nU69mBgc3Ru7YDoq7kasdTcYry87gwWz+gOhULQeS5EREREREREREREjYleinSenp4QBN6wJSK6Lz5DgGPzNY+X5ALXTwOte+g8BUEQ8OaDfnjs9+Oi8f2XMrAgPB7PDfLReS5EREREREREREREjYne2l2q1WqtvIiIjFabPoCJpXgsTp596QCgt7czJga5S8a/2XUJx+K5Px0RERERERERERHRnfSyku72KrrmzZujXbt2Db7OzZs3cenSJW2lRUTUuJhaAG37AVd2acbi9gCD3pItlY/GBOLstTxcSsvXiKnUwItrorDtxb5wtbWQLSciIiIiIiIiIiIiQ6aXIp2Pjw/i4uLg5+eHvXsbvtpj2bJleOKJJ7SYGRFRI+M9RLxIlxoJFGUDVk6ypGFppsQv07ph9E+HUVhWqRHPyC/FS6vPYMXsnlByfzoiIiIiIiIiIiIi/bS7DAoKglqtRlRUlD6mJyJqOnyGSgTU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JkkHFF7dunVzvek0Lx4eHpo5c6ZmzZpV7DezJCYmavTo0dq8ebNd7YOCgrR+/XqXfCqeJ+lQJmVlZWnMmDFat26dod7d3V3169dX5cqVFRERoWvXrllfi42N1YABA7Rlyxbdc889xRZbTEyMevXqpTNnzhjqvb29FRQUpKysLEVEROjmzZvW1/bv369evXrp559/NvVpP8AVufL8BlB4v/76q9asWePsMHLF+RsovIIk4QGUHHv27MnxAv7t5SMDAgKUkZGhyMhIw9/mkvSvf/1LJ0+e1E8//SRfX99ijTM8PFy9e/fO9hSQr6+vgoKClJqaqnPnzikjI8P62g8//KABAwZo69atLOGHMqmkzG8AjuPl5WW9rpaVlaW4uDidP39edz4DlpGRodmzZ+vChQtatGhRscWSmpqq/v37a+/evYZ6T09PNWzYUBUqVNDZs2eVkpJife3s2bPWz99NmjQpttgKgyQdyqT33nsv2wX8J598Uq+++qoCAwMl/X6hf926dXrmmWd0/vx5SdKNGzc0evRoHTt2rNjumJs4caLhAp+Xl5fmzJmjSZMmqWLFipKklJQULViwQDNnzrRe7Dt16pQee+wxrV+/vljiAkoKV57fd7L3Tp/bXPFOH8BV+Pr6Kjk52akxcP4GCs/Vk/AAiq5KlSoaO3asBg0apG7duqlSpUrW1zIzM7Vz50699tpr2rlzp7V+7969mjhxolavXl1scd26dUujRo0yJOiqVaumDz/8UA899JB1WeqEhATNnTtXb7/9trKysiRJv/zyi55//nnNmzev2OIDSgJXnd93CggI0PLlywvUp23btsUUDVAyBAYGatCgQerevbvuueceNWrUSOXKlTO0uXr1qlavXq033nhDFy9etNYvXrxYXbt21aOPPlossU2fPt2QoCtXrpxefvllTZs2TVWrVpX0+156K1as0PTp03X16lVJv9+kP3r0aP36669O30PvTix3iTInPj5ejRo1Miyb8/bbb+vFF1/Msf2lS5fUtWtXw7I6r732mmbPnu3w2H788Uf179/fWvbw8NCWLVtyXYJv+/bt6tu3r+GOvq1bt6pXr14Ojw0oCVx5ftsul8XpFyiYjz76SNOmTVOlSpUUEhKijh07qlOnTurYsaMiIiIM5z6zl8Pj/A0Uze35nRPbJLyzl7sszE02tWvXdnRYQIlw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FJ+EhARNnjzZUDds2DCHLpV1G+dwwFxmzu+iOHPmjO677z598MEHmjp1qrPDAVzGp59+qo8//jjPNm5ubvrjH/+ot99+W23btnV4DKX53M2edCgzbCedl5dXgcfw9vbOc8zCcuXYgJKgpMwhLy8vDR48WJ9++ql+/vlnxcTEKD09XUlJSTpz5oyWL1+uQYMGZUswvv/++5ozZ47D4wFQNCXldw8Ac92+yWbGjBnODgUoMbKysjRu3DhdvHjRWle5cmXNmzevWI7HORwwj9nz+05+fn4aPXq0Fi1apH379ik+Pl4ZGRm6du2aTpw4oUWLFqlr166GPpmZmZo2bZpWrlxZ7PEBpcmoUaP08ssvF0uCTird526epEOZcXut+NsKszGt7bI1qampRYrpNleODSgJSsIcmj59urp06SJ/f/9sr3l4eMjX11dBQUF6+OGHtWvXLj344IO6dOmStc3MmTM1YMCAYvtjB0DBlYTfPQCKzsvLS3379tWAAQPUrl07NWnSRFWqVFFaWppiYmL0yy+/6Ouvv9amTZsMy2+///778vf314svvujE6IGSYcaMGfr+++8NdV988UWh9nu1B+dwwDxmz+/b3nvvPQ0YMEC+vr7ZXvPz85Ofn5+aN2+uxx57TGvWrNFjjz2mxMRESZLFYtHjjz+unj17qlatWsUaJ1BafPPNN/rmm2/UrVs3LV68WE2aNHHo+KX53M2TdCgzbLPr6enpBR4jLS0tzzELy5VjA0qCkjCHhgwZkmOCLiddu3ZVaGioYQNci8WiV155xaExASiakvC7B0DRTJ8+XRcvXtT69ev11FNP6Z577lGNGjWy3WCzYcMG7dixI9s+FzNnztThw4edFD1QMsybN09z58411D3//PMaM2ZMsR2TczhgDmfM79tGjRqVY4IuJw888IC+//57w1M2N27c0Jtvvllc4QElykcffSSLxWL9unHjhi5cuKANGzbo8ccfN8ydnTt3qmPHjtq3b59DYyjN526SdCgzbE/Mttl3e9hm1+092efHlWMDSoLSOIeaNGmSbR+6TZs2KSEhwUkRAbBVGn/3ADDiJhugeK1YsULPPPOMoW7ixInFvtQ753Cg+DlrfhdW586d9fzzzxvqVqxYoaysLCdFBLgub29v1a1bV4MGDdLChQt15MgRtWvXzvp6YmKihg0bZn061RFK87mbJB3KDNtJd+PGDcNyNPZISUnJc8zCsh3H9jj2KK7YgJLAled3UYwfP141atSwlrOysrRlyxYnRgTgTpy/AdjiJhvAfhs2bNCECRMMf7cPHz5cCxcuzLZHs6NxDgeKlzPnd1FMnTpV7u7u1nJCQoLDnwYCSqMmTZpo8+bNhmVsL126lO3v4qIozeduknQoM6pXr274QyAjI0MxMTEFGuPO/aEkqWbNmg6JzXacOzfTtVdxxQaUBK48v4uiXLly6tmzp6EuPDzcOcEAyIbzN4CccJMNkL9t27Zp1KhRunXrlrWub9+++vrrrw0XyIuLI87hUVFReY4JlFXOnt9FUbVqVXXo0MFQx2dwwD7Vq1fX7NmzDXVLly512Pi251nbz9L2cNXP3yTpUGZ4e3urfv36hrrz588XaAzb9s2bNy9yXJJ01113GcoXLlwo8Bi2fRwVG1ASuPL8LirbzbRjY2OdFAkAW5y/AeSEm2yAvO3Zs0dDhgwxLFN17733as2aNfL09DQlBttzeEE/O8TExBji9/T0VFBQkENiA0oyV5jfRcVncKDwHnjgAcNN9FFRUYqMjHTI2EFBQSpfvry1nJqaWuD56arX/kjSoUyxnXjHjx8vUP8TJ07kOV5hNWjQwLDBZkpKSoF+gUVGRurGjRvWso+PT7Y/KoDSzlXnd1F5eHgYyhkZGU6KBIAtzt8AcsMFPiBnR44c0YABA5ScnGyta9++vTZt2iQfHx/T4rD9W//MmTNKT0+3u7/tZ4fGjRsbLhwCZZGrzO+i4jM4UHhVqlRRtWrVDHWXL192yNgeHh5q3Lixoa4g1/7S0tJ09uxZQ52rXPsjSYcy5c4NLCXp559/trtvdHS0zp07Zy17eHgoODjYIXG5ubmpTZs2hY5t9+7dhnKbNm1ceo1voDi46vwuKts/Zu5cPguAc3H+BpAbLvAB2YWHh6tv3766evWqta5Fixb64YcfVLlyZVNjqVWrlmrVqmUtp6Wlaf/+/Xb3tz2H234WAcoaV5rfRcVncMCxbP8uLoqiXPvbv3+/0tLSrOXatWuz3CXgDPfff7+hvGXLFsMmtnn58ccfDeVevXo5dHNJ29g2b95sd1/btoMHD3ZITEBJ4srzuyh27dplKPOUDeBaOH8DyAkX+ACjyMhI9enTx7BvdKNGjbR582anzY9BgwYZypzDgcJxxfldWGlpafr1118NdXwGB+yXlJSkhIQEQ11AQIDDxi+tn79J0qFMuffee1W9enVr+ezZswoNDbWr76JFiwzloUOHOjI0DRkyxFBetWqVYYmA3CQlJWnVqlXFGhtQErjy/C6s7du368yZM4a6++67z0nRAMgJ528AOeEmG+D/REdH67777tPFixetdXXq1NFPP/2kOnXqOC0u23P4kiVL7LrJ78yZM9q+fbu17OHhoYEDBzo8PqAkcNX5XVgrV640LEdfoUIFdenSxYkRASXLxo0bDefSGjVqqHbt2g4bf+DAgYblpUNDQ7MtYZkTi8WipUuXGupc6fM3STqUKeXKldPEiRMNdbNnz873D/GffvpJO3futJYrVaqk0aNHOzS2Nm3aqGPHjtZycnKy3n333Xz7vfvuu0pJSbGWO3fu7DLL9AFmcuX5XRgpKSl6+umnDXWtW7dmQ3rAxXD+BmCLm2yA/5OQkKC+ffsa5kSNGjW0efNmNWrUyImRSf3791fdunWt5XPnzmnJkiX59nv99dcNnzFGjBhR4pbzAxzBled3YVy+fFkvv/yyoa5fv36qWLGikyICSpbU1FTNmjXLUHf//ferXDnHpaCqVaumYcOGWcsWi0Wvv/56vv0WL15s2OamQYMG6tOnj8PiKjILUMbExsZafH19LZKsX2+//Xau7S9evGhp2LChof0rr7yS73HubC/Jsm3btnz7fP/994Y+Hh4elu3bt+faPjQ01OLh4WHos2XLlnyPA5RWrjq/n376aculS5cK9H307t0723G+++47u8cAypJt27YZ5kqDBg0KPRbnb8C1OHJ+myE5OdnSpk0bQ8ytW7d2dliAU1y/ft3SsWNHw3yoUqWK5eDBgw4/VkRERLZzeERERL79PvvsM0OfqlWrWsLCwnJt/9VXXxnau7u7W8LDwx34nQAlgyvP76ioKMtrr71mSUhIKNAx2rZtaziGm5ubZf/+/Q74DoCSZcaMGZa9e/cWqE98fLylT58+2c6RR44cybNfgwYNDH2WLFmS77HCwsIs5cqVM/RbsWJFnu2rVKliaL9w4cICfX/F7f+eDQTKiOrVq2vmzJmaOXOmte6ll17S+fPn9corrygwMFCSlJWVpfXr12vq1Kk6f/68tW1gYKCeffbZYontj3/8o/r162fdHysjI0P9+/fXnDlzNGnSJOvdOykpKfryyy/1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TIx++uknrVy5Uhs2bMgzpp9++klPPfWUISk0ePBgPfvss+rSpYthv7aEhAQtXLhQf//735WUlCTp96f73n33Xb3wwgsF/N/I3Z49e7I9uVXYvehs2Y4THR2tiIgINWrUSJK0efNm62s//vij3nvvPWt5xowZ6tevX7Yxg4KCitxX+j1B/fzzzxteHzFihF566SV16NAhW1IuKSlJv/76qzZt2qTly5fn/k2reN/nr776Sqmpqbpy5YrGjRtnre/Xr59mzJiRa0wtW7bMM2YAAACgxHLuapsAAAAASpOdO3fmuq9UgwYNLJMmTbIsXrzYcuzYMUtmZmahj7N3716Lm5ubYfzXXnstzz779u2z+Pn5GfpMnTo11/bO3JPu9tczzzxj9zEvX75sqVKliqF/t27d8ty37k7h4eGWCxcu5Pja1atXLbVq1bKOW65cOcuiRYvyHTMsLMxSo0YNaz9PT09LdHS03d9TfubPn2/4fn18fCxZWVkOGfvKlSvZ3o+NGzfm2LYoe50Vpu/GjRsNfcaPH2/38dLS0ixnzpzJ8TWz3mdn7bvmyD3pigt70gEAAJQtLHcJAAAAwGG6du2qV199NcfXIiMj9eWXX+qxxx5Tq1atVLlyZetTdvv27SvQcT788EPDknr333+/Zs+enWefkJAQLViwwFC3cOFCXbt2rUDHNsu9996ruXPn2t3+448/VmJiorXctGlTff/999n2CsxNs2bNVLdu3Rxf+/zzz3X58mVr+c0339Rjjz2W75jBwcFaunSptZyenq758+fbFY89EhISDOWAgIACL+2Ymxo1amRbztH2eM7y22+/GcpTpkyxu6+np6fhqbw7uer7DAAAAJRWJOkAAAAAONQbb7yhjz/+ONv+dLaSk5O1detWvfbaa+rYsaNatWqlxYsXZ9tjy1ZiYqK+/fZba9nNzU0ffPCBXbGNGTNGnTt3tpZTUlK0YsUKu/qa7Y033rA74ZSenq7PPvvMUPf555/Lx8enyHFkZmbqk08+sZbr16+fbUnNvAwcOFDt27e3lu9874rKNmmW1159BeXm5iY/P788j+cst5dtvc1238XCcOX3GQAAACitSNIBAAAAcLinn35ap06d0pQpU+xOnISFhenxxx9Xp06dFBkZmWu7X375Renp6dZy165d1axZM7tjs30yaMeOHXb3NUtAQIB69+5t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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from pyirf.gammapy import create_psf_3d\n", + "\n", + "psf_gammapy = create_psf_3d(psf, true_energy_bins, source_offset_bins, fov_offset_bins)\n", + "\n", + "plt.figure()\n", + "psf_gammapy.plot_psf_vs_rad(offset=[wobble_offset], energy_true=[1., 10.]*u.TeV)\n", + "plt.legend(plt.gca().lines, ['1 TeV', '10 TeV'])" + ] + }, + { + "cell_type": "markdown", + "id": "floral-aquarium", + "metadata": {}, + "source": [ + "### Energy Dispersion\n", + "\n", + "Describes how well the energy is estimated" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "north-compatibility", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:13.740545Z", + "iopub.status.busy": "2024-05-14T10:12:13.740136Z", + "iopub.status.idle": "2024-05-14T10:12:13.795233Z", + "shell.execute_reply": "2024-05-14T10:12:13.794564Z" + } + }, + "outputs": [], + "source": [ + "from pyirf.irf import energy_dispersion\n", + "\n", + "# logarithmic space, is \"symmetric\" in terms of ratios 0.1 is a factor of 10 from 1 is a factor of 10 from 10\n", + "migration_bins = np.geomspace(0.1, 10, 100)\n", + "\n", + "edisp = energy_dispersion(\n", + " gammas[gammas['selected']],\n", + " true_energy_bins=true_energy_bins,\n", + " fov_offset_bins=fov_offset_bins,\n", + " migration_bins=migration_bins,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "copyrighted-oakland", + "metadata": {}, + "source": [ + "Plot edisp" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "heard-plate", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:13.797847Z", + "iopub.status.busy": "2024-05-14T10:12:13.797640Z", + "iopub.status.idle": "2024-05-14T10:12:14.177483Z", + "shell.execute_reply": "2024-05-14T10:12:14.176697Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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ihe/+MVPee3+/v6IiDjttNPi7LPPPuprly1bFj/60Y8i4vcfryiGAQAAAAAAvmZYQnfddVd84xvfmJQQ1mw24w1veMOYENbe3h7nn39+rFixIubPnz/q+3bs2BGveMUr4qc//elRrz00NBQPP/xwRMQxha1DX3P//fcf6w8BAAAAAABIrJIx7Lrrrosvf/nLce+995aeks7cuXNP6PU++tGPxje/+c1R597xjnfEli1bYtOmTbF27dp47LHH4utf/3qce+65I6/Zu3dvXH311bFr165xr33oxzkey+558+Yd8b0AAAAAAMDUVcmPSfzoRz8atVotIiJmz54dz33uc+Oiiy4a+aurqyumT59+TNf6zW9+E5///OdjxYoVcfnll48KMtnNmzcvLr744rj00kvj+c9/flx66aWxefPmuOKKK07I9R999NH44Ac/OOrchz/84XjPe94z6lxbW1u89rWvjec///nxwhe+cORrlG3dujVuuummuOGGG454/cHBwZFvH8t/3zNmzDjiewEAAAAAgKmrkjHsSa1WKxqNRvzsZz+Ln/3sZyPnOzo64sILLxwVyFasWDHqyaAnPec5z4mvfe1r8bGPfSyWLVsW99xzz2T+EIp41ateFS9/+ctj6dKl0dY2+uG/zZs3n7D7fOQjHxn1BNaLX/ziuO6668Z9/VlnnRX/8R//ES996UtHzn3iE5+Id73rXXHGGWeMef2sWbNGvr1///6n3DM0NHTE9wIAAAAAAFNXZWNYq9Ua99yBAwfiV7/6Vdxzzz3xn//5nxERUavV4vzzzx8Vxy688MLYvXt37N69O1qtVqxfvz7uuuuuuPTSSyf1xzLZnv3sZ5/0ezSbzfjCF74w6twHPvCBkSf6xvOSl7wkXvSiF8Xtt98eEb//OMOvfvWr8c53vnPMaw+Nm3v27HnKTYe+5khhFAAAAAAAmHoqGcPWrFkTa9eujV/84hexdu3auOeee8Z87N2h0aXVakWr1Yr77rsvNm3aFF/72tfGvfbatWvTx7DJcOedd8aOHTtGji+44IJYuXLlMb33rW9960gMi4i45ZZbjhjDZsyYEYsWLYrt27fHAw888JTX3bJly8i3p9LHYQIAAAAAAOOrZAy75JJL4pJLLhk5bjabsWHDhlGB7O67745du3aNet+Tgezwp8oODWfH8nF7PLXvfOc7o45f9rKXPeVTYYe+9lC33XZbNBqNmDNnzpjXLl++PLZv3x67d++OrVu3xtlnnz3uddevXz/qfQCnkl/eeXHpCVTUiv/1gtITqLC9p51XegIVNXP+D0tPoKJmdkwrPYGKmnv/b0tPoMIOrCu9gKqavf300hOoqAObT/6nl3FqOdhqL3r/tqd+SXltbW2xfPny+Iu/+Iu46aab4sc//nE8/vjjsXHjxrj55pvjve99b7zsZS+L2bNnj4SwWq028teTpk2bNurrVXH87r777lHHl1122TG/d/HixdHZ2TlyvH///lEh61Dd3d0j3/7xj3887jUHBwdHvq7c+eefH+ecc84x7wEAAAAAAPI6JWLYeC644IK46qqr4oMf/GB873vfi507d8a3vvWteMlLXjLq6bAZM2bE8573vPj6178eS5cuLbg4jw0bNow6XrZs2YTef/jrD7/ek173uteNfPvf//3fx73eF7/4xdi7d++Y9wAAAAAAAFPbKR3DDtfe3h6vfOUr4wc/+EF86UtfilmzZkVExMUXXxw/+MEP4k/+5E8KL8xhcHBw1NfniogJP4l1+OvvvffeI75u+fLl8apXvSoiIm6//fb4t3/7tzGveeCBB+L666+PiIhZs2bFtddeO6EtAAAAAABAXpX8mmEnwhve8IY444wz4o//+I/jzjvvjCuvvDJWr14d06dPLz3tlPfII4+MevJu2rRpsWjRogld46yzzhp1vH379nFfe9NNN8Udd9wRjz32WLzjHe+ItWvXxutf//qYO3durFmzJj70oQ+NvP9DH/rQmGsfj+3bt8eOHTsm9J6NGzc+7fsCAAAAAAAnVtoYFhHx0pe+NP7qr/4qPvOZz8SvfvWreP/73x8f/vCHS8865e3Zs2fU8ezZs0d9bbZjMWfOnKNe81BLliyJ73znO3HVVVfFgw8+GJ/97Gfjs5/97KjXtLW1xfve9774m7/5mwntGM+nP/3puOGGG07ItQAAAAAAgHJSfUzikfz1X/91RES0Wq347Gc/G/v37y+86NR3eLiaOXPmhK/x5EdYjnfNw73gBS+I/v7++Od//ue4+OKLY8GCBTFz5sw4//zz4y1veUusXr06PvCBD0x4BwAAAAAAkFvqJ8MiIpYuXRqLFi2K7du3x+7du+O2226Ll7/85aVnndL27ds36vh4PnpyxowZo44HBwef8j0L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EG3atImjjz46zjrrrBg3blw0bdp0n3OUlpbGX//611S9vy0SIyJyc3Pj2GOPjYULF6bG/vrXv8bZZ5+933NrQl5eXlrdvXv3GDp0aJ1c+1Of+lScddZZMWXKlNTYpEmT4tZbb42IiNWrV8dTTz2Vei07OzvOP//8OukNAABqgm0SAQAA6lj5VWGHH3545OTkZKibPZWUlMSIESPinHPOiSeeeCIKCwtjx44dtX7dX//619GzZ8/4+c9/HgsWLIgPPvggtm/fHmvWrImZM2fG9773vTjqqKPi7bff3uc8L730UhQVFaXq/W2RuMuIESPS6qlTp8aGDRuq/kYOQPfu3dPqZcuW1cl1dxk7dmxafd9996W2WZwyZUraz/+LX/xiHHbYYXXZHgAAVIswDAAAoI4tWrQora7pLRKPP/74eOONN+LGG29Mjf3pT3+KN954I+3PPffcs9fzr7rqqpgxY0YMGTIk7rvvvliwYEHMnTs3fvzjH9don7u74YYb4vLLL49t27ZF9+7d47bbbovHH388XnnllXjyySfjP//zP6NJkybxr3/9K4YOHRqrV6+ucK4ZM2akvm7fvn184QtfqFQPF198cdp2hZs2bYpf//rXB/6mqqD8yr3Zs2fXyXV3OeWUU9ICrvfeey9mzpwZEXtukVg+OAMAgPrONokAAAB1rLafF5aTkxN9+/ZN2/Kve/fu0bdv30qdv2jRorj00kv3CIIGDRpUo33uMm/evBg/fnxERFx66aVxxx13ROPG6f9cHTZsWJx77rkxdOjQKCwsjJ/85Cdx991373W+3Z8XNnz48MjOrtzvgbZr1y4uvPDC+O1vf5sau/HGG2PEiBG1/ky3E044Idq0aRMfffRRRHwchi1ZsiR69+5dq9fdJTs7O0aPHh0333xzamzSpEnRsWPHtPB215aKAADQkFgZBgAAUMdqOwyrru7du8ftt99eZ9e76aaborS0NPr27Ru//OUv9wjCdhkwYEBccsklERFx7733xtatW/c4Zvny5bFkyZJUXdktEncZP358dO7cOVWXlJTEV77ylf1uzVhdTZo0icsvvzxVl5WVxXe+853Yvn17rV53d+VXfE2fPj1+85vfpI2NHDkybfUcAAA0BMIwAACAOrR169Y9gpX6Foadd9550aRJkzq5VnFxcTz99NMRETFq1Kho1KjRPo/ftZ3g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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from gammapy.irf import EnergyDispersion2D\n", + "\n", + "plt.figure()\n", + "plt.pcolormesh(\n", + " true_energy_bins.to_value(u.GeV),\n", + " migration_bins,\n", + " edisp[:, :, 0].T,\n", + " cmap='inferno'\n", + ")\n", + "\n", + "plt.xlabel('$E_\\mathrm{true} / \\mathrm{GeV}$')\n", + "plt.ylabel('$\\mu$')\n", + "plt.yscale('log')\n", + "plt.xscale('log')" + ] + }, + { + "cell_type": "markdown", + "id": "medical-dominican", + "metadata": {}, + "source": [ + "## Export to GADF FITS files\n", + "\n", + "We use the classes and methods from `astropy.io.fits` and `pyirf.io.gadf` to write files following the GADF \n", + "specification, which can be found here:\n", + "\n", + "https://gamma-astro-data-formats.readthedocs.io/en/latest/" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "twenty-equity", + "metadata": { + "execution": { + "iopub.execute_input": "2024-05-14T10:12:14.180796Z", + "iopub.status.busy": "2024-05-14T10:12:14.180295Z", + "iopub.status.idle": "2024-05-14T10:12:14.239227Z", + "shell.execute_reply": "2024-05-14T10:12:14.238636Z" + } + }, + "outputs": [], + "source": [ + "from pyirf.io.gadf import create_aeff2d_hdu, create_energy_dispersion_hdu, create_psf_table_hdu\n", + "from astropy.io import fits\n", + "from astropy.time import Time\n", + "from pyirf import __version__\n", + "\n", + "# set some common meta data for all hdus\n", + "meta = dict(\n", + " CREATOR='pyirf-v' + __version__,\n", + " TELESCOP='FACT',\n", + " INSTRUME='FACT',\n", + " DATE=Time.now().iso,\n", + ")\n", + "\n", + "hdus = []\n", + "\n", + "# every fits file has to have an Image HDU as first HDU.\n", + "# GADF only uses Binary Table HDUs, so we need to add an empty HDU in front\n", + "hdus.append(fits.PrimaryHDU(header=fits.Header(meta)))\n", + "\n", + "hdus.append(create_aeff2d_hdu(aeff_selected, true_energy_bins, fov_offset_bins, **meta))\n", + "hdus.append(create_energy_dispersion_hdu(edisp, true_energy_bins, migration_bins, fov_offset_bins, **meta))\n", + "hdus.append(create_psf_table_hdu(psf, true_energy_bins, source_offset_bins, fov_offset_bins, **meta))\n", + "\n", + "fits.HDUList(hdus).writeto('fact_irf.fits.gz', overwrite=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/index.html b/notebooks/index.html new file mode 100644 index 000000000..e600de234 --- /dev/null +++ b/notebooks/index.html @@ -0,0 +1,149 @@ + + + + + + + Example Notebooks — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Example Notebooks

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 000000000..86231079d Binary files /dev/null and b/objects.inv differ diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 000000000..a7c5980d3 --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,210 @@ + + + + + + Python Module Index — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ p +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ p
+ pyirf +
    + pyirf.benchmarks +
    + pyirf.binning +
    + pyirf.cut_optimization +
    + pyirf.cuts +
    + pyirf.gammapy +
    + pyirf.io +
    + pyirf.irf +
    + pyirf.sensitivity +
    + pyirf.simulations +
    + pyirf.spectral +
    + pyirf.statistics +
    + pyirf.utils +
+ + +
+
+
+ +
+ +
+

© Copyright 2020, Maximilian Nöthe, Michele Peresano, Thomas Vuillaume.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 000000000..5e248fa6e --- /dev/null +++ b/search.html @@ -0,0 +1,150 @@ + + + + + + Search — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2020, Maximilian Nöthe, Michele Peresano, Thomas Vuillaume.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 000000000..ae6104004 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"0.1.0 (2020-09-16)": [[79, "pyirf-0p1p0-release"]], "0.1.0-alpha (2020-05-27)": [[79, "alpha-2020-05-27"]], "0.2.0 (2020-09-27)": [[79, "pyirf-0p2p0-release"]], "0.3.0 (2020-10-05)": [[79, "pyirf-0p3p0-release"]], "0.4.0 (2020-11-09)": [[79, "pyirf-0p4p0-release"]], "0.4.1 (2021-03-22)": [[79, "pyirf-0p4p1-release"]], "API Changes": [[79, "api-changes"], [79, "id7"], [79, "id10"]], "API Documentation": [[85, null]], "Apply Event Selection": [[91, "Apply-Event-Selection"]], "Authors": [[0, "authors"]], "Background rate": [[90, "background-rate"]], "Base Classes": [[87, "base-classes"]], "BaseComponentEstimator": [[19, "basecomponentestimator"]], "BaseExtrapolator": [[20, "baseextrapolator"]], "BaseInterpolator": [[21, "baseinterpolator"]], "BaseNearestNeighborSearcher": [[22, "basenearestneighborsearcher"]], "Benchmarks": [[77, "benchmarks"]], "Binning and Histogram Utilities": [[78, "binning-and-histogram-utilities"]], "Bug Fixes": [[79, "bug-fixes"], [79, "id2"], [79, "id6"], [79, "id8"], [79, "id11"]], "Building the documentation": [[80, "building-the-documentation"]], "CRAB_HEGRA": [[58, "crab-hegra"]], "CRAB_MAGIC_JHEAP2015": [[59, "crab-magic-jheap2015"]], "Calculate IRFs": [[91, "Calculate-IRFs"]], "Calculate effective area": [[91, "Calculate-effective-area"]], "Calculating Sensitivity and IRFs for EventDisplay DL2 data": [[83, "calculating-sensitivity-and-irfs-for-eventdisplay-dl2-data"]], "Calculating and Applying Cuts": [[82, "calculating-and-applying-cuts"]], "Changelog": [[79, "changelog"]], "Citing this software": [[85, "citing-this-software"]], "Classes": [[87, "classes"], [94, "classes"], [95, "classes"]], "Column definitions for DL2 event lists": [[88, "id1"]], "Contributors": [[79, "contributors"], [79, "id21"], [79, "id32"], [79, "id58"], [79, "id67"]], "Create the binning": [[91, "Create-the-binning"]], "Creating new Estimator Classes": [[87, "creating-new-estimator-classes"]], "Current projects": [[80, "current-projects"]], "Cut Optimization": [[81, "cut-optimization"]], "DAMPE_P_He_SPECTRUM": [[60, "dampe-p-he-spectrum"]], "DIFFUSE_FLUX_UNIT": [[61, "diffuse-flux-unit"]], "DL2 Event List": [[91, "DL2-Event-List"]], "DL2 event lists": [[88, "dl2-event-lists"]], "Description": [[79, "description"], [79, "id33"], [79, "id59"], [79, "id66"]], "Development procedure": [[80, "development-procedure"]], "DiscretePDFComponentEstimator": [[23, "discretepdfcomponentestimator"]], "DiscretePDFExtrapolator": [[24, "discretepdfextrapolator"]], "DiscretePDFInterpolator": [[25, "discretepdfinterpolator"]], "DiscretePDFNearestNeighborSearcher": [[26, "discretepdfnearestneighborsearcher"]], "Download Data": [[91, "Download-Data"]], "Effective Area": [[90, "effective-area"]], "Effective area": [[91, "Effective-area"]], "EffectiveAreaEstimator": [[27, "effectiveareaestimator"]], "Energy Dispersion": [[91, "Energy-Dispersion"]], "Energy Dispersion Matrix": [[90, "energy-dispersion-matrix"]], "EnergyDispersionEstimator": [[28, "energydispersionestimator"]], "Event Weighting and Spectrum Definitions": [[95, "event-weighting-and-spectrum-definitions"]], "Example Notebooks": [[92, "example-notebooks"]], "Examples": [[83, "examples"]], "Export to GADF FITS files": [[91, "Export-to-GADF-FITS-files"]], "Feature changes": [[79, "feature-changes"]], "Full API": [[87, "full-api"]], "Functions": [[77, "functions"], [78, "functions"], [81, "functions"], [82, "functions"], [84, "functions"], [89, "functions"], [90, "functions"], [93, "functions"], [95, "functions"], [96, "functions"], [97, "functions"]], "Further details": [[80, "further-details"]], "Gammapy Interoperability": [[84, "gammapy-interoperability"]], "GridDataInterpolator": [[29, "griddatainterpolator"]], "Helper Classes": [[87, "helper-classes"]], "How to contribute": [[80, "how-to-contribute"]], "IRF Component Estimator Classes": [[87, "irf-component-estimator-classes"]], "IRFDOC_ELECTRON_SPECTRUM": [[62, "irfdoc-electron-spectrum"]], "IRFDOC_PROTON_SPECTRUM": [[63, "irfdoc-proton-spectrum"]], "Indices and tables": [[85, "indices-and-tables"]], "Input / Output": [[89, "input-output"]], "Input formats": [[88, "input-formats"]], "Installation": [[86, "installation"]], "Installing a released version": [[86, "installing-a-released-version"]], "Installing for development": [[86, "installing-for-development"]], "Instrument Response Functions": [[90, "instrument-response-functions"]], "Inter- and Extrapolation Classes": [[87, "inter-and-extrapolation-classes"]], "Interpolation and Extrapolation of IRFs": [[87, "interpolation-and-extrapolation-of-irfs"]], "Introduction": [[89, "introduction"]], "Introduction to pyirf": [[88, "introduction-to-pyirf"]], "Issue Tracker": [[80, "issue-tracker"]], "LogParabola": [[64, "logparabola"]], "Maintenance": [[79, "maintenance"], [79, "id4"], [79, "id13"], [79, "id15"]], "Making your contribution visible": [[80, "making-your-contribution-visible"]], "Merged Pull Requests": [[79, "merged-pull-requests"], [79, "id22"], [79, "id34"], [79, "id60"]], "MomentMorphInterpolator": [[30, "momentmorphinterpolator"]], "MomentMorphNearestSimplexExtrapolator": [[31, "momentmorphnearestsimplexextrapolator"]], "New Features": [[79, "new-features"], [79, "id3"], [79, "id9"], [79, "id12"], [79, "id14"]], "Older releases": [[79, "older-releases"]], "Overview": [[85, null]], "PDFNormalization": [[32, "pdfnormalization"]], "PDG_ALL_PARTICLE": [[65, "pdg-all-particle"]], "POINT_SOURCE_FLUX_UNIT": [[66, "point-source-flux-unit"]], "PSFTableEstimator": [[33, "psftableestimator"]], "ParametrizedComponentEstimator": [[34, "parametrizedcomponentestimator"]], "ParametrizedExtrapolator": [[35, "parametrizedextrapolator"]], "ParametrizedInterpolator": [[36, "parametrizedinterpolator"]], "ParametrizedNearestNeighborSearcher": [[37, "parametrizednearestneighborsearcher"]], "ParametrizedNearestSimplexExtrapolator": [[38, "parametrizednearestsimplexextrapolator"]], "ParametrizedVisibleEdgesExtrapolator": [[39, "parametrizedvisibleedgesextrapolator"]], "Point Spread Function": [[90, "point-spread-function"], [91, "Point-Spread-Function"]], "PowerLaw": [[67, "powerlaw"]], "PowerLawWithExponentialGaussian": [[68, "powerlawwithexponentialgaussian"]], "Project maintenance": [[79, "project-maintenance"]], "Pyirf 0.10.2.dev55+g0fefb93 (2024-05-14)": [[79, "pyirf-0-10-2-dev55-g0fefb93-2024-05-14"]], "Pyirf v0.10.0 (2023-08-23)": [[79, "pyirf-v0-10-0-2023-08-23"]], "QuantileInterpolator": [[40, "quantileinterpolator"]], "RadMaxEstimator": [[41, "radmaxestimator"]], "Read in the data": [[91, "Read-in-the-data"]], "Refactoring and Optimization": [[79, "refactoring-and-optimization"], [79, "id5"]], "Reference/API": [[78, "reference-api"], [81, "reference-api"], [82, "reference-api"], [84, "reference-api"], [89, "reference-api"], [90, "reference-api"], [93, "reference-api"], [94, "reference-api"], [95, "reference-api"], [96, "reference-api"], [97, "reference-api"]], "Related news": [[79, "related-news"]], "Running the tests and looking at coverage": [[80, "running-the-tests-and-looking-at-coverage"]], "Sensitivity": [[93, "sensitivity"]], "Simulated Event Info": [[91, "Simulated-Event-Info"]], "SimulatedEventsInfo": [[57, "simulatedeventsinfo"]], "Simulation Information": [[94, "simulation-information"]], "Statistics": [[96, "statistics"]], "Summary": [[79, "summary"], [79, "id20"], [79, "id31"], [79, "id57"], [79, "id65"]], "TableInterpolationSpectrum": [[69, "tableinterpolationspectrum"]], "Using Estimator Classes": [[87, "using-estimator-classes"]], "Using pyirf to calculate IRFs from the FACT Open Data": [[91, "Using-pyirf-to-calculate-IRFs-from-the-FACT-Open-Data"]], "Utility functions": [[97, "utility-functions"]], "Variables": [[95, "variables"]], "Visualization of the included Flux Models": [[83, "visualization-of-the-included-flux-models"]], "Welcome to pyirf\u2019s documentation!": [[85, "welcome-to-pyirf-s-documentation"]], "add_overflow_bins": [[4, "add-overflow-bins"]], "angular_resolution": [[1, "angular-resolution"]], "background_2d": [[48, "background-2d"]], "bin_center": [[5, "bin-center"]], "calculate_bin_indices": [[6, "calculate-bin-indices"]], "calculate_event_weights": [[70, "calculate-event-weights"]], "calculate_percentile_cut": [[13, "calculate-percentile-cut"]], "calculate_sensitivity": [[54, "calculate-sensitivity"]], "calculate_source_fov_offset": [[72, "calculate-source-fov-offset"]], "calculate_theta": [[73, "calculate-theta"]], "check_histograms": [[74, "check-histograms"]], "compare_irf_cuts": [[14, "compare-irf-cuts"]], "cone_solid_angle": [[75, "cone-solid-angle"]], "create_aeff2d_hdu": [[42, "create-aeff2d-hdu"]], "create_background_2d_hdu": [[43, "create-background-2d-hdu"]], "create_bins_per_decade": [[7, "create-bins-per-decade"]], "create_effective_area_table_2d": [[16, "create-effective-area-table-2d"]], "create_energy_dispersion_2d": [[17, "create-energy-dispersion-2d"]], "create_energy_dispersion_hdu": [[44, "create-energy-dispersion-hdu"]], "create_histogram_table": [[8, "create-histogram-table"]], "create_psf_3d": [[18, "create-psf-3d"]], "create_psf_table_hdu": [[45, "create-psf-table-hdu"]], "create_rad_max_hdu": [[46, "create-rad-max-hdu"]], "effective_area": [[49, "effective-area"]], "effective_area_per_energy": [[50, "effective-area-per-energy"]], "effective_area_per_energy_and_fov": [[51, "effective-area-per-energy-and-fov"]], "energy_bias_resolution": [[2, "energy-bias-resolution"]], "energy_bias_resolution_from_energy_dispersion": [[3, "energy-bias-resolution-from-energy-dispersion"]], "energy_dispersion": [[52, "energy-dispersion"]], "estimate_background": [[55, "estimate-background"]], "evaluate_binned_cut": [[15, "evaluate-binned-cut"]], "is_scalar": [[76, "is-scalar"]], "join_bin_lo_hi": [[9, "join-bin-lo-hi"]], "li_ma_significance": [[71, "li-ma-significance"]], "optimize_gh_cut": [[12, "optimize-gh-cut"]], "psf_table": [[53, "psf-table"]], "pyirf 0.8.1 (2023-03-16)": [[79, "pyirf-0-8-1-2023-03-16"]], "pyirf 0.9.0 (2023-07-19)": [[79, "pyirf-0-9-0-2023-07-19"]], "pyirf v0.10.1 (2023-09-15)": [[79, "pyirf-v0-10-1-2023-09-15"]], "pyirf v0.11.0 (2024-05-14)": [[79, "pyirf-v0-11-0-2024-05-14"]], "pyirf.benchmarks Package": [[77, "module-pyirf.benchmarks"]], "pyirf.binning Module": [[78, "module-pyirf.binning"]], "pyirf.cut_optimization Module": [[81, "module-pyirf.cut_optimization"]], "pyirf.cuts Module": [[82, "module-pyirf.cuts"]], "pyirf.gammapy Module": [[84, "module-pyirf.gammapy"]], "pyirf.io Package": [[89, "module-pyirf.io"]], "pyirf.irf Package": [[90, "module-pyirf.irf"]], "pyirf.sensitivity Module": [[93, "module-pyirf.sensitivity"]], "pyirf.simulations Module": [[94, "module-pyirf.simulations"]], "pyirf.spectral Module": [[95, "module-pyirf.spectral"]], "pyirf.statistics Module": [[96, "module-pyirf.statistics"]], "pyirf.utils Module": [[97, "module-pyirf.utils"]], "read_eventdisplay_fits": [[47, "read-eventdisplay-fits"]], "relative_sensitivity": [[56, "relative-sensitivity"]], "resample_histogram1d": [[10, "resample-histogram1d"]], "split_bin_lo_hi": [[11, "split-bin-lo-hi"]]}, "docnames": ["AUTHORS", "api/pyirf.benchmarks.angular_resolution", "api/pyirf.benchmarks.energy_bias_resolution", "api/pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion", "api/pyirf.binning.add_overflow_bins", "api/pyirf.binning.bin_center", "api/pyirf.binning.calculate_bin_indices", "api/pyirf.binning.create_bins_per_decade", "api/pyirf.binning.create_histogram_table", "api/pyirf.binning.join_bin_lo_hi", "api/pyirf.binning.resample_histogram1d", "api/pyirf.binning.split_bin_lo_hi", "api/pyirf.cut_optimization.optimize_gh_cut", "api/pyirf.cuts.calculate_percentile_cut", "api/pyirf.cuts.compare_irf_cuts", "api/pyirf.cuts.evaluate_binned_cut", "api/pyirf.gammapy.create_effective_area_table_2d", "api/pyirf.gammapy.create_energy_dispersion_2d", "api/pyirf.gammapy.create_psf_3d", "api/pyirf.interpolation.BaseComponentEstimator", "api/pyirf.interpolation.BaseExtrapolator", "api/pyirf.interpolation.BaseInterpolator", "api/pyirf.interpolation.BaseNearestNeighborSearcher", "api/pyirf.interpolation.DiscretePDFComponentEstimator", "api/pyirf.interpolation.DiscretePDFExtrapolator", "api/pyirf.interpolation.DiscretePDFInterpolator", "api/pyirf.interpolation.DiscretePDFNearestNeighborSearcher", "api/pyirf.interpolation.EffectiveAreaEstimator", "api/pyirf.interpolation.EnergyDispersionEstimator", "api/pyirf.interpolation.GridDataInterpolator", "api/pyirf.interpolation.MomentMorphInterpolator", "api/pyirf.interpolation.MomentMorphNearestSimplexExtrapolator", "api/pyirf.interpolation.PDFNormalization", "api/pyirf.interpolation.PSFTableEstimator", "api/pyirf.interpolation.ParametrizedComponentEstimator", "api/pyirf.interpolation.ParametrizedExtrapolator", "api/pyirf.interpolation.ParametrizedInterpolator", "api/pyirf.interpolation.ParametrizedNearestNeighborSearcher", "api/pyirf.interpolation.ParametrizedNearestSimplexExtrapolator", "api/pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator", "api/pyirf.interpolation.QuantileInterpolator", "api/pyirf.interpolation.RadMaxEstimator", "api/pyirf.io.create_aeff2d_hdu", "api/pyirf.io.create_background_2d_hdu", "api/pyirf.io.create_energy_dispersion_hdu", "api/pyirf.io.create_psf_table_hdu", "api/pyirf.io.create_rad_max_hdu", "api/pyirf.io.read_eventdisplay_fits", "api/pyirf.irf.background_2d", "api/pyirf.irf.effective_area", "api/pyirf.irf.effective_area_per_energy", "api/pyirf.irf.effective_area_per_energy_and_fov", "api/pyirf.irf.energy_dispersion", "api/pyirf.irf.psf_table", "api/pyirf.sensitivity.calculate_sensitivity", "api/pyirf.sensitivity.estimate_background", "api/pyirf.sensitivity.relative_sensitivity", "api/pyirf.simulations.SimulatedEventsInfo", "api/pyirf.spectral.CRAB_HEGRA", "api/pyirf.spectral.CRAB_MAGIC_JHEAP2015", "api/pyirf.spectral.DAMPE_P_He_SPECTRUM", "api/pyirf.spectral.DIFFUSE_FLUX_UNIT", "api/pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM", "api/pyirf.spectral.IRFDOC_PROTON_SPECTRUM", "api/pyirf.spectral.LogParabola", "api/pyirf.spectral.PDG_ALL_PARTICLE", "api/pyirf.spectral.POINT_SOURCE_FLUX_UNIT", "api/pyirf.spectral.PowerLaw", "api/pyirf.spectral.PowerLawWithExponentialGaussian", "api/pyirf.spectral.TableInterpolationSpectrum", "api/pyirf.spectral.calculate_event_weights", "api/pyirf.statistics.li_ma_significance", "api/pyirf.utils.calculate_source_fov_offset", "api/pyirf.utils.calculate_theta", "api/pyirf.utils.check_histograms", "api/pyirf.utils.cone_solid_angle", "api/pyirf.utils.is_scalar", "benchmarks/index", "binning", "changelog", "contribute", "cut_optimization", "cuts", "examples", "gammapy", "index", "install", "interpolation", "introduction", "io/index", "irf/index", "notebooks/fact_example", "notebooks/index", "sensitivity", "simulation", "spectral", "statistics", "utils"], "envversion": {"nbsphinx": 4, "sphinx": 61, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1}, "filenames": ["AUTHORS.rst", "api/pyirf.benchmarks.angular_resolution.rst", "api/pyirf.benchmarks.energy_bias_resolution.rst", "api/pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion.rst", "api/pyirf.binning.add_overflow_bins.rst", "api/pyirf.binning.bin_center.rst", "api/pyirf.binning.calculate_bin_indices.rst", "api/pyirf.binning.create_bins_per_decade.rst", "api/pyirf.binning.create_histogram_table.rst", "api/pyirf.binning.join_bin_lo_hi.rst", "api/pyirf.binning.resample_histogram1d.rst", "api/pyirf.binning.split_bin_lo_hi.rst", "api/pyirf.cut_optimization.optimize_gh_cut.rst", "api/pyirf.cuts.calculate_percentile_cut.rst", "api/pyirf.cuts.compare_irf_cuts.rst", "api/pyirf.cuts.evaluate_binned_cut.rst", "api/pyirf.gammapy.create_effective_area_table_2d.rst", "api/pyirf.gammapy.create_energy_dispersion_2d.rst", "api/pyirf.gammapy.create_psf_3d.rst", "api/pyirf.interpolation.BaseComponentEstimator.rst", "api/pyirf.interpolation.BaseExtrapolator.rst", "api/pyirf.interpolation.BaseInterpolator.rst", "api/pyirf.interpolation.BaseNearestNeighborSearcher.rst", "api/pyirf.interpolation.DiscretePDFComponentEstimator.rst", "api/pyirf.interpolation.DiscretePDFExtrapolator.rst", "api/pyirf.interpolation.DiscretePDFInterpolator.rst", "api/pyirf.interpolation.DiscretePDFNearestNeighborSearcher.rst", "api/pyirf.interpolation.EffectiveAreaEstimator.rst", "api/pyirf.interpolation.EnergyDispersionEstimator.rst", "api/pyirf.interpolation.GridDataInterpolator.rst", "api/pyirf.interpolation.MomentMorphInterpolator.rst", "api/pyirf.interpolation.MomentMorphNearestSimplexExtrapolator.rst", "api/pyirf.interpolation.PDFNormalization.rst", "api/pyirf.interpolation.PSFTableEstimator.rst", "api/pyirf.interpolation.ParametrizedComponentEstimator.rst", "api/pyirf.interpolation.ParametrizedExtrapolator.rst", "api/pyirf.interpolation.ParametrizedInterpolator.rst", "api/pyirf.interpolation.ParametrizedNearestNeighborSearcher.rst", "api/pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.rst", "api/pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator.rst", "api/pyirf.interpolation.QuantileInterpolator.rst", "api/pyirf.interpolation.RadMaxEstimator.rst", "api/pyirf.io.create_aeff2d_hdu.rst", "api/pyirf.io.create_background_2d_hdu.rst", "api/pyirf.io.create_energy_dispersion_hdu.rst", "api/pyirf.io.create_psf_table_hdu.rst", "api/pyirf.io.create_rad_max_hdu.rst", "api/pyirf.io.read_eventdisplay_fits.rst", "api/pyirf.irf.background_2d.rst", "api/pyirf.irf.effective_area.rst", "api/pyirf.irf.effective_area_per_energy.rst", "api/pyirf.irf.effective_area_per_energy_and_fov.rst", "api/pyirf.irf.energy_dispersion.rst", "api/pyirf.irf.psf_table.rst", "api/pyirf.sensitivity.calculate_sensitivity.rst", "api/pyirf.sensitivity.estimate_background.rst", "api/pyirf.sensitivity.relative_sensitivity.rst", "api/pyirf.simulations.SimulatedEventsInfo.rst", "api/pyirf.spectral.CRAB_HEGRA.rst", "api/pyirf.spectral.CRAB_MAGIC_JHEAP2015.rst", "api/pyirf.spectral.DAMPE_P_He_SPECTRUM.rst", "api/pyirf.spectral.DIFFUSE_FLUX_UNIT.rst", "api/pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM.rst", "api/pyirf.spectral.IRFDOC_PROTON_SPECTRUM.rst", "api/pyirf.spectral.LogParabola.rst", "api/pyirf.spectral.PDG_ALL_PARTICLE.rst", "api/pyirf.spectral.POINT_SOURCE_FLUX_UNIT.rst", "api/pyirf.spectral.PowerLaw.rst", "api/pyirf.spectral.PowerLawWithExponentialGaussian.rst", "api/pyirf.spectral.TableInterpolationSpectrum.rst", "api/pyirf.spectral.calculate_event_weights.rst", "api/pyirf.statistics.li_ma_significance.rst", "api/pyirf.utils.calculate_source_fov_offset.rst", "api/pyirf.utils.calculate_theta.rst", "api/pyirf.utils.check_histograms.rst", "api/pyirf.utils.cone_solid_angle.rst", "api/pyirf.utils.is_scalar.rst", "benchmarks/index.rst", "binning.rst", "changelog.rst", "contribute.rst", "cut_optimization.rst", "cuts.rst", "examples.rst", "gammapy.rst", "index.rst", "install.rst", "interpolation.rst", "introduction.rst", "io/index.rst", "irf/index.rst", "notebooks/fact_example.ipynb", "notebooks/index.rst", "sensitivity.rst", "simulation.rst", "spectral.rst", "statistics.rst", "utils.rst"], "indexentries": {"__call__() (pyirf.interpolation.basecomponentestimator method)": [[19, "pyirf.interpolation.BaseComponentEstimator.__call__", false]], "__call__() (pyirf.interpolation.baseextrapolator method)": [[20, "pyirf.interpolation.BaseExtrapolator.__call__", false]], "__call__() (pyirf.interpolation.baseinterpolator method)": [[21, "pyirf.interpolation.BaseInterpolator.__call__", false]], "__call__() (pyirf.interpolation.basenearestneighborsearcher method)": [[22, "pyirf.interpolation.BaseNearestNeighborSearcher.__call__", false]], "__call__() (pyirf.interpolation.discretepdfcomponentestimator method)": [[23, "pyirf.interpolation.DiscretePDFComponentEstimator.__call__", false]], "__call__() (pyirf.interpolation.discretepdfextrapolator method)": [[24, "pyirf.interpolation.DiscretePDFExtrapolator.__call__", false]], "__call__() (pyirf.interpolation.discretepdfinterpolator method)": [[25, "pyirf.interpolation.DiscretePDFInterpolator.__call__", false]], "__call__() (pyirf.interpolation.discretepdfnearestneighborsearcher method)": [[26, "pyirf.interpolation.DiscretePDFNearestNeighborSearcher.__call__", false]], "__call__() (pyirf.interpolation.effectiveareaestimator method)": [[27, "pyirf.interpolation.EffectiveAreaEstimator.__call__", false]], "__call__() (pyirf.interpolation.energydispersionestimator method)": [[28, "pyirf.interpolation.EnergyDispersionEstimator.__call__", false]], "__call__() (pyirf.interpolation.griddatainterpolator method)": [[29, "pyirf.interpolation.GridDataInterpolator.__call__", false]], "__call__() (pyirf.interpolation.momentmorphinterpolator method)": [[30, "pyirf.interpolation.MomentMorphInterpolator.__call__", false]], "__call__() (pyirf.interpolation.momentmorphnearestsimplexextrapolator method)": [[31, "pyirf.interpolation.MomentMorphNearestSimplexExtrapolator.__call__", false]], "__call__() (pyirf.interpolation.parametrizedcomponentestimator method)": [[34, "pyirf.interpolation.ParametrizedComponentEstimator.__call__", false]], "__call__() (pyirf.interpolation.parametrizedextrapolator method)": [[35, "pyirf.interpolation.ParametrizedExtrapolator.__call__", false]], "__call__() (pyirf.interpolation.parametrizedinterpolator method)": [[36, "pyirf.interpolation.ParametrizedInterpolator.__call__", false]], "__call__() (pyirf.interpolation.parametrizednearestneighborsearcher method)": [[37, "pyirf.interpolation.ParametrizedNearestNeighborSearcher.__call__", false]], "__call__() (pyirf.interpolation.parametrizednearestsimplexextrapolator method)": [[38, "pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.__call__", false]], "__call__() (pyirf.interpolation.parametrizedvisibleedgesextrapolator method)": [[39, "pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator.__call__", false]], "__call__() (pyirf.interpolation.psftableestimator method)": [[33, "pyirf.interpolation.PSFTableEstimator.__call__", false]], "__call__() (pyirf.interpolation.quantileinterpolator method)": [[40, "pyirf.interpolation.QuantileInterpolator.__call__", false]], "__call__() (pyirf.interpolation.radmaxestimator method)": [[41, "pyirf.interpolation.RadMaxEstimator.__call__", false]], "__call__() (pyirf.spectral.logparabola method)": [[64, "pyirf.spectral.LogParabola.__call__", false]], "__call__() (pyirf.spectral.powerlaw method)": [[67, "pyirf.spectral.PowerLaw.__call__", false]], "__call__() (pyirf.spectral.powerlawwithexponentialgaussian method)": [[68, "pyirf.spectral.PowerLawWithExponentialGaussian.__call__", false]], "__call__() (pyirf.spectral.tableinterpolationspectrum method)": [[69, "pyirf.spectral.TableInterpolationSpectrum.__call__", false]], "add_overflow_bins() (in module pyirf.binning)": [[4, "pyirf.binning.add_overflow_bins", false]], "angular_resolution() (in module pyirf.benchmarks)": [[1, "pyirf.benchmarks.angular_resolution", false]], "area (pyirf.interpolation.pdfnormalization attribute)": [[32, "pyirf.interpolation.PDFNormalization.AREA", false]], "background_2d() (in module pyirf.irf)": [[48, "pyirf.irf.background_2d", false]], "basecomponentestimator (class in pyirf.interpolation)": [[19, "pyirf.interpolation.BaseComponentEstimator", false]], "baseextrapolator (class in pyirf.interpolation)": [[20, "pyirf.interpolation.BaseExtrapolator", false]], "baseinterpolator (class in pyirf.interpolation)": [[21, "pyirf.interpolation.BaseInterpolator", false]], "basenearestneighborsearcher (class in pyirf.interpolation)": [[22, "pyirf.interpolation.BaseNearestNeighborSearcher", false]], "bin_center() (in module pyirf.binning)": [[5, "pyirf.binning.bin_center", false]], "calculate_bin_indices() (in module pyirf.binning)": [[6, "pyirf.binning.calculate_bin_indices", false]], "calculate_event_weights() (in module pyirf.spectral)": [[70, "pyirf.spectral.calculate_event_weights", false]], "calculate_n_showers_per_energy() (pyirf.simulations.simulatedeventsinfo method)": [[57, "pyirf.simulations.SimulatedEventsInfo.calculate_n_showers_per_energy", false]], "calculate_n_showers_per_energy_and_fov() (pyirf.simulations.simulatedeventsinfo method)": [[57, "pyirf.simulations.SimulatedEventsInfo.calculate_n_showers_per_energy_and_fov", false]], "calculate_n_showers_per_fov() (pyirf.simulations.simulatedeventsinfo method)": [[57, "pyirf.simulations.SimulatedEventsInfo.calculate_n_showers_per_fov", false]], "calculate_percentile_cut() (in module pyirf.cuts)": [[13, "pyirf.cuts.calculate_percentile_cut", false]], "calculate_sensitivity() (in module pyirf.sensitivity)": [[54, "pyirf.sensitivity.calculate_sensitivity", false]], "calculate_source_fov_offset() (in module pyirf.utils)": [[72, "pyirf.utils.calculate_source_fov_offset", false]], "calculate_theta() (in module pyirf.utils)": [[73, "pyirf.utils.calculate_theta", false]], "check_histograms() (in module pyirf.utils)": [[74, "pyirf.utils.check_histograms", false]], "compare_irf_cuts() (in module pyirf.cuts)": [[14, "pyirf.cuts.compare_irf_cuts", false]], "cone_solid_angle (pyirf.interpolation.pdfnormalization attribute)": [[32, "pyirf.interpolation.PDFNormalization.CONE_SOLID_ANGLE", false]], "cone_solid_angle() (in module pyirf.utils)": [[75, "pyirf.utils.cone_solid_angle", false]], "crab_hegra (in module pyirf.spectral)": [[58, "pyirf.spectral.CRAB_HEGRA", false]], "crab_magic_jheap2015 (in module pyirf.spectral)": [[59, "pyirf.spectral.CRAB_MAGIC_JHEAP2015", false]], "create_aeff2d_hdu() (in module pyirf.io)": [[42, "pyirf.io.create_aeff2d_hdu", false]], "create_background_2d_hdu() (in module pyirf.io)": [[43, "pyirf.io.create_background_2d_hdu", false]], "create_bins_per_decade() (in module pyirf.binning)": [[7, "pyirf.binning.create_bins_per_decade", false]], "create_effective_area_table_2d() (in module pyirf.gammapy)": [[16, "pyirf.gammapy.create_effective_area_table_2d", false]], "create_energy_dispersion_2d() (in module pyirf.gammapy)": [[17, "pyirf.gammapy.create_energy_dispersion_2d", false]], "create_energy_dispersion_hdu() (in module pyirf.io)": [[44, "pyirf.io.create_energy_dispersion_hdu", false]], "create_histogram_table() (in module pyirf.binning)": [[8, "pyirf.binning.create_histogram_table", false]], "create_psf_3d() (in module pyirf.gammapy)": [[18, "pyirf.gammapy.create_psf_3d", false]], "create_psf_table_hdu() (in module pyirf.io)": [[45, "pyirf.io.create_psf_table_hdu", false]], "create_rad_max_hdu() (in module pyirf.io)": [[46, "pyirf.io.create_rad_max_hdu", false]], "dampe_p_he_spectrum (in module pyirf.spectral)": [[60, "pyirf.spectral.DAMPE_P_He_SPECTRUM", false]], "diffuse_flux_unit (in module pyirf.spectral)": [[61, "pyirf.spectral.DIFFUSE_FLUX_UNIT", false]], "discretepdfcomponentestimator (class in pyirf.interpolation)": [[23, "pyirf.interpolation.DiscretePDFComponentEstimator", false]], "discretepdfextrapolator (class in pyirf.interpolation)": [[24, "pyirf.interpolation.DiscretePDFExtrapolator", false]], "discretepdfinterpolator (class in pyirf.interpolation)": [[25, "pyirf.interpolation.DiscretePDFInterpolator", false]], "discretepdfnearestneighborsearcher (class in pyirf.interpolation)": [[26, "pyirf.interpolation.DiscretePDFNearestNeighborSearcher", false]], "effective_area() (in module pyirf.irf)": [[49, "pyirf.irf.effective_area", false]], "effective_area_per_energy() (in module pyirf.irf)": [[50, "pyirf.irf.effective_area_per_energy", false]], "effective_area_per_energy_and_fov() (in module pyirf.irf)": [[51, "pyirf.irf.effective_area_per_energy_and_fov", false]], "effectiveareaestimator (class in pyirf.interpolation)": [[27, "pyirf.interpolation.EffectiveAreaEstimator", false]], "energy_bias_resolution() (in module pyirf.benchmarks)": [[2, "pyirf.benchmarks.energy_bias_resolution", false]], "energy_bias_resolution_from_energy_dispersion() (in module pyirf.benchmarks)": [[3, "pyirf.benchmarks.energy_bias_resolution_from_energy_dispersion", false]], "energy_dispersion() (in module pyirf.irf)": [[52, "pyirf.irf.energy_dispersion", false]], "energy_max (pyirf.simulations.simulatedeventsinfo attribute)": [[57, "pyirf.simulations.SimulatedEventsInfo.energy_max", false]], "energy_min (pyirf.simulations.simulatedeventsinfo attribute)": [[57, "pyirf.simulations.SimulatedEventsInfo.energy_min", false]], "energydispersionestimator (class in pyirf.interpolation)": [[28, "pyirf.interpolation.EnergyDispersionEstimator", false]], "estimate_background() (in module pyirf.sensitivity)": [[55, "pyirf.sensitivity.estimate_background", false]], "evaluate_binned_cut() (in module pyirf.cuts)": [[15, "pyirf.cuts.evaluate_binned_cut", false]], "extrapolate() (pyirf.interpolation.baseextrapolator method)": [[20, "pyirf.interpolation.BaseExtrapolator.extrapolate", false]], "extrapolate() (pyirf.interpolation.discretepdfextrapolator method)": [[24, "pyirf.interpolation.DiscretePDFExtrapolator.extrapolate", false]], "extrapolate() (pyirf.interpolation.momentmorphnearestsimplexextrapolator method)": [[31, "pyirf.interpolation.MomentMorphNearestSimplexExtrapolator.extrapolate", false]], "extrapolate() (pyirf.interpolation.parametrizedextrapolator method)": [[35, "pyirf.interpolation.ParametrizedExtrapolator.extrapolate", false]], "extrapolate() (pyirf.interpolation.parametrizednearestsimplexextrapolator method)": [[38, "pyirf.interpolation.ParametrizedNearestSimplexExtrapolator.extrapolate", false]], "extrapolate() (pyirf.interpolation.parametrizedvisibleedgesextrapolator method)": [[39, "pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator.extrapolate", false]], "from_file() (pyirf.spectral.tableinterpolationspectrum class method)": [[69, "pyirf.spectral.TableInterpolationSpectrum.from_file", false]], "from_simulation() (pyirf.spectral.powerlaw class method)": [[67, "pyirf.spectral.PowerLaw.from_simulation", false]], "from_table() (pyirf.spectral.tableinterpolationspectrum class method)": [[69, "pyirf.spectral.TableInterpolationSpectrum.from_table", false]], "griddatainterpolator (class in pyirf.interpolation)": [[29, "pyirf.interpolation.GridDataInterpolator", false]], "integrate_cone() (pyirf.spectral.powerlaw method)": [[67, "pyirf.spectral.PowerLaw.integrate_cone", false]], "interpolate() (pyirf.interpolation.baseinterpolator method)": [[21, "pyirf.interpolation.BaseInterpolator.interpolate", false]], "interpolate() (pyirf.interpolation.basenearestneighborsearcher method)": [[22, "pyirf.interpolation.BaseNearestNeighborSearcher.interpolate", false]], "interpolate() (pyirf.interpolation.discretepdfinterpolator method)": [[25, "pyirf.interpolation.DiscretePDFInterpolator.interpolate", false]], "interpolate() (pyirf.interpolation.discretepdfnearestneighborsearcher method)": [[26, "pyirf.interpolation.DiscretePDFNearestNeighborSearcher.interpolate", false]], "interpolate() (pyirf.interpolation.griddatainterpolator method)": [[29, "pyirf.interpolation.GridDataInterpolator.interpolate", false]], "interpolate() (pyirf.interpolation.momentmorphinterpolator method)": [[30, "pyirf.interpolation.MomentMorphInterpolator.interpolate", false]], "interpolate() (pyirf.interpolation.parametrizedinterpolator method)": [[36, "pyirf.interpolation.ParametrizedInterpolator.interpolate", false]], "interpolate() (pyirf.interpolation.parametrizednearestneighborsearcher method)": [[37, "pyirf.interpolation.ParametrizedNearestNeighborSearcher.interpolate", false]], "interpolate() (pyirf.interpolation.quantileinterpolator method)": [[40, "pyirf.interpolation.QuantileInterpolator.interpolate", false]], "irfdoc_electron_spectrum (in module pyirf.spectral)": [[62, "pyirf.spectral.IRFDOC_ELECTRON_SPECTRUM", false]], "irfdoc_proton_spectrum (in module pyirf.spectral)": [[63, "pyirf.spectral.IRFDOC_PROTON_SPECTRUM", false]], "is_scalar() (in module pyirf.utils)": [[76, "pyirf.utils.is_scalar", false]], "join_bin_lo_hi() (in module pyirf.binning)": [[9, "pyirf.binning.join_bin_lo_hi", false]], "li_ma_significance() (in module pyirf.statistics)": [[71, "pyirf.statistics.li_ma_significance", false]], "logparabola (class in pyirf.spectral)": [[64, "pyirf.spectral.LogParabola", false]], "max_impact (pyirf.simulations.simulatedeventsinfo attribute)": [[57, "pyirf.simulations.SimulatedEventsInfo.max_impact", false]], "module": [[77, "module-pyirf.benchmarks", false], [78, "module-pyirf.binning", false], [81, "module-pyirf.cut_optimization", false], [82, "module-pyirf.cuts", false], [84, "module-pyirf.gammapy", false], [89, "module-pyirf.io", false], [90, "module-pyirf.irf", false], [93, "module-pyirf.sensitivity", false], [94, "module-pyirf.simulations", false], [95, "module-pyirf.spectral", false], [96, "module-pyirf.statistics", false], [97, "module-pyirf.utils", false]], "momentmorphinterpolator (class in pyirf.interpolation)": [[30, "pyirf.interpolation.MomentMorphInterpolator", false]], "momentmorphnearestsimplexextrapolator (class in pyirf.interpolation)": [[31, "pyirf.interpolation.MomentMorphNearestSimplexExtrapolator", false]], "n_showers (pyirf.simulations.simulatedeventsinfo attribute)": [[57, "pyirf.simulations.SimulatedEventsInfo.n_showers", false]], "optimize_gh_cut() (in module pyirf.cut_optimization)": [[12, "pyirf.cut_optimization.optimize_gh_cut", false]], "parametrizedcomponentestimator (class in pyirf.interpolation)": [[34, "pyirf.interpolation.ParametrizedComponentEstimator", false]], "parametrizedextrapolator (class in pyirf.interpolation)": [[35, "pyirf.interpolation.ParametrizedExtrapolator", false]], "parametrizedinterpolator (class in pyirf.interpolation)": [[36, "pyirf.interpolation.ParametrizedInterpolator", false]], "parametrizednearestneighborsearcher (class in pyirf.interpolation)": [[37, "pyirf.interpolation.ParametrizedNearestNeighborSearcher", false]], "parametrizednearestsimplexextrapolator (class in pyirf.interpolation)": [[38, "pyirf.interpolation.ParametrizedNearestSimplexExtrapolator", false]], "parametrizedvisibleedgesextrapolator (class in pyirf.interpolation)": [[39, "pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator", false]], "pdfnormalization (class in pyirf.interpolation)": [[32, "pyirf.interpolation.PDFNormalization", false]], "pdg_all_particle (in module pyirf.spectral)": [[65, "pyirf.spectral.PDG_ALL_PARTICLE", false]], "point_source_flux_unit (in module pyirf.spectral)": [[66, "pyirf.spectral.POINT_SOURCE_FLUX_UNIT", false]], "powerlaw (class in pyirf.spectral)": [[67, "pyirf.spectral.PowerLaw", false]], "powerlawwithexponentialgaussian (class in pyirf.spectral)": [[68, "pyirf.spectral.PowerLawWithExponentialGaussian", false]], "psf_table() (in module pyirf.irf)": [[53, "pyirf.irf.psf_table", false]], "psftableestimator (class in pyirf.interpolation)": [[33, "pyirf.interpolation.PSFTableEstimator", false]], "pyirf.benchmarks": [[77, "module-pyirf.benchmarks", false]], "pyirf.binning": [[78, "module-pyirf.binning", false]], "pyirf.cut_optimization": [[81, "module-pyirf.cut_optimization", false]], "pyirf.cuts": [[82, "module-pyirf.cuts", false]], "pyirf.gammapy": [[84, "module-pyirf.gammapy", false]], "pyirf.io": [[89, "module-pyirf.io", false]], "pyirf.irf": [[90, "module-pyirf.irf", false]], "pyirf.sensitivity": [[93, "module-pyirf.sensitivity", false]], "pyirf.simulations": [[94, "module-pyirf.simulations", false]], "pyirf.spectral": [[95, "module-pyirf.spectral", false]], "pyirf.statistics": [[96, "module-pyirf.statistics", false]], "pyirf.utils": [[97, "module-pyirf.utils", false]], "quantileinterpolator (class in pyirf.interpolation)": [[40, "pyirf.interpolation.QuantileInterpolator", false]], "radmaxestimator (class in pyirf.interpolation)": [[41, "pyirf.interpolation.RadMaxEstimator", false]], "read_eventdisplay_fits() (in module pyirf.io)": [[47, "pyirf.io.read_eventdisplay_fits", false]], "relative_sensitivity() (in module pyirf.sensitivity)": [[56, "pyirf.sensitivity.relative_sensitivity", false]], "resample_histogram1d() (in module pyirf.binning)": [[10, "pyirf.binning.resample_histogram1d", false]], "simulatedeventsinfo (class in pyirf.simulations)": [[57, "pyirf.simulations.SimulatedEventsInfo", false]], "spectral_index (pyirf.simulations.simulatedeventsinfo attribute)": [[57, "pyirf.simulations.SimulatedEventsInfo.spectral_index", false]], "split_bin_lo_hi() (in module pyirf.binning)": [[11, "pyirf.binning.split_bin_lo_hi", false]], "tableinterpolationspectrum (class in pyirf.spectral)": [[69, "pyirf.spectral.TableInterpolationSpectrum", false]], "viewcone_max (pyirf.simulations.simulatedeventsinfo attribute)": [[57, "pyirf.simulations.SimulatedEventsInfo.viewcone_max", false]], "viewcone_min (pyirf.simulations.simulatedeventsinfo attribute)": [[57, "pyirf.simulations.SimulatedEventsInfo.viewcone_min", false]]}, "objects": {"pyirf": [[77, 0, 0, "-", "benchmarks"], [78, 0, 0, "-", "binning"], [81, 0, 0, "-", "cut_optimization"], [82, 0, 0, "-", "cuts"], [84, 0, 0, "-", "gammapy"], [89, 0, 0, "-", "io"], [90, 0, 0, "-", "irf"], [93, 0, 0, "-", "sensitivity"], [94, 0, 0, "-", "simulations"], [95, 0, 0, "-", "spectral"], [96, 0, 0, "-", "statistics"], [97, 0, 0, "-", "utils"]], "pyirf.benchmarks": [[1, 1, 1, "", "angular_resolution"], [2, 1, 1, "", "energy_bias_resolution"], [3, 1, 1, "", "energy_bias_resolution_from_energy_dispersion"]], "pyirf.binning": [[4, 1, 1, "", "add_overflow_bins"], [5, 1, 1, "", "bin_center"], [6, 1, 1, "", "calculate_bin_indices"], [7, 1, 1, "", "create_bins_per_decade"], [8, 1, 1, "", "create_histogram_table"], [9, 1, 1, "", "join_bin_lo_hi"], [10, 1, 1, "", "resample_histogram1d"], [11, 1, 1, "", "split_bin_lo_hi"]], "pyirf.cut_optimization": [[12, 1, 1, "", "optimize_gh_cut"]], "pyirf.cuts": [[13, 1, 1, "", "calculate_percentile_cut"], [14, 1, 1, "", "compare_irf_cuts"], [15, 1, 1, "", "evaluate_binned_cut"]], "pyirf.gammapy": [[16, 1, 1, "", "create_effective_area_table_2d"], [17, 1, 1, "", "create_energy_dispersion_2d"], [18, 1, 1, "", "create_psf_3d"]], "pyirf.interpolation": [[19, 2, 1, "", "BaseComponentEstimator"], [20, 2, 1, "", "BaseExtrapolator"], [21, 2, 1, "", "BaseInterpolator"], [22, 2, 1, "", "BaseNearestNeighborSearcher"], [23, 2, 1, "", "DiscretePDFComponentEstimator"], [24, 2, 1, "", "DiscretePDFExtrapolator"], [25, 2, 1, "", "DiscretePDFInterpolator"], [26, 2, 1, "", "DiscretePDFNearestNeighborSearcher"], [27, 2, 1, "", "EffectiveAreaEstimator"], [28, 2, 1, "", "EnergyDispersionEstimator"], [29, 2, 1, "", "GridDataInterpolator"], [30, 2, 1, "", "MomentMorphInterpolator"], [31, 2, 1, "", "MomentMorphNearestSimplexExtrapolator"], [32, 2, 1, "", "PDFNormalization"], [33, 2, 1, "", "PSFTableEstimator"], [34, 2, 1, "", "ParametrizedComponentEstimator"], [35, 2, 1, "", "ParametrizedExtrapolator"], [36, 2, 1, "", "ParametrizedInterpolator"], [37, 2, 1, "", "ParametrizedNearestNeighborSearcher"], [38, 2, 1, "", "ParametrizedNearestSimplexExtrapolator"], [39, 2, 1, "", "ParametrizedVisibleEdgesExtrapolator"], [40, 2, 1, "", "QuantileInterpolator"], [41, 2, 1, "", "RadMaxEstimator"]], "pyirf.interpolation.BaseComponentEstimator": [[19, 3, 1, "", "__call__"]], "pyirf.interpolation.BaseExtrapolator": [[20, 3, 1, "", "__call__"], [20, 3, 1, "", "extrapolate"]], "pyirf.interpolation.BaseInterpolator": [[21, 3, 1, "", "__call__"], [21, 3, 1, "", "interpolate"]], "pyirf.interpolation.BaseNearestNeighborSearcher": [[22, 3, 1, "", "__call__"], [22, 3, 1, "", "interpolate"]], "pyirf.interpolation.DiscretePDFComponentEstimator": [[23, 3, 1, "", "__call__"]], "pyirf.interpolation.DiscretePDFExtrapolator": [[24, 3, 1, "", "__call__"], [24, 3, 1, "", "extrapolate"]], "pyirf.interpolation.DiscretePDFInterpolator": [[25, 3, 1, "", "__call__"], [25, 3, 1, "", "interpolate"]], "pyirf.interpolation.DiscretePDFNearestNeighborSearcher": [[26, 3, 1, "", "__call__"], [26, 3, 1, "", "interpolate"]], "pyirf.interpolation.EffectiveAreaEstimator": [[27, 3, 1, "", "__call__"]], "pyirf.interpolation.EnergyDispersionEstimator": [[28, 3, 1, "", "__call__"]], "pyirf.interpolation.GridDataInterpolator": [[29, 3, 1, "", "__call__"], [29, 3, 1, "", "interpolate"]], "pyirf.interpolation.MomentMorphInterpolator": [[30, 3, 1, "", "__call__"], [30, 3, 1, "", "interpolate"]], "pyirf.interpolation.MomentMorphNearestSimplexExtrapolator": [[31, 3, 1, "", "__call__"], [31, 3, 1, "", "extrapolate"]], "pyirf.interpolation.PDFNormalization": [[32, 4, 1, "", "AREA"], [32, 4, 1, "", "CONE_SOLID_ANGLE"]], "pyirf.interpolation.PSFTableEstimator": [[33, 3, 1, "", "__call__"]], "pyirf.interpolation.ParametrizedComponentEstimator": [[34, 3, 1, "", "__call__"]], "pyirf.interpolation.ParametrizedExtrapolator": [[35, 3, 1, "", "__call__"], [35, 3, 1, "", "extrapolate"]], "pyirf.interpolation.ParametrizedInterpolator": [[36, 3, 1, "", "__call__"], [36, 3, 1, "", "interpolate"]], "pyirf.interpolation.ParametrizedNearestNeighborSearcher": [[37, 3, 1, "", "__call__"], [37, 3, 1, "", "interpolate"]], "pyirf.interpolation.ParametrizedNearestSimplexExtrapolator": [[38, 3, 1, "", "__call__"], [38, 3, 1, "", "extrapolate"]], "pyirf.interpolation.ParametrizedVisibleEdgesExtrapolator": [[39, 3, 1, "", "__call__"], [39, 3, 1, "", "extrapolate"]], "pyirf.interpolation.QuantileInterpolator": [[40, 3, 1, "", "__call__"], [40, 3, 1, "", "interpolate"]], "pyirf.interpolation.RadMaxEstimator": [[41, 3, 1, "", "__call__"]], "pyirf.io": [[42, 1, 1, "", "create_aeff2d_hdu"], [43, 1, 1, "", "create_background_2d_hdu"], [44, 1, 1, "", "create_energy_dispersion_hdu"], [45, 1, 1, "", "create_psf_table_hdu"], [46, 1, 1, "", "create_rad_max_hdu"], [47, 1, 1, "", "read_eventdisplay_fits"]], "pyirf.irf": [[48, 1, 1, "", "background_2d"], [49, 1, 1, "", "effective_area"], [50, 1, 1, "", "effective_area_per_energy"], [51, 1, 1, "", "effective_area_per_energy_and_fov"], [52, 1, 1, "", "energy_dispersion"], [53, 1, 1, "", "psf_table"]], "pyirf.sensitivity": [[54, 1, 1, "", "calculate_sensitivity"], [55, 1, 1, "", "estimate_background"], [56, 1, 1, "", "relative_sensitivity"]], "pyirf.simulations": [[57, 2, 1, "", "SimulatedEventsInfo"]], "pyirf.simulations.SimulatedEventsInfo": [[57, 3, 1, "", "calculate_n_showers_per_energy"], [57, 3, 1, "", "calculate_n_showers_per_energy_and_fov"], [57, 3, 1, "", "calculate_n_showers_per_fov"], [57, 4, 1, "", "energy_max"], [57, 4, 1, "", "energy_min"], [57, 4, 1, "", "max_impact"], [57, 4, 1, "", "n_showers"], [57, 4, 1, "", "spectral_index"], [57, 4, 1, "", "viewcone_max"], [57, 4, 1, "", "viewcone_min"]], "pyirf.spectral": [[58, 5, 1, "", "CRAB_HEGRA"], [59, 5, 1, "", "CRAB_MAGIC_JHEAP2015"], [60, 5, 1, "", "DAMPE_P_He_SPECTRUM"], [61, 5, 1, "", "DIFFUSE_FLUX_UNIT"], [62, 5, 1, "", "IRFDOC_ELECTRON_SPECTRUM"], [63, 5, 1, "", "IRFDOC_PROTON_SPECTRUM"], [64, 2, 1, "", "LogParabola"], [65, 5, 1, "", "PDG_ALL_PARTICLE"], [66, 5, 1, "", "POINT_SOURCE_FLUX_UNIT"], [67, 2, 1, "", "PowerLaw"], [68, 2, 1, "", "PowerLawWithExponentialGaussian"], [69, 2, 1, "", "TableInterpolationSpectrum"], [70, 1, 1, "", "calculate_event_weights"]], "pyirf.spectral.LogParabola": [[64, 3, 1, "", "__call__"]], "pyirf.spectral.PowerLaw": [[67, 3, 1, "", "__call__"], [67, 3, 1, "", "from_simulation"], [67, 3, 1, "", "integrate_cone"]], "pyirf.spectral.PowerLawWithExponentialGaussian": [[68, 3, 1, "", "__call__"]], "pyirf.spectral.TableInterpolationSpectrum": [[69, 3, 1, "", "__call__"], [69, 3, 1, "", "from_file"], [69, 3, 1, "", "from_table"]], "pyirf.statistics": [[71, 1, 1, "", "li_ma_significance"]], "pyirf.utils": [[72, 1, 1, "", "calculate_source_fov_offset"], [73, 1, 1, "", "calculate_theta"], [74, 1, 1, "", "check_histograms"], [75, 1, 1, "", "cone_solid_angle"], [76, 1, 1, "", "is_scalar"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "attribute", "Python attribute"], "5": ["py", "data", "Python data"]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:method", "4": "py:attribute", "5": "py:data"}, "terms": {"": [27, 30, 31, 38, 43, 49, 58, 59, 61, 62, 63, 65, 66, 79, 87, 91], "0": [1, 4, 6, 9, 10, 11, 12, 13, 40, 47, 52, 54, 56, 58, 59, 62, 63, 65, 71, 85, 87, 91], "00": 91, "001": 40, "002": 59, "00347": 91, "00348": 91, "00349": 91, "00350": 91, "00351": 91, "0042": 91, "00712": 91, "01": 59, "01347": 87, "02": 91, "020": 91, "033": [30, 87], "040": 91, "04353": 91, "05": [54, 56, 91], "050": 91, "05353": 91, "06": 63, "0660": 91, "080": 91, "09": 62, "0x7f233171e0d0": 91, "1": [1, 2, 6, 9, 10, 11, 12, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 45, 48, 54, 55, 56, 58, 59, 61, 62, 63, 64, 65, 66, 67, 68, 69, 87, 91], "10": [13, 30, 54, 56, 58, 59, 64, 68, 71, 87, 91], "100": [13, 79, 91], "101": 62, "1016": [30, 59, 87], "102": 79, "104": 79, "106": 79, "106806237815": 91, "107": 79, "1072962371723": 91, "108": 79, "1083122214430": 91, "1086": [58, 71], "108669183804": 91, "11": [58, 59, 91], "110": 79, "11325166838": 91, "11583403638": 91, "11th": 79, "12": 91, "120": 91, "12600000": 91, "13": [40, 87, 91], "130": 91, "133273103649": 91, "13486165680": 91, "135": 79, "137": 79, "14": 91, "142281221896": 91, "14269217956": 91, "15": [87, 91], "16": 91, "161295": 71, "16th": 79, "17": [56, 71, 91], "170": 91, "18": [79, 91], "18000": 65, "180000": 79, "18115575805640652": 91, "19": 91, "1983": 56, "1984": [39, 87], "1999": 87, "1d": [39, 87], "2": [9, 11, 22, 26, 27, 28, 32, 37, 52, 57, 58, 59, 62, 63, 65, 68, 71, 87, 91], "20": 87, "200": 91, "2004": 58, "2013": [40, 87], "2014": [30, 87], "2015": [30, 59, 87], "2020": [0, 65], "2021": 87, "207": 79, "2082": 91, "20deg": 79, "21": [87, 91], "210": 79, "211": 79, "22353": 91, "228": 79, "229": 79, "22nd": 79, "23": 91, "231": 79, "232": 79, "234": 79, "234550": 91, "236": 79, "237": 79, "239": 79, "23e": 59, "24": 59, "241": 79, "243": 79, "250": 79, "253": 79, "258": 79, "260": 91, "264": 79, "266": 79, "268": 79, "270": 91, "271": 79, "272": 56, "275": 79, "27626": 91, "27th": 79, "282": 79, "28885": 91, "29": 79, "2d": [43, 48, 79, 87], "2f": 91, "3": [59, 62, 63, 86, 87, 91], "30": [65, 87], "300": 91, "317": 56, "324": 56, "33": 91, "3479": 91, "35": 79, "350": 91, "357": 87, "36": 79, "360": 87, "37": 79, "3783": 91, "3789": 91, "380": 91, "385e": 62, "39": [30, 87, 91], "3b": [62, 63], "3gauss": [35, 36], "4": [9, 11, 62, 63, 85, 91], "40": 87, "40deg": 79, "423931": 58, "425": 87, "43": 62, "4579": 91, "46": 79, "47": 59, "472109": 91, "48": [30, 87], "490": 91, "4lst": 79, "5": [7, 40, 52, 54, 56, 79, 85, 87, 88, 91], "50": 91, "500": 58, "51": 87, "5170": 91, "52": 79, "52353": 91, "5281": 87, "5370": 91, "5383": 91, "54": 91, "5499840": [86, 87], "5566": 91, "5682": 91, "58": 79, "59": 91, "5th": 79, "6": [79, 91], "60": 87, "60deg": 79, "61": 91, "614": 58, "62": [58, 63], "62928": 91, "63": 79, "66353": 91, "67": 79, "670": 91, "68": [1, 13, 79], "682458": 91, "6826894921370859": 1, "69": 91, "695347": 91, "7": [65, 86, 91], "71": 79, "72420": 91, "741": 62, "75": [79, 91], "7583": 91, "76": 79, "761": 91, "77": 79, "770": 91, "771": [30, 87], "78": 79, "7881": 91, "79": 79, "7969": 91, "8": 91, "80": [58, 79], "8289": 91, "83353": 91, "83e": 58, "8461": 91, "85": 79, "86": 79, "86353": 91, "87353": 91, "8870": 91, "89": 79, "8e": 63, "9": [63, 87, 91], "90": [79, 91], "9002": 87, "90953": 91, "91": 79, "9183": 91, "92": 91, "9282": 91, "940": 91, "94353": 91, "95": [62, 79], "950": 91, "96": 79, "97": 79, "9766": 91, "98": [79, 87], "980": 91, "98353": 91, "99": 91, "A": [15, 30, 40, 54, 56, 64, 67, 68, 83, 87], "As": [79, 87], "At": [54, 79], "By": [60, 69], "For": [16, 17, 18, 27, 28, 33, 39, 41, 42, 44, 45, 46, 52, 79, 80, 87, 88], "If": [4, 6, 7, 10, 12, 13, 39, 42, 44, 47, 54, 57, 80, 85, 86], "In": [22, 26, 37, 56, 79, 80, 86, 91], "It": [8, 9, 79], "Its": 85, "No": 12, "Not": 87, "Of": 90, "The": [7, 9, 11, 12, 13, 15, 48, 49, 50, 51, 54, 55, 56, 57, 58, 65, 70, 71, 79, 80, 83, 85, 86, 87, 90, 91], "There": 87, "These": 86, "To": [0, 80, 86, 87, 88], "Will": 12, "__call__": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 64, 67, 68, 69, 87], "__init__": [27, 28, 33, 41, 87], "__version__": 91, "_build": 80, "_weight": 79, "abl": 79, "about": [57, 79], "abov": 79, "abstract": [20, 21, 24, 25, 35, 36], "accend": 39, "accord": [10, 54, 79, 88], "account": [12, 55, 79], "action": 80, "activ": 86, "actual": [19, 20, 21, 22, 23, 24, 25, 26, 34, 35, 36, 37, 57, 87], "ad": 79, "ada144660": [39, 87], "adapt": [40, 79, 87], "add": [4, 42, 43, 44, 45, 46, 79, 80, 86, 91], "addit": [39, 42, 43, 44, 45, 46, 68, 79], "addition": [79, 88], "advert": 79, "advis": 87, "aeff": [35, 36, 42, 91], "aeff2d": [16, 27], "aeff_2d": 27, "aeff_al": 91, "aeff_gammapi": 91, "aeff_interp": 27, "aeff_select": 91, "after": [42, 44, 49, 80, 83, 91], "again": 91, "aharonian": 58, "aict": 91, "aim": 88, "air": [58, 88], "al": [58, 59], "aleks\u00ecc": 59, "alf84": 87, "alfr": [39, 87], "algorithm": [79, 87], "all": [14, 19, 20, 21, 23, 24, 25, 34, 35, 36, 39, 55, 56, 57, 65, 79, 80, 86, 87, 88, 91], "allabl": 70, "allow": [79, 88], "along": 10, "alpha": [12, 54, 55, 56, 64, 68, 71], "alphabet": 79, "alreadi": [6, 12, 47, 57, 80, 87, 91], "also": [35, 36, 76, 79, 80, 86, 87, 88, 91], "alt": 72, "altern": 79, "although": 87, "altitud": [73, 88], "alwai": [6, 79, 80], "amount": [79, 85, 88], "an": [6, 8, 12, 15, 19, 23, 34, 40, 52, 55, 68, 79, 80, 86, 87, 88, 91], "anaconda": 86, "analog": 85, "analys": 49, "analysi": [56, 91], "angl": [12, 16, 17, 18, 32, 42, 43, 44, 45, 46, 48, 51, 52, 55, 57, 67, 73, 75, 79, 87, 90], "angular": [1, 72, 73, 79, 85, 90], "angular_resolut": 79, "ani": [76, 87], "anoth": 13, "anticip": 80, "api": [26, 37, 80, 88], "apj": 58, "app": [39, 87, 91], "append": 91, "appli": [12, 13, 14, 42, 44, 47, 54, 55, 85, 87], "applic": [15, 79], "approach": [22, 26, 37, 40, 79, 88], "appropri": [16, 17, 18, 42, 44, 45, 46, 52, 56, 88], "ar": [12, 13, 14, 22, 26, 27, 37, 48, 54, 55, 70, 79, 80, 83, 85, 86, 87, 88, 90, 91], "arbitrari": [87, 91], "area": [16, 24, 25, 27, 30, 31, 32, 40, 41, 42, 44, 49, 50, 51, 61, 66, 79, 85, 87, 88], "argument": [15, 79, 87], "around": [29, 53, 55, 57, 91], "arounf": 29, "arrai": [4, 6, 7, 8, 9, 10, 11, 15, 16, 17, 18, 27, 39, 42, 44, 45, 46, 56, 71, 85, 87, 88], "ask": 83, "assum": [55, 57, 73, 87, 91], "assumed_source_alt": 73, "assumed_source_az": 73, "astro": [42, 43, 44, 45, 46, 48, 91], "astronomi": [56, 80, 88], "astrophys": 56, "astropi": [1, 2, 6, 8, 10, 12, 13, 15, 16, 17, 18, 27, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 54, 55, 57, 64, 67, 68, 69, 70, 72, 73, 74, 75, 76, 79, 86, 87, 88, 91], "aswg": [62, 63], "attach": 87, "attribut": [32, 57, 64, 67, 68], "author": 85, "auto": 91, "automat": [79, 80], "autonotebook": 91, "avail": [39, 83, 87], "averageaz": 79, "ax": [87, 91], "axi": [9, 10, 28, 33, 79], "az": 72, "azimuth": [73, 88], "b": [12, 15, 40, 64, 68, 87], "baa": 87, "baak": [30, 87], "background": [12, 43, 48, 54, 55, 56, 79, 85], "background2d": 79, "background_2d": 43, "background_hist": 54, "bar": 12, "bare": 79, "baryzentr": [38, 87], "base": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 53, 57, 64, 67, 68, 69, 79], "basecomponentestim": [23, 34], "baseextrapol": [24, 35], "baseinterpol": [22, 25, 36], "basenearestneighborsearch": [26, 37], "basic": 79, "becaus": [79, 91], "been": [14, 91], "befor": [56, 79, 80, 91], "being": [85, 91], "belong": 6, "benchmark": [1, 2, 3, 79, 85], "best": [12, 79, 85, 90], "beta": [64, 68], "better": [79, 80, 88], "between": [30, 55, 56, 58, 71, 72, 73, 79, 85, 87, 91], "bewar": 68, "beyond": 87, "bg_rate": 48, "bia": [2, 3], "bias_funct": 2, "biederbeck": 79, "bin": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 24, 25, 30, 32, 40, 42, 43, 44, 45, 46, 48, 50, 51, 52, 53, 54, 55, 57, 74, 79, 84, 85, 87, 90], "bin_cent": 91, "bin_edg": [23, 24, 25, 26, 30, 31, 40, 87], "bin_hi": [9, 11], "bin_index": 6, "bin_lo": [9, 11], "bin_valu": [13, 15], "binari": [42, 43, 44, 45, 46, 91], "binned_pdf": [23, 24, 25, 26, 30, 31, 40, 87], "bins_per_decad": 7, "bintablehdu": [42, 43, 44, 45, 46], "bkg": [43, 48], "blend": [39, 79, 87], "blob": 47, "block": 88, "bool": [4, 6, 12, 15, 42, 44, 47, 76], "boolean": [6, 14, 15], "both": 79, "bound": 79, "branch": 80, "brentq": 79, "bring": 79, "broadcast": [56, 79], "broken": [79, 80], "browser": 80, "bug": 80, "build": [79, 86, 88, 90], "built": [12, 80], "bump": 68, "bunch": [22, 26, 30, 31, 37, 38, 39, 40], "cach": 79, "calcul": [1, 2, 3, 6, 10, 13, 42, 44, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 67, 70, 71, 72, 73, 75, 77, 79, 85, 88, 90, 92, 93, 95], "calculate_eventdisplay_irf": [79, 83], "calculate_n_showers_per_energi": 57, "calculate_n_showers_per_energy_and_fov": 57, "calculate_n_showers_per_fov": 57, "calculate_percentile_cut": [12, 15, 55], "calculate_sensit": 12, "calculate_source_fov_offset": 91, "call": [30, 64, 67, 68, 69, 80], "callabl": [2, 15, 54, 70], "camera": 55, "can": [35, 36, 72, 79, 80, 83, 86, 87, 88, 90, 91], "capabl": 85, "case": [22, 26, 37, 79, 87, 88], "cdf": 87, "cdot": [64, 68], "center": [12, 55, 88], "central": 79, "certain": [54, 67, 70, 88], "chain": 91, "chang": [80, 85], "changelog": [80, 85], "check": [14, 19, 23, 34, 74, 79, 80, 87], "cherenkov": [58, 85, 87, 88], "ci": [79, 80], "clarifi": 79, "class": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 57, 64, 67, 68, 69, 79, 84, 91], "classif": 88, "classmethod": [67, 69], "clean": 80, "cleanup": 79, "clone": 86, "close": [40, 79], "closer": 79, "cm2": [58, 59, 62, 63], "cmap": 91, "code": [0, 20, 21, 24, 25, 35, 36, 79, 80, 85], "codeown": 80, "col": [79, 91], "collect": 90, "colnam": 91, "column": [12, 15, 48, 50, 51, 52, 54, 55, 72, 79, 91], "com": 47, "combin": 47, "combinator": 79, "come": [80, 87], "comment": 80, "commit": [79, 80], "common": [20, 21, 22, 24, 25, 26, 29, 30, 31, 35, 36, 37, 38, 39, 40, 56, 91], "compar": [15, 79, 83], "comparison": [12, 79], "comparison_with_eventdisplai": 83, "compat": [10, 26, 37], "complet": 79, "compon": [19, 23, 24, 25, 26, 34, 35, 36, 37, 79], "compont": 79, "compos": 88, "comput": [12, 79, 85, 90], "concern": 79, "conda": 86, "condit": [54, 56, 79, 87], "cone": [32, 67, 75, 79], "cone_solid_angl": 32, "configur": 79, "confus": 91, "consid": [87, 90], "consist": [39, 79, 87], "consortium": [62, 63, 85, 87], "constel": 39, "conta": 8, "contain": [1, 2, 8, 47, 54, 72, 73, 79, 87, 89], "content": [79, 83], "content_new": [22, 26, 37], "context": [87, 91], "continu": 79, "contribut": [0, 85], "contributor": 0, "conveni": 79, "convent": 91, "converet": 91, "convers": 89, "convert": [47, 79, 83, 84, 89], "convex": [19, 21, 22, 23, 25, 26, 29, 30, 31, 34, 36, 37, 38, 40, 87], "coordin": 75, "copi": [86, 87, 91], "core": [49, 86], "correct": [79, 87], "correctli": 79, "correspond": [1, 14, 22, 26, 37, 39, 79, 84, 85, 90], "corsika": [57, 91], "corsika_event_header_total_energi": 91, "cosmic": [65, 83], "could": 79, "count": [79, 88], "count_nonzero": 91, "cov": 80, "coverag": 79, "crab": [58, 59, 83], "creat": [7, 12, 15, 16, 17, 18, 42, 43, 44, 45, 46, 74, 80, 86], "create_aeff2d_hdu": 91, "create_bins_per_decad": 91, "create_effective_area_table_2d": 91, "create_energy_dispersion_hdu": 91, "create_histogram_t": [54, 74], "create_psf_3d": 91, "create_psf_table_hdu": 91, "create_rad_max_hdu": 79, "creator": 91, "cref": 79, "cta": [62, 63, 79, 80, 85, 86, 87, 91], "ctao": 87, "ctapip": 80, "ctool": [80, 88], "curl": [83, 91], "current": [79, 85, 88, 89, 90, 91], "cut": [12, 13, 14, 15, 42, 44, 46, 47, 49, 55, 79, 83, 85, 88, 90, 91], "cut_optim": 12, "cut_tabl": 15, "cutoff": [85, 90], "d": 80, "data": [6, 8, 10, 42, 43, 44, 45, 46, 48, 79, 80, 87, 88, 89, 92], "dataset": 79, "date": [80, 91], "de": 91, "deal": 91, "decad": [7, 59], "def": 87, "default": [1, 2, 39, 40, 54, 56, 60, 69, 87], "defin": [6, 15, 52, 54, 56, 62, 63, 86, 88], "definit": [12, 62, 63, 80, 85], "deg": [12, 87, 88, 91], "degre": 39, "delet": 80, "demonstr": 87, "depend": [12, 86, 88], "deploi": 79, "deriv": [24, 25, 35, 36], "describ": [15, 80, 90, 91], "descript": [62, 63, 80], "desir": [55, 87], "detail": [83, 91], "detect": [54, 56, 88, 90], "develop": 85, "deviat": 52, "dict": 91, "diff": 87, "differ": [22, 26, 30, 31, 37, 38, 39, 40, 87, 91], "differenati": 79, "differenti": 79, "diffus": 61, "dim": 87, "dimens": [10, 19, 23, 34, 39, 57, 79, 87, 91], "dimension": [30, 39, 87], "dimes": 39, "direct": [12, 42, 44, 46, 47, 79, 87, 91], "directli": 80, "directori": [80, 87], "disabl": 87, "disagr": 79, "discard": 12, "discourag": 87, "discret": [23, 26, 30, 31, 32, 79, 87], "discretepdfcomponentestim": [28, 33, 87], "discretepdfextrapol": 31, "discretepdfinterpol": [30, 40], "discretepdfnearestneighborsearch": [79, 87], "discrimin": 85, "discurag": 79, "discuss": [39, 80], "dispers": [3, 17, 28, 44, 52, 79, 87], "displai": 91, "distanc": [1, 12, 55, 87, 88, 91], "distribut": [1, 40, 57, 68, 87, 91], "divid": 55, "dl2": [47, 48, 50, 51, 52, 54, 55, 89], "dl3": 91, "do": [79, 80], "doc": [29, 79, 80, 86, 91], "docstr": 79, "document": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 57, 62, 63, 64, 67, 68, 69, 79, 86, 88], "doe": [39, 79, 88, 91], "doi": [30, 58, 59, 71, 85, 87], "dortmund": 91, "download": [79, 83, 86, 87], "download_irf": [79, 86, 87], "download_private_data": 83, "draft": 80, "drawn": 67, "drop": 79, "dtic": [39, 87], "dtype": [1, 2, 91], "due": [39, 55, 80], "dummi": [22, 26, 37], "dure": 12, "e": [6, 9, 11, 12, 13, 15, 19, 23, 34, 35, 36, 40, 42, 43, 44, 45, 46, 47, 49, 55, 58, 59, 62, 63, 64, 65, 67, 68, 79, 86, 87], "e_": [64, 67, 68, 91], "e_i": 70, "e_max": 7, "e_min": 7, "e_ref": [64, 67, 68], "each": [1, 2, 13, 14, 15, 39, 57, 70, 79], "earli": 80, "easili": 87, "edg": [1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 15, 16, 17, 18, 32, 39, 42, 43, 44, 45, 46, 50, 51, 52, 55, 57, 79, 87], "edisp": [3, 17, 24, 25, 28, 44, 87, 91], "edisp_2d": 28, "edisp_estim": 87, "edisp_interp": 28, "edu": [40, 87], "effect": [16, 27, 41, 42, 44, 49, 50, 51, 79, 85, 87, 88], "effective_area": [16, 27, 42, 91], "effective_area_per_energi": 91, "effectiveareatable2d": [16, 91], "effici": [12, 79, 90], "either": [1, 2, 86, 87], "electron": [62, 79, 83], "element": 15, "els": [42, 44, 91], "empti": [87, 91], "en": [42, 43, 44, 45, 46, 48, 91], "enabl": [79, 86], "enclosur": [42, 44, 79, 91], "encod": 91, "encourag": [19, 23, 34], "end": 56, "endpoint": 7, "energi": [1, 2, 3, 7, 12, 16, 17, 18, 28, 42, 43, 44, 45, 46, 48, 50, 51, 52, 54, 55, 57, 59, 61, 64, 66, 67, 68, 69, 70, 79, 85, 87, 88], "energy_bias_resolution_from_energy_dispers": 79, "energy_bin": [1, 2, 57], "energy_dispers": [3, 17, 28, 44, 87, 91], "energy_max": [57, 91], "energy_min": [57, 91], "energy_tru": 91, "energy_typ": [1, 2], "energydispersion2d": [17, 91], "energydispersionestim": 87, "engin": [40, 87], "ensembl": [40, 87], "ensur": [19, 23, 34], "enter": 80, "entri": [6, 14, 15, 80], "enum": 32, "env": 86, "environ": 86, "equal": [7, 10, 27, 39], "equival": 91, "error": [79, 80, 87], "especi": 80, "essenti": 79, "estim": [19, 23, 27, 28, 33, 34, 41, 47, 55, 56, 79, 85, 91], "estimate_background": 79, "et": [58, 59], "etc": 76, "evalu": [15, 71], "event": [1, 2, 8, 12, 13, 15, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 67, 70, 71, 72, 73, 79, 83, 85, 90], "event_id": 91, "event_num": 91, "eventdisplai": [12, 47, 79, 89], "everi": [12, 80, 91], "everyth": [79, 86], "everywher": 79, "exactli": [7, 79], "exampl": [79, 85, 87], "except": [56, 80], "execut": 87, "exist": [39, 80, 87], "exp": [62, 68], "expect": [55, 57, 85, 88], "explan": [83, 88], "export": 88, "express": [54, 56], "extend": [31, 38, 85], "extens": 87, "extnam": [42, 43, 44, 45, 46], "extra": [80, 86], "extrapol": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 79, 85], "extrapolated_edisp": 87, "extrapolator_cl": [23, 27, 28, 33, 34, 41, 87], "extrapolator_kwarg": [23, 27, 28, 33, 34, 41, 87], "f": [12, 56, 68, 86, 91], "f_new": [30, 40], "fact": [0, 79, 92], "fact_irf": 91, "factdata": 91, "factor": [56, 88, 91], "fail": [47, 79, 80], "fals": [6, 15, 42, 44, 47, 76, 87, 91], "favour": 79, "featur": [80, 85], "feedback": 80, "fell": 6, "field": [16, 17, 18, 42, 43, 44, 45, 46, 48, 51, 52, 87, 88, 90], "fig": 40, "figur": 91, "file": [14, 40, 47, 79, 80, 83, 86, 87, 88, 89], "fill": 79, "fill_valu": [13, 41], "filter": 13, "find": [52, 56, 80, 85, 88], "finder": 87, "finer": 79, "finish": 80, "first": [22, 26, 37, 39, 74, 79, 80, 87, 91], "fit": [42, 43, 44, 45, 46, 47, 79, 83, 87, 88, 89], "fix": 80, "fixtur": 79, "flat": 79, "float": [1, 2, 12, 13, 54, 55, 56, 57, 64, 67, 68, 71, 79, 91], "float64": 91, "flux": [54, 56, 60, 61, 64, 66, 67, 68, 69, 70, 79, 88], "follow": [40, 57, 79, 80, 88, 91], "fork": 80, "form": 80, "format": [42, 43, 44, 45, 46, 48, 79, 80, 83, 89, 91], "former": 79, "formula": [56, 71], "found": [22, 26, 37, 80, 83, 91], "fov": [12, 55, 57, 72, 79, 88, 91], "fov_bin": 57, "fov_offset_bin": [16, 17, 18, 42, 43, 44, 45, 46, 48, 51, 52, 53, 91], "fov_offset_max": [12, 55], "fov_offset_min": [12, 55], "frac": [64, 67, 68, 70], "frequent": 79, "from": [0, 8, 12, 16, 17, 18, 22, 24, 25, 26, 35, 36, 37, 39, 47, 54, 55, 56, 57, 58, 59, 62, 63, 65, 67, 71, 79, 80, 83, 86, 87, 89, 90, 92], "from_fil": 69, "from_simul": [67, 79], "from_tabl": 69, "front": 91, "fsslo": 91, "fulfil": 79, "full": [42, 44, 79, 80, 91], "full_enclosur": [42, 43, 44, 45, 48], "function": [2, 6, 9, 11, 12, 15, 18, 20, 21, 24, 25, 30, 33, 35, 36, 40, 45, 48, 50, 51, 54, 55, 56, 62, 63, 64, 67, 68, 69, 71, 79, 85, 87, 88], "further": [19, 23, 34, 79], "futur": 85, "g": [6, 9, 11, 12, 15, 19, 23, 34, 35, 36, 42, 43, 44, 45, 46, 47, 49, 55, 79, 87], "gadatsch": [30, 87], "gadf": [42, 43, 44, 45, 46, 48, 79, 89], "gaia": 79, "gamma": [12, 42, 43, 44, 45, 46, 48, 56, 67, 68, 80, 85, 88, 90, 91], "gamma_energy_predict": 91, "gamma_predict": 91, "gamma_test_dl3": 91, "gammapi": [16, 17, 18, 79, 85, 87, 91], "gauss": [62, 68], "gaussian": [13, 40, 68, 87], "gaussianestim": 87, "gaussianestimatior": 87, "gca": 91, "ge": 12, "gener": [29, 57, 80, 85], "generate_dl2_fil": 47, "geomspac": 91, "gernot": 83, "get": 80, "gev": [58, 65, 91], "gevgevdegdegdegdegdeg": 91, "gh": [12, 79], "gh_cut_effici": 12, "gh_score": [12, 88, 91], "git": 0, "github": [0, 47, 79, 80, 85, 91], "gitlab": [62, 63], "give": [16, 17, 18, 42, 44, 45, 46, 52, 80], "given": [6, 10, 13, 15, 19, 23, 30, 31, 34, 38, 39, 40, 52, 55, 56, 57, 67, 85, 90], "glob": 87, "go": 80, "good": 80, "gov": 65, "grain": 79, "grid": [19, 20, 21, 22, 23, 25, 26, 29, 30, 31, 34, 36, 37, 38, 39, 40, 87], "grid_point": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 87], "griddata": [29, 87], "griddata_interpol": [27, 34, 41], "griddata_kwarg": 29, "griddatainterpol": [27, 34, 41, 87], "group": 80, "gt": 91, "guidelin": 80, "gz": [79, 87, 91], "h5py": 91, "ha": [10, 39, 79, 91], "had": 79, "hadro": 12, "hadron": [12, 88, 91], "handl": [19, 23, 34, 79, 87], "harrington": [30, 87], "have": [6, 7, 8, 13, 14, 16, 17, 18, 19, 23, 34, 39, 42, 43, 44, 45, 46, 47, 74, 80, 86, 88, 91], "hdf5": 91, "hdu": [42, 43, 44, 45, 46, 79, 91], "hdulist": 91, "header": [42, 43, 44, 45, 46, 47, 91], "header_card": [42, 43, 44, 45, 46], "hegra": 58, "help": 80, "here": [48, 55, 83, 91], "herein": 87, "hi": [9, 11], "high": 15, "hist": 8, "hist1": 74, "hist2": 74, "histogram": [6, 8, 10, 30, 47, 49, 54, 74, 79, 85, 87, 91], "hol": 87, "hold": 39, "hollist": [40, 87], "host": 85, "hostedtoolcach": 91, "how": [32, 48, 85, 91], "howev": 80, "html": [29, 42, 43, 44, 45, 46, 48, 80, 91], "htmlcov": 80, "http": [29, 30, 39, 40, 42, 43, 44, 45, 46, 47, 48, 59, 62, 63, 65, 71, 80, 86, 87, 91], "hugo": 79, "hull": [19, 21, 22, 23, 25, 26, 29, 30, 31, 34, 36, 37, 38, 40, 87], "i": [1, 2, 9, 10, 12, 13, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 30, 31, 32, 34, 36, 37, 38, 39, 40, 42, 44, 45, 46, 52, 54, 55, 56, 57, 60, 68, 69, 76, 79, 80, 83, 85, 86, 87, 88, 89, 90, 91], "iact": [79, 88], "idea": 80, "ignor": 79, "imag": [88, 91], "immedi": 80, "impact": 57, "implement": [1, 30, 40, 79, 80, 87, 91], "import": [79, 87, 91], "improv": 79, "inclin": 87, "includ": [6, 7, 57, 67, 79, 86], "inclus": 7, "incorpor": [54, 56], "increas": 79, "independ": [35, 36], "index": [6, 42, 43, 44, 45, 46, 48, 57, 67, 68, 85], "indic": [6, 15], "individu": [48, 80], "inf": [4, 6], "inferno": 91, "infil": 47, "inform": [1, 2, 8, 39, 47, 54, 57, 72, 73, 79, 85, 87, 88, 91], "inherit": [79, 87], "inidividula": 6, "inlin": 91, "inner": [57, 67], "input": [27, 28, 33, 41, 47, 56, 76, 79, 83, 85], "insid": [55, 57], "inspect": 80, "instal": [79, 80, 83, 85], "instanc": [67, 87, 88], "instanti": 79, "instead": [22, 26, 37, 40, 71, 79, 87], "instruct": 79, "instrum": [30, 42, 43, 44, 45, 46, 87, 91], "instrument": [62, 63, 85, 87, 88], "int": [6, 7, 10, 13, 39, 49, 54, 56, 57], "int64int64float64float64float64float64float64float64float64float64": 91, "integ": [39, 71], "integr": [32, 67, 79], "integrate_con": 67, "intepol": 79, "inter": [19, 21, 22, 23, 25, 26, 27, 28, 29, 30, 33, 34, 36, 37, 40, 41, 79], "inter_quantile_dist": 2, "interest": [85, 87], "interfac": [20, 21, 22, 24, 25, 26, 29, 30, 31, 35, 36, 37, 38, 39, 40, 87], "intern": [79, 80, 83, 88], "internal_report": [62, 63], "interoper": [85, 88], "interpoal": 40, "interpol": [10, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 60, 69, 79, 85], "interpolate_xyz": 79, "interpolated_edisp": 87, "interpolator_cl": [23, 27, 28, 33, 34, 41, 87], "interpolator_kwarg": [23, 27, 28, 33, 34, 41, 87], "interv": 57, "introduc": [30, 40, 79], "introduct": [80, 85, 91], "invalid": 79, "invert": 11, "investig": 79, "io": [42, 43, 44, 45, 46, 47, 48, 88, 91], "ioerror": 91, "iprogress": 91, "ipynb": 83, "ipywidget": 91, "irf": [0, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 34, 35, 36, 37, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 62, 63, 79, 80, 84, 85, 86, 88, 89, 92], "iso": 91, "isol": 86, "item": 91, "its": 85, "itself": 86, "j": [30, 59, 87], "jheap": 59, "join": 9, "join_bin_hi_lo": 11, "joint": [9, 11], "journal": 56, "jouvin": 79, "json": 80, "julien": 0, "jupyt": [83, 91], "just": [55, 79, 86, 87, 91], "kei": [8, 74], "kemenad": 79, "keyword": 79, "kind": 79, "know": [20, 21], "known": 91, "kwarg": 12, "l": 87, "label": [80, 91], "larg": [0, 85], "larger": [13, 54, 79], "last": [9, 79], "later": [79, 80], "latest": [42, 43, 44, 45, 46, 48, 85, 91], "latter": 79, "law": [57, 58, 67, 68, 79], "lbl": 65, "le": 12, "lea": 79, "learn": 88, "least": [54, 79], "lefauch": 0, "left": [64, 67, 68], "legend": 91, "len": [10, 48, 91], "length": [10, 57, 91], "less": 13, "let": [87, 91], "level": [79, 87], "li": [54, 56, 71, 79, 88], "li_ma_signific": [54, 56], "lib": 91, "librari": [79, 88], "like": [12, 16, 17, 18, 24, 25, 35, 36, 42, 44, 45, 46, 52, 55, 56, 57, 71, 79, 87, 90, 91], "likelihood": [54, 56, 88], "lile": 79, "limit": [55, 57, 79, 87], "line": 91, "linear": [30, 38, 87], "linearli": [60, 69], "link": [80, 85], "linspac": [87, 91], "list": [12, 14, 52, 54, 55, 76, 79, 83], "lo": [9, 11], "load": 87, "load_irf_dict_from_fil": 87, "loc": 87, "local": [80, 86], "log": [59, 60, 64, 69, 91], "log10": [59, 62], "log_": [64, 68], "log_energi": 69, "log_flux": 69, "logarithm": [7, 91], "logparabola": 59, "long": [9, 80], "longer": 79, "look": 88, "low": 15, "lower": [15, 27, 57, 79], "lowest": 79, "lst": 79, "lt": 91, "luka": 79, "m": [27, 30, 39, 80, 87, 91], "m2": [27, 61, 65, 66, 91], "ma": [54, 56, 71, 79, 88], "made": 79, "madison": [39, 87], "magic": 59, "magnet": 87, "magnitud": 80, "mai": [39, 79], "maier": 83, "mailmap": 80, "main": 85, "mainli": 79, "maintain": 83, "mainten": 80, "make": [0, 86, 91], "mani": [47, 79], "manual": 87, "map": 91, "march": 79, "mask": [6, 15], "master": 47, "match": [7, 14, 19, 23, 34, 48], "mathrm": 91, "matplotlib": 91, "matric": [28, 33, 79], "matrix": [3, 28, 52, 79], "max": [41, 46, 79, 87, 91], "max_impact": [57, 91], "max_valu": 13, "maximilian": 79, "maximum": [7, 12, 55, 57, 79], "md": 80, "mean": [12, 91], "meant": 87, "measur": [56, 59, 90], "meet": 79, "member": 85, "met": [54, 79], "meta": 91, "metadata": [42, 43, 44, 45, 46, 79, 91], "method": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 56, 57, 64, 67, 68, 69, 79, 87, 91], "mev": 43, "michel": 79, "migra": 87, "migra_bin": [28, 87], "migrat": [3, 17, 44, 79, 88, 90], "migration_bin": [3, 17, 44, 52, 91], "mil": [39, 87], "min": 91, "min_effective_area": 27, "min_ev": 13, "min_excess_over_background": [54, 56], "min_signal_ev": [54, 56], "min_signific": [54, 56], "min_valu": 13, "miniconda": 86, "minim": 91, "minimum": [7, 12, 54, 55, 56, 79], "minor": 80, "miss": [19, 23, 34, 80], "mode": [86, 91], "modul": [0, 85, 87, 89], "moment": [30, 31, 79, 87, 88], "momentmorphinterpol": 87, "momentmorphnearestsimplexextrapol": 87, "monoscop": 91, "more": [19, 23, 34, 80, 88], "morph": [30, 31, 79, 87], "most": [80, 87, 88], "move": 79, "mu": [68, 91], "much": 79, "multi": [30, 87], "multidiment": 9, "multipl": [22, 26, 37, 88], "must": [6, 8, 13, 15, 16, 17, 18, 42, 43, 44, 45, 46, 48, 54, 56, 57, 70, 79], "n": [39, 54], "n_background": [56, 79], "n_bin": [30, 31, 40], "n_bins_per_decad": 7, "n_dim": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41], "n_electron": 79, "n_energy_bin": [3, 16, 17, 18, 27, 28, 33, 41, 42, 43, 44, 45, 57], "n_event": 79, "n_fov_bin": 57, "n_fov_offset_bin": [16, 18, 27, 28, 33, 41, 42, 43, 45, 46], "n_fov_ofset_bin": 52, "n_migra_bin": [3, 17, 44], "n_migration_bin": [28, 52], "n_off": [54, 56, 71, 79], "n_on": [54, 56, 71], "n_param": 29, "n_point": [27, 28, 33, 41], "n_proton": 79, "n_reco_energy_bin": 46, "n_select": 49, "n_shower": [57, 91], "n_signal": [56, 79], "n_signal_weigth": 79, "n_simul": 49, "n_source_offset_bin": [3, 17, 18, 33, 44, 45], "n_true_energy_bin": 52, "n_weight": 54, "naiv": 71, "name": [42, 43, 44, 45, 46, 79, 87, 91], "nan": [54, 79], "nanmedian": 2, "ndarrai": [1, 2, 3, 6, 8, 10, 12, 13, 15, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 44, 49, 52, 57, 70], "ndim": [1, 2, 12, 57, 91], "nearest": [22, 26, 37, 39, 79, 87], "nearestneighbor": [22, 26, 37], "nearestsimplexextrapol": 79, "nebula": [58, 59, 83], "necessari": [19, 23, 34, 54, 56], "necessarili": 91, "need": [10, 19, 23, 27, 28, 33, 34, 41, 48, 79, 80, 87, 88, 91], "neg": 71, "neighbor": [22, 26, 37, 79, 87], "nep": 79, "new": [10, 30, 67, 80, 91], "new_edg": 10, "newaxi": 91, "nice": 91, "nickel": 79, "nima": [30, 87], "noah": 79, "non": [7, 30, 39, 40, 87], "none": [13, 19, 21, 22, 23, 25, 26, 27, 28, 29, 30, 33, 34, 36, 37, 40, 41, 87, 91], "norm": 87, "norm_ord": [22, 26, 37], "normal": [1, 24, 25, 30, 31, 32, 40, 64, 67, 68, 79, 80], "north": 79, "note": [22, 26, 37, 80, 87, 91], "notebook": [79, 83, 85], "notebook_tqdm": 91, "novemb": 79, "now": [79, 87, 91], "np": [4, 6, 8, 9, 11, 12, 15, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 87, 91], "nucl": [30, 87], "number": [7, 13, 39, 47, 49, 54, 55, 56, 57, 61, 66, 71, 79, 87, 88, 90], "numpi": [1, 2, 3, 10, 13, 15, 17, 22, 26, 30, 31, 37, 38, 39, 40, 44, 49, 52, 57, 70, 86, 87, 88, 91], "n\u00f6the": 79, "object": [1, 2, 8, 19, 20, 21, 47, 57, 60, 64, 67, 69, 72, 73, 76, 79, 87, 88], "obs_id": 91, "obs_idevent_idreco_energytrue_energytrue_azpointing_azthetagh_scoretrue_altpointing_alt": 91, "observ": [48, 56, 58, 67, 71, 85, 87], "observatori": [62, 63, 80, 85, 87, 91], "obstim": [67, 71], "obtain": [60, 69, 85, 87], "octob": 79, "off": [12, 55, 56, 71, 85], "offer": 88, "offset": [16, 17, 18, 42, 43, 44, 45, 46, 48, 51, 52, 72, 91], "often": [55, 80], "ogadf": 79, "ok": 86, "old_edg": 10, "onc": 91, "one": [6, 14, 19, 22, 23, 26, 34, 37, 39, 54, 56, 79, 80, 83, 87], "ones": [35, 36, 79, 90], "onli": [12, 16, 17, 18, 20, 21, 39, 42, 44, 45, 46, 52, 79, 87, 88, 89, 91], "op": [12, 15], "open": [32, 67, 75, 79, 80, 92], "open_fil": 91, "oper": [12, 79], "operatornam": 68, "opt": [88, 91], "optim": [12, 83, 85, 88], "optimize_gh_cut": 79, "option": [57, 79], "order": [39, 79, 86], "org": [29, 30, 58, 59, 62, 63, 71, 86, 87], "organ": 80, "orient": 91, "origin": [79, 88], "ot": 39, "other": [79, 87], "otherwis": 76, "our": 91, "out": 91, "outer": [57, 67], "output": [16, 17, 18, 79, 83, 85, 88], "outsid": [15, 19, 21, 22, 23, 25, 26, 29, 30, 31, 34, 36, 37, 38, 39, 40, 87], "over": [6, 32, 39, 59, 67, 79, 87, 90], "overal": [50, 51], "overflow": [4, 6], "overflow_index": 6, "overflown": 6, "overrid": [20, 21, 24, 25, 35, 36], "overview": 80, "overwrit": 91, "p": [39, 56, 87], "packag": [0, 85, 86, 91], "page": [0, 79, 80, 85], "pang": [40, 87], "parabola": [59, 64], "param": [29, 34, 35, 36, 37, 38, 39], "paramet": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 54, 55, 56, 57, 67, 70, 71, 72, 73, 74, 75, 76, 87], "parameter": 64, "parametr": [34, 35, 36, 37, 58, 59, 79, 87], "parametrizedcomponentestim": [27, 41], "parametrizedextrapol": 38, "parametrizedinterpol": 29, "parametrizednearestneighborsearch": [79, 87], "parametrizednearestsimplexextrapol": [39, 87], "parametrizedvisibleedgesextrapol": 87, "part": [0, 9, 80], "particl": [48, 49, 61, 65, 66, 79], "particle_typ": 79, "partli": 91, "pass": [12, 15, 42, 44, 80, 86, 90], "password": 83, "path": [47, 69, 87, 91], "pathlib": 47, "pcolormesh": 91, "pdf": [23, 26, 30, 31, 32, 39, 40, 65, 79, 87], "pdfnormal": [24, 25, 30, 31, 40], "pdg": 65, "per": [1, 2, 7, 47, 48, 57, 61, 66, 79, 90], "percentag": 13, "percentil": 13, "peresano": 79, "perform": [30, 31, 38, 39, 40, 86], "phi": [64, 67, 68], "phi_": 70, "phi_0": [64, 67, 68], "phy": [30, 87], "physic": [65, 88], "pip": 86, "pipe": 91, "pipelin": 85, "plan": 85, "pleas": [0, 79, 80, 83, 85, 91], "plot": [83, 91], "plot_energy_depend": 91, "plot_psf_vs_rad": 91, "plot_spectra": 83, "plt": 91, "plu": [86, 90], "plug": 87, "po": 79, "pocedur": 30, "point": [16, 17, 18, 19, 20, 21, 22, 23, 26, 29, 30, 31, 33, 34, 37, 38, 39, 40, 42, 44, 45, 46, 47, 52, 55, 57, 60, 66, 69, 72, 79, 85, 87, 88], "point_lik": [42, 44, 46, 79], "pointing_alt": [88, 91], "pointing_az": [88, 91], "pointing_position_az": 91, "pointing_position_zd": 91, "poissionian": 57, "posit": [4, 49, 55, 72, 73, 90, 91], "possibl": [55, 79], "power": [57, 58, 67, 68, 79], "powerlaw": [57, 58, 63, 65, 68, 79, 91], "powerlawwithexponentialgaussian": 62, "pr": 80, "pre": [12, 79], "prefix": 72, "prescrib": 91, "present": 88, "prevent": 80, "previou": 0, "previous": 79, "primaryhdu": 91, "print": [80, 91], "prior": 79, "probabl": [40, 52, 87, 90], "problem": 79, "procedur": 12, "process": 85, "prod3b": [62, 63, 80], "prod5": [79, 87], "prod5_irf_path": 87, "produc": 47, "product": [62, 63], "progress": 12, "project": 86, "proport": [54, 56, 90], "protect": 83, "proton": [47, 63, 79], "protopip": 0, "protopyp": 85, "prototyp": 85, "provid": [20, 21, 22, 24, 25, 26, 29, 30, 31, 35, 36, 37, 38, 39, 40, 42, 44, 47, 79, 83, 84, 86, 87, 88], "psf": [18, 33, 45, 53, 79, 87, 90, 91], "psf3d": 18, "psf_gammapi": 91, "psf_interp": 33, "psf_tabl": [33, 45, 91], "public": 85, "publish": [58, 59], "pull": 80, "pulsar": 58, "push": 80, "py": [47, 79, 83, 86, 87, 91], "pyfact": [0, 79], "pyirf": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 80, 83, 86, 87, 92], "pypi": 79, "pyplot": 91, "pytest": [80, 86], "python": [79, 80, 83, 86, 91], "python3": 91, "q": 56, "qtabl": [1, 2, 8, 12, 14, 47, 48, 50, 51, 52, 54, 55, 69, 72, 73, 88, 91], "quantil": [1, 40], "quantile_interpol": [23, 28, 33], "quantile_resolut": 40, "quantileinterpol": [23, 28, 33, 87], "quantiti": [4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 27, 33, 34, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 55, 57, 64, 67, 68, 69, 70, 72, 73, 75, 76, 84, 87, 88, 91], "quantity_input": 87, "quick": 80, "quit": 80, "r": [30, 87, 91], "rad": [41, 46, 87], "rad_max": [41, 46, 79], "rad_max_2d": 41, "rad_max_interp": 41, "radial": [48, 51, 53, 90], "radian": 32, "radiu": [57, 91], "rai": [56, 65, 80, 83, 88, 90, 91], "rais": [19, 23, 34, 39, 87, 91], "rang": [52, 57, 79, 88], "rapid": 85, "rare": 80, "rate": [12, 43, 48, 55, 79], "ratio": [12, 54, 56, 71, 88, 90, 91], "re": [30, 87], "rea99": 87, "reach": [79, 85], "read": [47, 79, 87, 89], "readi": 80, "readm": 80, "readthedoc": [42, 43, 44, 45, 46, 48, 91], "reappear": 80, "reason": 87, "rebin": 10, "receiv": 80, "reco": [1, 2, 72, 79, 88], "reco_alt": 88, "reco_az": 88, "reco_energi": [3, 8, 12, 17, 44, 48, 52, 55, 74, 88, 91], "reco_energy_bin": [12, 43, 46, 48, 55], "reco_source_fov_offset": [12, 48, 55, 79, 88], "recommend": 52, "reconstruct": [1, 2, 8, 12, 43, 46, 47, 48, 54, 55, 72, 73, 88, 90, 91], "record": 86, "ref": [64, 67, 68], "refer": [29, 30, 39, 40, 56, 67, 79], "reference_energi": 69, "regardless": 87, "regener": 80, "region": [1, 12, 39, 54, 55, 56, 71], "rel": [3, 17, 44, 52, 54, 56, 79], "relative_sensit": [54, 79], "releas": 85, "relev": 87, "reli": 88, "remak": 80, "remov": [79, 80, 91], "renam": 80, "replac": [13, 79, 87], "report": [40, 62, 63, 79, 80, 87], "repositori": [80, 83, 85, 86], "repres": [23, 32, 34, 87], "represent": 88, "reproduc": 79, "rept": [39, 87], "request": 80, "requir": [7, 12, 15, 50, 51, 52, 54, 55, 56, 79, 80, 83, 85, 86, 88, 91], "resampl": 79, "resolut": [1, 2, 3, 79, 85], "resolution_funct": 2, "respect": [6, 19, 23, 34, 86, 87], "respons": [62, 63, 85, 87, 88], "rest": 86, "restructur": 80, "result": [1, 2, 6, 15, 19, 20, 21, 22, 23, 24, 25, 26, 29, 30, 31, 34, 35, 36, 37, 38, 39, 40, 56, 67, 71, 76, 79, 80, 83, 87, 88], "ret": 91, "return": [1, 2, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 47, 48, 52, 54, 55, 57, 67, 70, 71, 72, 73, 75, 76, 79, 87, 88], "returncod": 91, "reus": 57, "reusabl": 79, "rev": 65, "review": [65, 80], "reweight": 70, "rewritten": [0, 79], "right": [64, 67, 68], "ring": 79, "root": [47, 83, 87, 89, 91], "rpp2020": 65, "rudimentari": 88, "run": [0, 47, 79, 83, 86, 91], "run_id": [47, 91], "s0168": 87, "s_lima": 71, "sake": 87, "same": [6, 8, 10, 13, 14, 27, 28, 33, 39, 41, 55, 74, 79], "saniti": [19, 23, 34], "satisfi": 79, "scalar": [34, 38, 39, 76], "scale": [12, 54, 55, 56, 79, 87], "scan": 12, "schema": 79, "scheme": 79, "scipi": [29, 86, 87], "scope": 87, "score": [12, 85], "scratch": 79, "script": [83, 89], "search": 85, "second": [74, 79, 90], "section": [62, 63, 79], "see": [0, 39, 42, 43, 44, 45, 46, 54, 79, 80, 87, 91], "seem": 79, "seen": [22, 26, 37], "select": [48, 49, 55, 88], "selected_ev": [50, 51, 52], "selected_gh": 91, "selected_theta": 91, "self": [64, 67, 68, 69, 87], "sens": 80, "sensit": [12, 54, 55, 56, 71, 79, 85, 88], "sensitivity_t": 54, "sensitivti": 79, "separ": [12, 72, 73, 79, 86, 91], "septemb": [0, 79], "serv": 80, "server": 80, "set": [14, 15, 27, 42, 43, 44, 45, 46, 54, 87, 88, 91], "setup": 86, "sever": 79, "sh": 83, "shape": [3, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 52, 56, 91], "shortlog": 0, "should": [12, 13, 15, 22, 26, 37, 56, 57, 79, 80, 86], "show": [12, 79, 80], "shower": [57, 88], "shown": 40, "sigma": [1, 13, 54, 56, 68], "sign": [57, 67], "signal": [12, 54, 55, 56, 79, 85], "signal_hist": 54, "signatur": [12, 54, 70], "signific": [54, 56, 71, 79, 88], "significance_funct": [54, 56], "sim": 79, "similar": [79, 88], "simpl": 87, "simplex": [39, 79, 87], "simpli": [86, 87], "simplic": 87, "simualt": 57, "simul": [12, 47, 49, 50, 51, 57, 62, 63, 67, 70, 79, 85, 88], "simulated_ev": 47, "simulated_event_info": 67, "simulated_spectrum": 70, "simulatedeventsinfo": [47, 50, 51, 91], "simulatedeventsinform": 79, "simulation_info": [50, 51, 91], "simulationinfo": 79, "sinc": [12, 47, 79, 91], "singl": [16, 17, 18, 42, 44, 45, 46, 52, 56, 79, 91], "site": [40, 87, 91], "size": [12, 54, 55, 71], "sky": [72, 73], "small": [79, 88], "smaller": [13, 79], "smallest": 88, "smooth": [13, 39, 79, 87], "sne": 0, "so": [12, 54, 56, 79, 85, 91], "soe": [40, 87], "solid": [18, 32, 45, 48, 57, 67, 75, 79, 90], "solid_angl": [61, 75], "some": [79, 80, 86, 87, 91], "someth": 80, "sort": [13, 15, 39, 87], "sourc": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 64, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 85, 88, 91], "source_fov_offset": 79, "source_offset_bin": [18, 33, 45, 53, 91], "source_position_az": 91, "source_position_zd": 91, "sp": 91, "space": [7, 60, 69, 91], "specif": [15, 19, 42, 43, 44, 45, 46, 79, 87, 88, 91], "specifi": [27, 28, 33, 41, 48, 79, 88], "spectral": [57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70], "spectral_index": [57, 91], "spectrum": [58, 59, 60, 62, 63, 65, 69, 70, 85], "sphinx": 80, "spin": 85, "split": [11, 79], "spread": [18, 33, 45, 85, 88], "sprint": 79, "squeez": 87, "sr": [33, 43, 61, 62, 63, 65], "stabl": [85, 91], "standard": [79, 80, 88], "start": [0, 4, 79], "stat": 87, "statist": [50, 51, 55, 71, 85], "stderr": 91, "stdout": 91, "step": [40, 79], "stereoscop": 58, "sti": [39, 87], "still": 79, "store": [79, 83], "str": [1, 2, 42, 43, 44, 45, 46, 47, 72], "string": 8, "strongli": 87, "structur": 79, "style": 80, "sub": 85, "subgroup": 88, "submodul": 80, "subprocess": 91, "sum": 79, "summari": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 57, 64, 67, 68, 69, 87], "super": 87, "suppli": 6, "support": [15, 76, 79, 87, 88, 89, 91], "sure": [80, 86], "surviv": [47, 49, 55, 79], "symmetr": [48, 53, 90, 91], "system": [80, 86], "t": [40, 56, 87, 91], "t_ob": 48, "tabl": [1, 2, 8, 12, 15, 27, 33, 41, 42, 43, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 55, 69, 72, 73, 74, 79, 87, 88, 91], "tableinterpolationspectrum": 60, "tailor": 87, "take": [15, 22, 26, 30, 31, 37, 38, 39, 40, 55, 79, 80, 88], "taken": [12, 55], "target": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 70, 79, 87, 91], "target_point": [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 87], "target_sensit": 79, "target_signific": [54, 56], "target_spectrum": 70, "technic": [39, 40, 87], "telescop": [42, 43, 44, 45, 46, 58, 59, 85, 87, 88, 91], "templat": [39, 85, 87], "ten": 54, "term": [13, 79, 91], "termin": 80, "terminologi": 91, "test": [54, 56, 79, 85, 86, 88], "tev": [58, 59, 61, 62, 63, 64, 66, 67, 69, 88, 91], "text": [64, 67, 68, 70, 80], "than": [13, 54, 79], "thei": [80, 87], "them": [14, 79, 83, 86, 87], "theta": [12, 46, 55, 72, 73, 91], "theta_cut": [12, 55], "theta_deg": 91, "thi": [1, 6, 10, 12, 13, 30, 39, 40, 42, 43, 44, 45, 46, 47, 48, 50, 51, 54, 55, 56, 57, 67, 71, 79, 80, 83, 84, 87, 88, 89, 91], "think": 80, "thoma": 79, "those": [30, 31, 38, 39, 40, 87], "three": [59, 79, 86], "threshold": 90, "thu": 87, "time": [48, 54, 56, 61, 66, 67, 85, 88, 91], "to_valu": [87, 91], "togeth": [79, 80], "tool": 91, "top": [79, 87], "total": [49, 57], "towncrier": 79, "tqdm": 91, "tqdmwarn": 91, "translat": 88, "travi": [79, 80], "treat": [35, 80], "triangular": [39, 87], "trigger": 49, "true": [1, 2, 4, 12, 16, 17, 18, 42, 44, 45, 46, 47, 50, 51, 52, 53, 69, 70, 72, 76, 79, 88, 90, 91], "true_alt": [88, 91], "true_az": [88, 91], "true_energi": [3, 17, 44, 50, 51, 52, 70, 88, 91], "true_energy_bin": [16, 17, 18, 42, 44, 45, 50, 51, 52, 53, 91], "true_source_fov_offset": [51, 52, 79, 88, 91], "tu": 91, "tutori": 80, "two": [15, 39, 54, 74, 79, 80, 87], "type": [79, 87], "typeerror": [19, 23, 34, 39], "u": [4, 7, 9, 11, 33, 57, 87, 91], "ucsc": [40, 87], "under": [4, 6, 86], "underflow": [4, 6], "underflow_index": 6, "understand": 80, "unfortun": 91, "uniformli": 57, "uniqu": [47, 79], "unit": [6, 7, 8, 10, 12, 13, 15, 16, 17, 18, 19, 23, 27, 34, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 55, 57, 61, 64, 66, 67, 68, 70, 72, 73, 75, 79, 80, 86, 87, 88, 91], "univ": [39, 87], "unnorm": 68, "unpack": 86, "unsur": 80, "unweight": 54, "unzip": 83, "up": [54, 56, 79, 80], "updat": [79, 91], "upper": [15, 57, 79], "url": [80, 91], "us": [0, 1, 2, 6, 10, 12, 13, 15, 19, 23, 24, 25, 30, 34, 35, 36, 39, 42, 43, 44, 45, 46, 47, 48, 56, 57, 72, 79, 80, 83, 85, 86, 88, 89, 92], "usabl": [19, 22, 23, 26, 34, 37, 87], "usag": [79, 87], "use_histogram": 47, "user": [79, 86], "user_instal": 91, "usual": 88, "utf": 91, "util": [72, 73, 74, 75, 76, 79, 85, 91], "v": [80, 91], "v0": [85, 87], "v1": 91, "val": 76, "valid": 6, "valu": [6, 13, 15, 22, 26, 27, 29, 30, 31, 32, 37, 38, 39, 40, 56, 57, 76, 79], "valueerror": [19, 23, 34, 39], "van": 79, "variabl": [8, 80], "vector": [15, 79], "veri": 88, "verifi": 79, "verkerk": [30, 87], "verna": 79, "version": [0, 40, 79, 85, 87, 90], "view": [16, 17, 18, 42, 43, 44, 45, 46, 48, 51, 52, 75, 80, 88, 90], "viewcon": [57, 79], "viewcone_max": [57, 79, 91], "viewcone_min": [57, 79, 91], "violat": [54, 56], "virtual": 86, "visibl": [39, 79, 87], "visit": [0, 80], "visual": [40, 87], "vuillaum": 79, "w": [30, 87], "w_i": 70, "wa": [0, 42, 44, 49, 57, 79], "wait": 80, "want": [39, 79, 80, 86, 91], "warn": [19, 23, 34, 79, 80], "we": [55, 56, 79, 80, 86, 88, 91], "week": 79, "weight": [12, 48, 54, 56, 57, 70, 79, 85, 88], "well": [79, 85, 88, 91], "were": [57, 79, 83, 89], "what": 79, "when": [19, 21, 22, 23, 25, 26, 29, 30, 34, 36, 37, 40, 71, 79, 80, 87], "where": [32, 39, 68, 79, 87], "which": [1, 13, 15, 22, 26, 30, 31, 37, 38, 39, 40, 49, 50, 51, 55, 57, 79, 80, 85, 86, 87, 88, 89, 90, 91], "while": [19, 23, 34, 39, 87], "who": 0, "wide": 88, "width": 13, "wiki": 80, "wisconsin": [39, 87], "wise": 15, "without": [79, 80], "witht": 91, "wobbl": [56, 79, 91], "wobble_offset": 91, "work": [9, 19, 23, 34, 80, 86, 91], "workaround": [76, 79], "workflow": 80, "would": [71, 79, 87], "wrapper": 29, "write": [89, 91], "writeto": 91, "written": 91, "wrong": [79, 80], "wrongli": 79, "x": [68, 87], "x64": 91, "xlabel": 91, "xlim": 91, "xscale": 91, "xyz": 79, "xyzestim": 79, "y": 56, "yet": [79, 80, 85, 87, 91], "yield": [54, 56, 79], "ylabel": 91, "yml": [79, 86], "you": [79, 80, 85, 86, 91], "your": 86, "yscale": 91, "z": 91, "zen_pnt": 87, "zenith": 87, "zenodo": [80, 86, 87], "zero": [27, 39], "zip": 91, "\u03c3": 88}, "titles": ["Authors", "angular_resolution", "energy_bias_resolution", "energy_bias_resolution_from_energy_dispersion", "add_overflow_bins", "bin_center", "calculate_bin_indices", "create_bins_per_decade", "create_histogram_table", "join_bin_lo_hi", "resample_histogram1d", "split_bin_lo_hi", "optimize_gh_cut", "calculate_percentile_cut", "compare_irf_cuts", "evaluate_binned_cut", "create_effective_area_table_2d", "create_energy_dispersion_2d", "create_psf_3d", "BaseComponentEstimator", "BaseExtrapolator", "BaseInterpolator", "BaseNearestNeighborSearcher", "DiscretePDFComponentEstimator", "DiscretePDFExtrapolator", "DiscretePDFInterpolator", "DiscretePDFNearestNeighborSearcher", "EffectiveAreaEstimator", "EnergyDispersionEstimator", "GridDataInterpolator", "MomentMorphInterpolator", "MomentMorphNearestSimplexExtrapolator", "PDFNormalization", "PSFTableEstimator", "ParametrizedComponentEstimator", "ParametrizedExtrapolator", "ParametrizedInterpolator", "ParametrizedNearestNeighborSearcher", "ParametrizedNearestSimplexExtrapolator", "ParametrizedVisibleEdgesExtrapolator", "QuantileInterpolator", "RadMaxEstimator", "create_aeff2d_hdu", "create_background_2d_hdu", "create_energy_dispersion_hdu", "create_psf_table_hdu", "create_rad_max_hdu", "read_eventdisplay_fits", "background_2d", "effective_area", "effective_area_per_energy", "effective_area_per_energy_and_fov", "energy_dispersion", "psf_table", "calculate_sensitivity", "estimate_background", "relative_sensitivity", "SimulatedEventsInfo", "CRAB_HEGRA", "CRAB_MAGIC_JHEAP2015", "DAMPE_P_He_SPECTRUM", "DIFFUSE_FLUX_UNIT", "IRFDOC_ELECTRON_SPECTRUM", "IRFDOC_PROTON_SPECTRUM", "LogParabola", "PDG_ALL_PARTICLE", "POINT_SOURCE_FLUX_UNIT", "PowerLaw", "PowerLawWithExponentialGaussian", "TableInterpolationSpectrum", "calculate_event_weights", "li_ma_significance", "calculate_source_fov_offset", "calculate_theta", "check_histograms", "cone_solid_angle", "is_scalar", "Benchmarks", "Binning and Histogram Utilities", "Changelog", "How to contribute", "Cut Optimization", "Calculating and Applying Cuts", "Examples", "Gammapy Interoperability", "Welcome to pyirf\u2019s documentation!", "Installation", "Interpolation and Extrapolation of IRFs", "Introduction to pyirf", "Input / Output", "Instrument Response Functions", "Using pyirf to calculate IRFs from the FACT Open Data", "Example Notebooks", "Sensitivity", "Simulation Information", "Event Weighting and Spectrum Definitions", "Statistics", "Utility functions"], "titleterms": {"": 85, "0": 79, "03": 79, "05": 79, "07": 79, "08": 79, "09": 79, "1": 79, "10": 79, "11": 79, "14": 79, "15": 79, "16": 79, "19": 79, "2": 79, "2020": 79, "2021": 79, "2023": 79, "2024": 79, "22": 79, "23": 79, "27": 79, "3": 79, "4": 79, "8": 79, "9": 79, "add_overflow_bin": 4, "alpha": 79, "angular_resolut": 1, "api": [78, 79, 81, 82, 84, 85, 87, 89, 90, 93, 94, 95, 96, 97], "appli": [82, 91], "area": [90, 91], "author": 0, "background": 90, "background_2d": 48, "base": 87, "basecomponentestim": 19, "baseextrapol": 20, "baseinterpol": 21, "basenearestneighborsearch": 22, "benchmark": 77, "bin": [78, 91], "bin_cent": 5, "bug": 79, "build": 80, "calcul": [82, 83, 91], "calculate_bin_indic": 6, "calculate_event_weight": 70, "calculate_percentile_cut": 13, "calculate_sensit": 54, "calculate_source_fov_offset": 72, "calculate_theta": 73, "chang": 79, "changelog": 79, "check_histogram": 74, "cite": 85, "class": [87, 94, 95], "column": 88, "compare_irf_cut": 14, "compon": 87, "cone_solid_angl": 75, "contribut": 80, "contributor": 79, "coverag": 80, "crab_hegra": 58, "crab_magic_jheap2015": 59, "creat": [87, 91], "create_aeff2d_hdu": 42, "create_background_2d_hdu": 43, "create_bins_per_decad": 7, "create_effective_area_table_2d": 16, "create_energy_dispersion_2d": 17, "create_energy_dispersion_hdu": 44, "create_histogram_t": 8, "create_psf_3d": 18, "create_psf_table_hdu": 45, "create_rad_max_hdu": 46, "current": 80, "cut": [81, 82], "cut_optim": 81, "dampe_p_he_spectrum": 60, "data": [83, 91], "definit": [88, 95], "descript": 79, "detail": 80, "dev55": 79, "develop": [80, 86], "diffuse_flux_unit": 61, "discretepdfcomponentestim": 23, "discretepdfextrapol": 24, "discretepdfinterpol": 25, "discretepdfnearestneighborsearch": 26, "dispers": [90, 91], "dl2": [83, 88, 91], "document": [80, 85], "download": 91, "effect": [90, 91], "effective_area": 49, "effective_area_per_energi": 50, "effective_area_per_energy_and_fov": 51, "effectiveareaestim": 27, "energi": [90, 91], "energy_bias_resolut": 2, "energy_bias_resolution_from_energy_dispers": 3, "energy_dispers": 52, "energydispersionestim": 28, "estim": 87, "estimate_background": 55, "evaluate_binned_cut": 15, "event": [88, 91, 95], "eventdisplai": 83, "exampl": [83, 92], "export": 91, "extrapol": 87, "fact": 91, "featur": 79, "file": 91, "fit": 91, "fix": 79, "flux": 83, "format": 88, "from": 91, "full": 87, "function": [77, 78, 81, 82, 84, 89, 90, 91, 93, 95, 96, 97], "further": 80, "g0fefb93": 79, "gadf": 91, "gammapi": 84, "griddatainterpol": 29, "helper": 87, "histogram": 78, "how": 80, "includ": 83, "indic": 85, "info": 91, "inform": 94, "input": [88, 89], "instal": 86, "instrument": 90, "inter": 87, "interoper": 84, "interpol": 87, "introduct": [88, 89], "io": 89, "irf": [83, 87, 90, 91], "irfdoc_electron_spectrum": 62, "irfdoc_proton_spectrum": 63, "is_scalar": 76, "issu": 80, "join_bin_lo_hi": 9, "li_ma_signific": 71, "list": [88, 91], "logparabola": 64, "look": 80, "mainten": 79, "make": 80, "matrix": 90, "merg": 79, "model": 83, "modul": [78, 81, 82, 84, 93, 94, 95, 96, 97], "momentmorphinterpol": 30, "momentmorphnearestsimplexextrapol": 31, "new": [79, 87], "notebook": 92, "older": 79, "open": 91, "optim": [79, 81], "optimize_gh_cut": 12, "output": 89, "overview": 85, "packag": [77, 89, 90], "parametrizedcomponentestim": 34, "parametrizedextrapol": 35, "parametrizedinterpol": 36, "parametrizednearestneighborsearch": 37, "parametrizednearestsimplexextrapol": 38, "parametrizedvisibleedgesextrapol": 39, "pdfnormal": 32, "pdg_all_particl": 65, "point": [90, 91], "point_source_flux_unit": 66, "powerlaw": 67, "powerlawwithexponentialgaussian": 68, "procedur": 80, "project": [79, 80], "psf_tabl": 53, "psftableestim": 33, "pull": 79, "pyirf": [77, 78, 79, 81, 82, 84, 85, 88, 89, 90, 91, 93, 94, 95, 96, 97], "quantileinterpol": 40, "radmaxestim": 41, "rate": 90, "read": 91, "read_eventdisplay_fit": 47, "refactor": 79, "refer": [78, 81, 82, 84, 89, 90, 93, 94, 95, 96, 97], "relat": 79, "relative_sensit": 56, "releas": [79, 86], "request": 79, "resample_histogram1d": 10, "respons": 90, "run": 80, "select": 91, "sensit": [83, 93], "simul": [91, 94], "simulatedeventsinfo": 57, "softwar": 85, "spectral": 95, "spectrum": 95, "split_bin_lo_hi": 11, "spread": [90, 91], "statist": 96, "summari": 79, "tabl": 85, "tableinterpolationspectrum": 69, "test": 80, "thi": 85, "tracker": 80, "us": [87, 91], "util": [78, 97], "v0": 79, "variabl": 95, "version": 86, "visibl": 80, "visual": 83, "weight": 95, "welcom": 85, "your": 80}}) \ No newline at end of file diff --git a/sensitivity.html b/sensitivity.html new file mode 100644 index 000000000..e691b3b3c --- /dev/null +++ b/sensitivity.html @@ -0,0 +1,174 @@ + + + + + + + Sensitivity — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Sensitivity

+
+

Reference/API

+
+
+

pyirf.sensitivity Module

+

Functions to calculate sensitivity

+
+

Functions

+ + + + + + + + + + + + +

relative_sensitivity(n_on, n_off, alpha[, ...])

Calculate the relative sensitivity defined as the flux relative to the reference source that is detectable with significance target_significance.

calculate_sensitivity(signal_hist, ...[, ...])

Calculate sensitivity for DL2 event lists in bins of reconstructed energy.

estimate_background(events, ...)

Estimate the number of background events for a point-like sensitivity.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/simulation.html b/simulation.html new file mode 100644 index 000000000..6ba46b8f4 --- /dev/null +++ b/simulation.html @@ -0,0 +1,165 @@ + + + + + + + Simulation Information — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Simulation Information

+
+

Reference/API

+
+
+

pyirf.simulations Module

+
+

Classes

+ + + + + + +

SimulatedEventsInfo(n_showers, energy_min, ...)

Information about all simulated events, for calculating event weights.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/spectral.html b/spectral.html new file mode 100644 index 000000000..5da7e8e79 --- /dev/null +++ b/spectral.html @@ -0,0 +1,236 @@ + + + + + + + Event Weighting and Spectrum Definitions — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Event Weighting and Spectrum Definitions

+
+

Reference/API

+
+
+

pyirf.spectral Module

+

Functions and classes for calculating spectral weights

+
+

Functions

+ + + + + + +

calculate_event_weights(true_energy, ...)

Calculate event weights

+
+
+

Classes

+ + + + + + + + + + + + + + + +

PowerLaw(normalization, index[, e_ref])

A power law with normalization, reference energy and index.

LogParabola(normalization, a, b[, e_ref])

A log parabola flux parameterization.

PowerLawWithExponentialGaussian(...)

A power law with an additional Gaussian bump.

TableInterpolationSpectrum(energy, flux[, ...])

Interpolate flux points to obtain a spectrum.

+
+
+

Variables

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

POINT_SOURCE_FLUX_UNIT

Unit of a point source flux

DIFFUSE_FLUX_UNIT

Unit of a diffuse flux

CRAB_HEGRA

Power Law parametrization of the Crab Nebula spectrum as published by HEGRA

CRAB_MAGIC_JHEAP2015

Log-Parabola parametrization of the Crab Nebula spectrum as published by MAGIC

PDG_ALL_PARTICLE

All particle spectrum

IRFDOC_PROTON_SPECTRUM

Proton spectrum definition defined in the CTA Prod3b IRF Document

IRFDOC_ELECTRON_SPECTRUM

Electron spectrum definition defined in the CTA Prod3b IRF Document

DAMPE_P_He_SPECTRUM

Interpolate flux points to obtain a spectrum.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/statistics.html b/statistics.html new file mode 100644 index 000000000..3ef36bded --- /dev/null +++ b/statistics.html @@ -0,0 +1,165 @@ + + + + + + + Statistics — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Statistics

+
+

Reference/API

+
+
+

pyirf.statistics Module

+
+

Functions

+ + + + + + +

li_ma_significance(n_on, n_off[, alpha])

Calculate the Li & Ma significance.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/utils.html b/utils.html new file mode 100644 index 000000000..1e0b3fae0 --- /dev/null +++ b/utils.html @@ -0,0 +1,181 @@ + + + + + + + Utility functions — pyirf documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Utility functions

+
+

Reference/API

+
+
+

pyirf.utils Module

+
+

Functions

+ + + + + + + + + + + + + + + + + + +

is_scalar(val)

Workaround that also supports astropy quantities

calculate_theta(events, assumed_source_az, ...)

Calculate sky separation between assumed and reconstructed positions.

calculate_source_fov_offset(events[, prefix])

Calculate angular separation between true and pointing positions.

check_histograms(hist1, hist2[, key])

Check if two histogram tables have the same binning

cone_solid_angle(angle)

Calculate the solid angle of a view cone.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file