From 934e065dc45e5bf56a2644af22382f0727fbf6b9 Mon Sep 17 00:00:00 2001 From: Marcel R Date: Fri, 17 Feb 2023 17:14:50 +0100 Subject: [PATCH] Rename selection_mode to str_selection_mode. --- order/category.py | 10 +++--- order/mixins.py | 83 +++++++++++++++++++++++--------------------- order/util.py | 14 ++++++-- order/variable.py | 6 ++-- tests/test_mixins.py | 18 +++++----- 5 files changed, 74 insertions(+), 57 deletions(-) diff --git a/order/category.py b/order/category.py index adec473..d422969 100644 --- a/order/category.py +++ b/order/category.py @@ -16,7 +16,7 @@ @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, selection_mode=None, tags=None, aux=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 @@ -30,7 +30,7 @@ class Category(UniqueObject, CopyMixin, AuxDataMixin, TagMixin, SelectionMixin, are initialized with *categories*. *label* and *label_short* are forwarded to the :py:class:`~order.mixins.LabelMixin`, *selection* - and *selection_mode* to the :py:class:`~order.mixins.SelectionMixin`, *tags* to the + 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. @@ -54,7 +54,7 @@ class Category(UniqueObject, CopyMixin, AuxDataMixin, TagMixin, SelectionMixin, import order as od # toggle the default selection mode to Root-style selection string concatenation - od.Category.default_selection_mode = "root" + od.Category.default_str_selection_mode = "root" cat = od.Category( name="4j", @@ -172,7 +172,7 @@ def __init__( label=None, label_short=None, selection=None, - selection_mode=None, + str_selection_mode=None, tags=None, aux=None, ): @@ -180,7 +180,7 @@ def __init__( CopyMixin.__init__(self) AuxDataMixin.__init__(self, aux=aux) TagMixin.__init__(self, tags=tags) - SelectionMixin.__init__(self, selection=selection, selection_mode=selection_mode) + SelectionMixin.__init__(self, selection=selection, str_selection_mode=str_selection_mode) LabelMixin.__init__(self, label=label, label_short=label_short) # register empty attributes diff --git a/order/mixins.py b/order/mixins.py index 0a7d51a..a004770 100644 --- a/order/mixins.py +++ b/order/mixins.py @@ -670,12 +670,13 @@ def data_source(self): class SelectionMixin(object): """ - Mixin-class that adds attibutes and methods to describe a selection rule using ROOT- or - numexpr-style expression syntax, or a bare callable. + Mixin-class that adds attibutes and methods to describe a selection rule, either as strings, + bare callables, or sequences of the two. When rules are defined as strings, ROOT- or + numexpr-style expression syntax are supported with convenient logical concatenation. **Arguments** - *selection* and *selection_mode* initialize the same-named attributes. + *selection* and *str_selection_mode* initialize the same-named attributes. **Example** @@ -686,8 +687,8 @@ class SelectionMixin(object): class MyClass(od.SelectionMixin): pass - # ROOT-style expressions - c = MyClass(selection="branchA > 0", selection_mode=MyClass.MODE_ROOT) + # ROOT-style string expressions + c = MyClass(selection="branchA > 0", str_selection_mode=MyClass.MODE_ROOT) c.selection # -> "branchA > 0" @@ -700,8 +701,8 @@ class MyClass(od.SelectionMixin): c.selection # -> "((myBranchA > 0) && (myBranchB < 100)) * (myWeight)" - # numexpr-style expressions - c = MyClass(selection="branchA > 0", selection_mode=MyClass.MODE_NUMEXPR) + # numexpr-style string expressions + c = MyClass(selection="branchA > 0", str_selection_mode=MyClass.MODE_NUMEXPR) c.add_selection("myBranchB < 100") c.selection @@ -713,7 +714,7 @@ def my_selection(*args, **kwargs): c.selection = my_selection c.selection # -> - c.selection_mode + c.str_selection_mode # -> None c.add_selection("myBranchB < 100") # -> TypeError @@ -730,20 +731,20 @@ def my_selection(*args, **kwargs): Flag denoting the numexpr-style selection mode (``"numexpr"``). - .. py:classattribute:: default_selection_mode + .. py:classattribute:: default_str_selection_mode type: string - The default *selection_mode* when none is given in the instance constructor. It is initially - set to *MODE_NUMEXPR* if :py:attr:`order.util.ROOT_DEFAULT` is *false*, or to *MODE_ROOT* - otherwise. + The default *str_selection_mode* when none is given in the instance constructor. It is + initially set to *MODE_NUMEXPR* if :py:attr:`order.util.ROOT_DEFAULT` is *false*, or to + *MODE_ROOT* otherwise. .. py:attribute:: selection - type: string, callable + type: string, callable, list - The selection string or a callable. When a string, :py:attr:`selection_mode` decides how the - string is treated. + The selection string, callable or a sequence of them. When a string, + :py:attr:`str_selection_mode` decides how the string is treated. - .. py:attribute:: selection_mode + .. py:attribute:: str_selection_mode type: string, None The selection mode. Should either be *MODE_ROOT* or *MODE_NUMEXPR*. Only considered when @@ -753,23 +754,23 @@ def my_selection(*args, **kwargs): MODE_ROOT = "root" MODE_NUMEXPR = "numexpr" - default_selection_mode = MODE_ROOT if ROOT_DEFAULT else MODE_NUMEXPR + default_str_selection_mode = MODE_ROOT if ROOT_DEFAULT else MODE_NUMEXPR copy_specs = [] - def __init__(self, selection=None, selection_mode=None): + def __init__(self, selection=None, str_selection_mode=None): super(SelectionMixin, self).__init__() # instance members self._selection = "1" - self._selection_mode = None + self._str_selection_mode = None # fallback to default selection mode - if selection_mode is None: - selection_mode = self.default_selection_mode + if str_selection_mode is None: + str_selection_mode = self.default_str_selection_mode # set initial values - self.selection_mode = selection_mode + self.str_selection_mode = str_selection_mode if selection is not None: self.selection = selection @@ -780,15 +781,19 @@ def selection(self): @selection.setter def selection(self, selection): - if callable(selection): + if selection is None: + raise TypeError("invalid selection: {}".format(selection)) + + # just store the valud when not a string + if not isinstance(selection, six.string_types): self._selection = selection - self._selection_mode = None + self._str_selection_mode = None return - # get the selection mode - if self.selection_mode == self.MODE_ROOT: + # interpret as string, get the selection mode + if self.str_selection_mode == self.MODE_ROOT: join = join_root_selection - elif self.selection_mode == self.MODE_NUMEXPR: + elif self.str_selection_mode == self.MODE_NUMEXPR: join = join_numexpr_selection else: raise Exception("when selection is a string, selection mode must be set") @@ -799,19 +804,19 @@ def selection(self, selection): raise TypeError("invalid selection type: {}".format(selection)) @typed - def selection_mode(self, selection_mode): - # selection mode parser - if selection_mode is None: - return selection_mode + def str_selection_mode(self, str_selection_mode): + # str_selection_mode parser + if str_selection_mode is None: + return str_selection_mode - if not isinstance(selection_mode, six.string_types): - raise TypeError("invalid selection_mode type: {}".format(selection_mode)) + if not isinstance(str_selection_mode, six.string_types): + raise TypeError("invalid str_selection_mode type: {}".format(str_selection_mode)) - selection_mode = str(selection_mode) - if selection_mode not in (self.MODE_ROOT, self.MODE_NUMEXPR): - raise ValueError("unknown selection_mode: {}".format(selection_mode)) + str_selection_mode = str(str_selection_mode) + if str_selection_mode not in (self.MODE_ROOT, self.MODE_NUMEXPR): + raise ValueError("unknown str_selection_mode: {}".format(str_selection_mode)) - return selection_mode + return str_selection_mode def add_selection(self, selection, **kwargs): """ @@ -826,9 +831,9 @@ def add_selection(self, selection, **kwargs): "selection {}".format(self.selection), ) - if self.selection_mode == self.MODE_ROOT: + if self.str_selection_mode == self.MODE_ROOT: join = join_root_selection - elif self.selection_mode == self.MODE_NUMEXPR: + elif self.str_selection_mode == self.MODE_NUMEXPR: join = join_numexpr_selection else: raise Exception("when selection is a string, selection mode must be set") diff --git a/order/util.py b/order/util.py index bfb312c..ab2cbd4 100644 --- a/order/util.py +++ b/order/util.py @@ -174,6 +174,16 @@ def flatten(struct, depth=-1): return [struct] +def try_float(obj): + """ + Tries to cast *obj* to float and returns it. If the conversion fails, *None* is returned. + """ + try: + return float(obj) + except: + return None + + def to_root_latex(s): """ Converts latex expressions in a string *s* to ROOT-compatible latex. @@ -193,8 +203,8 @@ def _parse_selection(*selection): if s == 1: continue elif isinstance(s, six.string_types): - # special case: skip empty strings - if not s.strip(): + # special case: skip empty strings and ones + if not s.strip() or try_float(s) == 1: continue else: raise Exception("invalid selection string: {}".format(s)) diff --git a/order/variable.py b/order/variable.py index 175ce03..f247d45 100644 --- a/order/variable.py +++ b/order/variable.py @@ -36,7 +36,7 @@ class Variable(UniqueObject, CopyMixin, AuxDataMixin, TagMixin, SelectionMixin): *y_title_short*, *unit*, *unit_format* and *null_value*. See the attribute listing below for further information. - *selection* and *selection_mode* are passed to the :py:class:`~order.mixins.SelectionMixin`, + *selection* and *str_selection_mode* are passed 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 automatically increasing id) to the :py:class:`~order.unique.UniqueObject` constructor. @@ -258,7 +258,7 @@ def __init__( unit_format="{title} / {unit}", null_value=None, selection=None, - selection_mode=None, + str_selection_mode=None, tags=None, aux=None, # backwards compatibility @@ -271,7 +271,7 @@ def __init__( CopyMixin.__init__(self) AuxDataMixin.__init__(self, aux=aux) TagMixin.__init__(self, tags=tags) - SelectionMixin.__init__(self, selection=selection, selection_mode=selection_mode) + SelectionMixin.__init__(self, selection=selection, str_selection_mode=str_selection_mode) # instance members self._expression = None diff --git a/tests/test_mixins.py b/tests/test_mixins.py index fe64cff..a0582b2 100644 --- a/tests/test_mixins.py +++ b/tests/test_mixins.py @@ -223,7 +223,7 @@ class C(DataSourceMixin): class SelectionMixinTest(unittest.TestCase): def test_constructor_root(self): - s = SelectionMixin("myBranchC > 0", selection_mode=SelectionMixin.MODE_ROOT) + s = SelectionMixin("myBranchC > 0", str_selection_mode=SelectionMixin.MODE_ROOT) self.assertEqual(s.selection, "myBranchC > 0") s.add_selection("myBranchD < 100", bracket=True) @@ -239,7 +239,7 @@ def test_constructor_root(self): ) def test_constructor_numexpr(self): - s = SelectionMixin("myBranchC > 0", selection_mode=SelectionMixin.MODE_NUMEXPR) + s = SelectionMixin("myBranchC > 0", str_selection_mode=SelectionMixin.MODE_NUMEXPR) self.assertEqual(s.selection, "myBranchC > 0") s.add_selection("myBranchD < 100", bracket=True) @@ -255,15 +255,15 @@ def test_constructor_numexpr(self): ) def test_constructor_callable(self): - s = SelectionMixin("myBranchC > 0", selection_mode=SelectionMixin.MODE_NUMEXPR) + s = SelectionMixin("myBranchC > 0", str_selection_mode=SelectionMixin.MODE_NUMEXPR) self.assertEqual(s.selection, "myBranchC > 0") s.selection = lambda: None self.assertTrue(callable(s.selection)) - self.assertIsNone(s.selection_mode) + self.assertIsNone(s.str_selection_mode) def test_selections(self): - s = SelectionMixin(selection_mode=SelectionMixin.MODE_ROOT) + s = SelectionMixin(str_selection_mode=SelectionMixin.MODE_ROOT) s.selection = "myBranchC > 0" self.assertEqual(s.selection, "myBranchC > 0") @@ -275,11 +275,13 @@ def test_selections(self): s.add_selection("myBranchD > 0", op="||", bracket=True) self.assertEqual(s.selection, "((myBranchC > 0) || (myBranchD > 0))") - s.selection = ["myBranchC > 0", "myBranchE > 0"] + s.selection = "1" + s.add_selection(["myBranchC > 0", "myBranchE > 0"]) self.assertEqual(s.selection, "(myBranchC > 0) && (myBranchE > 0)") - s.selection_mode = SelectionMixin.MODE_NUMEXPR - s.selection = ["myBranchC > 0", "myBranchE > 0"] + s.selection = "1" + s.str_selection_mode = SelectionMixin.MODE_NUMEXPR + s.add_selection(["myBranchC > 0", "myBranchE > 0"]) self.assertEqual(s.selection, "(myBranchC > 0) & (myBranchE > 0)")