Skip to content

Commit 1d83bf7

Browse files
refactor template histogram creation to accept list of tasks
1 parent 5ece244 commit 1d83bf7

File tree

3 files changed

+114
-51
lines changed

3 files changed

+114
-51
lines changed

Diff for: example.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,20 @@
1717
cabinetry.configuration.print_overview(config)
1818

1919
# create template histograms
20-
cabinetry.templates.build(config, method="uproot")
20+
from dask.distributed import Client, LocalCluster, wait
21+
22+
def produce_single_template(template):
23+
cabinetry.templates.build(config, template_list=[template])
24+
25+
template_list = cabinetry.route.required_templates(config)
26+
27+
with LocalCluster(n_workers=2) as cluster:
28+
client = Client(cluster)
29+
wait(client.map(produce_single_template, template_list))
30+
31+
# cabinetry.templates.build(config, template_list=template_list)
32+
33+
raise SystemExit
2134

2235
# perform histogram post-processing
2336
cabinetry.templates.postprocess(config)

Diff for: src/cabinetry/route.py

+64-43
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import fnmatch
44
import logging
5-
from typing import Any, Callable, Dict, List, Literal, Optional
5+
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple
66

77
import boost_histogram as bh
88

@@ -38,6 +38,12 @@
3838
# (which returns a histogram) into a function that returns None
3939
WrapperFunc = Callable[[UserTemplateFunc], ProcessorFunc]
4040

41+
# type of tuple capturing all relevant information to obtain a template histogram
42+
# this includes region, sample, systematic and template (up/down)
43+
TemplateHistogramInformation = Tuple[
44+
Dict[str, Any], Dict[str, Any], Dict[str, Any], Optional[Literal["Up", "Down"]]
45+
]
46+
4147

4248
class Router:
4349
"""Holds user-defined processing functions and matches functions to templates.
@@ -257,33 +263,18 @@ def _find_template_builder_match(
257263
return None
258264

259265

260-
def apply_to_all_templates(
261-
config: Dict[str, Any],
262-
default_func: ProcessorFunc,
263-
*,
264-
match_func: Optional[MatchFunc] = None,
265-
) -> None:
266-
"""Applies the supplied function ``default_func`` to all templates.
267-
268-
The templates are specified by the configuration file. The function takes four
269-
arguments in this order:
270-
271-
- the dict specifying region information
272-
- the dict specifying sample information
273-
- the dict specifying systematic information
274-
- the template being considered: "Up", "Down", or None for the nominal template
275-
276-
In addition it is possible to specify a function that returns custom overrides. If
277-
one is found for a given template, it is used instead of the default.
266+
def required_templates(config: Dict[str, Any]) -> List[TemplateHistogramInformation]:
267+
"""Returns relevant information needed to produce all required template histograms.
278268
279269
Args:
280270
config (Dict[str, Any]): cabinetry configuration
281-
default_func (ProcessorFunc): function to be called for every template by
282-
default
283-
match_func: (Optional[MatchFunc], optional): function that returns user-defined
284-
functions to override the call to ``default_func``, defaults to None (then
285-
it is not used)
271+
272+
Returns:
273+
List[TemplateHistogramInformation]: list of relevant information for each
274+
template histogram
286275
"""
276+
all_templates = []
277+
287278
for region in config["Regions"]:
288279
log.debug(f" in region {region['Name']}")
289280

@@ -321,22 +312,52 @@ def apply_to_all_templates(
321312
f"{' ' + template if template is not None else ''}"
322313
)
323314

324-
func_override = None
325-
if match_func is not None:
326-
# check whether a user-defined function was registered that
327-
# matches this region-sample-systematic-template
328-
systematic_name = (
329-
systematic["Name"] if template is not None else ""
330-
)
331-
func_override = match_func(
332-
region["Name"], sample["Name"], systematic_name, template
333-
)
334-
if func_override is not None:
335-
# call the user-defined function
336-
log.debug(
337-
f"executing user-defined override {func_override.__name__}"
338-
)
339-
func_override(region, sample, systematic, template)
340-
else:
341-
# call the provided default function
342-
default_func(region, sample, systematic, template)
315+
all_templates.append((region, sample, systematic, template))
316+
317+
return all_templates
318+
319+
320+
def apply_to_templates(
321+
default_func: ProcessorFunc, # BREAKING API CHANGE
322+
template_list: List[TemplateHistogramInformation],
323+
*,
324+
match_func: Optional[MatchFunc] = None,
325+
) -> None:
326+
"""Applies the supplied function ``default_func`` to all templates.
327+
328+
The templates are specified by the configuration file. The function takes four
329+
arguments in this order:
330+
331+
- the dict specifying region information
332+
- the dict specifying sample information
333+
- the dict specifying systematic information
334+
- the template being considered: "Up", "Down", or None for the nominal template
335+
336+
In addition it is possible to specify a function that returns custom overrides. If
337+
one is found for a given template, it is used instead of the default.
338+
339+
Args:
340+
default_func (ProcessorFunc): function to be called for every template by
341+
default
342+
template_list (List[TemplateHistogramInformation]): list of template information
343+
to apply function to
344+
match_func: (Optional[MatchFunc], optional): function that returns user-defined
345+
functions to override the call to ``default_func``, defaults to None (then
346+
it is not used)
347+
"""
348+
for region, sample, systematic, template in template_list:
349+
func_override = None
350+
if match_func is not None:
351+
# check whether a user-defined function was registered that
352+
# matches this region-sample-systematic-template
353+
systematic_name = systematic["Name"] if template is not None else ""
354+
func_override = match_func(
355+
region["Name"], sample["Name"], systematic_name, template
356+
)
357+
if func_override is not None:
358+
# call the user-defined function
359+
log.debug(f"executing user-defined override {func_override.__name__}")
360+
func_override(region, sample, systematic, template)
361+
else:
362+
# call the provided default function
363+
default_func(region, sample, systematic, template)

