From 0508274a3dd97029b914c9031df5e9ff753f042b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Mar 2018 10:36:08 -0600 Subject: [PATCH] Add useful warnings for add_nuclide/add_element --- docs/source/usersguide/materials.rst | 14 ++++++-- openmc/data/ace.py | 4 +-- openmc/data/data.py | 5 +-- openmc/data/endf.py | 4 +-- openmc/material.py | 49 ++++++++++------------------ tests/unit_tests/test_material.py | 4 +-- 6 files changed, 38 insertions(+), 42 deletions(-) diff --git a/docs/source/usersguide/materials.rst b/docs/source/usersguide/materials.rst index b1e0c151f2..8472768489 100644 --- a/docs/source/usersguide/materials.rst +++ b/docs/source/usersguide/materials.rst @@ -43,14 +43,22 @@ of an element, you specify the element itself. For example, Internally, OpenMC stores data on the atomic masses and natural abundances of all known isotopes and then uses this data to determine what isotopes should be added to the material. When the material is later exported to XML for use by the -:ref:`scripts_openmc` executable, you'll see that any natural elements are +:ref:`scripts_openmc` executable, you'll see that any natural elements were expanded to the naturally-occurring isotopes. +The :meth:`Material.add_element` method can also be used to add uranium at a +specified enrichment through the `enrichment` argument. For example, the +following would add 3.2% enriched uranium to a material:: + + mat.add_element('U', 1.0, enrichment=3.2) + +In addition to U235 and U238, concentrations of U234 and U236 will be present +and are determined through a correlation based on measured data. + Often, cross section libraries don't actually have all naturally-occurring isotopes for a given element. For example, in ENDF/B-VII.1, cross section evaluations are given for O16 and O17 but not for O18. If OpenMC is aware of -what cross sections you will be using (either through the -:attr:`Materials.cross_sections` attribute or the +what cross sections you will be using (through the :envvar:`OPENMC_CROSS_SECTIONS` environment variable), it will attempt to only put isotopes in your model for which you have cross section data. In the case of oxygen in ENDF/B-VII.1, the abundance of O18 would end up being lumped with O16. diff --git a/openmc/data/ace.py b/openmc/data/ace.py index 385408bd48..fa2705220b 100644 --- a/openmc/data/ace.py +++ b/openmc/data/ace.py @@ -22,7 +22,7 @@ import sys import numpy as np from openmc.mixin import EqualityMixin -from openmc.data.endf import ENDF_FLOAT_RE +from openmc.data.endf import _ENDF_FLOAT_RE def ascii_to_binary(ascii_file, binary_file): """Convert an ACE file in ASCII format (type 1) to binary format (type 2). @@ -349,7 +349,7 @@ class Library(EqualityMixin): # after it). If it's too short, then we apply the ENDF float regular # expression. We don't do this by default because it's expensive! if xss.size != nxs[1] + 1: - datastr = ENDF_FLOAT_RE.sub(r'\1e\2', datastr) + datastr = _ENDF_FLOAT_RE.sub(r'\1e\2', datastr) xss = np.fromstring(datastr, sep=' ') assert xss.size == nxs[1] + 1 diff --git a/openmc/data/data.py b/openmc/data/data.py index fd13299617..3a625b6229 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -136,6 +136,8 @@ ATOMIC_NUMBER = {value: key for key, value in ATOMIC_SYMBOL.items()} _ATOMIC_MASS = {} +_GND_NAME_RE = re.compile(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)') + def atomic_mass(isotope): """Return atomic mass of isotope in atomic mass units. @@ -352,8 +354,7 @@ def zam(name): """ try: - symbol, A, state = re.match(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)', - name).groups() + symbol, A, state = _GND_NAME_RE.match(name).groups() except AttributeError: raise ValueError("'{}' does not appear to be a nuclide name in GND " "format.".format(name)) diff --git a/openmc/data/endf.py b/openmc/data/endf.py index c44c66be0c..0d1f402c2f 100644 --- a/openmc/data/endf.py +++ b/openmc/data/endf.py @@ -45,7 +45,7 @@ SUM_RULES = {1: [2, 3], 106: list(range(750, 800)), 107: list(range(800, 850))} -ENDF_FLOAT_RE = re.compile(r'([\s\-\+]?\d*\.\d+)([\+\-]\d+)') +_ENDF_FLOAT_RE = re.compile(r'([\s\-\+]?\d*\.\d+)([\+\-]\d+)') def float_endf(s): @@ -68,7 +68,7 @@ def float_endf(s): The number """ - return float(ENDF_FLOAT_RE.sub(r'\1e\2', s)) + return float(_ENDF_FLOAT_RE.sub(r'\1e\2', s)) def get_text_record(file_obj): diff --git a/openmc/material.py b/openmc/material.py index d52d8a27b4..053e516fae 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -379,33 +379,27 @@ class Material(IDManagerMixin): Parameters ---------- nuclide : str - Nuclide to add + Nuclide to add, e.g., 'Mo95' percent : float Atom or weight percent percent_type : {'ao', 'wo'} 'ao' for atom percent and 'wo' for weight percent """ + cv.check_type('nuclide', nuclide, str) + cv.check_type('percent', percent, Real) + cv.check_value('percent type', percent_type, {'ao', 'wo'}) if self._macroscopic is not None: msg = 'Unable to add a Nuclide to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) - if not isinstance(nuclide, str): - msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \ - 'non-string value "{}"'.format(self._id, nuclide) - raise ValueError(msg) - - elif not isinstance(percent, Real): - msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \ - 'non-floating point value "{}"'.format(self._id, percent) - raise ValueError(msg) - - elif percent_type not in ('ao', 'wo'): - msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \ - 'percent type "{}"'.format(self._id, percent_type) - raise ValueError(msg) + # If nuclide name doesn't look valid, give a warning + try: + openmc.data.zam(nuclide) + except ValueError as e: + warnings.warn(str(e)) self._nuclides.append((nuclide, percent, percent_type)) @@ -493,7 +487,7 @@ class Material(IDManagerMixin): Parameters ---------- element : str - Element to add + Element to add, e.g., 'Zr' percent : float Atom or weight percent percent_type : {'ao', 'wo'}, optional @@ -505,27 +499,15 @@ class Material(IDManagerMixin): (natural composition). """ + cv.check_type('nuclide', element, str) + cv.check_type('percent', percent, Real) + cv.check_value('percent type', percent_type, {'ao', 'wo'}) if self._macroscopic is not None: msg = 'Unable to add an Element to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) - if not isinstance(element, str): - msg = 'Unable to add an Element to Material ID="{}" with a ' \ - 'non-string value "{}"'.format(self._id, element) - raise ValueError(msg) - - if not isinstance(percent, Real): - msg = 'Unable to add an Element to Material ID="{}" with a ' \ - 'non-floating point value "{}"'.format(self._id, percent) - raise ValueError(msg) - - if percent_type not in ['ao', 'wo']: - msg = 'Unable to add an Element to Material ID="{}" with a ' \ - 'percent type "{}"'.format(self._id, percent_type) - raise ValueError(msg) - if enrichment is not None: if not isinstance(enrichment, Real): msg = 'Unable to add an Element to Material ID="{}" with a ' \ @@ -551,6 +533,11 @@ class Material(IDManagerMixin): format(enrichment, self._id) warnings.warn(msg) + # Make sure element name is just that + if not element.isalpha(): + raise ValueError("Element name should be given by the " + "element's symbol, e.g., 'Zr'") + # Add naturally-occuring isotopes element = openmc.Element(element) for nuclide in element.expand(percent, percent_type, enrichment): diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index c251df3a6d..b7b745408d 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -15,9 +15,9 @@ def test_nuclides(uo2): """Test adding/removing nuclides.""" m = openmc.Material() m.add_nuclide('U235', 1.0) - with pytest.raises(ValueError): + with pytest.raises(TypeError): m.add_nuclide('H1', '1.0') - with pytest.raises(ValueError): + with pytest.raises(TypeError): m.add_nuclide(1.0, 'H1') with pytest.raises(ValueError): m.add_nuclide('H1', 1.0, 'oa')