From 6da0b3ec5758a74abddc30ce25512646caf9362f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 8 Aug 2019 16:42:14 -0500 Subject: [PATCH] Add openmc.deplete.Chain.validate method Closes #1308 by providing a way to validate the contents of the depletion chain. This method iterates over all the nuclides and calls their validation method with the same input arguments, (strict, quiet, and tolerance). For the case where strict == False, quiet == False, and a nuclide fails the validation, the method returns early rather than continuing to iterate over all other nuclides. Type and value checking are also done on the tolerance argument. Two tests were added to test_deplete_chain.py The more interesting test works with the simple chain and various manipulations to check the robustness of the method. The other just checks the type and value checking on tolerance. --- openmc/deplete/chain.py | 53 +++++++++++++++++++++++++- openmc/deplete/nuclide.py | 4 ++ tests/unit_tests/test_deplete_chain.py | 52 +++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 1f16d9caf..d2ccf227c 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -10,9 +10,10 @@ import math import re from collections import OrderedDict, defaultdict from collections.abc import Mapping +from numbers import Real from warnings import warn -from openmc.checkvalue import check_type, check_less_than +from openmc.checkvalue import check_type, check_less_than, check_greater_than from openmc.data import gnd_name, zam # Try to use lxml if it is available. It preserves the order of attributes and @@ -613,3 +614,53 @@ class Chain(object): new_ratios[ground_tgt] = ground_br parent.reactions.append(ReactionTuple( "(n,gamma)", ground_tgt, capt_Q, ground_br)) + + def validate(self, strict=True, quiet=False, tolerance=1e-4): + """Search for possible inconsistencies + + The following checks are performed for all nuclides present: + + 1) For all non-fission reactions, do the sum of branching + ratios equal about 1? + 2) For fission reactions, do the sum of fission yield + fractions equal about 2? + + Parameters + ---------- + strict : bool, optional + Raise exceptions at the first inconsistency if true. + Otherwise mark a warning + quiet : bool, optional + Flag to supress warnings and return immediately at + the first inconsistency. Used only if + ``strict`` does not evaluate to ``True``. + tolerance : float, optional + Absolute tolerance for comparisons. Used to compare computed + value ``x`` to intended value ``y`` as:: + + valid = (y - tolerance <= x <= y + tolerance) + Returns + ------- + valid : bool + True if no inconsistencies were found + + Raises + ------ + ValueError + If ``strict`` evaluates to ``True`` and an inconistency was + found + + See Also + -------- + openmc.deplete.Nuclide.validate + """ + check_type("tolerance", tolerance, Real) + check_greater_than("tolerance", tolerance, 0.0, True) + valid = True + # Sort through nuclides by name + for name in sorted(self.nuclide_dict): + stat = self[name].validate(strict, quiet, tolerance) + if quiet and not stat: + return stat + valid &= stat + return valid diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 9c07a91a9..63503cbfb 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -260,6 +260,10 @@ class Nuclide(object): ValueError If ``strict`` evaluates to ``True`` and an inconistency was found + + See Also + -------- + openmc.deplete.Chain.validate """ branch_getter = attrgetter("branching_ratio") diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 33b13b596..294e56637 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -329,3 +329,55 @@ def test_capture_branch_failures(simple_chain): br = {"C": {"A": 1.0, "B": 1.0}} with pytest.raises(ValueError, match="C ratios"): simple_chain.set_capture_branches(br) + + +def test_validate(simple_chain): + """Test the validate method""" + + # current chain is invalid + # fission yields do not sum to 2.0 + with pytest.raises(ValueError, match="Nuclide C.*fission yields"): + simple_chain.validate(strict=True, tolerance=0.0) + + with pytest.warns(UserWarning) as record: + assert not simple_chain.validate(strict=False, quiet=False, tolerance=0.0) + assert not simple_chain.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 1 + assert "Nuclide C" in record[0].message.args[0] + + # Fix fission yields but keep to restore later + old_yields = simple_chain["C"].yield_data + simple_chain["C"].yield_data = {0.0253: [("A", 1.4), ("B", 0.6)]} + + assert simple_chain.validate(strict=True, tolerance=0.0) + with pytest.warns(None) as record: + assert simple_chain.validate(strict=False, quiet=False, tolerance=0.0) + assert len(record) == 0 + + # Mess up "earlier" nuclide's reactions + decay_mode = simple_chain["A"].decay_modes.pop() + + with pytest.raises(ValueError, match="Nuclide A.*decay mode"): + simple_chain.validate(strict=True, tolerance=0.0) + + # restore old fission yields + simple_chain["C"].yield_data = old_yields + + with pytest.warns(UserWarning) as record: + assert not simple_chain.validate(strict=False, quiet=False, tolerance=0.0) + assert len(record) == 2 + assert "Nuclide A" in record[0].message.args[0] + assert "Nuclide C" in record[1].message.args[0] + + # restore decay modes + simple_chain["A"].decay_modes.append(decay_mode) + + +def test_validate_inputs(): + c = Chain() + + with pytest.raises(TypeError, match="tolerance"): + c.validate(tolerance=None) + + with pytest.raises(ValueError, match="tolerance"): + c.validate(tolerance=-1)