From efb3c0488f0d6b08c6b75d517bf06fba6ac34ab1 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 6 Sep 2015 18:51:43 -0400 Subject: [PATCH] Implemented Tally.summation(...) routine --- openmc/mgxs/mgxs.py | 61 +++++++++++++------------- openmc/tallies.py | 103 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 126 insertions(+), 38 deletions(-) diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index c01c8438a1..b2019a20aa 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -114,6 +114,7 @@ class MultiGroupXS(object): def __init__(self, domain=None, domain_type=None, energy_groups=None, name=''): + self._name = '' self._xs_type = None self._domain = None @@ -141,7 +142,7 @@ class MultiGroupXS(object): def __deepcopy__(self, memo): existing = memo.get(id(self)) - # If this is the first time we have tried to copy this object, create a copy + # If this is the first time we have tried to copy this object, copy it if existing is None: clone = type(self).__new__(type(self)) clone._name = self.name @@ -151,7 +152,7 @@ class MultiGroupXS(object): clone._energy_groups = copy.deepcopy(self.energy_groups, memo) clone._num_groups = self.num_groups clone._xs_tally = copy.deepcopy(self.xs_tally, memo) - clone._subdomain_offsets = copy.deepcopy(self.subdomain_indices, memo) + clone._subdomain_indices = copy.deepcopy(self.subdomain_indices, memo) clone._offset = copy.deepcopy(self.offset, memo) clone._tallies = dict() @@ -227,15 +228,15 @@ class MultiGroupXS(object): def _find_domain_offset(self): """Finds and stores the offset of the domain tally filter""" - tally = self.tallies[self.tallies.keys()[0]] + tally = self.tallies.values()[0] filter = tally.find_filter(self.domain_type, [self.domain.id]) self._offset = filter.offset - def set_subdomain_index(self, subdomain_id, offset): + def set_subdomain_index(self, subdomain_id, index): """Set the filter bin index for a subdomain of the domain. - This is primary useful when the domain type is 'distribcell', in which - case it can be useful to map each subdomain (a cell instance) to its + This is primarily useful when the domain type is 'distribcell', in + which case one may wish to map each subdomain (a cell instance) to its filter bin in the derived multi-group cross-section tally data array. Parameters @@ -248,10 +249,10 @@ class MultiGroupXS(object): """ cv.check_type('subdomain id', subdomain_id, Integral) - cv.check_type('subdomain offset', offset, Integral) + cv.check_type('subdomain offset', index, Integral) cv.check_greater_than('subdomain id', subdomain_id, 0, True) cv.check_greater_than('subdomain offset', subdomain_id, 0, True) - self._subdomain_indices[subdomain_id] = offset + self._subdomain_indices[subdomain_id] = index @abc.abstractmethod def _create_tallies(self, scores, all_filters, keys, estimator): @@ -274,8 +275,7 @@ class MultiGroupXS(object): """ cv.check_value('scores', scores, openmc.SCORE_TYPES) - # FIXME : Use @smharper's recursive iterable checker - # cv.check_type('filters', all_filters, openmc.Filter) + cv.check_iterable_type('filters', all_filters, openmc.Filter, 1, 2) cv.check_type('keys', keys, Iterable, basestring) cv.check_length('scores', scores, len(keys)) cv.check_value('estimator', estimator, ['analog', 'tracklength']) @@ -299,14 +299,17 @@ class MultiGroupXS(object): This method can be used to extract the indices into the multi-group cross-section tally data array for a subdomain (i.e., cell instance). + See also : get_subdomains + Parameters ---------- subdomains : Iterable of Integral or 'all' Subdomain IDs of interest Returns - indices : NumPy ndarray - Array of subdomain indices indexed in the order of the subdomains + ---------- + indices : ndarray + The subdomain indices indexed in the order of the subdomains Raises ------ @@ -319,8 +322,8 @@ class MultiGroupXS(object): cv.check_type('subdomains', subdomains, Iterable, Integral) if subdomains == 'all': - # FIXME: This isn't correct any more!! - # indices = np.arange(self.xs.shape[1]) + num_subdomains = len(self.subdomain_indices) + indices = np.arange(num_subdomains) else: indices = np.zeros(len(subdomains), dtype=np.int64) @@ -340,7 +343,7 @@ class MultiGroupXS(object): This method can be used to extract the subdomains for the multi-group cross-section from their indices in the tally data array. - See also : get_subdomain_offsets + See also : get_subdomain_indices Parameters ---------- @@ -348,7 +351,8 @@ class MultiGroupXS(object): Subdomain indices of interest Returns - subdomains : NumPy ndarray + ---------- + subdomains : ndarray Array of subdomain IDs indexed in the order of the indices Raises @@ -370,9 +374,9 @@ class MultiGroupXS(object): for i, index in enumerate(indices): if index in values: - subdomains[i] = keys[values.index(indices)] + subdomains[i] = keys[values.index(index)] else: - msg = 'Unable to get subdomain for offset "{0}" since it ' \ + msg = 'Unable to get subdomain for index "{0}" since it ' \ 'is not a valid index'.format(index) raise ValueError(msg) @@ -408,21 +412,20 @@ class MultiGroupXS(object): if self.xs_tally is None: msg = 'Unable to get cross-section since it has not been computed' raise ValueError(msg) - if groups != 'all': - cv.check_value('groups', groups, Iterable, Integral) - if subdomains != 'all': - cv.check_value('subdomains', subdomains, Iterable, Integral) filters = [] filter_bins = [] # Construct a collection of the domain filter bins - filters.append(self.domain_type) - filter_bins.append(subdomains) + if subdomains != 'all': + cv.check_value('subdomains', subdomains, Iterable, Integral) + filters.append(self.domain_type) + filter_bins.append(tuple(subdomains)) - # Construct a collection of the energy group filter bins - filters.append('energy') - filter_bins.append(self.energy_groups.get_group_bounds(groups)) + if groups != 'all': + cv.check_value('groups', groups, Iterable, Integral) + filters.append('energy') + filter_bins.append(self.energy_groups.get_group_bounds(groups)) # Query the multi-group cross-section tally for the data xs = self.xs_tally.get_values(filters=filters, @@ -465,9 +468,9 @@ class MultiGroupXS(object): for key, old_tally in avg_xs.tallies.items(): # FIXME: Need to create Tally.mean(...) slice_tally = old_tally.slice(filters=[avg_xs.domain_type], - filter_bins=subdomains) + filter_bins=[tuple(subdomains)]) avg_tally = slice_tally.mean(filters=[avg_xs.domain_type], - filter_bins=subdomains) + filter_bins=[tuple(subdomains)]) avg_xs.tallies[key] = avg_tally avg_xs.compute_xs() diff --git a/openmc/tallies.py b/openmc/tallies.py index 003acd9435..1140e18c88 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3,6 +3,8 @@ import copy import os import pickle import itertools +import functools +from operator import mul from numbers import Integral, Real from xml.etree import ElementTree as ET import sys @@ -1677,8 +1679,8 @@ class Tally(object): new_tally._derived = True new_tally.with_batch_statistics = True new_tally.name = self.name - new_tally._mean = self._mean + other - new_tally._std_dev = self._std_dev + new_tally._mean = self.mean + other + new_tally._std_dev = self.std_dev new_tally.estimator = self.estimator new_tally.with_summary = self.with_summary new_tally.num_realization = self.num_realizations @@ -1746,8 +1748,8 @@ class Tally(object): new_tally = Tally(name='derived') new_tally._derived = True new_tally.name = self.name - new_tally._mean = self._mean - other - new_tally._std_dev = self._std_dev + new_tally._mean = self.mean - other + new_tally._std_dev = self.std_dev new_tally.estimator = self.estimator new_tally.with_summary = self.with_summary new_tally.num_realization = self.num_realizations @@ -1816,8 +1818,8 @@ class Tally(object): new_tally = Tally(name='derived') new_tally._derived = True new_tally.name = self.name - new_tally._mean = self._mean * other - new_tally._std_dev = self._std_dev * np.abs(other) + new_tally._mean = self.mean * other + new_tally._std_dev = self.std_dev * np.abs(other) new_tally.estimator = self.estimator new_tally.with_summary = self.with_summary new_tally.num_realization = self.num_realizations @@ -1886,8 +1888,8 @@ class Tally(object): new_tally = Tally(name='derived') new_tally._derived = True new_tally.name = self.name - new_tally._mean = self._mean / other - new_tally._std_dev = self._std_dev * np.abs(1. / other) + new_tally._mean = self.mean / other + new_tally._std_dev = self.std_dev * np.abs(1. / other) new_tally.estimator = self.estimator new_tally.with_summary = self.with_summary new_tally.num_realization = self.num_realizations @@ -2085,7 +2087,7 @@ class Tally(object): """Build a sliced tally for the specified filters, scores and nuclides. This method constructs a new tally to encapsulate a subset of the data - represented by this tally. The subset of data to included in the tally + represented by this tally. The subset of data to include in the tally slice is determined by the scores, filters and nuclides specified in the input parameters. @@ -2200,6 +2202,89 @@ class Tally(object): return new_tally + def summation(self, scores=[], filters=[], filter_bins=[], nuclides=[]): + """Build a sliced tally for the specified filters, scores and nuclides. + + This method constructs a new tally to encapsulate a subset of the data + represented by this tally. The subset of data to include in the tally + slice is determined by the scores, filters and nuclides specified in + the input parameters. + + Parameters + ---------- + scores : list + A list of one or more score strings to sum across + (e.g., ['absorption', 'nu-fission']; default is []) + + filters : list + A list of filter type strings to sum across + (e.g., ['mesh', 'energy']; default is []) + + filter_bins : list of Iterables + A list of the filter bins corresponding to the filter_types + parameter (e.g., [(1,), (0., 0.625e-6)]; default is []). Each bin + in the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for the + cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of + floats for 'energy' and 'energyout' filters corresponding to the + energy boundaries of the bin of interest. The bin is a (x,y,z) + 3-tuple for 'mesh' filters corresponding to the mesh cell of + interest. The order of the bins in the list must correspond of the + filter_types parameter. + + nuclides : list + A list of nuclide name strings to sum across + (e.g., ['U-235', 'U-238']; default is []) + + Returns + ------- + Tally + A new tally which encapsulates the sum of data requested. + + """ + + # If user did not specify any scores, do not sum across scores + if len(scores) == 0: + scores = [[]] + # Sum across any scores specified by the user + else: + scores = [[score] for score in scores] + + # If user did not specify any nuclides, do not sum across nuclides + if len(nuclides) == 0: + nuclides = [[]] + # Sum across any nuclides specified by the user + else: + nuclides = [[nuclide] for nuclide in nuclides] + + # If user did not specify any filter bins, do not sum across filter bins + if len(filters) == 0: + filter_bins = [[]] + filters = [[]] + # Sum across any filter bins specified by the user + else: + filter_bins = list(itertools.product(*filter_bins)) + filter_bins = [list(filter_bin) for filter_bin in filter_bins] + filters = [filters] + + # Initialize Tally sum + tally_sum = 0 + + # Iterate over all Tally slice operands in summation + prod = [scores, filters, filter_bins, nuclides] + for scores, filters, filter_bins, nuclides in itertools.product(*prod): + tally_slice = self.get_slice(scores, filters, filter_bins, nuclides) + + # Remove filters summed across to avoid bulky CrossFilters + for filter in reversed(tally_slice.filters): + if filter.type in filters: + tally_slice.remove_filter(filter) + + # Accumulate this Tally slice into the Tally sum + tally_sum += tally_slice + + return tally_sum + class TalliesFile(object): """Tallies file used for an OpenMC simulation. Corresponds directly to the