Source code for order.category

# coding: utf-8

"""
Classes to describe object that help distinguishing events.
"""


__all__ = ["Channel", "Category"]


from order.unique import UniqueObject, UniqueObjectIndex, unique_tree
from order.mixins import CopyMixin, AuxDataMixin, TagMixin, SelectionMixin, LabelMixin
from order.util import to_root_latex


[docs]@unique_tree(parents=-1, deep_children=True, deep_parents=True) class Category(UniqueObject, CopyMixin, AuxDataMixin, TagMixin, SelectionMixin, LabelMixin): """ __init__(name, id="+", channel=None, categories=None, label=None, label_short=None, \ selection=None, str_selection_mode=None, tags=None, aux=None) Class that describes an analysis category. This is not to be confused with an analysis :py:class:`Channel`. While the definition of a channel can be understood as being fixed by e.g. the final state of an event, a category describes an arbitrary sub phase-space. Therefore, a category can be (optionally) uniquely assigned to a channel - it *has* a channel. Also, categories can be nested, i.e., they can have child and parent categories. **Arguments** *channel* should be a reference to a :py:class:`Channel` instance or *None*. Child categories are initialized with *categories*. *label* and *label_short* are forwarded to the :py:class:`~order.mixins.LabelMixin`, *selection* and *str_selection_mode* to the :py:class:`~order.mixins.SelectionMixin`, *tags* to the :py:class:`~order.mixins.TagMixin`, *aux* to the :py:class:`~order.mixins.AuxDataMixin`, and *name* and *id* (defaulting to an auto id) to the :py:class:`~order.unique.UniqueObject` constructor. **Copy behavior** ``copy()`` The :py:attr:`channel` attribute is carried over as a reference, all remaining attributes are copied. Note that the copied category is also registered in the channel. Also, please be aware that deep copies of heavily nested category structures might lead to python running into a recursion error (``maximum recursion depth exceeded while calling a Python object``). If this is the case, you might want to consider `increasing the recursion depth <https://docs.python.org/3/library/sys.html#sys.setrecursionlimit>`__. ``copy_shallow()`` All attributs are copied except for the :py:attr:`channel`, (child) :py:attr:`categories` and :py:attr:`parent_categories` which are set to default values instead. **Example** .. code-block:: python import order as od # toggle the default selection mode to Root-style selection string concatenation od.Category.default_str_selection_mode = "root" cat = od.Category( name="4j", label="4 jets", label_short="4j", selection="nJets == 4", ) # note that no id needs to be passed to the Category constructor # its id is set automatically based on the maximum id of currently existing category # instances plus one (which is - of course - one in this example) cat.id # -> 1 cat.label # -> "4 jets" # add a channel ch = od.Channel( name="dilepton", id=1, label="Dilepton", label_short="DL" ) cat.channel = ch # the category is also assigned to the channel now cat in ch.categories # -> True # and we can create the full category label cat.full_label # -> "Dilepton, 4 jets" # and the short version of it cat.full_label_short # -> "DL, 4j" # add a sub category cat2 = cat.add_category( name="4j_2b", label=cat.label + ", 2 b-tags", ) # set the selection string (could also be set in add_category above) cat2.selection = [cat.selection, "nBTags == 2"] cat2.selection # -> "(nJets == 4) && (nBTags == 2)" **Members** .. py:attribute:: channel type: :py:class:`Channel`, None The channel instance of this category, or *None* when not set. .. py:attribute:: full_label type: string (read-only) The label of this category, prefix with the channel label if a channel is set. .. py:attribute:: full_label_short type: string (read-only) The short label of this category, prefix with the short channel label if a channel is set. .. py:attribute:: full_label_root type: string (read-only) The label of this category, prefix with the channel label if a channel is set, converted to ROOT-style latex. .. py:attribute:: full_label_short_root type: string (read-only) The short label of this category, prefix with the short channel label if a channel is set, converted to ROOT-style latex. """ cls_name_singular = "category" cls_name_plural = "categories" # attributes for copying copy_specs = ( [ { "attr": "_channel", "ref": True, "skip_shallow": True, }, { "attr": "_categories", "skip_shallow": True, "skip_value": CopyMixin.Deferred(lambda inst: UniqueObjectIndex(cls=Category)), }, { "attr": "_parent_categories", "skip_shallow": True, "skip_value": CopyMixin.Deferred(lambda inst: UniqueObjectIndex(cls=Category)), }, ] + UniqueObject.copy_specs + AuxDataMixin.copy_specs + TagMixin.copy_specs + SelectionMixin.copy_specs + LabelMixin.copy_specs ) def __init__( self, name, id=UniqueObject.AUTO_ID, channel=None, categories=None, label=None, label_short=None, selection=None, str_selection_mode=None, tags=None, aux=None, ): UniqueObject.__init__(self, name, id) CopyMixin.__init__(self) AuxDataMixin.__init__(self, aux=aux) TagMixin.__init__(self, tags=tags) SelectionMixin.__init__(self, selection=selection, str_selection_mode=str_selection_mode) LabelMixin.__init__(self, label=label, label_short=label_short) # register empty attributes self._channel = None # set initial values self.channel = channel if categories is not None: self.extend_categories(categories)
[docs] def copy(self, *args, **kwargs): inst = super(Category, self).copy(*args, **kwargs) # register in the channel if inst.channel: inst.channel.categories.add(inst) return inst
@property def channel(self): # channel getter return self._channel @channel.setter def channel(self, channel): # channel setter if channel is not None and not isinstance(channel, Channel): raise TypeError("invalid channel type: {}".format(channel)) # remove this category from the current channels' categories index if self._channel: self._channel.categories.remove(self) # add this category to the channels' categories index if channel: channel.categories.add(self) self._channel = channel @property def full_label(self): if self.channel: return "{}, {}".format(self.channel.label, self.label) return self.label @property def full_label_short(self): if self.channel: return "{}, {}".format(self.channel.label_short, self.label_short) return self.label_short @property def full_label_root(self): return to_root_latex(self.full_label) @property def full_label_short_root(self): return to_root_latex(self.full_label_short)
[docs]@unique_tree(parents=1, deep_children=True, deep_parents=True) @unique_tree(cls=Category, plural="categories", parents=False, deep_children=True) class Channel(UniqueObject, CopyMixin, AuxDataMixin, TagMixin, LabelMixin): """ An object that descibes an analysis channel, often defined by a particular decay *channel* that results in distinct final state objects. A channel can have parent-child relations to other channels with one parent per child, and child relations to categories. **Arguments** References to contained categories are initialized with *categories*. *label* and *label_short* are passed to the :py:class:`~order.mixins.LabelMixin`, *tags* to the :py:class:`~order.mixins.TagMixin`, *aux* to the :py:class:`~order.mixins.AuxDataMixin`, and *name* and *id* to the :py:class:`~order.unique.UniqueObject` constructor. **Copy behavior** ``copy()`` All attributes are copied. ``copy_shallow()`` All attributs are copied except for child :py:attr:`categories` which is set to a default value instead. **Example** .. code-block:: python import order as od # create a channel SL_channel = od.Channel("SL", 1, label="lepton+jets") # add child channels e_channel = SL_channel.add_channel("e", 1, label="e+jets") mu_channel = SL_channel.add_channel("mu", 2) len(SL_channel.channels) # -> 2 len(e_channel.parent_channels) # -> 1 e_channel.parent_channel # -> SL_channel # add categories cat_e_2j = e_channel.add_category( name="e_2j", label="2 jets", selection="nJets == 2", ) # print the category label cat_e_2j.full_label # -> "e+jets, 2 jets" **Members** """ cls_name_singular = "channel" cls_name_plural = "channels" # attributes for copying copy_specs = ( [ { "attr": "_categories", "skip_shallow": True, "skip_value": CopyMixin.Deferred(lambda inst: UniqueObjectIndex(cls=Category)), }, ] + UniqueObject.copy_specs + AuxDataMixin.copy_specs + TagMixin.copy_specs + LabelMixin.copy_specs ) def __init__( self, name, id, categories=None, label=None, label_short=None, tags=None, aux=None, ): UniqueObject.__init__(self, name, id) CopyMixin.__init__(self) AuxDataMixin.__init__(self, aux=aux) TagMixin.__init__(self, tags=tags) LabelMixin.__init__(self, label=label, label_short=label_short) # set initial categories if categories is not None: self.extend_categories(categories)
[docs] def add_category(self, *args, **kwargs): """ Adds a child category to the :py:attr:`categories` index and returns it. See :py:meth:`UniqueObjectIndex.add` for more info. Also sets the *channel* of the added category to *this* instance. """ category = self.categories.add(*args, **kwargs) # update the category's channel category.channel = None category._channel = self return category
[docs] def remove_category(self, *args, **kwargs): """ Removes a child category from the :py:attr:`categories` index and returns the removed object. See :py:meth:`UniqueObjectIndex.remove` for more info. Also resets the *channel* of the removed category. """ category = self.categories.remove(*args, **kwargs) # reset the category's channel if category: category._channel = None return category