Skip to content

Commit

Permalink
feat: support SamplePath overrides for histogram inputs (#437)
Browse files Browse the repository at this point in the history
* support SamplePath overrides when using histogram inputs
* update documentation for histogram inputs
* add debug-level log message with paths to ntuple and histogram inputs
  • Loading branch information
alexander-held authored Sep 19, 2023
1 parent 2b7c091 commit c7301b4
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 39 deletions.
3 changes: 1 addition & 2 deletions docs/core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,12 @@ SamplePath

The ``SamplePath`` setting sets the value for the ``{SamplePath}`` placeholder.
In contrast to the ntuple case, this value cannot be a list of strings.
It also cannot be overridden on a per-systematic basis, just like ``RegionPath``.

VariationPath
"""""""""""""

Each systematic template can set the value for the ``{VariationPath}`` placeholder via the ``VariationPath`` setting.
``RegionPath`` and ``SamplePath`` settings cannot be overridden.
The ``RegionPath`` setting cannot be overridden.

An example
""""""""""
Expand Down
6 changes: 6 additions & 0 deletions src/cabinetry/contrib/histogram_creator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Creates histograms from ntuples with uproot."""

import logging
import pathlib
from typing import List, Optional

Expand All @@ -8,6 +9,9 @@
import uproot


log = logging.getLogger(__name__)


def with_uproot(
ntuple_paths: List[pathlib.Path],
pos_in_file: str,
Expand Down Expand Up @@ -51,6 +55,8 @@ def with_uproot(
weight_is_expression = False
weight = "1.0"

log.debug(f"reading input ntuples {paths_with_trees}")

if weight_is_expression:
# need to read observables and weights
array_generator = uproot.iterate(
Expand Down
6 changes: 6 additions & 0 deletions src/cabinetry/contrib/histogram_reader.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"""Reads histograms with uproot."""

import logging

import boost_histogram as bh
import uproot


log = logging.getLogger(__name__)


def with_uproot(histo_path: str) -> bh.Histogram:
"""Reads a histogram with uproot and returns it.
Expand All @@ -14,5 +19,6 @@ def with_uproot(histo_path: str) -> bh.Histogram:
Returns:
bh.Histogram: histogram containing data
"""
log.debug(f"reading input histogram {histo_path}")
hist = uproot.open(histo_path).to_boost()
return hist
6 changes: 3 additions & 3 deletions src/cabinetry/templates/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ def _ntuple_paths(
A path is built starting from the path specified in the general options in the
configuration file. This path can contain placeholders for region- and sample-
specific overrides, via ``{Region}`` and ``{Sample}``. For non-nominal templates, it
is possible to override the sample path if the ``SamplePath`` option is specified
for the template. If ``SamplePath`` is a list, return a list of paths (one per
entry in the list).
is possible to override the path if the ``RegionPath`` or ``SamplePath`` options are
specified for the template. If ``SamplePath`` is a list, return a list of paths
(one per entry in the list).
Args:
general_path (str): path specified in general settings, with sections that can
Expand Down
34 changes: 23 additions & 11 deletions src/cabinetry/templates/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ def _histo_path(
location of the histogram within the file. Due to the presence of this colon, the
return value is a string instead of a pathlib path. A path is built starting from
the path specified in the general options in the configuration file. This path
contains placeholders for region-, sample-, and systematic-specific values.
contains placeholders for region-, sample-, and systematic-specific values. For
non-nominal templates there are overrides for ``SamplePath`` and ``VariationPath``
which can be used.
Args:
general_path (str): path specified in general settings, with sections that can
Expand All @@ -53,24 +55,25 @@ def _histo_path(

# check whether a systematic is being processed
if template is not None:
# determine whether the template has an override for VariationPath specified
# determine whether the template has an override for SamplePath specified
sample_override = utils._check_for_override(systematic, template, "SamplePath")
if sample_override is not None:
sample_path = sample_override

# check for VariationPath override
variation_override = utils._check_for_override(
systematic, template, "VariationPath"
)
if variation_override is not None:
# _check_for_override should return Optional[str] for VariationPath, but
# mypy cannot know that it will not be a list, so explicitly cast to str
variation_path = cast(str, variation_override)
else:
log.warning(
f"no VariationPath override specified for {region['Name']} / "
f"{sample['Name']} / {systematic['Name']} {template}"
)
# apply variation-specific setting
path = general_path.replace("{VariationPath}", variation_path)

# create a new string for path handling, with placeholders replaced subsequently
path = general_path

# handle region-specific setting
region_template_exists = "{RegionPath}" in general_path
region_template_exists = "{RegionPath}" in path
if region_path is not None:
if not region_template_exists:
log.warning(
Expand All @@ -81,7 +84,7 @@ def _histo_path(
raise ValueError(f"no path setting found for region {region['Name']}")

# handle sample-specific setting
sample_template_exists = "{SamplePath}" in general_path
sample_template_exists = "{SamplePath}" in path
if sample_path is not None:
if not sample_template_exists:
log.warning(
Expand All @@ -91,6 +94,15 @@ def _histo_path(
elif sample_template_exists:
raise ValueError(f"no path setting found for sample {sample['Name']}")

# handle variation-specific setting (variation_path is always specified via function
# argument and possibly also via override)
if "{VariationPath}" not in path:
log.warning(
"variation override specified, but {VariationPath} not found in default "
"path"
)
path = path.replace("{VariationPath}", variation_path)

# check for presence of colon to distinguish path to file and location within file
if ":" not in path:
log.warning(f"no colon found in path {path}, may not be able to find histogram")
Expand Down
41 changes: 18 additions & 23 deletions tests/templates/test_templates_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,31 @@ def test__histo_path(caplog):
# only general path, no override
assert collector._histo_path("path.root:h1", "", {}, {}, {}, None) == "path.root:h1"

# general path with region, sample and nominal variation
# general path with region, sample and nominal variation, template with no impact
assert (
collector._histo_path(
"{RegionPath}.root:{SamplePath}_{VariationPath}",
"nominal",
{"RegionPath": "region"},
{"SamplePath": "sample"},
{},
None,
"Up",
)
== "region.root:sample_nominal"
)

# systematic with override for VariationPath
# systematic with override for SamplePath and VariationPath
assert (
collector._histo_path(
"{RegionPath}.root:{SamplePath}_{VariationPath}",
"nominal",
{"RegionPath": "reg_1"},
{"SamplePath": "path"},
{"Name": "variation", "Up": {"VariationPath": "up"}},
"Up",
)
== "reg_1.root:path_up"
)

caplog.clear()

# systematic without override, results in warning from VariationPath
assert (
collector._histo_path(
"f.root:h1_{VariationPath}",
"",
{"Name": "reg"},
{"Name": "sam"},
{"Name": "variation"},
{"Name": "variation", "Up": {"SamplePath": "var", "VariationPath": "up"}},
"Up",
)
== "f.root:h1_"
== "reg_1.root:var_up"
)
assert "no VariationPath override specified for reg / sam / variation Up" in [
rec.message for rec in caplog.records
]
caplog.clear()

# warning: no region path in template
Expand All @@ -82,6 +64,19 @@ def test__histo_path(caplog):
]
caplog.clear()

# warning: no variation path in template
assert (
collector._histo_path(
"f.root:h1", "", {}, {"VariationPath": "sample.root"}, {}, None
)
== "f.root:h1"
)
assert (
"variation override specified, but {VariationPath} not found in default path"
in [rec.message for rec in caplog.records]
)
caplog.clear()

# warning: no colon in path
assert collector._histo_path("f.root", "", {}, {}, {}, None) == "f.root"
assert "no colon found in path f.root, may not be able to find histogram" in [
Expand Down

0 comments on commit c7301b4

Please sign in to comment.