From 720c6d80dd5b3676e19e1d76f0411fb27318f458 Mon Sep 17 00:00:00 2001 From: Graeme Watt Date: Wed, 14 Feb 2024 14:52:26 +0000 Subject: [PATCH] Clarify behaviour if zero uncertainties (#251) * Add zero_uncertainties_warning option to Variable * tests: add test for zero uncertainties --- docs/usage.rst | 10 +++++++++- hepdata_lib/__init__.py | 6 ++++-- tests/test_uncertainty.py | 26 +++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 8d450e32..b937df15 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -337,4 +337,12 @@ After creating the Uncertainty objects, the only additional step is to attach th variable.add_uncertainty(unc1) variable.add_uncertainty(unc2) - +See `Uncertainties`_ for more guidance. In particular, note that ``hepdata_lib`` will omit the ``errors`` key from the +YAML output if all uncertainties are zero for a particular bin, printing a warning message "Note that bins with zero +content should preferably be omitted completely from the HEPData table". A legitimate use case is where there are +multiple dependent variables and a (different) subset of the bins has missing content for some dependent variables. +In this case the uncertainties should be set to zero for the missing bins with a non-numeric central value like ``'-'``. +The warning message can be suppressed by passing an optional argument ``zero_uncertainties_warning=False`` when +defining an instance of the ``Variable`` class. + +.. _`Uncertainties`: https://hepdata-submission.readthedocs.io/en/latest/data_yaml.html#uncertainties \ No newline at end of file diff --git a/hepdata_lib/__init__.py b/hepdata_lib/__init__.py index a4f1896a..4ce095c9 100644 --- a/hepdata_lib/__init__.py +++ b/hepdata_lib/__init__.py @@ -115,13 +115,15 @@ class Variable: # pylint: disable=too-many-instance-attributes # Eight is reasonable in this case. - def __init__(self, name, is_independent=True, is_binned=True, units="", values=None): + def __init__(self, name, is_independent=True, is_binned=True, units="", values=None, + zero_uncertainties_warning=True): # pylint: disable=too-many-arguments self.name = name self.is_independent = is_independent self.is_binned = is_binned self.qualifiers = [] self.units = units + self.zero_uncertainties_warning = zero_uncertainties_warning # needed to make pylint happy, see https://github.com/PyCQA/pylint/issues/409 self._values = None self.values = values if values else [] @@ -273,7 +275,7 @@ def make_dict(self): }, "label": unc.label }) - elif self.uncertainties: + elif self.uncertainties and self.zero_uncertainties_warning: print( "Warning: omitting 'errors' since all uncertainties " \ "are zero for bin {} of variable '{}'.".format(i+1, self.name) diff --git a/tests/test_uncertainty.py b/tests/test_uncertainty.py index 3dd70a12..e121e3bf 100644 --- a/tests/test_uncertainty.py +++ b/tests/test_uncertainty.py @@ -47,7 +47,7 @@ def test_scale_values(self): def test_set_values_from_intervals(self): '''Test behavior of Uncertainy.test_set_values_from_intervals function''' - # Dummy central values and variatons relative to central value + # Dummy central values and variations relative to central value npoints = 100 values = list(range(0, npoints, 1)) uncertainty = [(-random.uniform(0, 1), random.uniform(0, 1)) @@ -91,3 +91,27 @@ def test_mixed_uncertainties(self): pattern = ['symerror', 'asymerror', 'asymerror', 'symerror'] self.assertTrue((list(dictionary['values'][i]['errors'][0].keys())[ 0], value) for i, value in enumerate(pattern)) + + def test_zero_uncertainties(self): + '''Test cases where a data point has zero uncertainties''' + + # Asymmetric uncertainties + var = Variable("testvar", is_binned=False, values=[1, 2, 3, 4]) + unc = Uncertainty("fake_unc", is_symmetric=False) + unc.values = [(-1, 1), (-1.5, 2), (0, 0), (-2.5, 2.5)] + var.add_uncertainty(unc) + dictionary = var.make_dict() + # Check that 'errors' key is missing only if zero uncertainties + self.assertTrue(all('errors' in dictionary['values'][i] for i in [0, 1, 3])) + self.assertTrue('errors' not in dictionary['values'][2]) + + # Symmetric uncertainties (and use "zero_uncertainties_warning=False" option) + var = Variable("testvar", is_binned=False, values=[1, 2, 3, 4], + zero_uncertainties_warning=False) + unc = Uncertainty("fake_unc", is_symmetric=True) + unc.values = [1, 1.5, 0, 2.5] + var.add_uncertainty(unc) + dictionary = var.make_dict() + # Check that 'errors' key is missing only if zero uncertainties + self.assertTrue(all('errors' in dictionary['values'][i] for i in [0, 1, 3])) + self.assertTrue('errors' not in dictionary['values'][2])