mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-26 21:25:36 -04:00
Added Legendre moments back to consistent scattering matrix
This commit is contained in:
parent
b03fdb9a02
commit
1dc199e2f4
1 changed files with 688 additions and 15 deletions
|
|
@ -4617,6 +4617,18 @@ class ScatterProbabilityMatrix(MatrixMGXS):
|
|||
|
||||
Attributes
|
||||
----------
|
||||
scatter_format : {'legendre', or 'histogram'}
|
||||
Representation of the angular scattering distribution (default is
|
||||
'legendre')
|
||||
legendre_order : int
|
||||
The highest Legendre moment in the scattering matrix; this is used if
|
||||
:attr:`ScatterProbabilityMatrix.scatter_format` is 'legendre'.
|
||||
(default is 0)
|
||||
histogram_bins : int
|
||||
The number of equally-spaced bins for the histogram representation of
|
||||
the angular scattering distribution; this is used if
|
||||
:attr:`ScatterProbabilityMatrix.scatter_format` is 'histogram'.
|
||||
(default is 16)
|
||||
name : str, optional
|
||||
Name of the multi-group cross section
|
||||
rxn_type : str
|
||||
|
|
@ -4690,26 +4702,60 @@ class ScatterProbabilityMatrix(MatrixMGXS):
|
|||
domain, domain_type, groups, by_nuclide,
|
||||
name, num_polar, num_azimuthal)
|
||||
|
||||
self._scatter_format = 'legendre'
|
||||
self._legendre_order = 0
|
||||
self._histogram_bins = 16
|
||||
self._estimator = 'analog'
|
||||
self._valid_estimators = ['analog']
|
||||
self.nu = nu
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
clone = super(ScatterProbabilityMatrix, self).__deepcopy__(memo)
|
||||
clone._scatter_format = self.scatter_format
|
||||
clone._legendre_order = self.legendre_order
|
||||
clone._histogram_bins = self.histogram_bins
|
||||
clone._nu = self.nu
|
||||
return clone
|
||||
|
||||
@property
|
||||
def _dont_squeeze(self):
|
||||
"""Create a tuple of axes which should not be removed during the get_xs
|
||||
process
|
||||
"""
|
||||
if self.num_polar > 1 or self.num_azimuthal > 1:
|
||||
if self.scatter_format == 'histogram':
|
||||
return (0, 1, 3, 4, 5)
|
||||
else:
|
||||
return (0, 1, 3, 4)
|
||||
else:
|
||||
if self.scatter_format == 'histogram':
|
||||
return (1, 2, 3)
|
||||
else:
|
||||
return (1, 2)
|
||||
|
||||
@property
|
||||
def scatter_format(self):
|
||||
return self._scatter_format
|
||||
|
||||
@property
|
||||
def legendre_order(self):
|
||||
return self._legendre_order
|
||||
|
||||
@property
|
||||
def histogram_bins(self):
|
||||
return self._histogram_bins
|
||||
|
||||
@property
|
||||
def nu(self):
|
||||
return self._nu
|
||||
|
||||
|
||||
@property
|
||||
def scores(self):
|
||||
if self.nu:
|
||||
return ['nu-scatter']
|
||||
else:
|
||||
return ['scatter']
|
||||
|
||||
if self.scatter_format == 'legendre':
|
||||
return ['{}-P{}'.format(self.rxn_type, self.legendre_order)]
|
||||
elif self.scatter_format == 'histogram':
|
||||
return [self.rxn_type]
|
||||
|
||||
@property
|
||||
def filters(self):
|
||||
# Create the non-domain specific Filters for the Tallies
|
||||
|
|
@ -4721,29 +4767,66 @@ class ScatterProbabilityMatrix(MatrixMGXS):
|
|||
|
||||
@property
|
||||
def rxn_rate_tally(self):
|
||||
|
||||
if self._rxn_rate_tally is None:
|
||||
self._rxn_rate_tally = self.tallies[self.rxn_type]
|
||||
if self.scatter_format == 'legendre':
|
||||
tally_key = '{}-P{}'.format(self.rxn_type,
|
||||
self.legendre_order)
|
||||
self._rxn_rate_tally = self.tallies[tally_key]
|
||||
elif self.scatter_format == 'histogram':
|
||||
self._rxn_rate_tally = self.tallies[self.rxn_type]
|
||||
|
||||
self._rxn_rate_tally.sparse = self.sparse
|
||||
|
||||
return self._rxn_rate_tally
|
||||
|
||||
@property
|
||||
def xs_tally(self):
|
||||
|
||||
if self._xs_tally is None:
|
||||
energyout_bins = \
|
||||
[self.energy_groups.get_group_bounds(i) for i in range(self.num_groups, 0, -1)]
|
||||
norm = self.rxn_rate_tally.summation(
|
||||
energyout_bins = [self.energy_groups.get_group_bounds(i)
|
||||
for i in range(self.num_groups, 0, -1)]
|
||||
norm = self.rxn_rate_tally.get_slice(
|
||||
scores=['{}-0'.format(self.rxn_type)])
|
||||
norm = norm.summation(
|
||||
filter_type=openmc.EnergyoutFilter, filter_bins=energyout_bins)
|
||||
|
||||
# Remove the AggregateFilter summed across energyout bins
|
||||
norm._filters = norm._filters[:2]
|
||||
|
||||
# Compute the group-to-group probailities
|
||||
self._xs_tally = self.tallies[self.rxn_type] / norm
|
||||
tally_key = '{}-P{}'.format(self.rxn_type, self.legendre_order)
|
||||
self._xs_tally = self.tallies[tally_key] / norm
|
||||
super(ScatterProbabilityMatrix, self)._compute_xs()
|
||||
|
||||
return self._xs_tally
|
||||
|
||||
@scatter_format.setter
|
||||
def scatter_format(self, scatter_format):
|
||||
cv.check_value('scatter_format', scatter_format, MU_TREATMENTS)
|
||||
self._scatter_format = scatter_format
|
||||
|
||||
@legendre_order.setter
|
||||
def legendre_order(self, legendre_order):
|
||||
cv.check_type('legendre_order', legendre_order, Integral)
|
||||
cv.check_greater_than('legendre_order', legendre_order, 0,
|
||||
equality=True)
|
||||
cv.check_less_than('legendre_order', legendre_order, _MAX_LEGENDRE,
|
||||
equality=True)
|
||||
|
||||
if self.scatter_format == 'histogram':
|
||||
msg = 'The legendre order will be ignored since the ' \
|
||||
'scatter format is set to histogram'
|
||||
warnings.warn(msg)
|
||||
|
||||
self._legendre_order = legendre_order
|
||||
|
||||
@histogram_bins.setter
|
||||
def histogram_bins(self, histogram_bins):
|
||||
cv.check_type('histogram_bins', histogram_bins, Integral)
|
||||
cv.check_greater_than('histogram_bins', histogram_bins, 0)
|
||||
self._histogram_bins = histogram_bins
|
||||
|
||||
@nu.setter
|
||||
def nu(self, nu):
|
||||
cv.check_type('nu', nu, bool)
|
||||
|
|
@ -4755,6 +4838,552 @@ class ScatterProbabilityMatrix(MatrixMGXS):
|
|||
self._rxn_type = 'nu-scatter'
|
||||
self._hdf5_key = 'nu-scatter probability matrix'
|
||||
|
||||
def load_from_statepoint(self, statepoint):
|
||||
"""Extracts tallies in an OpenMC StatePoint with the data needed to
|
||||
compute multi-group cross sections.
|
||||
|
||||
This method is needed to compute cross section data from tallies
|
||||
in an OpenMC StatePoint object.
|
||||
|
||||
.. note:: The statepoint must be linked with an OpenMC Summary object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
statepoint : openmc.StatePoint
|
||||
An OpenMC StatePoint object with tally data
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
When this method is called with a statepoint that has not been
|
||||
linked with a summary object.
|
||||
|
||||
"""
|
||||
|
||||
# Clear any tallies previously loaded from a statepoint
|
||||
if self.loaded_sp:
|
||||
self._tallies = None
|
||||
self._xs_tally = None
|
||||
self._rxn_rate_tally = None
|
||||
self._loaded_sp = False
|
||||
|
||||
if self.scatter_format == 'legendre':
|
||||
# Expand scores to match the format in the statepoint
|
||||
# e.g., "scatter-P2" -> "scatter-0", "scatter-1", "scatter-2"
|
||||
tally_key = '{}-P{}'.format(self.rxn_type, self.legendre_order)
|
||||
self.tallies[tally_key].scores = \
|
||||
[self.rxn_type + '-{}'.format(i)
|
||||
for i in range(self.legendre_order + 1)]
|
||||
elif self.scatter_format == 'histogram':
|
||||
self.tallies[self.rxn_type].scores = [self.rxn_type]
|
||||
|
||||
super(ScatterProbabilityMatrix, self).load_from_statepoint(statepoint)
|
||||
|
||||
def get_slice(self, nuclides=[], in_groups=[], out_groups=[],
|
||||
legendre_order='same'):
|
||||
"""Build a sliced ScatterProbabilityMatrix for the specified nuclides
|
||||
and energy groups.
|
||||
|
||||
This method constructs a new MGXS to encapsulate a subset of the data
|
||||
represented by this MGXS. The subset of data to include in the tally
|
||||
slice is determined by the nuclides and energy groups specified in
|
||||
the input parameters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nuclides : list of str
|
||||
A list of nuclide name strings
|
||||
(e.g., ['U235', 'U238']; default is [])
|
||||
in_groups : list of int
|
||||
A list of incoming energy group indices starting at 1 for the high
|
||||
energies (e.g., [1, 2, 3]; default is [])
|
||||
out_groups : list of int
|
||||
A list of outgoing energy group indices starting at 1 for the high
|
||||
energies (e.g., [1, 2, 3]; default is [])
|
||||
legendre_order : int or 'same'
|
||||
The highest Legendre moment in the sliced MGXS. If order is 'same'
|
||||
then the sliced MGXS will have the same Legendre moments as the
|
||||
original MGXS (default). If order is an integer less than the
|
||||
original MGXS' order, then only those Legendre moments up to that
|
||||
order will be included in the sliced MGXS.
|
||||
|
||||
Returns
|
||||
-------
|
||||
openmc.mgxs.MatrixMGXS
|
||||
A new MatrixMGXS which encapsulates the subset of data requested
|
||||
for the nuclide(s) and/or energy group(s) requested in the
|
||||
parameters.
|
||||
|
||||
"""
|
||||
|
||||
# Call super class method and null out derived tallies
|
||||
slice_xs = \
|
||||
super(ScatterProbabilityMatrix, self).get_slice(nuclides, in_groups)
|
||||
slice_xs._rxn_rate_tally = None
|
||||
slice_xs._xs_tally = None
|
||||
|
||||
# Slice the Legendre order if needed
|
||||
if legendre_order != 'same' and self.scatter_format == 'legendre':
|
||||
cv.check_type('legendre_order', legendre_order, Integral)
|
||||
cv.check_less_than('legendre_order', legendre_order,
|
||||
self.legendre_order, equality=True)
|
||||
slice_xs.legendre_order = legendre_order
|
||||
|
||||
# Slice the scattering tally
|
||||
tally_key = '{}-P{}'.format(self.rxn_type, self.legendre_order)
|
||||
expand_scores = \
|
||||
[self.rxn_type + '-{}'.format(i)
|
||||
for i in range(self.legendre_order + 1)]
|
||||
slice_xs.tallies[tally_key] = \
|
||||
slice_xs.tallies[tally_key].get_slice(scores=expand_scores)
|
||||
|
||||
# Slice outgoing energy groups if needed
|
||||
if len(out_groups) != 0:
|
||||
filter_bins = []
|
||||
for group in out_groups:
|
||||
group_bounds = self.energy_groups.get_group_bounds(group)
|
||||
filter_bins.append(group_bounds)
|
||||
filter_bins = [tuple(filter_bins)]
|
||||
|
||||
# Slice each of the tallies across energyout groups
|
||||
for tally_type, tally in slice_xs.tallies.items():
|
||||
if tally.contains_filter(openmc.EnergyoutFilter):
|
||||
tally_slice = tally.get_slice(
|
||||
filters=[openmc.EnergyoutFilter], filter_bins=filter_bins)
|
||||
slice_xs.tallies[tally_type] = tally_slice
|
||||
|
||||
slice_xs.sparse = self.sparse
|
||||
return slice_xs
|
||||
|
||||
def get_xs(self, in_groups='all', out_groups='all',
|
||||
subdomains='all', nuclides='all', moment='all',
|
||||
xs_type='macro', order_groups='increasing',
|
||||
row_column='inout', value='mean', squeeze=True):
|
||||
r"""Returns an array of multi-group cross sections.
|
||||
|
||||
This method constructs a 5D NumPy array for the requested
|
||||
multi-group cross section data for one or more subdomains
|
||||
(1st dimension), energy groups in (2nd dimension), energy groups out
|
||||
(3rd dimension), nuclides (4th dimension), and moments/histograms
|
||||
(5th dimension).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
in_groups : Iterable of Integral or 'all'
|
||||
Incoming energy groups of interest. Defaults to 'all'.
|
||||
out_groups : Iterable of Integral or 'all'
|
||||
Outgoing energy groups of interest. Defaults to 'all'.
|
||||
subdomains : Iterable of Integral or 'all'
|
||||
Subdomain IDs of interest. Defaults to 'all'.
|
||||
nuclides : Iterable of str or 'all' or 'sum'
|
||||
A list of nuclide name strings (e.g., ['U235', 'U238']). The
|
||||
special string 'all' will return the cross sections for all nuclides
|
||||
in the spatial domain. The special string 'sum' will return the
|
||||
cross section summed over all nuclides. Defaults to 'all'.
|
||||
moment : int or 'all'
|
||||
The scattering matrix moment to return. All moments will be
|
||||
returned if the moment is 'all' (default); otherwise, a specific
|
||||
moment will be returned.
|
||||
xs_type: {'macro', 'micro'}
|
||||
Return the macro or micro cross section in units of cm^-1 or barns.
|
||||
Defaults to 'macro'.
|
||||
order_groups: {'increasing', 'decreasing'}
|
||||
Return the cross section indexed according to increasing or
|
||||
decreasing energy groups (decreasing or increasing energies).
|
||||
Defaults to 'increasing'.
|
||||
row_column: {'inout', 'outin'}
|
||||
Return the cross section indexed first by incoming group and
|
||||
second by outgoing group ('inout'), or vice versa ('outin').
|
||||
Defaults to 'inout'.
|
||||
value : {'mean', 'std_dev', 'rel_err'}
|
||||
A string for the type of value to return. Defaults to 'mean'.
|
||||
squeeze : bool
|
||||
A boolean representing whether to eliminate the extra dimensions
|
||||
of the multi-dimensional array to be returned. Defaults to True.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
A NumPy array of the multi-group cross section indexed in the order
|
||||
each group and subdomain is listed in the parameters.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
When this method is called before the multi-group cross section is
|
||||
computed from tally data.
|
||||
|
||||
"""
|
||||
|
||||
cv.check_value('value', value, ['mean', 'std_dev', 'rel_err'])
|
||||
cv.check_value('xs_type', xs_type, ['macro', 'micro'])
|
||||
|
||||
# FIXME: Unable to get microscopic xs for mesh domain because the mesh
|
||||
# cells do not know the nuclide densities in each mesh cell.
|
||||
if self.domain_type == 'mesh' and xs_type == 'micro':
|
||||
msg = 'Unable to get micro xs for mesh domain since the mesh ' \
|
||||
'cells do not know the nuclide densities in each mesh cell.'
|
||||
raise ValueError(msg)
|
||||
|
||||
filters = []
|
||||
filter_bins = []
|
||||
|
||||
# Construct a collection of the domain filter bins
|
||||
if not isinstance(subdomains, string_types):
|
||||
cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3)
|
||||
filters.append(_DOMAIN_TO_FILTER[self.domain_type])
|
||||
subdomain_bins = []
|
||||
for subdomain in subdomains:
|
||||
subdomain_bins.append(subdomain)
|
||||
filter_bins.append(tuple(subdomain_bins))
|
||||
|
||||
# Construct list of energy group bounds tuples for all requested groups
|
||||
if not isinstance(in_groups, string_types):
|
||||
cv.check_iterable_type('groups', in_groups, Integral)
|
||||
filters.append(openmc.EnergyFilter)
|
||||
energy_bins = []
|
||||
for group in in_groups:
|
||||
energy_bins.append(
|
||||
(self.energy_groups.get_group_bounds(group),))
|
||||
filter_bins.append(tuple(energy_bins))
|
||||
|
||||
# Construct list of energy group bounds tuples for all requested groups
|
||||
if not isinstance(out_groups, string_types):
|
||||
cv.check_iterable_type('groups', out_groups, Integral)
|
||||
for group in out_groups:
|
||||
filters.append(openmc.EnergyoutFilter)
|
||||
filter_bins.append((self.energy_groups.get_group_bounds(group),))
|
||||
|
||||
# Construct CrossScore for requested scattering moment
|
||||
if moment != 'all' and self.scatter_format == 'legendre':
|
||||
cv.check_type('moment', moment, Integral)
|
||||
cv.check_greater_than('moment', moment, 0, equality=True)
|
||||
cv.check_less_than(
|
||||
'moment', moment, self.legendre_order, equality=True)
|
||||
scores = [self.xs_tally.scores[moment]]
|
||||
else:
|
||||
scores = []
|
||||
|
||||
# Construct a collection of the nuclides to retrieve from the xs tally
|
||||
if self.by_nuclide:
|
||||
if nuclides == 'all' or nuclides == 'sum' or nuclides == ['sum']:
|
||||
query_nuclides = self.get_nuclides()
|
||||
else:
|
||||
query_nuclides = nuclides
|
||||
else:
|
||||
query_nuclides = ['total']
|
||||
|
||||
# Use tally summation if user requested the sum for all nuclides
|
||||
if nuclides == 'sum' or nuclides == ['sum']:
|
||||
xs_tally = self.xs_tally.summation(nuclides=query_nuclides)
|
||||
xs = xs_tally.get_values(scores=scores, filters=filters,
|
||||
filter_bins=filter_bins, value=value)
|
||||
else:
|
||||
xs = self.xs_tally.get_values(scores=scores, filters=filters,
|
||||
filter_bins=filter_bins,
|
||||
nuclides=query_nuclides, value=value)
|
||||
|
||||
# Divide by atom number densities for microscopic cross sections
|
||||
if xs_type == 'micro':
|
||||
if self.by_nuclide:
|
||||
densities = self.get_nuclide_densities(nuclides)
|
||||
else:
|
||||
densities = self.get_nuclide_densities('sum')
|
||||
if value == 'mean' or value == 'std_dev':
|
||||
xs /= densities[np.newaxis, :, np.newaxis]
|
||||
|
||||
# Convert and nans to zero
|
||||
xs = np.nan_to_num(xs)
|
||||
|
||||
if in_groups == 'all':
|
||||
num_in_groups = self.num_groups
|
||||
else:
|
||||
num_in_groups = len(in_groups)
|
||||
|
||||
if out_groups == 'all':
|
||||
num_out_groups = self.num_groups
|
||||
else:
|
||||
num_out_groups = len(out_groups)
|
||||
|
||||
if self.scatter_format == 'histogram':
|
||||
num_mu_bins = self.histogram_bins
|
||||
else:
|
||||
num_mu_bins = 1
|
||||
|
||||
# Reshape tally data array with separate axes for domain and energy
|
||||
# Accomodate the polar and azimuthal bins if needed
|
||||
num_subdomains = int(xs.shape[0] / (num_mu_bins * num_in_groups *
|
||||
num_out_groups * self.num_polar *
|
||||
self.num_azimuthal))
|
||||
if self.num_polar > 1 or self.num_azimuthal > 1:
|
||||
if self.scatter_format == 'histogram':
|
||||
new_shape = (self.num_polar, self.num_azimuthal,
|
||||
num_subdomains, num_in_groups, num_out_groups,
|
||||
num_mu_bins)
|
||||
else:
|
||||
new_shape = (self.num_polar, self.num_azimuthal,
|
||||
num_subdomains, num_in_groups, num_out_groups)
|
||||
new_shape += xs.shape[1:]
|
||||
xs = np.reshape(xs, new_shape)
|
||||
|
||||
# Transpose the scattering matrix if requested by user
|
||||
if row_column == 'outin':
|
||||
xs = np.swapaxes(xs, 3, 4)
|
||||
|
||||
# Reverse data if user requested increasing energy groups since
|
||||
# tally data is stored in order of increasing energies
|
||||
if order_groups == 'increasing':
|
||||
xs = xs[:, :, :, ::-1, ::-1, ...]
|
||||
else:
|
||||
if self.scatter_format == 'histogram':
|
||||
new_shape = (num_subdomains, num_in_groups, num_out_groups,
|
||||
num_mu_bins)
|
||||
else:
|
||||
new_shape = (num_subdomains, num_in_groups, num_out_groups)
|
||||
new_shape += xs.shape[1:]
|
||||
xs = np.reshape(xs, new_shape)
|
||||
|
||||
# Transpose the scattering matrix if requested by user
|
||||
if row_column == 'outin':
|
||||
xs = np.swapaxes(xs, 1, 2)
|
||||
|
||||
# Reverse data if user requested increasing energy groups since
|
||||
# tally data is stored in order of increasing energies
|
||||
if order_groups == 'increasing':
|
||||
xs = xs[:, ::-1, ::-1, ...]
|
||||
|
||||
if squeeze:
|
||||
# We want to squeeze out everything but the angles, in_groups,
|
||||
# out_groups, and, if needed, num_mu_bins dimension. These must
|
||||
# not be squeezed so 1-group, 1-angle problems have the correct
|
||||
# shape.
|
||||
xs = self._squeeze_xs(xs)
|
||||
return xs
|
||||
|
||||
def get_pandas_dataframe(self, groups='all', nuclides='all', moment='all',
|
||||
xs_type='macro', distribcell_paths=True):
|
||||
"""Build a Pandas DataFrame for the MGXS data.
|
||||
|
||||
This method leverages :meth:`openmc.Tally.get_pandas_dataframe`, but
|
||||
renames the columns with terminology appropriate for cross section data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
groups : Iterable of Integral or 'all'
|
||||
Energy groups of interest. Defaults to 'all'.
|
||||
nuclides : Iterable of str or 'all' or 'sum'
|
||||
The nuclides of the cross-sections to include in the dataframe. This
|
||||
may be a list of nuclide name strings (e.g., ['U235', 'U238']).
|
||||
The special string 'all' will include the cross sections for all
|
||||
nuclides in the spatial domain. The special string 'sum' will
|
||||
include the cross sections summed over all nuclides. Defaults
|
||||
to 'all'.
|
||||
moment : int or 'all'
|
||||
The scattering matrix moment to return. All moments will be
|
||||
returned if the moment is 'all' (default); otherwise, a specific
|
||||
moment will be returned.
|
||||
xs_type: {'macro', 'micro'}
|
||||
Return macro or micro cross section in units of cm^-1 or barns.
|
||||
Defaults to 'macro'.
|
||||
distribcell_paths : bool, optional
|
||||
Construct columns for distribcell tally filters (default is True).
|
||||
The geometric information in the Summary object is embedded into a
|
||||
Multi-index column with a geometric "path" to each distribcell
|
||||
instance.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pandas.DataFrame
|
||||
A Pandas DataFrame for the cross section data.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
When this method is called before the multi-group cross section is
|
||||
computed from tally data.
|
||||
|
||||
"""
|
||||
|
||||
df = super(ScatterProbabilityMatrix, self).get_pandas_dataframe(
|
||||
groups, nuclides, xs_type, distribcell_paths)
|
||||
|
||||
if self.scatter_format == 'legendre':
|
||||
# Add a moment column to dataframe
|
||||
if self.legendre_order > 0:
|
||||
# Insert a column corresponding to the Legendre moments
|
||||
moments = ['P{}'.format(i)
|
||||
for i in range(self.legendre_order + 1)]
|
||||
moments = np.tile(moments, int(df.shape[0] / len(moments)))
|
||||
df['moment'] = moments
|
||||
|
||||
# Place the moment column before the mean column
|
||||
columns = df.columns.tolist()
|
||||
mean_index \
|
||||
= [i for i, s in enumerate(columns) if 'mean' in s][0]
|
||||
if self.domain_type == 'mesh':
|
||||
df = df[columns[:mean_index] + [('moment', '')] +
|
||||
columns[mean_index:-1]]
|
||||
else:
|
||||
df = df[columns[:mean_index] + ['moment'] +
|
||||
columns[mean_index:-1]]
|
||||
|
||||
# Select rows corresponding to requested scattering moment
|
||||
if moment != 'all':
|
||||
cv.check_type('moment', moment, Integral)
|
||||
cv.check_greater_than('moment', moment, 0, equality=True)
|
||||
cv.check_less_than(
|
||||
'moment', moment, self.legendre_order, equality=True)
|
||||
df = df[df['moment'] == 'P{}'.format(moment)]
|
||||
|
||||
return df
|
||||
|
||||
def print_xs(self, subdomains='all', nuclides='all',
|
||||
xs_type='macro', moment=0):
|
||||
"""Prints a string representation for the multi-group cross section.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
subdomains : Iterable of Integral or 'all'
|
||||
The subdomain IDs of the cross sections to include in the report.
|
||||
Defaults to 'all'.
|
||||
nuclides : Iterable of str or 'all' or 'sum'
|
||||
The nuclides of the cross-sections to include in the report. This
|
||||
may be a list of nuclide name strings (e.g., ['U235', 'U238']).
|
||||
The special string 'all' will report the cross sections for all
|
||||
nuclides in the spatial domain. The special string 'sum' will report
|
||||
the cross sections summed over all nuclides. Defaults to 'all'.
|
||||
xs_type: {'macro', 'micro'}
|
||||
Return the macro or micro cross section in units of cm^-1 or barns.
|
||||
Defaults to 'macro'.
|
||||
moment : int
|
||||
The scattering moment to print (default is 0)
|
||||
|
||||
"""
|
||||
|
||||
# Construct a collection of the subdomains to report
|
||||
if not isinstance(subdomains, string_types):
|
||||
cv.check_iterable_type('subdomains', subdomains, Integral)
|
||||
elif self.domain_type == 'distribcell':
|
||||
subdomains = np.arange(self.num_subdomains, dtype=np.int)
|
||||
elif self.domain_type == 'mesh':
|
||||
xyz = [range(1, x + 1) for x in self.domain.dimension]
|
||||
subdomains = list(itertools.product(*xyz))
|
||||
else:
|
||||
subdomains = [self.domain.id]
|
||||
|
||||
# Construct a collection of the nuclides to report
|
||||
if self.by_nuclide:
|
||||
if nuclides == 'all':
|
||||
nuclides = self.get_nuclides()
|
||||
if nuclides == 'sum':
|
||||
nuclides = ['sum']
|
||||
else:
|
||||
cv.check_iterable_type('nuclides', nuclides, string_types)
|
||||
else:
|
||||
nuclides = ['sum']
|
||||
|
||||
cv.check_value('xs_type', xs_type, ['macro', 'micro'])
|
||||
|
||||
if self.scatter_format == 'legendre':
|
||||
rxn_type = '{0} (P{1})'.format(self.rxn_type, moment)
|
||||
else:
|
||||
rxn_type = self.rxn_type
|
||||
|
||||
# Build header for string with type and domain info
|
||||
string = 'Multi-Group XS\n'
|
||||
string += '{0: <16}=\t{1}\n'.format('\tReaction Type', rxn_type)
|
||||
string += '{0: <16}=\t{1}\n'.format('\tDomain Type', self.domain_type)
|
||||
string += '{0: <16}=\t{1}\n'.format('\tDomain ID', self.domain.id)
|
||||
|
||||
# Generate the header for an individual XS
|
||||
xs_header = '\tCross Sections [{0}]:'.format(self.get_units(xs_type))
|
||||
|
||||
# If cross section data has not been computed, only print string header
|
||||
if self.tallies is None:
|
||||
print(string)
|
||||
return
|
||||
|
||||
string += '{0: <16}\n'.format('\tEnergy Groups:')
|
||||
template = '{0: <12}Group {1} [{2: <10} - {3: <10}eV]\n'
|
||||
|
||||
# Loop over energy groups ranges
|
||||
for group in range(1, self.num_groups + 1):
|
||||
bounds = self.energy_groups.get_group_bounds(group)
|
||||
string += template.format('', group, bounds[0], bounds[1])
|
||||
|
||||
# Set polar and azimuthal bins if necessary
|
||||
if self.num_polar > 1 or self.num_azimuthal > 1:
|
||||
pol_bins = np.linspace(0., np.pi, num=self.num_polar + 1,
|
||||
endpoint=True)
|
||||
azi_bins = np.linspace(-np.pi, np.pi, num=self.num_azimuthal + 1,
|
||||
endpoint=True)
|
||||
|
||||
# Loop over all subdomains
|
||||
for subdomain in subdomains:
|
||||
|
||||
if self.domain_type == 'distribcell' or self.domain_type == 'mesh':
|
||||
string += '{0: <16}=\t{1}\n'.format('\tSubdomain', subdomain)
|
||||
|
||||
# Loop over all Nuclides
|
||||
for nuclide in nuclides:
|
||||
|
||||
# Build header for nuclide type
|
||||
if xs_type != 'sum':
|
||||
string += '{0: <16}=\t{1}\n'.format('\tNuclide', nuclide)
|
||||
|
||||
# Build header for cross section type
|
||||
string += '{0: <16}\n'.format(xs_header)
|
||||
template = '{0: <12}Group {1} -> Group {2}:\t\t'
|
||||
|
||||
average_xs = self.get_xs(nuclides=[nuclide],
|
||||
subdomains=[subdomain],
|
||||
xs_type=xs_type, value='mean',
|
||||
moment=moment)
|
||||
rel_err_xs = self.get_xs(nuclides=[nuclide],
|
||||
subdomains=[subdomain],
|
||||
xs_type=xs_type, value='rel_err',
|
||||
moment=moment)
|
||||
rel_err_xs = rel_err_xs * 100.
|
||||
|
||||
if self.num_polar > 1 or self.num_azimuthal > 1:
|
||||
# Loop over polar, azi, and in/out energy group ranges
|
||||
for pol in range(len(pol_bins) - 1):
|
||||
pol_low, pol_high = pol_bins[pol: pol + 2]
|
||||
for azi in range(len(azi_bins) - 1):
|
||||
azi_low, azi_high = azi_bins[azi: azi + 2]
|
||||
string += '\t\tPolar Angle: [{0:5f} - {1:5f}]'.format(
|
||||
pol_low, pol_high) + \
|
||||
'\tAzimuthal Angle: [{0:5f} - {1:5f}]'.format(
|
||||
azi_low, azi_high) + '\n'
|
||||
for in_group in range(1, self.num_groups + 1):
|
||||
for out_group in range(1, self.num_groups + 1):
|
||||
string += '\t' + template.format('',
|
||||
in_group,
|
||||
out_group)
|
||||
string += '{0:.2e} +/- {1:.2e}%'.format(
|
||||
average_xs[pol, azi, in_group - 1,
|
||||
out_group - 1],
|
||||
rel_err_xs[pol, azi, in_group - 1,
|
||||
out_group - 1])
|
||||
string += '\n'
|
||||
string += '\n'
|
||||
string += '\n'
|
||||
else:
|
||||
# Loop over incoming/outgoing energy groups ranges
|
||||
for in_group in range(1, self.num_groups + 1):
|
||||
for out_group in range(1, self.num_groups + 1):
|
||||
string += template.format('', in_group, out_group)
|
||||
string += '{0:.2e} +/- {1:.2e}%'.format(
|
||||
average_xs[in_group - 1, out_group - 1],
|
||||
rel_err_xs[in_group - 1, out_group - 1])
|
||||
string += '\n'
|
||||
string += '\n'
|
||||
string += '\n'
|
||||
string += '\n'
|
||||
string += '\n'
|
||||
|
||||
print(string)
|
||||
|
||||
|
||||
@add_metaclass(ABCMeta)
|
||||
class ConvolvedMGXS(MGXS):
|
||||
|
|
@ -5112,6 +5741,7 @@ class ConvolvedMGXS(MGXS):
|
|||
|
||||
|
||||
class ConsistentScatterMatrixXS(ConvolvedMGXS, ScatterMatrixXS):
|
||||
#class ConsistentScatterMatrixXS(ScatterMatrixXS, ConvolvedMGXS):
|
||||
r"""A scattering matrix multi-group cross section computed as the product
|
||||
of the scatter cross section and group-to-group scattering probabilities.
|
||||
|
||||
|
|
@ -5410,22 +6040,22 @@ class ConsistentScatterMatrixXS(ConvolvedMGXS, ScatterMatrixXS):
|
|||
|
||||
@ScatterMatrixXS.correction.setter
|
||||
def correction(self, correction):
|
||||
ScatterMatrixXS.correction = correction
|
||||
ScatterMatrixXS.correction.fset(self, correction)
|
||||
self.mgxs[2].correction = correction
|
||||
|
||||
@ScatterMatrixXS.scatter_format.setter
|
||||
def scatter_format(self, scatter_format):
|
||||
ScatterMatrixXS.scatter_format = scatter_format
|
||||
ScatterMatrixXS.scatter_format.fset(self, scatter_format)
|
||||
self.mgxs[1].scatter_format = scatter_format
|
||||
|
||||
@ScatterMatrixXS.legendre_order.setter
|
||||
def legendre_order(self, legendre_order):
|
||||
ScatterMatrixXS.legendre_order = legendre_order
|
||||
ScatterMatrixXS.legendre_order.fset(self, legendre_order)
|
||||
self.mgxs[1].legendre_order = legendre_order
|
||||
|
||||
@ScatterMatrixXS.histogram_bins.setter
|
||||
def histogram_bins(self, histogram_bins):
|
||||
ScatterMatrixXS.histogram_bins = histogram_bins
|
||||
ScatterMatrixXS.histogram_bins.fset(self, histogram_bins)
|
||||
self.mgxs[1].histogram_bins = histogram_bins
|
||||
|
||||
@ScatterMatrixXS.nu.setter
|
||||
|
|
@ -5443,6 +6073,49 @@ class ConsistentScatterMatrixXS(ConvolvedMGXS, ScatterMatrixXS):
|
|||
for mgxs in self.mgxs:
|
||||
mgxs.nu = nu
|
||||
|
||||
def load_from_statepoint(self, statepoint):
|
||||
"""Extracts tallies in an OpenMC StatePoint with the data needed to
|
||||
compute multi-group cross sections.
|
||||
|
||||
This method is needed to compute cross section data from tallies
|
||||
in an OpenMC StatePoint object.
|
||||
|
||||
.. note:: The statepoint must be linked with an OpenMC Summary object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
statepoint : openmc.StatePoint
|
||||
An OpenMC StatePoint object with tally data
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
When this method is called with a statepoint that has not been
|
||||
linked with a summary object.
|
||||
|
||||
"""
|
||||
|
||||
# Clear any tallies previously loaded from a statepoint
|
||||
if self.loaded_sp:
|
||||
self._tallies = None
|
||||
self._xs_tally = None
|
||||
self._rxn_rate_tally = None
|
||||
self._loaded_sp = False
|
||||
|
||||
if self.scatter_format == 'legendre':
|
||||
# Expand scores to match the format in the statepoint
|
||||
# e.g., "scatter-P2" -> "scatter-0", "scatter-1", "scatter-2"
|
||||
tally_key = '{}-P{}'.format(self.rxn_type, self.legendre_order)
|
||||
tally_key = \
|
||||
'{} probability matrix : {}'.format(self.rxn_type, tally_key)
|
||||
self.tallies[tally_key].scores = \
|
||||
[self.rxn_type + '-{}'.format(i)
|
||||
for i in range(self.legendre_order + 1)]
|
||||
elif self.scatter_format == 'histogram':
|
||||
self.tallies[self.rxn_type].scores = [self.rxn_type]
|
||||
|
||||
super(ConsistentScatterMatrixXS, self).load_from_statepoint(statepoint)
|
||||
|
||||
def get_condensed_xs(self, coarse_groups):
|
||||
condense_xs = \
|
||||
super(ConsistentScatterMatrixXS, self).get_condensed_xs(coarse_groups)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue