From 663636ce2ea0601026663050e73022220255746a Mon Sep 17 00:00:00 2001 From: Sam Shaner Date: Sun, 16 Oct 2016 22:09:25 -0400 Subject: [PATCH] added enrichment and wo element support in Python API and added new tests --- docs/source/usersguide/input.rst | 14 +++- examples/python/pincell/build-xml.py | 12 ++-- openmc/element.py | 4 +- openmc/material.py | 63 ++++++++++++----- src/input_xml.F90 | 38 +++++----- tests/test_element_wo/inputs_true.dat | 1 + tests/test_element_wo/results_true.dat | 5 ++ tests/test_element_wo/test_element_wo.py | 90 ++++++++++++++++++++++++ tests/test_enrichment/inputs_true.dat | 1 + tests/test_enrichment/results_true.dat | 5 ++ tests/test_enrichment/test_enrichment.py | 90 ++++++++++++++++++++++++ tests/testing_harness.py | 1 + 12 files changed, 278 insertions(+), 46 deletions(-) create mode 100644 tests/test_element_wo/inputs_true.dat create mode 100644 tests/test_element_wo/results_true.dat create mode 100644 tests/test_element_wo/test_element_wo.py create mode 100644 tests/test_enrichment/inputs_true.dat create mode 100644 tests/test_enrichment/results_true.dat create mode 100644 tests/test_enrichment/test_enrichment.py diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index b1eedf59c..94936592f 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -1415,9 +1415,9 @@ Each ``material`` element can have the following attributes or sub-elements: Specifies that a natural element is present in the material. The natural element is split up into individual isotopes based on `IUPAC Isotopic Compositions of the Elements 2009`_. This element has - attributes/sub-elements called ``name``, and ``ao``. The ``name`` - attribute is the atomic symbol of the element. Finally, the ``ao`` - attribute specifies the atom percent of the element within the material, + attributes/sub-elements called ``name``, and ``ao`` or ``wo``. The ``name`` + attribute is the atomic symbol of the element. Finally, the ``ao`` and ``wo`` + attributes specify the atom or weight percent of the element within the material, respectively. One example would be as follows: .. code-block:: xml @@ -1447,6 +1447,14 @@ Each ``material`` element can have the following attributes or sub-elements: .. note:: The ``scattering`` attribute/sub-element is not used in the multi-group :ref:`energy_mode`. + An optional attribute/sub-element for uranium is ``enrichment``. This + attribute lets the user set the weight-percent enrichment of U-235 in + uranium. The weight-percent of U-234 is computed as 0.008 times the weight + percent of U-235 with U-238 comprising the balance. Valid values for + enrichment range between 0 and 1/1.008. + + *Default*: None + :sab: Associates an S(a,b) table with the material. This element has one attribute/sub-element called ``name``. The ``name`` attribute diff --git a/examples/python/pincell/build-xml.py b/examples/python/pincell/build-xml.py index 8ce4eab62..1e1e15d6d 100644 --- a/examples/python/pincell/build-xml.py +++ b/examples/python/pincell/build-xml.py @@ -45,18 +45,14 @@ sn119 = openmc.Nuclide('Sn119') sn120 = openmc.Nuclide('Sn120') sn122 = openmc.Nuclide('Sn122') sn124 = openmc.Nuclide('Sn124') -u234 = openmc.Nuclide('U234') -u235 = openmc.Nuclide('U235') -u238 = openmc.Nuclide('U238') +u = openmc.Element('U') +o = openmc.Element('O') # Instantiate some Materials and register the appropriate Nuclides uo2 = openmc.Material(material_id=1, name='UO2 fuel at 2.4% wt enrichment') uo2.set_density('g/cm3', 10.29769) -uo2.add_nuclide(u234, 4.4843e-6) -uo2.add_nuclide(u235, 5.5815e-4) -uo2.add_nuclide(u238, 2.2408e-2) -uo2.add_nuclide(o16, 4.5829e-2) -uo2.add_nuclide(o17, 1.1164e-4) +uo2.add_element(u, 1., enrichment=0.05) +uo2.add_element(o, 2.) helium = openmc.Material(material_id=2, name='Helium for gap') helium.set_density('g/cm3', 0.001598) diff --git a/openmc/element.py b/openmc/element.py index 1b1680614..6d8f69d25 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -11,8 +11,8 @@ if sys.version_info[0] >= 3: class Element(object): - """A natural element used in a material via . Internally, OpenMC will - expand the natural element into isotopes based on the known natural + """A natural element used in a material via . Internally, OpenMC + will expand the natural element into isotopes based on the known natural abundances. Parameters diff --git a/openmc/material.py b/openmc/material.py index 0639db562..ee071d0f4 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -58,9 +58,9 @@ class Material(object): 'atom/b-cm', 'atom/cm3', 'sum', or 'macro'. The 'macro' unit only applies in the case of a multi-group calculation. elements : list of tuple - List in which each item is a 3-tuple consisting of an - :class:`openmc.Element` instance, the percent density, and the percent - type ('ao' or 'wo'). + List in which each item is a 3-tuple or 4-tuple consisting of an + :class:`openmc.Element` instance, the percent density, the percent + type ('ao' or 'wo'), and an optional weight-percent enrichment. nuclides : list of tuple List in which each item is a 3-tuple consisting of an :class:`openmc.Nuclide` instance, the percent density, and the percent @@ -83,7 +83,7 @@ class Material(object): # (only one is allowed, hence this is different than _nuclides, etc) self._macroscopic = None - # A list of tuples (element, percent, percent type) + # A list of tuples (element, percent, percent type, enrichment (optional)) self._elements = [] # If specified, a list of table names @@ -149,9 +149,13 @@ class Material(object): string += '{0: <16}\n'.format('\tElements') - for element, percent, percent_type in self._elements: - string += '{0: <16}'.format('\t{0.name}'.format(element)) - string += '=\t{0: <12} [{1}]\n'.format(percent, percent_type) + for element in self._elements: + string += '{0: <16}'.format('\t{0.name}'.format(element[0])) + if len(element) == 3: + string += '=\t{0: <12} [{1}]\n'.format(element[1], element[2]) + else: + string += '=\t{0: <12} [{1}] {2:4.2f} % enrichment\n'\ + .format(element[1], element[2], element[3]*100.) return string @@ -402,7 +406,8 @@ class Material(object): if macroscopic.name == self._macroscopic.name: self._macroscopic = None - def add_element(self, element, percent, percent_type='ao', expand=False): + def add_element(self, element, percent, percent_type='ao', enrichment=None, + expand=False): """Add a natural element to the material Parameters @@ -414,6 +419,8 @@ class Material(object): percent_type : {'ao', 'wo'}, optional 'ao' for atom percent and 'wo' for weight percent. Defaults to atom percent. + enrichment : float, optional + Optional weight percent enrichment for uranium. Defaults to None. expand : bool, optional Whether to expand the natural element into its naturally-occurring isotopes. Defaults to False. @@ -440,6 +447,19 @@ class Material(object): 'percent type "{1}"'.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="{0}" with a ' \ + 'non-floating point enrichment value "{1}"'\ + .format(self._id, enrichment) + raise ValueError(msg) + + elif element != 'U': + msg = 'Unable to use enrichment for element {0} which is not ' \ + 'uranium for Material ID="{1}"'.format(enrichment, + self._id) + raise ValueError(msg) + # Copy this Element to separate it from same Element in other Materials if isinstance(element, openmc.Element): element = deepcopy(element) @@ -450,10 +470,16 @@ class Material(object): if percent_type == 'wo': raise NotImplementedError('Expanding natural element based on ' 'weight percent is not yet supported.') + for isotope, abundance in element.expand(): self._nuclides.append((isotope, percent*abundance, percent_type)) + else: - self._elements.append((element, percent, percent_type)) + if enrichment is not None: + self._elements.append((element, percent, percent_type, + enrichment)) + else: + self._elements.append((element, percent, percent_type)) def remove_element(self, element): """Remove a natural element from the material @@ -525,9 +551,9 @@ class Material(object): for nuclide, density, density_type in self._nuclides: nuclides.append(nuclide.name) - for element, density, density_type in self._elements: + for element in self._elements: # Expand natural element into isotopes - for isotope, abundance in element.expand(): + for isotope, abundance in element[0].expand(): nuclides.append(isotope.name) return nuclides @@ -548,10 +574,10 @@ class Material(object): for nuclide, density, density_type in self._nuclides: nuclides[nuclide.name] = (nuclide, density) - for element, density, density_type in self._elements: + for element in self._elements: # Expand natural element into isotopes - for isotope, abundance in element.expand(): - nuclides[isotope.name] = (isotope, density*abundance) + for isotope, abundance in element[0].expand(): + nuclides[isotope.name] = (isotope, element[1]*abundance) return nuclides @@ -586,6 +612,9 @@ class Material(object): else: xml_element.set("wo", str(element[1])) + if len(element) == 4: + xml_element.set("enrichment", str(element[3])) + if not element[0].scattering is None: xml_element.set("scattering", element[0].scattering) @@ -657,12 +686,12 @@ class Material(object): comps = [] allnucs = self._nuclides + self._elements dist_per_type = allnucs[0][2] - for nuc, per, typ in allnucs: - if not typ == dist_per_type: + for nuc in allnucs: + if not nuc[2] == dist_per_type: msg = 'All nuclides and elements in a distributed ' \ 'material must have the same type, either ao or wo' raise ValueError(msg) - comps.append(per) + comps.append(nuc[1]) if self._distrib_otf_file is None: # Create values and units subelements diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 1225c91ac..8bf3f2825 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4573,10 +4573,9 @@ contains end subroutine read_mg_cross_sections_xml !=============================================================================== -! EXPAND_NATURAL_ELEMENT converts natural elements specified using an -! tag within a material into individual isotopes based on IUPAC Isotopic -! Compositions of the Elements 2009 (doi:10.1351/PAC-REP-10-06-02). In some -! cases, modifications have been made to work with ENDF/B-VII.1 where +! EXPAND_NATURAL_ELEMENT_NAMES converts natural elements specified using an +! tag within a material and adds the names to the input names array. +! In some cases, modifications have been made to work with ENDF/B-VII.1 where ! evaluations of particular isotopes don't exist. !=============================================================================== @@ -5101,21 +5100,28 @@ contains end subroutine expand_natural_element_names +!=============================================================================== +! EXPAND_NATURAL_ELEMENT_DENSITIES converts natural elements specified using an +! tag within a material into individual isotopes based on IUPAC +! Isotopic Compositions of the Elements 2009 (doi:10.1351/PAC-REP-10-06-02). In +! some cases, modifications have been made to work with ENDF/B-VII.1 where +! evaluations of particular isotopes don't exist. +!=============================================================================== subroutine expand_natural_element_densities(name, expand_by, density, & enrichment, densities) - character(*), intent(in) :: name - character(*), intent(in) :: expand_by - real(8), intent(in) :: density - real(8), intent(in) :: enrichment - type(VectorReal), intent(inout) :: densities + character(*), intent(in) :: name ! element name + character(*), intent(in) :: expand_by ! "ao" or "wo" + real(8), intent(in) :: density ! value for "ao" or "wo" + real(8), intent(in) :: enrichment ! enrichment in weight % + type(VectorReal), intent(inout) :: densities ! isotope densities vector - integer :: i - integer :: n_isotopes - character(2) :: element_name - real(8) :: element_awr - real(8), allocatable :: awr(:) - real(8), allocatable :: mf(:) + integer :: i ! iterator + integer :: n_isotopes ! number of isotopes in the element + character(2) :: element_name ! element atomic symbol + real(8) :: element_awr ! element atomic weight ratio + real(8), allocatable :: awr(:) ! isotope atomic weight ratios + real(8), allocatable :: mf(:) ! isotope mole fractions element_name = name(1:2) @@ -7247,7 +7253,7 @@ contains awr(3) = nuclides(nuclide_dict % get_key('u238')) % awr end if - ! Modify mole fractions in enrichment provided + ! Modify mole fractions if enrichment provided if (enrichment /= -ONE) then ! Calculate the mass fractions of isotopes diff --git a/tests/test_element_wo/inputs_true.dat b/tests/test_element_wo/inputs_true.dat new file mode 100644 index 000000000..136eedb7a --- /dev/null +++ b/tests/test_element_wo/inputs_true.dat @@ -0,0 +1 @@ +7db97c5303ca1f0beaba1b610c0d57cede072af2200b7494515caeca7f0ca43b1e1dbda1968f4f052af6c632cbba8c92203cd53670c19350c55d2339d28b2fb2 \ No newline at end of file diff --git a/tests/test_element_wo/results_true.dat b/tests/test_element_wo/results_true.dat new file mode 100644 index 000000000..2c37e7f78 --- /dev/null +++ b/tests/test_element_wo/results_true.dat @@ -0,0 +1,5 @@ +k-combined: +8.863826E-01 5.114399E-02 +tally 1: +7.906207E+01 +1.251720E+03 diff --git a/tests/test_element_wo/test_element_wo.py b/tests/test_element_wo/test_element_wo.py new file mode 100644 index 000000000..1efc3e81b --- /dev/null +++ b/tests/test_element_wo/test_element_wo.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +import os +import sys +import glob +import hashlib +sys.path.insert(0, os.pardir) +from testing_harness import PyAPITestHarness +from input_set import PinCellInputSet +import openmc +import openmc.mgxs + + +class ElementWOTestHarness(PyAPITestHarness): + def _build_inputs(self): + + # Set the input set to use the pincell model + self._input_set = PinCellInputSet() + + # Define materials. + fuel = openmc.Material(name='Fuel') + fuel.set_density('g/cm3', 10.29769) + fuel.add_element("U", 0.88, 'wo') + fuel.add_element("O", 0.12, 'wo') + + clad = openmc.Material(name='Cladding') + clad.set_density('g/cm3', 6.55) + clad.add_element("Zr", 1.0, 'wo') + + hot_water = openmc.Material(name='Hot borated water') + hot_water.set_density('g/cm3', 0.740582) + hot_water.add_element("H", 2./18., 'wo') + hot_water.add_element("O", 16./18., 'wo') + hot_water.add_s_alpha_beta('c_H_in_H2O') + + # Define the materials file. + self._input_set.materials += (fuel, clad, hot_water) + + # Instantiate ZCylinder surfaces + fuel_or = openmc.ZCylinder(x0=0, y0=0, R=0.39218, name='Fuel OR') + clad_or = openmc.ZCylinder(x0=0, y0=0, R=0.45720, name='Clad OR') + left = openmc.XPlane(x0=-0.63, name='left') + right = openmc.XPlane(x0=0.63, name='right') + bottom = openmc.YPlane(y0=-0.63, name='bottom') + top = openmc.YPlane(y0=0.63, name='top') + + left.boundary_type = 'reflective' + right.boundary_type = 'reflective' + top.boundary_type = 'reflective' + bottom.boundary_type = 'reflective' + + # Instantiate Cells + fuel_pin = openmc.Cell(name='cell 1') + cladding = openmc.Cell(name='cell 3') + water = openmc.Cell(name='cell 2') + + # Use surface half-spaces to define regions + fuel_pin.region = -fuel_or + cladding.region = +fuel_or & -clad_or + water.region = +clad_or & +left & -right & +bottom & -top + + # Register Materials with Cells + fuel_pin.fill = fuel + cladding.fill = clad + water.fill = hot_water + + # Instantiate Universe + root = openmc.Universe(universe_id=0, name='root universe') + + # Register Cells with Universe + root.add_cells([fuel_pin, cladding, water]) + + # Instantiate a Geometry, register the root Universe, and export to XML + self._input_set.geometry.root_universe = root + + + mat_filter = openmc.MaterialFilter((fuel.id,)) + flux_tally = openmc.Tally() + flux_tally.filters = [mat_filter] + flux_tally.scores = ['flux'] + + self._input_set.tallies = openmc.Tallies() + self._input_set.tallies += [flux_tally] + self._input_set.build_default_settings() + self._input_set.export() + + +if __name__ == '__main__': + harness = ElementWOTestHarness('statepoint.10.*', True) + harness.main() diff --git a/tests/test_enrichment/inputs_true.dat b/tests/test_enrichment/inputs_true.dat new file mode 100644 index 000000000..c3a23931f --- /dev/null +++ b/tests/test_enrichment/inputs_true.dat @@ -0,0 +1 @@ +9284e022d343431db161faee9290658addde59427a4e42adbdfd5069cd6bd54929bedf87ccb2fb369c91ca52c2c572e8825c4997b662ca2ac34005bf4c58ba83 \ No newline at end of file diff --git a/tests/test_enrichment/results_true.dat b/tests/test_enrichment/results_true.dat new file mode 100644 index 000000000..cd84b2d40 --- /dev/null +++ b/tests/test_enrichment/results_true.dat @@ -0,0 +1,5 @@ +k-combined: +1.457508E+00 5.577598E-02 +tally 1: +6.199709E+01 +7.692381E+02 diff --git a/tests/test_enrichment/test_enrichment.py b/tests/test_enrichment/test_enrichment.py new file mode 100644 index 000000000..f17c65df1 --- /dev/null +++ b/tests/test_enrichment/test_enrichment.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +import os +import sys +import glob +import hashlib +sys.path.insert(0, os.pardir) +from testing_harness import PyAPITestHarness +from input_set import PinCellInputSet +import openmc +import openmc.mgxs + + +class EnrichmentTestHarness(PyAPITestHarness): + def _build_inputs(self): + + # Set the input set to use the pincell model + self._input_set = PinCellInputSet() + + # Define materials. + fuel = openmc.Material(name='Fuel') + fuel.set_density('g/cm3', 10.29769) + fuel.add_element("U", 0.88, 'wo', enrichment=0.05) + fuel.add_element("O", 0.12, 'wo') + + clad = openmc.Material(name='Cladding') + clad.set_density('g/cm3', 6.55) + clad.add_element("Zr", 1.0, 'wo') + + hot_water = openmc.Material(name='Hot borated water') + hot_water.set_density('g/cm3', 0.740582) + hot_water.add_element("H", 2./18., 'wo') + hot_water.add_element("O", 16./18., 'wo') + hot_water.add_s_alpha_beta('c_H_in_H2O') + + # Define the materials file. + self._input_set.materials += (fuel, clad, hot_water) + + # Instantiate ZCylinder surfaces + fuel_or = openmc.ZCylinder(x0=0, y0=0, R=0.39218, name='Fuel OR') + clad_or = openmc.ZCylinder(x0=0, y0=0, R=0.45720, name='Clad OR') + left = openmc.XPlane(x0=-0.63, name='left') + right = openmc.XPlane(x0=0.63, name='right') + bottom = openmc.YPlane(y0=-0.63, name='bottom') + top = openmc.YPlane(y0=0.63, name='top') + + left.boundary_type = 'reflective' + right.boundary_type = 'reflective' + top.boundary_type = 'reflective' + bottom.boundary_type = 'reflective' + + # Instantiate Cells + fuel_pin = openmc.Cell(name='cell 1') + cladding = openmc.Cell(name='cell 3') + water = openmc.Cell(name='cell 2') + + # Use surface half-spaces to define regions + fuel_pin.region = -fuel_or + cladding.region = +fuel_or & -clad_or + water.region = +clad_or & +left & -right & +bottom & -top + + # Register Materials with Cells + fuel_pin.fill = fuel + cladding.fill = clad + water.fill = hot_water + + # Instantiate Universe + root = openmc.Universe(universe_id=0, name='root universe') + + # Register Cells with Universe + root.add_cells([fuel_pin, cladding, water]) + + # Instantiate a Geometry, register the root Universe, and export to XML + self._input_set.geometry.root_universe = root + + + mat_filter = openmc.MaterialFilter((fuel.id,)) + flux_tally = openmc.Tally() + flux_tally.filters = [mat_filter] + flux_tally.scores = ['flux'] + + self._input_set.tallies = openmc.Tallies() + self._input_set.tallies += [flux_tally] + self._input_set.build_default_settings() + self._input_set.export() + + +if __name__ == '__main__': + harness = EnrichmentTestHarness('statepoint.10.*', True) + harness.main() diff --git a/tests/testing_harness.py b/tests/testing_harness.py index 150d124c8..0a13e612b 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -339,6 +339,7 @@ class PyAPITestHarness(TestHarness): output = [os.path.join(os.getcwd(), 'materials.xml')] output.append(os.path.join(os.getcwd(), 'geometry.xml')) output.append(os.path.join(os.getcwd(), 'settings.xml')) + output.append(os.path.join(os.getcwd(), 'tallies.xml')) output.append(os.path.join(os.getcwd(), 'inputs_test.dat')) output.append(os.path.join(os.getcwd(), 'summary.h5')) for f in output: