diff --git a/src/cabinetry/configuration.py b/src/cabinetry/configuration.py index 5185d384..171864cf 100644 --- a/src/cabinetry/configuration.py +++ b/src/cabinetry/configuration.py @@ -231,10 +231,10 @@ def histogram_is_needed( else: # handle non-nominal, non-data histograms # this assumes that the systematic dict satisfies config schema requirements - if systematic["Type"] == "Normalization": + if systematic[template] == "Normalization": # no histogram needed for normalization variation histo_needed = False - elif systematic["Type"] == "NormPlusShape": + elif systematic["Type"] == "NormPlusShape" or systematic["Type"] == "Normalization": # for a variation defined via a template, a histogram is needed (if # sample is affected in region) histo_needed = region_contains_modifier(region, systematic) diff --git a/src/cabinetry/workspace.py b/src/cabinetry/workspace.py index 8521c5e8..b7cfc8e0 100644 --- a/src/cabinetry/workspace.py +++ b/src/cabinetry/workspace.py @@ -91,12 +91,16 @@ def normfactor_modifiers( ) return modifiers - @staticmethod - def normalization_modifier(systematic: Dict[str, Any]) -> Dict[str, Any]: + # @staticmethod -- VK: had to disable that to pass self + def normalization_modifier( + self, region: Dict[str, Any], sample: Dict[str, Any], systematic: Dict[str, Any] + ) -> Dict[str, Any]: """Returns a normalization modifier (OverallSys in `HistFactory`). Args: - systematic (Dict[str, Any]): systematic for which modifier is constructed + region (Dict[str, Any]): region the systematic variation acts in + sample (Dict[str, Any]): sample the systematic variation acts on + systematic (Dict[str, Any]): the systematic variation under consideration Returns: Dict[str, Any]: single `normsys` modifier for ``pyhf`` workspace @@ -107,14 +111,66 @@ def normalization_modifier(systematic: Dict[str, Any]) -> Dict[str, Any]: modifier = {} modifier.update({"name": modifier_name}) modifier.update({"type": "normsys"}) + + # VK: need to add a check agains Up/Down type agreement -- maybe at the level of the config file creation/validation + if systematic.get("Up").get("Normalization"): + if systematic.get("Up").get("Symmetrize"): + raise NotImplementedError("Symmetrization should happen on the Down variation.") + elif systematic.get("Down").get("Symmetrize"): + norm_effect_up = 1 + systematic["Up"]["Normalization"] + norm_effect_down = 1 - systematic["Up"]["Normalization"] + else: + norm_effect_up = 1 + systematic["Up"]["Normalization"] + norm_effect_down = 1 + systematic["Down"]["Normalization"] + else: + # need to calculate the normalisation factor from the histograms + if systematic.get("Up").get("Symmetrize"): + raise NotImplementedError("Symmetrization should happen on the Down variation.") + # load the up systematic variation histogram + histogram_up = histo.Histogram.from_config( + self.histogram_folder, + region, + sample, + systematic, + template="Up", + modified=True, + ) + # also need the nominal histogram + histogram_nominal = histo.Histogram.from_config( + self.histogram_folder, region, sample, {}, modified=True + ) + + if systematic.get("Down").get("Symmetrize"): + # symmetrization according to "method 1" from issue #26: + # first normalization, then symmetrization + + # normalize the variation to the same yield as nominal + norm_effect = histogram_up.normalize_to_yield(histogram_nominal) + norm_effect_up = norm_effect + norm_effect_down = 2 - norm_effect + else: + histogram_down = histo.Histogram.from_config( + self.histogram_folder, + region, + sample, + systematic, + template="Down", + modified=True, + ) + + norm_effect_up = sum(histogram_up.yields) / sum(histogram_nominal.yields) + norm_effect_down = sum(histogram_down.yields) / sum(histogram_nominal.yields) + + # update the modifier data modifier.update( { "data": { - "hi": 1 + systematic["Up"]["Normalization"], - "lo": 1 + systematic["Down"]["Normalization"], + "hi": norm_effect_up, + "lo": norm_effect_down, } } ) + return modifier def normplusshape_modifiers( @@ -240,7 +296,7 @@ def sys_modifiers( f"adding OverallSys {systematic['Name']} to sample" f" {sample['Name']} in region {region['Name']}" ) - modifiers.append(self.normalization_modifier(systematic)) + modifiers.append(self.normalization_modifier(region, sample, systematic)) elif systematic["Type"] == "NormPlusShape": # two modifiers are needed - an OverallSys for the norm effect, # and a HistoSys for the shape variation