From 25e67baa3222222693e11283838b2d01ebbbc733 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Mon, 28 Sep 2015 15:26:01 -0400 Subject: [PATCH] Made Python API MultiGroupXS object xs_type attribute by_nuclide; nuclides paramter can now be all, sum or a list of nuclides --- openmc/mgxs/mgxs.py | 500 +++++++++++++++++++++++++++----------------- 1 file changed, 309 insertions(+), 191 deletions(-) diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 4f0de50494..7123223e09 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -44,6 +44,8 @@ class MultiGroupXS(object): The domain type for spatial homogenization energy_groups : EnergyGroups The energy group structure for energy condensation + by_nuclide : bool + If true, computes multi-group cross sections for each nuclide in domain name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC tallies.xml file. @@ -54,6 +56,8 @@ class MultiGroupXS(object): Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) + by_nuclide : bool + If true, computes multi-group cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} @@ -74,11 +78,11 @@ class MultiGroupXS(object): __metaclass__ = abc.ABCMeta def __init__(self, domain=None, domain_type=None, - energy_groups=None, xs_type='macro', name=''): + energy_groups=None, by_nuclide=False, name=''): self._name = '' self._rxn_type = None - self._xs_type = None + self._by_nuclide = None self._domain = None self._domain_type = None self._energy_groups = None @@ -87,7 +91,7 @@ class MultiGroupXS(object): self._xs_tally = None self.name = name - self.xs_type = xs_type + self.by_nuclide = by_nuclide if domain_type is not None: self.domain_type = domain_type if domain is not None: @@ -103,7 +107,7 @@ class MultiGroupXS(object): clone = type(self).__new__(type(self)) clone._name = self.name clone._rxn_type = self.rxn_type - clone._xs_type = self.xs_type + clone._by_nuclide = self.by_nuclide clone._domain = self.domain clone._domain_type = self.domain_type clone._energy_groups = copy.deepcopy(self.energy_groups, memo) @@ -131,8 +135,8 @@ class MultiGroupXS(object): return self._rxn_type @property - def xs_type(self): - return self._xs_type + def by_nuclide(self): + return self._by_nuclide @property def domain(self): @@ -169,10 +173,10 @@ class MultiGroupXS(object): cv.check_type('name', name, basestring) self._name = name - @xs_type.setter - def xs_type(self, xs_type): - cv.check_value('xs_type', xs_type, ('macro', 'micro')) - self._xs_type = xs_type + @by_nuclide.setter + def by_nuclide(self, by_nuclide): + cv.check_type('by_nuclide', by_nuclide, bool) + self._by_nuclide = by_nuclide @domain.setter def domain(self, domain): @@ -213,16 +217,18 @@ class MultiGroupXS(object): return nuclides.keys() def get_nuclide_density(self, nuclide): - """Get the atomic number density for a nuclide in the cross section's - spatial domain. + """Get the atomic number density in units of atoms/b-cm for a nuclide + in the cross section's spatial domain. + Paramters + --------- nuclide : str A nuclide name string (e.g., 'U-235') Returns ------- density : Real - The atomic number density for the nuclide of interest + The atomic number density (atom/b-cm) for the nuclide of interest Raises ------ @@ -246,17 +252,22 @@ class MultiGroupXS(object): return density def get_nuclide_densities(self, nuclides='all'): - """Get all atomic number densities in the cross section's spatial domain. + """Get an array of atomic number densities in units of atom/b-cm for all + nuclides in the cross section's spatial domain. - nuclides : Iterable of str or 'all' - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is 'all') + Paramters + --------- + nuclides : Iterable of str or 'all' or 'sum' + A list of nuclide name strings (e.g., ['U-235', 'U-238']). The + special string 'all' will return the atom densities for all nuclides + in the spatial domain. The special string 'sum' will return the atom + density summed across all nuclides in the spatial domain. Returns ------- densities : ndarray of float - The atomic number densities corresponding to each of the nuclides - in the problem domain + An array of the atomic number densities (atom/b-cm) for each of the + nuclides in the problem domain Raises ------ @@ -268,15 +279,21 @@ class MultiGroupXS(object): if self.domain is None: raise ValueError('Unable to get nuclide densities without a domain') - # If the user requested the densities for all nuclides, get a list of - # all of the nuclide name strings if nuclides == 'all': - nuclides =self.domain.get_all_nuclides() + nuclides = self.domain.get_all_nuclides() - # Loop over each nuclide and find and store its atomic number density - densities = np.zeros(len(nuclides), dtype=np.float) - for i, nuclide in enumerate(nuclides): - densities[i] = self.get_nuclide_density(nuclide) + # Sum the atomic number densities for all nuclides + elif nuclides == 'sum': + nuclides = self.get_all_nuclides() + densities = np.zeros(1, dtype=np.float) + for i, nuclide in enumerate(nuclides): + densities[0] += self.get_nuclide_density(nuclide) + + # Store each nuclide's atomic number density in an array + else: + densities = np.zeros(len(nuclides), dtype=np.float) + for i, nuclide in enumerate(nuclides): + densities[i] = self.get_nuclide_density(nuclide) return densities @@ -323,23 +340,25 @@ class MultiGroupXS(object): for filter in filters: self.tallies[key].add_filter(filter) - # If this is a microscopic cross-section, add all nuclides to tally - if self.xs_type == 'micro' and score != 'flux': + # If this is a by nuclide cross-section, add all nuclides to Tally + if self.by_nuclide and score != 'flux': all_nuclides = self.domain.get_all_nuclides() for nuclide in all_nuclides: self.tallies[key].add_nuclide(nuclide) @abc.abstractmethod def compute_xs(self): - """Computes multi-group cross sections using OpenMC tally arithmetic.""" + """Performs generic cleanup after a subclass' uses tally arithmetic to + compute a multi-group cross section as a derived tally.""" - # If a microscopic cross-section, replace CrossNuclides with originals - if self.xs_type == 'micro': + # If computing xs for each nuclide, replace CrossNuclides with originals + if self.by_nuclide: self.xs_tally._nuclides = [] nuclides = self.domain.get_all_nuclides() for nuclide in nuclides: - self.xs_tally.add_nuclide(nuclide) + self.xs_tally.add_nuclide(openmc.Nuclide(nuclide)) + # Remove NaNs which may have resulted from divide-by-zero operations self._xs_tally._mean = np.nan_to_num(self.xs_tally.mean) self._xs_tally._std_dev = np.nan_to_num(self.xs_tally.std_dev) @@ -350,6 +369,8 @@ class MultiGroupXS(object): This method is needed to compute cross section data from tallies in an OpenMC StatePoint object. + NOTE: The statepoint must first be linked with an OpenMC Summary object. + Parameters ---------- statepoint : openmc.StatePoint @@ -413,9 +434,11 @@ class MultiGroupXS(object): subdomains : Iterable of Integral or 'all' Subdomain IDs of interest - nuclides : Iterable of str or 'all' - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is 'all') + nuclides : Iterable of str or 'all' or 'sum' + A list of nuclide name strings (e.g., ['U-235', 'U-238']). The + special string 'all' (default) 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. xs_type: {'macro' or 'micro'} Return the macro or micro cross section in units of cm^-1 or barns @@ -462,20 +485,32 @@ class MultiGroupXS(object): filters.append('energy') filter_bins.append((self.energy_groups.get_group_bounds(group),)) - # Construct list of nuclides for all requested nuclides - if nuclides != 'all' and nuclides != ['total']: - cv.check_iterable_type('nuclides', nuclides, basestring) + # Construct a collection of the nuclides to retrieve from the xs tally + # NOTE: We must not override the "nuclides" parameter since it is used + # to retrieve atomic number densities for micro xs + if self.by_nuclide: + if nuclides == 'all' or nuclides == 'sum': + query_nuclides = self.get_all_nuclides() + else: + query_nuclides = nuclides else: - nuclides = [] + query_nuclides = ['total'] - # Query the multi-group cross section tally for the data - xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, - nuclides=nuclides, value=value) + # 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(filters=filters, + filter_bins=filter_bins, value=value) + else: + xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, + nuclides=query_nuclides, value=value) - # If user requested microscopic cross sections from an object with - # microscopic cross sections, divide by atom number densities - if self.xs_type == 'micro' and xs_type == 'micro': - densities = self.get_nuclide_densities(nuclides) + # 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] @@ -559,8 +594,8 @@ class MultiGroupXS(object): """Construct a subdomain-averaged version of this cross section. This is primarily useful for averaging across distribcell instances. - This routine performs spatial homogenization to compute the - scalar flux-weighted average cross section across the subdomains. + This routine performs spatial homogenization to compute the scalar + flux-weighted average cross section across the subdomains. Parameters ---------- @@ -586,13 +621,12 @@ class MultiGroupXS(object): raise ValueError(msg) # Construct a collection of the subdomain filter bins to average across - if subdomains == 'all': - if self.domain_type == 'distribcell': - subdomains = np.arange(self.num_subdomains) - else: - subdomains = [self.domain.id] - else: + if subdomains != 'all': cv.check_iterable_type('subdomains', subdomains, Integral) + elif self.domain_type == 'distribcell': + subdomains = np.arange(self.num_subdomains) + else: + subdomains = [self.domain.id] # Clone this MultiGroupXS to initialize the subdomain-averaged version avg_xs = copy.deepcopy(self) @@ -644,25 +678,38 @@ class MultiGroupXS(object): subdomains : Iterable of Integral or 'all' The subdomain IDs of the cross sections to include in the report - nuclides : Iterable of str or 'all' - The nuclides of the cross-sections to include in the report + 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., ['U-235', 'U-238']). + The special string 'all' (default) 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. xs_type: {'macro' or 'micro'} Return the macro or micro cross section in units of cm^-1 or barns """ - cv.check_value('xs_type', xs_type, ['macro', 'micro']) - + # Construct a collection of the subdomains to report if subdomains != 'all': cv.check_iterable_type('subdomains', subdomains, Integral) - if nuclides != 'all': - cv.check_iterable_type('nuclides', nuclides, basestring) + elif self.domain_type == 'distribcell': + subdomains = np.arange(self.num_subdomains, dtype=np.int) else: - if self.xs_type == 'micro': - nuclides = self.domain.get_all_nuclides() + subdomains = [self.domain.id] + + # Construct a collection of the nuclides to report + if self.by_nuclide: + if nuclides == 'all': + nuclides = self.get_all_nuclides() + if nuclides == 'sum': + nuclides = ['sum'] else: - nuclides = ['total'] + cv.check_iterable_type('nuclides', nuclides, basestring) + else: + nuclides = ['sum'] + + cv.check_value('xs_type', xs_type, ['macro', 'micro']) # Build header for string with type and domain info string = 'Multi-Group XS\n' @@ -675,12 +722,6 @@ class MultiGroupXS(object): print(string) return - if subdomains == 'all': - if self.domain_type == 'distribcell': - subdomains = np.arange(self.num_subdomains, dtype=np.int) - else: - subdomains = [self.domain.id] - # Loop over all subdomains for subdomain in subdomains: @@ -691,7 +732,7 @@ class MultiGroupXS(object): for nuclide in nuclides: # Build header for nuclide type - if xs_type != 'total': + if nuclide != 'sum': string += '{0: <16}=\t{1}\n'.format('\tNuclide', nuclide) # Build header for cross section type @@ -719,7 +760,8 @@ class MultiGroupXS(object): print(string) - def build_hdf5_store(self, filename='mgxs', directory='mgxs', append=True): + def build_hdf5_store(self, filename='mgxs', directory='mgxs', + xs_type='macro', append=True): """Export the multi-group cross section data into an HDF5 binary file. This routine constructs an HDF5 file which stores the multi-group @@ -738,6 +780,9 @@ class MultiGroupXS(object): directory : str Directory for the HDF5 file (default is 'mgxs') + xs_type: {'macro' or 'micro'} + Store the macro or micro cross section in units of cm^-1 or barns + append : boolean If true, appends to an existing HDF5 file with the same filename directory (if one exists) @@ -776,13 +821,15 @@ class MultiGroupXS(object): else: xs_results = h5py.File(filename, 'w') - if self.xs_type == 'micro': + cv.check_value('xs_type', xs_type, ['macro', 'micro']) + + if self.by_nuclide: nuclides = self.domain.get_all_nuclides() densities = np.zeros(len(nuclides), dtype=np.float) for i, nuclide in enumerate(nuclides): densities[i] = nuclides[nuclide][1] else: - nuclides = ['total'] + nuclides = ['sum'] # Create an HDF5 group within the file for the domain domain_type_group = xs_results.require_group(self.domain_type) @@ -813,7 +860,7 @@ class MultiGroupXS(object): # Create a separate HDF5 group for each nuclide for j, nuclide in enumerate(nuclides): - if nuclide != 'total': + if nuclide != 'sum': nuclide_group = rxn_group.require_group(nuclide) nuclide_group.require_dataset('density', dtype=np.float64, data=[densities[j]], shape=(1,)) @@ -822,9 +869,9 @@ class MultiGroupXS(object): # Extract the cross section for this subdomain and nuclide average = self.get_xs(subdomains=[subdomain], nuclides=[nuclide], - xs_type=self.xs_type, value='mean') + xs_type=xs_type, value='mean') std_dev = self.get_xs(subdomains=[subdomain], nuclides=[nuclide], - xs_type=self.xs_type, value='std_dev') + xs_type=xs_type, value='std_dev') average = average.squeeze() std_dev = std_dev.squeeze() @@ -837,7 +884,8 @@ class MultiGroupXS(object): # Close the MultiGroup results HDF5 file xs_results.close() - def export_xs_data(self, filename='mgxs', directory='mgxs', format='csv'): + def export_xs_data(self, filename='mgxs', directory='mgxs', + format='csv', groups='all', xs_type='macro'): """Export the multi-group cross section data to a file. This routine leverages the functionality in the Pandas library to @@ -855,23 +903,18 @@ class MultiGroupXS(object): format : {'csv', 'excel', 'pickle', 'latex'} The format for the exported data file - groups : {'indices' or 'bounds'} - When set to 'indices' (default), integer group indices are inserted - in the energy in/out column(s) of the DataFrame. When it is 'bounds' - the lower and upper energy bounds are used. + groups : Iterable of Integral or 'all' + Energy groups of interest - summary : None or Summary - An optional Summary object to be used to construct columns for - distribcell tally filters (default is None). The geometric - information in the Summary object is embedded into a Multi-index - column with a geometric "path" to each distribcell intance. - NOTE: This option requires the OpenCG Python package. + xs_type: {'macro' or 'micro'} + Store the macro or micro cross section in units of cm^-1 or barns """ cv.check_type('filename', filename, basestring) cv.check_type('directory', directory, basestring) cv.check_value('format', format, ['csv', 'excel', 'pickle', 'latex']) + cv.check_value('xs_type', xs_type, ['macro', 'micro']) # Make directory if it does not exist if not os.path.exists(directory): @@ -881,7 +924,7 @@ class MultiGroupXS(object): filename = filename.replace(' ', '-') # Get a Pandas DataFrame for the data - df = self.get_pandas_dataframe() + df = self.get_pandas_dataframe(groups=groups, xs_type=xs_type) # Capitalize column label strings df.columns = df.columns.astype(str) @@ -916,7 +959,8 @@ class MultiGroupXS(object): modified.write(data) modified.write('\n\\end{document}') - def get_pandas_dataframe(self, groups='indices', summary=None): + def get_pandas_dataframe(self, groups='all', nuclides='all', + xs_type='macro', summary=None): """Build a Pandas DataFrame for the MultiGroupXS data. This routine leverages the Tally.get_pandas_dataframe(...) routine, but @@ -924,15 +968,23 @@ class MultiGroupXS(object): Parameters ---------- - groups : {'indices' or 'bounds'} - When set to 'indices' (default), integer group indices are inserted - in the energy in/out column(s) of the DataFrame. When it is 'bounds' - the lower and upper energy bounds are used. + groups : Iterable of Integral or 'all' + Energy groups of interest + + 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., ['U-235', 'U-238']). + The special string 'all' (default) 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. + + xs_type: {'macro' or 'micro'} + Return macro or micro cross section in units of cm^-1 or barns summary : None or Summary An optional Summary object to be used to construct columns for distribcell tally filters (default is None). The geometric - information in the Summary object is embedded into a Multi-index + information in the Summary object is embedded into a multi-index column with a geometric "path" to each distribcell intance. NOTE: This option requires the OpenCG Python package. @@ -954,6 +1006,12 @@ class MultiGroupXS(object): 'cross section has not been computed' raise ValueError(msg) + if groups != 'all': + cv.check_iterable_type('groups', groups, Integral) + if nuclides != 'all' and nuclides != 'sum': + cv.check_iterable_type('nuclides', nuclides, basestring) + cv.check_value('xs_type', xs_type, ['macro', 'micro']) + # Get a Pandas DataFrame from the derived xs tally df = self.xs_tally.get_pandas_dataframe(summary=summary) @@ -963,30 +1021,51 @@ class MultiGroupXS(object): else: df = df.drop('score', axis=1) - # Use group indices in place of energy bounds ("1" for fastest group) - if groups == 'indices': + # Rename energy(out) columns + columns = [] + if 'energy [MeV]' in df: + df.rename(columns={'energy [MeV]': 'group in'}, inplace=True) + columns.append('group in') + if 'energyout [MeV]' in df: + df.rename(columns={'energyout [MeV]': 'group out'}, inplace=True) + columns.append('group out') - # Rename the column label for energy in the dataframe - columns = [] - if 'energy [MeV]' in df: - df.rename(columns={'energy [MeV]': 'group in'}, inplace=True) - columns.append('group in') - if 'energyout [MeV]' in df: - df.rename(columns={'energyout [MeV]': 'group out'}, inplace=True) - columns.append('group out') + # Loop over all energy groups and override the bounds with indices + template = '({0:.1e} - {1:.1e})' + bins = self.energy_groups.group_edges + for column in columns: + for i in range(self.num_groups): + group = template.format(bins[i], bins[i+1]) + row_indices = df[column] == group + df.loc[row_indices, column] = self.num_groups - i - # Loop over all energy groups and override the bounds with indices - template = '({0:.1e} - {1:.1e})' - bins = self.energy_groups.group_edges - for column in columns: - for i in range(self.num_groups): - group = template.format(bins[i], bins[i+1]) - row_indices = df[column] == group - df.loc[row_indices, column] = self.num_groups - i + # Select out those groups the user requested + if groups != 'all': + if 'group in' in df: + df = df[df['group in'].isin(groups)] + if 'group out' in df: + df = df[df['group out'].isin(groups)] - # Sort the dataframe by domain type id (e.g., distribcell id) and - # energy groups such that data is from fast to thermal - df.sort([self.domain_type] + columns, inplace=True) + # Sum up cross sections across nuclides if requested + if self.by_nuclide and nuclides == 'sum': + non_nuclide_cols = list(df.columns[df.columns != 'nuclide']) + df = df.groupby(non_nuclide_cols, as_index=False)['nuclide'].sum() + # If the user requested specific nuclides, remove others from dataframe + elif nuclides != 'all' and nuclides != 'sum': + df = df[df.nuclide.isin(nuclides)] + + # If user requested micro cross sections, divide out the atom densities + if xs_type == 'micro': + if self.by_nuclide: + densities = self.get_nuclide_densities(nuclides) + else: + densities = self.get_nuclide_densities('sum') + df['mean'] /= densities + df['std. dev.'] /= densities + + # Sort the dataframe by domain type id (e.g., distribcell id) and + # energy groups such that data is from fast to thermal + df.sort([self.domain_type] + columns, inplace=True) return df @@ -994,8 +1073,8 @@ class MultiGroupXS(object): class TotalXS(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(TotalXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(TotalXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'total' def create_tallies(self): @@ -1025,8 +1104,8 @@ class TotalXS(MultiGroupXS): class TransportXS(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(TransportXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(TransportXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'transport' def create_tallies(self): @@ -1064,8 +1143,8 @@ class TransportXS(MultiGroupXS): class AbsorptionXS(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(AbsorptionXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(AbsorptionXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'absorption' def create_tallies(self): @@ -1095,8 +1174,8 @@ class AbsorptionXS(MultiGroupXS): class CaptureXS(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(CaptureXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(CaptureXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'capture' def create_tallies(self): @@ -1127,8 +1206,8 @@ class CaptureXS(MultiGroupXS): class FissionXS(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(FissionXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(FissionXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'fission' def create_tallies(self): @@ -1158,8 +1237,8 @@ class FissionXS(MultiGroupXS): class NuFissionXS(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(NuFissionXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(NuFissionXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'nu-fission' def create_tallies(self): @@ -1189,8 +1268,8 @@ class NuFissionXS(MultiGroupXS): class ScatterXS(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(ScatterXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(ScatterXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'scatter' def create_tallies(self): @@ -1220,8 +1299,8 @@ class ScatterXS(MultiGroupXS): class NuScatterXS(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(NuScatterXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(NuScatterXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'nu-scatter' def create_tallies(self): @@ -1251,8 +1330,8 @@ class NuScatterXS(MultiGroupXS): class ScatterMatrixXS(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(ScatterMatrixXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(ScatterMatrixXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'scatter matrix' def create_tallies(self): @@ -1316,9 +1395,11 @@ class ScatterMatrixXS(MultiGroupXS): subdomains : Iterable of Integral or 'all' Subdomain IDs of interest - nuclides : Iterable of str or 'all' - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is 'all') + nuclides : Iterable of str or 'all' or 'sum' + A list of nuclide name strings (e.g., ['U-235', 'U-238']). The + special string 'all' (default) 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. xs_type: {'macro' or 'micro'} Return the macro or micro cross section in units of cm^-1 or barns @@ -1372,21 +1453,34 @@ class ScatterMatrixXS(MultiGroupXS): filters.append('energyout') filter_bins.append((self.energy_groups.get_group_bounds(group),)) - # Construct list of nuclides for all requested nuclides - if nuclides != 'all' and nuclides != ['total']: - cv.check_iterable_type('nuclides', nuclides, basestring) + # Construct a collection of the nuclides to retrieve from the xs tally + # NOTE: We must not override the "nuclides" parameter since it is used + # to retrieve atomic number densities for micro xs + if self.by_nuclide: + if nuclides == 'all' or nuclides == 'sum': + query_nuclides = self.get_all_nuclides() + else: + query_nuclides = nuclides else: - nuclides = [] + 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(filters=filters, + filter_bins=filter_bins, value=value) + else: + xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, + nuclides=query_nuclides, value=value) - # Query the multi-group cross section tally for the data - xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, - nuclides=nuclides, value=value) xs = np.nan_to_num(xs) - # If user requested microscopic cross sections from an object with - # microscopic cross sections, divide by atom number densities - if self.xs_type == 'micro' and xs_type == 'micro': - densities = self.get_nuclide_densities(nuclides) + # 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] @@ -1400,25 +1494,38 @@ class ScatterMatrixXS(MultiGroupXS): subdomains : Iterable of Integral or 'all' The subdomain IDs of the cross sections to include in the report - nuclides : Iterable of str or 'all' - The nuclides of the cross-sections to include in the report + 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., ['U-235', 'U-238']). + The special string 'all' (default) 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. xs_type: {'macro' or 'micro'} Return the macro or micro cross section in units of cm^-1 or barns """ - cv.check_value('xs_type', xs_type, ['macro', 'micro']) - + # Construct a collection of the subdomains to report if subdomains != 'all': cv.check_iterable_type('subdomains', subdomains, Integral) - if nuclides != 'all': - cv.check_iterable_type('nuclides', nuclides, basestring) + elif self.domain_type == 'distribcell': + subdomains = np.arange(self.num_subdomains, dtype=np.int) else: - if self.xs_type == 'micro': - nuclides = self.domain.get_all_nuclides() + subdomains = [self.domain.id] + + # Construct a collection of the nuclides to report + if self.by_nuclide: + if nuclides == 'all': + nuclides = self.get_all_nuclides() + if nuclides == 'sum': + nuclides = ['sum'] else: - nuclides = ['total'] + cv.check_iterable_type('nuclides', nuclides, basestring) + else: + nuclides = ['sum'] + + cv.check_value('xs_type', xs_type, ['macro', 'micro']) # Build header for string with type and domain info string = 'Multi-Group XS\n' @@ -1456,7 +1563,7 @@ class ScatterMatrixXS(MultiGroupXS): for nuclide in nuclides: # Build header for nuclide type - if xs_type != 'total': + if xs_type != 'sum': string += '{0: <16}=\t{1}\n'.format('\tNuclide', nuclide) # Build header for cross section type @@ -1491,8 +1598,8 @@ class ScatterMatrixXS(MultiGroupXS): class NuScatterMatrixXS(ScatterMatrixXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(NuScatterMatrixXS, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(NuScatterMatrixXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'nu-scatter matrix' def create_tallies(self): @@ -1541,8 +1648,8 @@ class NuScatterMatrixXS(ScatterMatrixXS): class Chi(MultiGroupXS): def __init__(self, domain=None, domain_type=None, - groups=None, xs_type='macro', name=''): - super(Chi, self).__init__(domain, domain_type, groups, xs_type, name) + groups=None, by_nuclide=False, name=''): + super(Chi, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'chi' def create_tallies(self): @@ -1593,9 +1700,9 @@ class Chi(MultiGroupXS): nuclides : Iterable of str or 'all' or 'sum' A list of nuclide name strings (e.g., ['U-235', 'U-238']). The - special string 'all' will return the cross-section for all nuclides - in the spatial domain. The special string 'sum' will return the sum - across all nuclides weighted by the isotope-specific fission source. + special string 'all' (default) 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. xs_type: {'macro' or 'micro'} This parameter is not relevant for chi but is included here to @@ -1624,6 +1731,7 @@ class Chi(MultiGroupXS): raise ValueError(msg) cv.check_value('value', value, ['mean', 'std_dev', 'rel_err']) + cv.check_value('xs_type', xs_type, ['macro', 'micro']) filters = [] filter_bins = [] @@ -1642,33 +1750,43 @@ class Chi(MultiGroupXS): filters.append('energyout') filter_bins.append((self.energy_groups.get_group_bounds(group),)) - # Construct list of nuclides for all requested nuclides - if nuclides != 'all' and nuclides != ['total']: - cv.check_iterable_type('nuclides', nuclides, basestring) + # If chi was computed for each nuclide in the domain + if self.by_nuclide: + + # Get the sum as the fission source weighted average chi for all + # nuclides in the domain + if nuclides == 'sum': + + # Retrieve the fission production tallies + nu_fission_in = self.tallies['nu-fission-in'] + nu_fission_out = self.tallies['nu-fission-out'] + + # Sum out all nuclides + nuclides = self.get_all_nuclides() + nu_fission_in = nu_fission_in.summation(nuclides=nuclides) + nu_fission_out = nu_fission_out.summation(nuclides=nuclides) + + # Compute chi and store it as the xs_tally attribute so we can use + # the generic get_xs routine + xs_tally = nu_fission_out / nu_fission_in + xs = xs_tally.get_values(filters=filters, + filter_bins=filter_bins, value=value) + + # Get chi for all nuclides in the domain + elif nuclides == 'all': + nuclides = self.get_all_nuclides() + xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, + nuclides=nuclides, value=value) + + # Get chi for user-specified nuclides in the domain + else: + cv.check_iterable_type('nuclides', nuclides, basestring) + xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, + nuclides=nuclides, value=value) + + # If chi was computed as an average of nuclides in the domain else: - nuclides = [] - - # Special case for the "macroscopic-from-microscopic" chi - # This is needed since chi is fission source rather than flux-weighted - if nuclides == 'sum' and self.xs_type == 'micro': - - # Retrieve the fission production tallies - nu_fission_in = self.tallies['nu-fission-in'] - nu_fission_out = self.tallies['nu-fission-out'] - - # Sum out all nuclides - nuclides = self.get_all_nuclides() - nu_fission_in = nu_fission_in.summation(nuclides=nuclides) - nu_fission_out = nu_fission_out.summation(nuclides=nuclides) - - # Compute chi and store it as the xs_tally attribute so we can use - # the generic get_xs routine - xs_tally = nu_fission_out / nu_fission_in - xs = xs_tally.get_values(filters=filters, - filter_bins=filter_bins, value=value) - - else: - xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, - nuclides=nuclides, value=value) + xs = self.xs_tally.get_values(filters=filters, + filter_bins=filter_bins, value=value) return xs \ No newline at end of file