Diff for: src/cabinetry/templates/__init__.py

+36-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import logging
44
import pathlib
5-
from typing import Any, Dict, Optional
5+
from typing import Any, Dict, List, Optional
66

77
from cabinetry import route
88
from cabinetry.templates import builder
@@ -18,6 +18,7 @@ def build(
1818
*,
1919
method: str = "uproot",
2020
router: Optional[route.Router] = None,
21+
template_list: Optional[List[route.TemplateHistogramInformation]] = None,
2122
) -> None:
2223
"""Produces all required histograms specified by the configuration file.
2324
@@ -31,6 +32,8 @@ def build(
3132
"uproot"
3233
router (Optional[route.Router], optional): instance of cabinetry.route.Router
3334
that contains user-defined overrides, defaults to None
35+
template_list (Optional[List[route.TemplateHistogramInformation]]): list of
36+
information for templates to process, defaults to None (all templates)
3437
"""
3538
# create an instance of the class doing the template building
3639
histogram_folder = pathlib.Path(config["General"]["HistogramFolder"])
@@ -44,12 +47,21 @@ def build(
4447
# get a function that can be queried to return a user-defined template builder
4548
match_func = router._find_template_builder_match
4649

47-
route.apply_to_all_templates(
48-
config, template_builder._create_histogram, match_func=match_func
50+
# get list of required templates to process if not provided already
51+
if template_list is None:
52+
template_list = route.required_templates(config)
53+
54+
route.apply_to_templates(
55+
template_builder._create_histogram, template_list, match_func=match_func
4956
)
5057

5158

52-
def collect(config: Dict[str, Any], *, method: str = "uproot") -> None:
59+
def collect(
60+
config: Dict[str, Any],
61+
*,
62+
method: str = "uproot",
63+
template_list: Optional[List[route.TemplateHistogramInformation]] = None,
64+
) -> None:
5365
"""Collects all required histograms specified by the configuration file.
5466
5567
Histograms must already exist, and this collects and saves them in the format used
@@ -60,6 +72,8 @@ def collect(config: Dict[str, Any], *, method: str = "uproot") -> None:
6072
config (Dict[str, Any]): cabinetry configuration
6173
method (str, optional): backend to use for histogram production, defaults to
6274
"uproot"
75+
template_list (Optional[List[route.TemplateHistogramInformation]]): list of
76+
information for templates to process, defaults to None (all templates)
6377
"""
6478
histogram_folder = pathlib.Path(config["General"]["HistogramFolder"])
6579
general_path = config["General"]["InputPath"]
@@ -71,15 +85,30 @@ def collect(config: Dict[str, Any], *, method: str = "uproot") -> None:
7185
processor = collector._collector(
7286
histogram_folder, general_path, variation_path, method
7387
)
74-
route.apply_to_all_templates(config, processor)
7588

89+
# get list of required templates to process if not provided already
90+
if template_list is None:
91+
template_list = route.required_templates(config)
92+
93+
route.apply_to_templates(processor, template_list)
7694

77-
def postprocess(config: Dict[str, Any]) -> None:
95+
96+
def postprocess(
97+
config: Dict[str, Any],
98+
template_list: Optional[List[route.TemplateHistogramInformation]] = None,
99+
) -> None:
78100
"""Applies postprocessing to all histograms.
79101
80102
Args:
81103
config (Dict[str, Any]): cabinetry configuration
104+
template_list (Optional[List[route.TemplateHistogramInformation]]): list of
105+
information for templates to process, defaults to None (all templates)
82106
"""
83107
histogram_folder = pathlib.Path(config["General"]["HistogramFolder"])
84108
processor = postprocessor._postprocessor(histogram_folder)
85-
route.apply_to_all_templates(config, processor)
109+
110+
# get list of required templates to process if not provided already
111+
if template_list is None:
112+
template_list = route.required_templates(config)
113+
114+
route.apply_to_templates(processor, template_list)

0 commit comments

Comments
 (0)