diff --git a/docs/source/developers.rst b/docs/source/developers.rst deleted file mode 100644 index 7d32b5831c..0000000000 --- a/docs/source/developers.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. _developers: - -================ -Development Team -================ - -The following people have contributed to development of the OpenMC Monte Carlo -code: - -* `Paul Romano `_ -* `Bryan Herman `_ -* `Nick Horelik `_ -* `Adam Nelson `_ -* `Jon Walsh `_ -* `Sterling Harper `_ -* `Will Boyd `_ -* `Samuel Shaner `_ -* `Jingang Liang `_ -* `Colin Josey `_ -* `Amanda Lund `_ -* `Guillaume Giudicelli `_ -* `Isaac Meyer `_ -* `Patrick Shriwise `_ -* `Shikhar Kumar `_ -* `Andrew Davis `_ -* `Benoit Forget `_ -* `Kord Smith `_ -* `Andrew Siegel `_ diff --git a/docs/source/index.rst b/docs/source/index.rst index 5500890e2b..2ce5072d01 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -44,4 +44,3 @@ list `_. io_formats/index publications license - developers diff --git a/docs/source/license.rst b/docs/source/license.rst index c0728e1a8c..98f6655bc2 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -4,7 +4,7 @@ License Agreement ================= -Copyright © 2011-2018 Massachusetts Institute of Technology and OpenMC contributors +Copyright © 2011-2019 Massachusetts Institute of Technology and OpenMC contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/docs/source/publications.rst b/docs/source/publications.rst index d554fe5a14..f33cf79587 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -57,6 +57,9 @@ Benchmarking Coupling and Multi-physics -------------------------- +- Miriam A. Kreher, Benoit Forget, and Kord Smith, "Single-Batch Monte Carlo + Multiphysics Coupling," *Proc. M&C*, Portland, Oregon, Aug. 25-29 (2019). + - Ze-Long Zhao, Yongwei Yang, and Shuang Hong, "`Application of FLUKA and OpenMC in coupled physics calculation of target and subcritical reactor for ADS `_," *Nucl. Sci. Tech.*, **30**: 10 @@ -113,6 +116,10 @@ Coupling and Multi-physics Geometry and Visualization -------------------------- +- Sterling Harper, Paul Romano, Benoit Forget, and Kord Smith, "Efficient + dynamic threadsafe neighbor lists for Monte Carlo ray tracing," *Proc. M&C*, + Portland, Oregon, Aug. 25-29 (2019). + - Jin-Yang Li, Long Gu, Hu-Shan Xu, Nadezha Korepanova, Rui Yu, Yan-Lei Zhu, and Chang-Ping Qin, "`CAD modeling study on FLUKA and OpenMC for accelerator driven system simulation `_", @@ -303,6 +310,11 @@ Multigroup Cross Section Generation Doppler Broadening ------------------ +- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, and Forrest B. Brown, + "`On-the-fly Doppler broadening of unresolved resonance region cross sections + `_," *Prog. Nucl. Energy*, + **101**, 444-460 (2017). + - Colin Josey, Pablo Ducru, Benoit Forget, and Kord Smith, "`Windowed multipole for cross section Doppler broadening `_," *J. Comput. Phys.*, **307**, @@ -313,6 +325,12 @@ Doppler Broadening via Probability Band Interpolation," *Proc. PHYSOR*, Sun Valley, Idaho, May 1-5, 2016. +- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, Brian C. Kiedrowski, and + Forrest B. Brown, "`Direct, on-the-fly calculation of unresolved resonance + region cross sections in Monte Carlo simulations + `_," *Proc. Joint Int. Conf. M&C+SNA+MC*, + Nashville, Tennessee, Apr. 19--23 (2015). + - Colin Josey, Benoit Forget, and Kord Smith, "`Windowed multipole sensitivity to target accuracy of the optimization procedure `_," @@ -366,11 +384,6 @@ Nuclear Data `_", *Comput. Phys. Commun.*, **196**, 134-142 (2015). -- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, Brian C. Kiedrowski, and - Forrest B. Brown, "Direct, on-the-fly calculation of unresolved resonance - region cross sections in Monte Carlo simulations," *Proc. Joint - Int. Conf. M&C+SNA+MC*, Nashville, Tennessee, Apr. 19--23 (2015). - - Amanda L. Lund, Andrew R. Siegel, Benoit Forget, Colin Josey, and Paul K. Romano, "Using fractional cascading to accelerate cross section lookups in Monte Carlo particle transport calculations," *Proc. Joint @@ -481,6 +494,10 @@ Parallelism Depletion --------- +- Jose L. Salcedo-Perez, Benoit Forget, Kord Smith, and Paul Romano, "Hybrid + tallies to improve performance in depletion Monte Carlo simulations," *Proc. + M&C*, Aug. 25-29 (2019). + - Zhao-Qing Liu, Ze-Long Zhao, Yong-Wei Yang, Yu-Cui Gao, Hai-Yan Meng, and Qing-Yu Gao, "`Development and validation of depletion code system IMPC-Burnup for ADS `_," *Nucl. Sci. Tech.*, diff --git a/docs/source/usersguide/scripts.rst b/docs/source/usersguide/scripts.rst index 76c7c2f222..388130947c 100644 --- a/docs/source/usersguide/scripts.rst +++ b/docs/source/usersguide/scripts.rst @@ -106,6 +106,37 @@ Compton profile data using an existing data library from `Geant4 `_. Note that OpenMC includes this data file by default so it should not be necessary in practice to generate it yourself. + +.. _scripts_depletion_chain: + +------------------------------- +``openmc-make-depletion-chain`` +------------------------------- + +This script generates a depletion chain file called ``chain_endfb71.xml`` +using ENDF/B-VII.1 nuclear data. If the :envvar:`OPENMC_ENDF_DATA` variable +is not set, and ``"neutron"``, ``"decay"``, ``"nfy"`` directories +do not exist, then ENDF/B-VII.1 data will be downloaded. + +.. _scripts_depletion_chain_casl: + +------------------------------------ +``openmc-make-depletion-chain-casl`` +------------------------------------ + +This script generates a depletion chain called ``chain_casl.xml`` +using ENDF/B-VII.1 nuclear data for a simplified chain. +The nuclides were chosen by CASL-ORIGEN, which can be found in +Appendix A of Kang Seog Kim, `"Specification for the VERA Depletion +Benchmark Suite" `_, +CASL-U-2015-1014-000, Rev. 0, ORNL/TM-2016/53, 2016. +``Te129`` has been added into this chain due to its link to +``I129`` production. + +If the :envvar:`OPENMC_ENDF_DATA` variable is not set, +and ``"neutron"``, ``"decay"``, ``"nfy"`` directories +to not exist, then ENDF/B-VII.1 data will be downloaded. + .. _scripts_stopping: ------------------------------- diff --git a/include/openmc/nuclide.h b/include/openmc/nuclide.h index 4be898a64e..a936ac0102 100644 --- a/include/openmc/nuclide.h +++ b/include/openmc/nuclide.h @@ -126,6 +126,12 @@ namespace data { extern std::array energy_min; extern std::array energy_max; +//! Minimum temperature in [K] that nuclide data is available at +extern double temperature_min; + +//! Maximum temperature in [K] that nuclide data is available at +extern double temperature_max; + extern std::vector> nuclides; extern std::unordered_map nuclide_map; diff --git a/man/man1/openmc.1 b/man/man1/openmc.1 index f574ea10ae..229b64fd1a 100644 --- a/man/man1/openmc.1 +++ b/man/man1/openmc.1 @@ -54,7 +54,7 @@ Indicates the default path to an HDF5 file that contains multi-group cross section libraries if the user has not specified the tag in .I materials.xml\fP. .SH LICENSE -Copyright \(co 2011-2018 Massachusetts Institute of Technology and OpenMC +Copyright \(co 2011-2019 Massachusetts Institute of Technology and OpenMC contributors. .PP Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index e38385ac52..1d03269283 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, Iterable +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 from .nuclide import FissionYieldDistribution @@ -666,3 +667,53 @@ class Chain(object): yields = [yields] check_type("fission_yields", yields, Iterable, Mapping) self._fission_yields = yields + 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, does the sum of branching + ratios equal about one? + 2) For fission reactions, does the sum of fission yield + fractions equal about two? + + Parameters + ---------- + strict : bool, optional + Raise exceptions at the first inconsistency if true. + Otherwise mark a warning + quiet : bool, optional + Flag to suppress 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 = valid and stat + return valid diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 068891a5f0..db503c8a59 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -3,9 +3,9 @@ Contains the per-nuclide components of a depletion chain. """ -from collections import namedtuple from collections.abc import Mapping - +from collections import namedtuple, defaultdict +from warnings import warn try: import lxml.etree as ET except ImportError: @@ -241,6 +241,103 @@ class Nuclide(object): return elem + def validate(self, strict=True, quiet=False, tolerance=1e-4): + """Search for possible inconsistencies + + The following checks are performed: + + 1) for all non-fission reactions and decay modes, + does the sum of branching ratios equal about one? + 2) for fission reactions, does the sum of fission yield + fractions equal about two? + + Parameters + ---------- + strict : bool, optional + Raise exceptions at the first inconsistency if true. + Otherwise mark a warning + quiet : bool, optional + Flag to suppress 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.Chain.validate + """ + + msg_func = ("Nuclide {name} has {prop} that sum to {actual} " + "instead of {expected} +/- {tol:7.4e}").format + valid = True + + # check decay modes + if self.decay_modes: + sum_br = sum(m.branching_ratio for m in self.decay_modes) + stat = 1.0 - tolerance <= sum_br <= 1.0 + tolerance + if not stat: + msg = msg_func( + name=self.name, actual=sum_br, expected=1.0, tol=tolerance, + prop="decay mode branch ratios") + if strict: + raise ValueError(msg) + elif quiet: + return False + warn(msg) + valid = False + + if self.reactions: + type_map = defaultdict(set) + for reaction in self.reactions: + type_map[reaction.type].add(reaction) + for rxn_type, reactions in type_map.items(): + sum_rxn = sum(rx.branching_ratio for rx in reactions) + stat = 1.0 - tolerance <= sum_rxn <= 1.0 + tolerance + if stat: + continue + msg = msg_func( + name=self.name, actual=sum_br, expected=1.0, tol=tolerance, + prop="{} reaction branch ratios".format(rxn_type)) + if strict: + raise ValueError(msg) + elif quiet: + return False + warn(msg) + valid = False + + if self.yield_data: + for energy, yield_list in self.yield_data.items(): + sum_yield = sum(y[1] for y in yield_list) + stat = 2.0 - tolerance <= sum_yield <= 2.0 + tolerance + if stat: + continue + msg = msg_func( + name=self.name, actual=sum_yield, + expected=2.0, tol=tolerance, + prop="fission yields (E = {:7.4e} eV)".format(energy)) + if strict: + raise ValueError(msg) + elif quiet: + return False + warn(msg) + valid = False + + return valid class FissionYieldDistribution(Mapping): """Energy-dependent fission product yields for a single nuclide diff --git a/scripts/casl_chain.py b/scripts/casl_chain.py index 86389e3ced..94fc9dd966 100755 --- a/scripts/casl_chain.py +++ b/scripts/casl_chain.py @@ -6,6 +6,8 @@ # Note 32 of the 255 nuclides appeare twice as they are both activation # nuclides (category 1) and fission product nuclides (category 3). +# Te129 has been added due to it's link to I129 production. + CASL_CHAIN = { # Nuclide: (Stable, CAT, IFPY, Special yield treatment) # Stable: True if nuclide has no decay reactions @@ -187,6 +189,7 @@ CASL_CHAIN = { 'Sb127': (False, 3, 2, None), 'Te127': (False, 3, -1, None), 'Te127_m1': (False, 3, -1, None), + 'Te129': (False, 3, 1, None), 'Te129_m1': (False, 3, 2, None), 'Te132': (False, 3, 2, None), 'I127': (True, 3, 1, None), diff --git a/scripts/openmc-make-depletion-chain b/scripts/openmc-make-depletion-chain index 7c08f984e9..51092a7224 100755 --- a/scripts/openmc-make-depletion-chain +++ b/scripts/openmc-make-depletion-chain @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -import glob import os +from pathlib import Path from zipfile import ZipFile from openmc._utils import download @@ -15,15 +15,28 @@ URLS = [ ] def main(): - for url in URLS: - basename = download(url) - with ZipFile(basename, 'r') as zf: - print('Extracting {}...'.format(basename)) - zf.extractall() + endf_dir = os.environ.get("OPENMC_ENDF_DATA") + if endf_dir is not None: + endf_dir = Path(endf_dir) + elif all(os.path.isdir(lib) for lib in ("neutrons", "decay", "nfy")): + endf_dir = Path(".") + else: + for url in URLS: + basename = download(url) + with ZipFile(basename, 'r') as zf: + print('Extracting {}...'.format(basename)) + zf.extractall() + endf_dir = Path(".") - decay_files = glob.glob(os.path.join('decay', '*.endf')) - nfy_files = glob.glob(os.path.join('nfy', '*.endf')) - neutron_files = glob.glob(os.path.join('neutrons', '*.endf')) + decay_files = tuple((endf_dir / "decay").glob("*endf")) + neutron_files = tuple((endf_dir / "neutrons").glob("*endf")) + nfy_files = tuple((endf_dir / "nfy").glob("*endf")) + + # check files exist + for flist, ftype in [(decay_files, "decay"), (neutron_files, "neutron"), + (nfy_files, "neutron fission product yield")]: + if not flist: + raise IOError("No {} endf files found in {}".format(ftype, endf_dir)) chain = openmc.deplete.Chain.from_endf(decay_files, nfy_files, neutron_files) chain.export_to_xml('chain_endfb71.xml') diff --git a/src/cell.cpp b/src/cell.cpp index 3c079f7ae5..cfb717c85e 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -16,6 +16,7 @@ #include "openmc/hdf5_interface.h" #include "openmc/lattice.h" #include "openmc/material.h" +#include "openmc/nuclide.h" #include "openmc/settings.h" #include "openmc/surface.h" #include "openmc/xml_interface.h" @@ -244,6 +245,16 @@ Cell::temperature(int32_t instance) const void Cell::set_temperature(double T, int32_t instance) { + if (settings::temperature_method == TEMPERATURE_INTERPOLATION) { + if (T < data::temperature_min) { + throw std::runtime_error{"Temperature is below minimum temperature at " + "which data is available."}; + } else if (T > data::temperature_max) { + throw std::runtime_error{"Temperature is above maximum temperature at " + "which data is available."}; + } + } + if (instance >= 0) { sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T); } else { @@ -1057,7 +1068,6 @@ openmc_cell_set_fill(int32_t index, int type, int32_t n, return 0; } -//TODO: make sure data is loaded for this temperature extern "C" int openmc_cell_set_temperature(int32_t index, double T, const int32_t* instance) { diff --git a/src/cross_sections.cpp b/src/cross_sections.cpp index 8ea59c1f08..f9bfe1d229 100644 --- a/src/cross_sections.cpp +++ b/src/cross_sections.cpp @@ -357,6 +357,12 @@ read_ce_cross_sections(const std::vector>& nuc_temps, } } + // Show minimum/maximum temperature + write_message("Minimum neutron data temperature: " + + std::to_string(data::temperature_min) + " K"); + write_message("Maximum neutron data temperature: " + + std::to_string(data::temperature_max) + " K"); + // If the user wants multipole, make sure we found a multipole library. if (settings::temperature_multipole) { bool mp_found = false; diff --git a/src/finalize.cpp b/src/finalize.cpp index 633cf2991d..120903f3fe 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -117,6 +117,8 @@ int openmc_finalize() data::energy_max = {INFTY, INFTY}; data::energy_min = {0.0, 0.0}; + data::temperature_min = 0.0; + data::temperature_max = INFTY; model::root_universe = -1; openmc::openmc_set_seed(DEFAULT_SEED); diff --git a/src/nuclide.cpp b/src/nuclide.cpp index 6094b79831..3b092ddf4b 100644 --- a/src/nuclide.cpp +++ b/src/nuclide.cpp @@ -18,7 +18,7 @@ #include "xtensor/xbuilder.hpp" #include "xtensor/xview.hpp" -#include // for sort +#include // for sort, min_element #include // for to_string, stoi namespace openmc { @@ -30,6 +30,8 @@ namespace openmc { namespace data { std::array energy_min {0.0, 0.0}; std::array energy_max {INFTY, INFTY}; +double temperature_min {0.0}; +double temperature_max {INFTY}; std::vector> nuclides; std::unordered_map nuclide_map; } // namespace data @@ -154,6 +156,12 @@ Nuclide::Nuclide(hid_t group, const std::vector& temperature, int i_nucl // Sort temperatures to read std::sort(temps_to_read.begin(), temps_to_read.end()); + double T_min_read = *std::min_element(temps_to_read.cbegin(), temps_to_read.cend()); + double T_max_read = *std::max_element(temps_to_read.cbegin(), temps_to_read.cend()); + + data::temperature_min = std::max(data::temperature_min, T_min_read); + data::temperature_max = std::min(data::temperature_max, T_max_read); + hid_t energy_group = open_group(group, "energy"); for (const auto& T : temps_to_read) { std::string dset {std::to_string(T) + "K"}; diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index b1ab1ae248..ce45b84d27 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -360,3 +360,54 @@ def test_fission_yield_attribute(simple_chain): with pytest.raises( ValueError, match="fission yield.*not equal.*compositions"): cram.deplete(empty_chain, dummy_conc, None, 0.5) + +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) diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index 3386e28984..d81691932b 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -153,3 +153,91 @@ def test_fission_yield_distribution(): assert numpy.array_equal(orig_yield_obj.yields * 2, mod_yields.yields) mod_yields += orig_yield_obj assert numpy.array_equal(orig_yield_obj.yields * 3, mod_yields.yields) + +def test_validate(): + + nuc = nuclide.Nuclide() + nuc.name = "Test" + + # decay modes: type, target, branching_ratio + + nuc.decay_modes = [ + nuclide.DecayTuple("type 0", "0", 0.5), + nuclide.DecayTuple("type 1", "1", 0.5), + ] + + # reactions: type, target, Q, branching_ratio + nuc.reactions = [ + nuclide.ReactionTuple("0", "0", 1000, 0.3), + nuclide.ReactionTuple("0", "1", 1000, 0.3), + nuclide.ReactionTuple("1", "2", 1000, 1.0), + nuclide.ReactionTuple("0", "3", 1000, 0.4), + ] + + # fission yields + + nuc.yield_data = { + 0.0253: [("0", 1.5), ("1", 0.5)], + 1e6: [("0", 1.5), ("1", 0.5)], + } + + # nuclide is good and should have no warnings raise + with pytest.warns(None) as record: + assert nuc.validate(strict=True, quiet=False, tolerance=0.0) + assert len(record) == 0 + + # invalidate decay modes + decay = nuc.decay_modes.pop() + with pytest.raises(ValueError, match="decay mode"): + nuc.validate(strict=True, quiet=False, tolerance=0.0) + + with pytest.warns(UserWarning) as record: + assert not nuc.validate(strict=False, quiet=False, tolerance=0.0) + assert not nuc.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 1 + assert "decay mode" in record[0].message.args[0] + + # restore decay modes, invalidate reactions + nuc.decay_modes.append(decay) + reaction = nuc.reactions.pop() + + with pytest.raises(ValueError, match="0 reaction"): + nuc.validate(strict=True, quiet=False, tolerance=0.0) + + with pytest.warns(UserWarning) as record: + assert not nuc.validate(strict=False, quiet=False, tolerance=0.0) + assert not nuc.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 1 + assert "0 reaction" in record[0].message.args[0] + + # restore reactions, invalidate fission yields + nuc.reactions.append(reaction) + nuc.yield_data[1e6].pop() + + with pytest.raises(ValueError, match=r"fission yields.*1\.0*e"): + nuc.validate(strict=True, quiet=False, tolerance=0.0) + + with pytest.warns(UserWarning) as record: + assert not nuc.validate(strict=False, quiet=False, tolerance=0.0) + assert not nuc.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 1 + assert "1.0" in record[0].message.args[0] + + # invalidate everything, check that error is raised at decay modes + + decay = nuc.decay_modes.pop() + reaction = nuc.reactions.pop() + + with pytest.raises(ValueError, match="decay mode"): + nuc.validate(strict=True, quiet=False, tolerance=0.0) + + # check for warnings + # should be one warning for decay modes, reactions, fission yields + + with pytest.warns(UserWarning) as record: + assert not nuc.validate(strict=False, quiet=False, tolerance=0.0) + assert not nuc.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 3 + assert "decay mode" in record[0].message.args[0] + assert "0 reaction" in record[1].message.args[0] + assert "1.0" in record[2].message.args[0]