mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-28 22:26:08 -04:00
Added python API support for the new options; dont yet have a capability to build the mgxs xml file. Also wrote (and check as needed) the run_CE flag to the summary file and state points.
This commit is contained in:
parent
85a4c3f272
commit
baad6cf274
7 changed files with 288 additions and 23 deletions
|
|
@ -1151,14 +1151,17 @@ Each ``material`` element can have the following attributes or sub-elements:
|
|||
The "sum" unit indicates that the density should be calculated as the sum
|
||||
of the atom fractions for each nuclide in the material. This should not be
|
||||
used in conjunction with weight percents. The "macro" unit is used with
|
||||
a ``macroscopic`` to indicate that the density is already included in the
|
||||
library and thus not needed here. However, if a value is provided for the
|
||||
``value``, then this is treated as a number density multiplier on the
|
||||
macroscopic cross sections in the multi-group data. This can be used,
|
||||
a ``macroscopic`` quantity to indicate that the density is already included
|
||||
in the library and thus not needed here. However, if a value is provided
|
||||
for the ``value``, then this is treated as a number density multiplier on
|
||||
the macroscopic cross sections in the multi-group data. This can be used,
|
||||
for example, when perturbing the density slightly.
|
||||
|
||||
*Default*: None
|
||||
|
||||
.. note:: A ``macroscopic`` quantity can not be used in conjunction with a
|
||||
``nuclide``, ``element``, or ``sab`` quantity.
|
||||
|
||||
:nuclide:
|
||||
An element with attributes/sub-elements called ``name``, ``xs``, and ``ao``
|
||||
or ``wo``. The ``name`` attribute is the name of the cross-section for a
|
||||
|
|
|
|||
85
openmc/macroscopic.py
Normal file
85
openmc/macroscopic.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
from numbers import Integral
|
||||
import sys
|
||||
|
||||
from openmc.checkvalue import check_type
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
basestring = str
|
||||
|
||||
|
||||
class Macroscopic(object):
|
||||
"""A nuclide that can be used in a material.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Name of the macroscopic data, e.g. UO2
|
||||
xs : str
|
||||
Cross section identifier, e.g. 71c
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name : str
|
||||
Name of the nuclide, e.g. UO2
|
||||
xs : str
|
||||
Cross section identifier, e.g. 71c
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name='', xs=None):
|
||||
# Initialize class attributes
|
||||
self._name = ''
|
||||
self._xs = None
|
||||
|
||||
# Set the Material class attributes
|
||||
self.name = name
|
||||
|
||||
if xs is not None:
|
||||
self.xs = xs
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Macroscopic):
|
||||
if self._name != other._name:
|
||||
return False
|
||||
elif self._xs != other._xs:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
elif isinstance(other, basestring) and other == self.name:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._name, self._xs))
|
||||
|
||||
def __repr__(self):
|
||||
string = 'Nuclide - {0}\n'.format(self._name)
|
||||
string += '{0: <16}{1}{2}\n'.format('\tXS', '=\t', self._xs)
|
||||
return string
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def xs(self):
|
||||
return self._xs
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
check_type('name', name, basestring)
|
||||
self._name = name
|
||||
|
||||
@xs.setter
|
||||
def xs(self, xs):
|
||||
check_type('cross-section identifier', xs, basestring)
|
||||
self._xs = xs
|
||||
|
||||
def __repr__(self):
|
||||
string = 'Macroscopic - {0}\n'.format(self._name)
|
||||
string += '{0: <16}{1}{2}\n'.format('\tXS', '=\t', self.xs)
|
||||
return string
|
||||
|
|
@ -26,14 +26,15 @@ def reset_auto_material_id():
|
|||
|
||||
|
||||
# Units for density supported by OpenMC
|
||||
DENSITY_UNITS = ['g/cm3', 'g/cc', 'kg/cm3', 'atom/b-cm', 'atom/cm3', 'sum']
|
||||
DENSITY_UNITS = ['g/cm3', 'g/cc', 'kg/cm3', 'atom/b-cm', 'atom/cm3', 'sum',
|
||||
'macro']
|
||||
|
||||
# Constant for density when not needed
|
||||
NO_DENSITY = 99999.
|
||||
|
||||
|
||||
class Material(object):
|
||||
"""A material composed of a collection of nuclides/elements that can be
|
||||
"""A material composed of a collection of nuclides/elements that can be
|
||||
assigned to a region of space.
|
||||
|
||||
Parameters
|
||||
|
|
@ -53,7 +54,8 @@ class Material(object):
|
|||
Density of the material (units defined separately)
|
||||
density_units : str
|
||||
Units used for `density`. Can be one of 'g/cm3', 'g/cc', 'kg/cm3',
|
||||
'atom/b-cm', 'atom/cm3', or 'sum'.
|
||||
'atom/b-cm', 'atom/cm3', 'sum', or 'macro' (the latter only applies
|
||||
if in multi-group mode).
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -69,6 +71,10 @@ class Material(object):
|
|||
# Values - tuple (nuclide, percent, percent type)
|
||||
self._nuclides = OrderedDict()
|
||||
|
||||
# The single instance of Macroscopic data present in this material
|
||||
# (only one is allowed, hence this is different than _nuclides, etc)
|
||||
self._macroscopic = None
|
||||
|
||||
# An ordered dictionary of Elements (order affects OpenMC results)
|
||||
# Keys - Element names
|
||||
# Values - tuple (element, percent, percent type)
|
||||
|
|
@ -105,6 +111,10 @@ class Material(object):
|
|||
string += '{0: <16}'.format('\t{0}'.format(nuclide))
|
||||
string += '=\t{0: <12} [{1}]\n'.format(percent, percent_type)
|
||||
|
||||
if self._macroscopic is not None:
|
||||
string += '{0: <16}\n'.format('\tMacroscopic Data')
|
||||
string += '{0: <16}'.format('\t{0}'.format(self._macroscopic))
|
||||
|
||||
string += '{0: <16}\n'.format('\tElements')
|
||||
|
||||
for element in self._elements:
|
||||
|
|
@ -126,6 +136,7 @@ class Material(object):
|
|||
clone._density = self._density
|
||||
clone._density_units = self._density_units
|
||||
clone._nuclides = deepcopy(self._nuclides, memo)
|
||||
clone._macroscopic = self._macroscopic
|
||||
clone._elements = deepcopy(self._elements, memo)
|
||||
clone._sab = deepcopy(self._sab, memo)
|
||||
clone._convert_to_distrib_comps = self._convert_to_distrib_comps
|
||||
|
|
@ -256,6 +267,11 @@ class Material(object):
|
|||
|
||||
"""
|
||||
|
||||
if self._macroscopic is not None:
|
||||
msg = 'Unable to add a Nuclide to Material ID="{0}" as a ' \
|
||||
'macroscopic data-set has already been added'.format(self._id)
|
||||
raise ValueError(msg)
|
||||
|
||||
if not isinstance(nuclide, (openmc.Nuclide, str)):
|
||||
msg = 'Unable to add a Nuclide to Material ID="{0}" with a ' \
|
||||
'non-Nuclide value "{1}"'.format(self._id, nuclide)
|
||||
|
|
@ -299,6 +315,66 @@ class Material(object):
|
|||
if nuclide._name in self._nuclides:
|
||||
del self._nuclides[nuclide._name]
|
||||
|
||||
def add_macroscopic(self, macroscopic):
|
||||
"""Add a macroscopic to the material
|
||||
|
||||
Parameters
|
||||
----------
|
||||
macroscopic : str or openmc.macroscopic.Macroscopic
|
||||
Macroscopic to add
|
||||
|
||||
"""
|
||||
|
||||
# Ensureno nuclides, elements, or sab are added since these would be
|
||||
# incompatible with macroscopics
|
||||
if (not. self._nuclides) and (not self._elements) and (not self._sab):
|
||||
msg = 'Unable to add a Macroscopic data set to Material ID="{0}" ' \
|
||||
'with a macroscopic value "{1}" as an incompatible data ' \
|
||||
'member (i.e., nuclide, element, or S(a,b) table) ' \
|
||||
'has already been added'.format(self._id, macroscopic)
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
|
||||
if not isinstance(macroscopic, (openmc.Macroscopic, str)):
|
||||
msg = 'Unable to add a Macroscopic to Material ID="{0}" with a ' \
|
||||
'non-Macroscopic value "{1}"'.format(self._id, macroscopic)
|
||||
raise ValueError(msg)
|
||||
|
||||
if isinstance(macroscopic, openmc.Macroscopic):
|
||||
# Copy this Macroscopic to separate it from the Macroscopic in
|
||||
# other Materials
|
||||
macroscopic = deepcopy(macroscopic)
|
||||
else:
|
||||
macroscopic = openmc.Macroscopic(macroscopic)
|
||||
|
||||
if self._macroscopic is not None:
|
||||
self._macroscopic = macroscopic
|
||||
else:
|
||||
msg = 'Unable to add a Macroscopic to Material ID="{0}", ' \
|
||||
'Only One Macroscopic allowed per ' \
|
||||
'Material!'.format(self._id, macroscopic)
|
||||
raise ValueError(msg)
|
||||
|
||||
def remove_macroscopic(self, macroscopic):
|
||||
"""Remove a macroscopic from the material
|
||||
|
||||
Parameters
|
||||
----------
|
||||
macroscopic : openmc.macroscopic.Macroscopic
|
||||
Macroscopic to remove
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(macroscopic, openmc.Macroscopic):
|
||||
msg = 'Unable to remove a Macroscopic "{0}" in Material ID="{1}" ' \
|
||||
'since it is not a Macroscopic'.format(self._id, macroscopic)
|
||||
raise ValueError(msg)
|
||||
|
||||
# If the Material contains the Macroscopic, delete it
|
||||
if macroscopic._name == self._macroscopic.name:
|
||||
self._macroscopic = None
|
||||
|
||||
def add_element(self, element, percent, percent_type='ao'):
|
||||
"""Add a natural element to the material
|
||||
|
||||
|
|
@ -313,6 +389,11 @@ class Material(object):
|
|||
|
||||
"""
|
||||
|
||||
if self._macroscopic is not None:
|
||||
msg = 'Unable to add an Element to Material ID="{0}" as a ' \
|
||||
'macroscopic data-set has already been added'.format(self._id)
|
||||
raise ValueError(msg)
|
||||
|
||||
if not isinstance(element, openmc.Element):
|
||||
msg = 'Unable to add an Element to Material ID="{0}" with a ' \
|
||||
'non-Element value "{1}"'.format(self._id, element)
|
||||
|
|
@ -359,6 +440,11 @@ class Material(object):
|
|||
|
||||
"""
|
||||
|
||||
if self._macroscopic is not None:
|
||||
msg = 'Unable to add an S(a,b) table to Material ID="{0}" as a ' \
|
||||
'macroscopic data-set has already been added'.format(self._id)
|
||||
raise ValueError(msg)
|
||||
|
||||
if not isinstance(name, basestring):
|
||||
msg = 'Unable to add an S(a,b) table to Material ID="{0}" with a ' \
|
||||
'non-string table name "{1}"'.format(self._id, name)
|
||||
|
|
@ -415,6 +501,15 @@ class Material(object):
|
|||
|
||||
return xml_element
|
||||
|
||||
def _get_macroscopic_xml(self, macroscopic, distrib=False):
|
||||
xml_element = ET.Element("macroscopic")
|
||||
xml_element.set("name", macroscopic[0]._name)
|
||||
|
||||
if macroscopic[0].xs is not None:
|
||||
xml_element.set("xs", macroscopic[0].xs)
|
||||
|
||||
return xml_element
|
||||
|
||||
def _get_element_xml(self, element, distrib=False):
|
||||
xml_element = ET.Element("element")
|
||||
xml_element.set("name", str(element[0]._name))
|
||||
|
|
@ -470,14 +565,19 @@ class Material(object):
|
|||
subelement.set("units", self._density_units)
|
||||
|
||||
if not self._convert_to_distrib_comps:
|
||||
# Create nuclide XML subelements
|
||||
subelements = self._get_nuclides_xml(self._nuclides)
|
||||
for subelement in subelements:
|
||||
element.append(subelement)
|
||||
if self._macroscopic is None:
|
||||
# Create nuclide XML subelements
|
||||
subelements = self._get_nuclides_xml(self._nuclides)
|
||||
for subelement in subelements:
|
||||
element.append(subelement)
|
||||
|
||||
# Create element XML subelements
|
||||
subelements = self._get_elements_xml(self._elements)
|
||||
for subelement in subelements:
|
||||
# Create element XML subelements
|
||||
subelements = self._get_elements_xml(self._elements)
|
||||
for subelement in subelements:
|
||||
element.append(subelement)
|
||||
else:
|
||||
# Create macroscopic XML subelements
|
||||
subelement = self._get_macroscopic_xml(self, self._macroscopic)
|
||||
element.append(subelement)
|
||||
|
||||
else:
|
||||
|
|
@ -504,14 +604,21 @@ class Material(object):
|
|||
subsubelement = ET.SubElement(subelement, "otf_file_path")
|
||||
subsubelement.text = self._distrib_otf_file
|
||||
|
||||
# Create nuclide XML subelements
|
||||
subelements = self.get_nuclides_xml(self._nuclides, distrib=True)
|
||||
for subelement_nuc in subelements:
|
||||
subelement.append(subelement_nuc)
|
||||
if self._macroscopic is None:
|
||||
# Create nuclide XML subelements
|
||||
subelements = self.get_nuclides_xml(self._nuclides, distrib=True)
|
||||
for subelement_nuc in subelements:
|
||||
subelement.append(subelement_nuc)
|
||||
|
||||
# Create element XML subelements
|
||||
subelements = self._get_elements_xml(self._elements, distrib=True)
|
||||
for subelement_ele in subelements:
|
||||
# Create element XML subelements
|
||||
subelements = self._get_elements_xml(self._elements, distrib=True)
|
||||
for subelement_ele in subelements:
|
||||
subelement.append(subelement_ele)
|
||||
else:
|
||||
# Create macroscopic XML subelements
|
||||
subelement_ele = self._get_macroscopic_xml(self,
|
||||
self._macroscopic,
|
||||
distrib=True)
|
||||
subelement.append(subelement_ele)
|
||||
|
||||
if len(self._sab) > 0:
|
||||
|
|
|
|||
|
|
@ -67,11 +67,17 @@ class SettingsFile(object):
|
|||
cross_sections : str
|
||||
Indicates the path to an XML cross section listing file (usually named
|
||||
cross_sections.xml). If it is not set, the :envvar:`CROSS_SECTIONS`
|
||||
environment variable will be used to find the path to the XML cross
|
||||
section listing.
|
||||
environment variable will be used for continuous-energy calculations
|
||||
and :envvar:`MG_CROSS_SECTIONS` will be used for multi-group
|
||||
calculations to find the path to the XML cross section file.
|
||||
energy_grid : str
|
||||
Set the method used to search energy grids. Acceptable values are
|
||||
'nuclide', 'logarithm', and 'material-union'.
|
||||
energy_mode : str
|
||||
Set whether the calculation should be continuous-energy or multi-group.
|
||||
Acceptable values are 'continuous-energy' or 'multi-group'
|
||||
max_order : int
|
||||
Maximum scattering order to apply globally when in multi-grup mode.
|
||||
ptables : bool
|
||||
Determine whether probability tables are used.
|
||||
run_cmfd : bool
|
||||
|
|
@ -129,6 +135,10 @@ class SettingsFile(object):
|
|||
self._particles = None
|
||||
self._keff_trigger = None
|
||||
|
||||
# Energy mode subelement
|
||||
self._energy_mode = None
|
||||
self._max_order = None
|
||||
|
||||
# Source subelement
|
||||
self._source_subelement = None
|
||||
self._source_file = None
|
||||
|
|
@ -219,6 +229,14 @@ class SettingsFile(object):
|
|||
def keff_trigger(self):
|
||||
return self._keff_trigger
|
||||
|
||||
@property
|
||||
def energy_mode(self):
|
||||
return self._energy_mode
|
||||
|
||||
@property
|
||||
def max_order(self):
|
||||
return self._max_order
|
||||
|
||||
@property
|
||||
def source_file(self):
|
||||
return self._source_file
|
||||
|
|
@ -452,6 +470,18 @@ class SettingsFile(object):
|
|||
|
||||
self._keff_trigger = keff_trigger
|
||||
|
||||
@energy_mode.setter
|
||||
def energy_mode(self, energy_mode):
|
||||
check_value('energy mode', energy_mode,
|
||||
['continuous-energy', 'multi-group'])
|
||||
self._energy_mode = energy_mode
|
||||
|
||||
@max_order.setter
|
||||
def max_order(self, max_order):
|
||||
check_type('maximum scattering order', max_order, Integral)
|
||||
check_greater_than('maximum scattering order', max_order, 0)
|
||||
self._max_order = max_order
|
||||
|
||||
@source_file.setter
|
||||
def source_file(self, source_file):
|
||||
check_type('source file', source_file, basestring)
|
||||
|
|
@ -917,6 +947,16 @@ class SettingsFile(object):
|
|||
subelement = ET.SubElement(element, key)
|
||||
subelement.text = str(self._keff_trigger[key]).lower()
|
||||
|
||||
def _create_energy_mode_subelement(self):
|
||||
if self._energy_mode is not None:
|
||||
element = ET.SubElement(self._settings_file, "energy_mode")
|
||||
element.text = str(self._energy_mode)
|
||||
|
||||
def _create_max_order_subelement(self):
|
||||
if self._max_order is not None:
|
||||
element = ET.SubElement(self._settings_file, "max_order")
|
||||
element.text = str(self._max_order)
|
||||
|
||||
def _create_source_subelement(self):
|
||||
self._create_source_space_subelement()
|
||||
self._create_source_energy_subelement()
|
||||
|
|
@ -1186,6 +1226,8 @@ class SettingsFile(object):
|
|||
self._source_element = None
|
||||
|
||||
self._create_eigenvalue_subelement()
|
||||
self._create_energy_mode_subelement()
|
||||
self._create_max_order_subelement()
|
||||
self._create_source_subelement()
|
||||
self._create_output_subelement()
|
||||
self._create_statepoint_subelement()
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ class Summary(object):
|
|||
# Read date and time
|
||||
self.date_and_time = self._f['date_and_time'][...]
|
||||
|
||||
# Read if continuous-energy or multi-group
|
||||
self.run_CE = bool(self._f['run_CE'].value)
|
||||
|
||||
self.n_batches = self._f['n_batches'].value
|
||||
self.n_particles = self._f['n_particles'].value
|
||||
self.n_active = self._f['n_active'].value
|
||||
|
|
|
|||
|
|
@ -93,6 +93,11 @@ contains
|
|||
call write_dataset(file_id, "seed", seed)
|
||||
|
||||
! Write run information
|
||||
if (run_CE) then
|
||||
call write_dataset(file_id, "run_CE", 1)
|
||||
else
|
||||
call write_dataset(file_id, "run_CE", 0)
|
||||
end if
|
||||
select case(run_mode)
|
||||
case (MODE_FIXEDSOURCE)
|
||||
call write_dataset(file_id, "run_mode", "fixed source")
|
||||
|
|
@ -695,6 +700,7 @@ contains
|
|||
integer(HID_T) :: tally_group
|
||||
real(8) :: real_array(3)
|
||||
logical :: source_present
|
||||
integer :: sp_run_CE
|
||||
character(MAX_WORD_LEN) :: word
|
||||
type(TallyObject), pointer :: tally
|
||||
|
||||
|
|
@ -719,6 +725,19 @@ contains
|
|||
! Read and overwrite random number seed
|
||||
call read_dataset(file_id, "seed", seed)
|
||||
|
||||
! It is not impossible for a state point to be generated from a CE run but
|
||||
! to be loaded in to an MG run (or vice versa), check to prevent that.
|
||||
call read_dataset(file_id, "run_CE", sp_run_CE)
|
||||
if (sp_run_CE == 0) then
|
||||
if (run_CE) &
|
||||
call fatal_error("State point file is from multi-group run but &
|
||||
& current run is continous-energy!")
|
||||
else if (sp_run_CE == 1) then
|
||||
if (.not. run_CE) &
|
||||
call fatal_error("State point file is from continuous-energy run but &
|
||||
& current run is multi-group!")
|
||||
end if
|
||||
|
||||
! Read and overwrite run information except number of batches
|
||||
call read_dataset(file_id, "run_mode", word)
|
||||
select case(word)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ contains
|
|||
! Write header information
|
||||
call write_header(file_id)
|
||||
|
||||
if (run_CE) then
|
||||
call write_dataset(file_id, "run_CE", 1)
|
||||
else
|
||||
call write_dataset(file_id, "run_CE", 0)
|
||||
end if
|
||||
|
||||
! Write number of particles
|
||||
call write_dataset(file_id, "n_particles", n_particles)
|
||||
call write_dataset(file_id, "n_batches", n_batches)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue