44Tasks related to upper limits on resonant scenarios.
55"""
66
7- from collections import OrderedDict
7+ import re
8+ from collections import OrderedDict , defaultdict
89
910import law
1011import luigi
1314from dhi .tasks .remote import HTCondorWorkflow
1415from dhi .tasks .combine import (
1516 MultiDatacardTask ,
16- MultiDatacardTransposedTask ,
1717 POITask ,
1818 POIPlotTask ,
1919 CombineCommandTask ,
2323from dhi .config import br_hh
2424
2525
26- class ResonantBase (POITask , MultiDatacardTransposedTask ):
26+ class ResonantBase (POITask ):
2727
28+ datacard_pattern = luigi .Parameter (
29+ default = r"^.*_(\d+)\.txt$" ,
30+ description = "a regular expression with a single match group that is supposed to point to "
31+ "the resonance mass value in the datacard path; default: ^.*_(\\ d+)\\ .txt$" ,
32+ )
2833 hh_model = law .NO_STR
2934 allow_empty_hh_model = True
3035
3136 poi = "r_xhh"
3237 scan_parameter = "mhh"
3338
3439 @classmethod
35- def modify_param_values (cls , params ):
36- params = POITask .modify_param_values .__func__ .__get__ (cls )(params )
37- params = MultiDatacardTransposedTask .modify_param_values .__func__ .__get__ (cls )(params )
38- return params
40+ def _group_datacards (cls , datacards , cre ):
41+ groups = defaultdict (list )
42+ for datacard in datacards :
43+ m = cre .match (datacard )
44+ if not m :
45+ raise Exception (
46+ f"no resonance mass could be extracted from datacard '{ datacard } ' "
47+ f"with pattern '{ cre .pattern } '" ,
48+ )
49+ groups [int (m .group (1 ))].append (datacard )
50+
51+ return OrderedDict ([
52+ (mass , sorted (groups [mass ]))
53+ for mass in sorted (groups )
54+ ])
3955
4056 def __init__ (self , * args , ** kwargs ):
4157 super (ResonantBase , self ).__init__ (* args , ** kwargs )
4258
43- # convert keys in multi_datacards_transposed to integers and store them as resonant cards
44- pairs = []
45- for info , datacards in self .multi_datacards_transposed .items ():
46- try :
47- mass = int (info )
48- except :
49- raise Exception (
50- "datacards contain a mass point '{}' which cannot be interpreted as an "
51- "integer" .format (info ),
52- )
53- pairs .append ((mass , datacards ))
54- self .resonant_datacards = OrderedDict (sorted (pairs , key = lambda pair : pair [0 ]))
59+ # group datacards into a dictionary mass -> [cards]
60+ self .resonant_datacards = self .group_datacards ()
61+
62+ def group_datacards (self ):
63+ cre = re .compile (self .datacard_pattern )
64+ return self ._group_datacards (self .datacards , cre )
5565
5666 @property
5767 def other_pois (self ):
@@ -68,11 +78,10 @@ class ResonantLimits(ResonantBase, CombineCommandTask, law.LocalWorkflow, HTCond
6878 run_command_in_tmp = True
6979
7080 def create_branch_map (self ):
71- branch_map = []
72- for mass , cards in self .resonant_datacards .items ():
73- for _cards in cards :
74- branch_map .append ({"mass" : mass , "cards" : _cards })
75- return branch_map
81+ return [
82+ {"mass" : mass , "cards" : cards }
83+ for mass , cards in self .resonant_datacards .items ()
84+ ]
7685
7786 def workflow_requires (self ):
7887 reqs = super (ResonantLimits , self ).workflow_requires ()
@@ -288,45 +297,21 @@ def run(self):
288297 )
289298
290299
291- class PlotMultipleResonantLimits (PlotResonantLimits ):
292-
293- datacard_names = MultiDatacardTask .datacard_names
294- datacard_order = MultiDatacardTask .datacard_order
295- group_duplicate_cards = True
300+ class PlotMultipleResonantLimits (PlotResonantLimits , MultiDatacardTask ):
296301
297302 default_plot_function = "dhi.plots.limits.plot_limit_scans"
298303
299- def __init__ (self , * args , ** kwargs ):
300- super (PlotMultipleResonantLimits , self ).__init__ (* args , ** kwargs )
301-
302- # check that each mass point has the same amount of cards
303- n_entries = {len (cards ) for cards in self .resonant_datacards .values ()}
304- if len (n_entries ) != 1 :
305- raise Exception ("founds different amount of entries in input datacards: {}" .format (
306- "," .join (map (str , n_entries )),
307- ))
308- self .n_entries = list (n_entries )[0 ]
309-
310- # the lengths of names and order indices must match multi_datacards when given
311- if self .datacard_names and len (self .datacard_names ) != self .n_entries :
312- raise Exception ("found {} entries in datacard_names whereas {} are expected" .format (
313- len (self .datacard_names ), self .n_entries ,
314- ))
315- if self .datacard_order and len (self .datacard_order ) != self .n_entries :
316- raise Exception ("found {} entries in datacard_order whereas {} are expected" .format (
317- len (self .datacard_order ), self .n_entries ,
318- ))
304+ def group_datacards (self ):
305+ cre = re .compile (self .datacard_pattern )
306+ return [
307+ self ._group_datacards (datacards , cre )
308+ for datacards in self .multi_datacards
309+ ]
319310
320311 def requires (self ):
321312 return [
322- MergeResonantLimits .req (
323- self ,
324- multi_datacards = tuple (
325- tuple (cards [i ])
326- for cards in self .resonant_datacards .values ()
327- ),
328- )
329- for i in range (self .n_entries )
313+ MergeResonantLimits .req (self , datacards = tuple (sum (groups .values (), [])))
314+ for groups in self .resonant_datacards
330315 ]
331316
332317 def output (self ):
0 commit comments