Source code for openmc.mgxs.mgxs

from __future__ import division

from collections import OrderedDict
from numbers import Integral
import warnings
import os
import sys
import copy
import abc

import numpy as np

import openmc
import openmc.checkvalue as cv
from openmc.mgxs import EnergyGroups


if sys.version_info[0] >= 3:
    basestring = str


# Supported cross section types
MGXS_TYPES = ['total',
              'transport',
              'nu-transport',
              'absorption',
              'capture',
              'fission',
              'nu-fission',
              'kappa-fission',
              'scatter',
              'nu-scatter',
              'scatter matrix',
              'nu-scatter matrix',
              'multiplicity matrix',
              'nu-fission matrix',
              'chi']


# Supported domain types
# TODO: Implement Mesh domains
DOMAIN_TYPES = ['cell',
                'distribcell',
                'universe',
                'material']

# Supported domain classes
# TODO: Implement Mesh domains
_DOMAINS = (openmc.Cell,
            openmc.Universe,
            openmc.Material)


[docs]class MGXS(object): """An abstract multi-group cross section for some energy group structure within some spatial domain. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. NOTE: Users should instantiate the subclasses of this abstract class. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ # This is an abstract class which cannot be instantiated __metaclass__ = abc.ABCMeta def __init__(self, domain=None, domain_type=None, energy_groups=None, by_nuclide=False, name=''): self._name = '' self._rxn_type = None self._by_nuclide = None self._nuclides = None self._domain = None self._domain_type = None self._energy_groups = None self._tally_trigger = None self._tallies = None self._rxn_rate_tally = None self._xs_tally = None self._sparse = False self._loaded_sp = False self._derived = False self._hdf5_key = None self.name = name self.by_nuclide = by_nuclide if domain_type is not None: self.domain_type = domain_type if domain is not None: self.domain = domain if energy_groups is not None: self.energy_groups = energy_groups def __deepcopy__(self, memo): existing = memo.get(id(self)) # 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 clone._rxn_type = self.rxn_type clone._by_nuclide = self.by_nuclide clone._nuclides = copy.deepcopy(self._nuclides) clone._domain = self.domain clone._domain_type = self.domain_type clone._energy_groups = copy.deepcopy(self.energy_groups, memo) clone._tally_trigger = copy.deepcopy(self.tally_trigger, memo) clone._rxn_rate_tally = copy.deepcopy(self._rxn_rate_tally, memo) clone._xs_tally = copy.deepcopy(self._xs_tally, memo) clone._sparse = self.sparse clone._derived = self.derived clone._tallies = OrderedDict() for tally_type, tally in self.tallies.items(): clone.tallies[tally_type] = copy.deepcopy(tally, memo) memo[id(self)] = clone return clone # If this object has been copied before, return the first copy made else: return existing @property def name(self): return self._name @property def rxn_type(self): return self._rxn_type @property def by_nuclide(self): return self._by_nuclide @property def domain(self): return self._domain @property def domain_type(self): return self._domain_type @property def energy_groups(self): return self._energy_groups @property def tally_trigger(self): return self._tally_trigger @property def num_groups(self): return self.energy_groups.num_groups @property def scores(self): return ['flux', self.rxn_type] @property def filters(self): group_edges = self.energy_groups.group_edges energy_filter = openmc.Filter('energy', group_edges) return [[energy_filter]] * len(self.scores) @property def tally_keys(self): return self.scores @property def estimator(self): return 'tracklength' @property def tallies(self): # Instantiate tallies if they do not exist if self._tallies is None: # Initialize a collection of Tallies self._tallies = OrderedDict() # Create a domain Filter object domain_filter = openmc.Filter(self.domain_type, self.domain.id) # Create each Tally needed to compute the multi group cross section tally_metadata = zip(self.scores, self.tally_keys, self.filters) for score, key, filters in tally_metadata: self._tallies[key] = openmc.Tally(name=self.name) self._tallies[key].scores = [score] self._tallies[key].estimator = self.estimator self._tallies[key].filters = [domain_filter] # If a tally trigger was specified, add it to each tally if self.tally_trigger: trigger_clone = copy.deepcopy(self.tally_trigger) trigger_clone.scores = [score] self._tallies[key].triggers.append(trigger_clone) # Add non-domain specific Filters (e.g., 'energy') to the Tally for add_filter in filters: self._tallies[key].filters.append(add_filter) # If this is a by-nuclide cross-section, add nuclides to Tally if self.by_nuclide and score != 'flux': all_nuclides = self.get_all_nuclides() for nuclide in all_nuclides: self._tallies[key].nuclides.append(nuclide) else: self._tallies[key].nuclides.append('total') return self._tallies @property def rxn_rate_tally(self): if self._rxn_rate_tally is None: 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: if self.tallies is None: msg = 'Unable to get xs_tally since tallies have ' \ 'not been loaded from a statepoint' raise ValueError(msg) self._xs_tally = self.rxn_rate_tally / self.tallies['flux'] self._compute_xs() return self._xs_tally @property def sparse(self): return self._sparse @property def num_subdomains(self): domain_filter = self.xs_tally.find_filter(self.domain_type) return domain_filter.num_bins @property def num_nuclides(self): if self.by_nuclide: return len(self.get_all_nuclides()) else: return 1 @property def nuclides(self): if self.by_nuclide: return self.get_all_nuclides() else: return 'sum' @property def loaded_sp(self): return self._loaded_sp @property def derived(self): return self._derived @property def hdf5_key(self): if self._hdf5_key is not None: return self._hdf5_key else: return self._rxn_type @name.setter def name(self, name): cv.check_type('name', name, basestring) self._name = name @by_nuclide.setter def by_nuclide(self, by_nuclide): cv.check_type('by_nuclide', by_nuclide, bool) self._by_nuclide = by_nuclide @nuclides.setter def nuclides(self, nuclides): cv.check_iterable_type('nuclides', nuclides, basestring) self._nuclides = nuclides @domain.setter def domain(self, domain): cv.check_type('domain', domain, _DOMAINS) self._domain = domain # Assign a domain type if self.domain_type is None: if isinstance(domain, openmc.Material): self._domain_type = 'material' elif isinstance(domain, openmc.Cell): self._domain_type = 'cell' elif isinstance(domain, openmc.Universe): self._domain_type = 'universe' @domain_type.setter def domain_type(self, domain_type): cv.check_value('domain type', domain_type, DOMAIN_TYPES) self._domain_type = domain_type @energy_groups.setter def energy_groups(self, energy_groups): cv.check_type('energy groups', energy_groups, openmc.mgxs.EnergyGroups) self._energy_groups = energy_groups @tally_trigger.setter def tally_trigger(self, tally_trigger): cv.check_type('tally trigger', tally_trigger, openmc.Trigger) self._tally_trigger = tally_trigger @sparse.setter def sparse(self, sparse): """Convert tally data from NumPy arrays to SciPy list of lists (LIL) sparse matrices, and vice versa. This property may be used to reduce the amount of data in memory during tally data processing. The tally data will be stored as SciPy LIL matrices internally within the Tally object. All tally data access properties and methods will return data as a dense NumPy array. """ cv.check_type('sparse', sparse, bool) # Sparsify or densify the derived MGXS tallies and the base tallies if self._xs_tally: self.xs_tally.sparse = sparse if self._rxn_rate_tally: self.rxn_rate_tally.sparse = sparse for tally_name in self.tallies: self.tallies[tally_name].sparse = sparse self._sparse = sparse @staticmethod
[docs] def get_mgxs(mgxs_type, domain=None, domain_type=None, energy_groups=None, by_nuclide=False, name=''): """Return a MGXS subclass object for some energy group structure within some spatial domain for some reaction type. This is a factory method which can be used to quickly create MGXS subclass objects for various reaction types. Parameters ---------- mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', chi'} The type of multi-group cross section object to return domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes cross sections for each nuclide in domain. Defaults to False name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. Defaults to the empty string. Returns ------- openmc.mgxs.MGXS A subclass of the abstract MGXS class for the multi-group cross section type requested by the user """ cv.check_value('mgxs_type', mgxs_type, MGXS_TYPES) if mgxs_type == 'total': mgxs = TotalXS(domain, domain_type, energy_groups) elif mgxs_type == 'transport': mgxs = TransportXS(domain, domain_type, energy_groups) elif mgxs_type == 'nu-transport': mgxs = NuTransportXS(domain, domain_type, energy_groups) elif mgxs_type == 'absorption': mgxs = AbsorptionXS(domain, domain_type, energy_groups) elif mgxs_type == 'capture': mgxs = CaptureXS(domain, domain_type, energy_groups) elif mgxs_type == 'fission': mgxs = FissionXS(domain, domain_type, energy_groups) elif mgxs_type == 'nu-fission': mgxs = NuFissionXS(domain, domain_type, energy_groups) elif mgxs_type == 'kappa-fission': mgxs = KappaFissionXS(domain, domain_type, energy_groups) elif mgxs_type == 'scatter': mgxs = ScatterXS(domain, domain_type, energy_groups) elif mgxs_type == 'nu-scatter': mgxs = NuScatterXS(domain, domain_type, energy_groups) elif mgxs_type == 'scatter matrix': mgxs = ScatterMatrixXS(domain, domain_type, energy_groups) elif mgxs_type == 'nu-scatter matrix': mgxs = NuScatterMatrixXS(domain, domain_type, energy_groups) elif mgxs_type == 'multiplicity matrix': mgxs = MultiplicityMatrixXS(domain, domain_type, energy_groups) elif mgxs_type == 'nu-fission matrix': mgxs = NuFissionMatrixXS(domain, domain_type, energy_groups) elif mgxs_type == 'chi': mgxs = Chi(domain, domain_type, energy_groups) mgxs.by_nuclide = by_nuclide mgxs.name = name return mgxs
[docs] def get_all_nuclides(self): """Get all nuclides in the cross section's spatial domain. Returns ------- list of str A list of the string names for each nuclide in the spatial domain (e.g., ['U-235', 'U-238', 'O-16']) Raises ------ ValueError When this method is called before the spatial domain has been set. """ if self.domain is None: raise ValueError('Unable to get all nuclides without a domain') # If the user defined nuclides, return them if self._nuclides: return self._nuclides # Otherwise, return all nuclides in the spatial domain else: nuclides = self.domain.get_all_nuclides() return list(nuclides.keys())
[docs] def get_nuclide_density(self, nuclide): """Get the atomic number density in units of atoms/b-cm for a nuclide in the cross section's spatial domain. Parameters ---------- nuclide : str A nuclide name string (e.g., 'U-235') Returns ------- float The atomic number density (atom/b-cm) for the nuclide of interest Raises ------- ValueError When the density is requested for a nuclide which is not found in the spatial domain. """ cv.check_type('nuclide', nuclide, basestring) # Get list of all nuclides in the spatial domain nuclides = self.domain.get_all_nuclides() if nuclide not in nuclides: msg = 'Unable to get density for nuclide "{0}" which is not in ' \ '{1} "{2}"'.format(nuclide, self.domain_type, self.domain.id) ValueError(msg) density = nuclides[nuclide][1] return density
[docs] def get_nuclide_densities(self, nuclides='all'): """Get an array of atomic number densities in units of atom/b-cm for all nuclides in the cross section's spatial domain. Parameters ---------- 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. Defaults to 'all'. Returns ------- numpy.ndarray of float An array of the atomic number densities (atom/b-cm) for each of the nuclides in the spatial domain Raises ------ ValueError When this method is called before the spatial domain has been set. """ if self.domain is None: raise ValueError('Unable to get nuclide densities without a domain') # Sum the atomic number densities for all nuclides if nuclides == 'sum': nuclides = self.get_all_nuclides() densities = np.zeros(1, dtype=np.float) for nuclide in nuclides: densities[0] += self.get_nuclide_density(nuclide) # Tabulate the atomic number densities for all nuclides elif nuclides == 'all': nuclides = self.get_all_nuclides() densities = np.zeros(self.num_nuclides, dtype=np.float) for i, nuclide in enumerate(nuclides): densities[i] += self.get_nuclide_density(nuclide) # Tabulate the atomic number densities for each specified nuclide else: densities = np.zeros(len(nuclides), dtype=np.float) for i, nuclide in enumerate(nuclides): densities[i] = self.get_nuclide_density(nuclide) return densities
def _compute_xs(self): """Performs generic cleanup after a subclass' uses tally arithmetic to compute a multi-group cross section as a derived tally. This method replaces CrossNuclides generated by tally arithmetic with the original Nuclide objects in the xs_tally instance attribute. The simple Nuclides allow for cleaner output through Pandas DataFrames as well as simpler data access through the get_xs(...) class method. In addition, this routine resets NaNs in the multi group cross section array to 0.0. This may be needed occur if no events were scored in certain tally bins, which will lead to a divide-by-zero situation. """ # If computing xs for each nuclide, replace CrossNuclides with originals if self.by_nuclide: self.xs_tally._nuclides = [] nuclides = self.get_all_nuclides() for nuclide in nuclides: self.xs_tally.nuclides.append(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) self.xs_tally.sparse = self.sparse
[docs] 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 first 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. """ cv.check_type('statepoint', statepoint, openmc.statepoint.StatePoint) if statepoint.summary is None: msg = 'Unable to load data from a statepoint which has not been ' \ 'linked with a summary file' raise ValueError(msg) # Override the domain object that loaded from an OpenMC summary file # NOTE: This is necessary for micro cross-sections which require # the isotopic number densities as computed by OpenMC if self.domain_type == 'cell' or self.domain_type == 'distribcell': self.domain = statepoint.summary.get_cell_by_id(self.domain.id) elif self.domain_type == 'universe': self.domain = statepoint.summary.get_universe_by_id(self.domain.id) elif self.domain_type == 'material': self.domain = statepoint.summary.get_material_by_id(self.domain.id) else: msg = 'Unable to load data from a statepoint for domain type {0} ' \ 'which is not yet supported'.format(self.domain_type) raise ValueError(msg) # Use tally "slicing" to ensure that tallies correspond to our domain # NOTE: This is important if tally merging was used if self.domain_type != 'distribcell': filters = [self.domain_type] filter_bins = [(self.domain.id,)] # Distribcell filters only accept single cell - neglect it when slicing else: filters = [] filter_bins = [] # 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 # Find, slice and store Tallies from StatePoint # The tally slicing is needed if tally merging was used for tally_type, tally in self.tallies.items(): sp_tally = statepoint.get_tally( tally.scores, tally.filters, tally.nuclides, estimator=tally.estimator, exact_filters=True) sp_tally = sp_tally.get_slice( tally.scores, filters, filter_bins, tally.nuclides) sp_tally.sparse = self.sparse self.tallies[tally_type] = sp_tally self._loaded_sp = True
[docs] def get_xs(self, groups='all', subdomains='all', nuclides='all', xs_type='macro', order_groups='increasing', value='mean', **kwargs): """Returns an array of multi-group cross sections. This method constructs a 2D NumPy array for the requested multi-group cross section data data for one or more energy groups and subdomains. Parameters ---------- groups : Iterable of Integral or 'all' 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., ['U-235', 'U-238']). 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'. 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'. value : {'mean', 'std_dev', 'rel_err'} A string for the type of value to return. Defaults to 'mean'. Returns ------- numpy.ndarray A NumPy array of the multi-group cross section indexed in the order each group, subdomain and nuclide 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']) filters = [] filter_bins = [] # Construct a collection of the domain filter bins if not isinstance(subdomains, basestring): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=2) for subdomain in subdomains: filters.append(self.domain_type) filter_bins.append((subdomain,)) # Construct list of energy group bounds tuples for all requested groups if not isinstance(groups, basestring): cv.check_iterable_type('groups', groups, Integral) for group in groups: filters.append('energy') filter_bins.append((self.energy_groups.get_group_bounds(group),)) # 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_all_nuclides() else: query_nuclides = nuclides else: query_nuclides = ['total'] # If user requested the sum for all nuclides, use tally summation 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) # 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] # Reverse data if user requested increasing energy groups since # tally data is stored in order of increasing energies if order_groups == 'increasing': if groups == 'all': num_groups = self.num_groups else: num_groups = len(groups) # Reshape tally data array with separate axes for domain and energy num_subdomains = int(xs.shape[0] / num_groups) new_shape = (num_subdomains, num_groups) + xs.shape[1:] xs = np.reshape(xs, new_shape) # Reverse energies to align with increasing energy groups xs = xs[:, ::-1, :] # Eliminate trivial dimensions xs = np.squeeze(xs) xs = np.atleast_1d(xs) return xs
[docs] def get_condensed_xs(self, coarse_groups): """Construct an energy-condensed version of this cross section. Parameters ---------- coarse_groups : openmc.mgxs.EnergyGroups The coarse energy group structure of interest Returns ------- MGXS A new MGXS condensed to the group structure of interest """ cv.check_type('coarse_groups', coarse_groups, EnergyGroups) cv.check_less_than('coarse groups', coarse_groups.num_groups, self.num_groups, equality=True) cv.check_value('upper coarse energy', coarse_groups.group_edges[-1], [self.energy_groups.group_edges[-1]]) cv.check_value('lower coarse energy', coarse_groups.group_edges[0], [self.energy_groups.group_edges[0]]) # Clone this MGXS to initialize the condensed version condensed_xs = copy.deepcopy(self) condensed_xs._rxn_rate_tally = None condensed_xs._xs_tally = None condensed_xs._sparse = False condensed_xs._energy_groups = coarse_groups # Build energy indices to sum across energy_indices = [] for group in range(coarse_groups.num_groups, 0, -1): low, high = coarse_groups.get_group_bounds(group) low_index = np.where(self.energy_groups.group_edges == low)[0][0] energy_indices.append(low_index) fine_edges = self.energy_groups.group_edges # Condense each of the tallies to the coarse group structure for tally in condensed_xs.tallies.values(): # Make condensed tally derived and null out sum, sum_sq tally._derived = True tally._sum = None tally._sum_sq = None # Get tally data arrays reshaped with one dimension per filter mean = tally.get_reshaped_data(value='mean') std_dev = tally.get_reshaped_data(value='std_dev') # Sum across all applicable fine energy group filters for i, tally_filter in enumerate(tally.filters): if 'energy' not in tally_filter.type: continue elif len(tally_filter.bins) != len(fine_edges): continue elif not np.allclose(tally_filter.bins, fine_edges): continue else: tally_filter.bins = coarse_groups.group_edges mean = np.add.reduceat(mean, energy_indices, axis=i) std_dev = np.add.reduceat(std_dev**2, energy_indices, axis=i) std_dev = np.sqrt(std_dev) # Reshape condensed data arrays with one dimension for all filters mean = np.reshape(mean, tally.shape) std_dev = np.reshape(std_dev, tally.shape) # Override tally's data with the new condensed data tally._mean = mean tally._std_dev = std_dev # Compute the energy condensed multi-group cross section condensed_xs.sparse = self.sparse return condensed_xs
[docs] def get_subdomain_avg_xs(self, subdomains='all'): """Construct a subdomain-averaged version of this cross section. This method is useful for averaging cross sections across distribcell instances. The method performs spatial homogenization to compute the scalar flux-weighted average cross section across the subdomains. Parameters ---------- subdomains : Iterable of Integral or 'all' The subdomain IDs to average across. Defaults to 'all'. Returns ------- openmc.mgxs.MGXS A new MGXS averaged across the subdomains of interest Raises ------ ValueError When this method is called before the multi-group cross section is computed from tally data. """ # Construct a collection of the subdomain filter bins to average across if not isinstance(subdomains, basestring): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains) else: subdomains = None # Clone this MGXS to initialize the subdomain-averaged version avg_xs = copy.deepcopy(self) if self.derived: avg_xs._rxn_rate_tally = avg_xs.rxn_rate_tally.average( filter_type=self.domain_type, filter_bins=subdomains) else: avg_xs._rxn_rate_tally = None avg_xs._xs_tally = None # Average each of the tallies across subdomains for tally_type, tally in avg_xs.tallies.items(): tally_avg = tally.average(filter_type=self.domain_type, filter_bins=subdomains) avg_xs.tallies[tally_type] = tally_avg avg_xs._domain_type = 'avg({0})'.format(self.domain_type) avg_xs.sparse = self.sparse return avg_xs
[docs] def get_slice(self, nuclides=[], groups=[]): """Build a sliced MGXS 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., ['U-235', 'U-238']; default is []) groups : list of int A list of energy group indices starting at 1 for the high energies (e.g., [1, 2, 3]; default is []) Returns ------- openmc.mgxs.MGXS A new MGXS object which encapsulates the subset of data requested for the nuclide(s) and/or energy group(s) requested in the parameters. """ cv.check_iterable_type('nuclides', nuclides, basestring) cv.check_iterable_type('energy_groups', groups, Integral) # Build lists of filters and filter bins to slice if len(groups) == 0: filters = [] filter_bins = [] else: filter_bins = [] for group in groups: group_bounds = self.energy_groups.get_group_bounds(group) filter_bins.append(group_bounds) filter_bins = [tuple(filter_bins)] filters = ['energy'] # Clone this MGXS to initialize the sliced version slice_xs = copy.deepcopy(self) slice_xs._rxn_rate_tally = None slice_xs._xs_tally = None # Slice each of the tallies across nuclides and energy groups for tally_type, tally in slice_xs.tallies.items(): slice_nuclides = [nuc for nuc in nuclides if nuc in tally.nuclides] if len(groups) != 0 and tally.contains_filter('energy'): tally_slice = tally.get_slice(filters=filters, filter_bins=filter_bins, nuclides=slice_nuclides) else: tally_slice = tally.get_slice(nuclides=slice_nuclides) slice_xs.tallies[tally_type] = tally_slice # Assign sliced energy group structure to sliced MGXS if groups: new_group_edges = [] for group in groups: group_edges = self.energy_groups.get_group_bounds(group) new_group_edges.extend(group_edges) new_group_edges = np.unique(new_group_edges) slice_xs.energy_groups.group_edges = sorted(new_group_edges) # Assign sliced nuclides to sliced MGXS if nuclides: slice_xs.nuclides = nuclides slice_xs.sparse = self.sparse return slice_xs
[docs] def can_merge(self, other): """Determine if another MGXS can be merged with this one If results have been loaded from a statepoint, then MGXS are only mergeable along one and only one of enegy groups or nuclides. Parameters ---------- other : openmc.mgxs.MGXS MGXS to check for merging """ if not isinstance(other, type(self)): return False # Compare reaction type, energy groups, nuclides, domain type if self.rxn_type != other.rxn_type: return False elif not self.energy_groups.can_merge(other.energy_groups): return False elif self.by_nuclide != other.by_nuclide: return False elif self.domain_type != other.domain_type: return False elif 'distribcell' not in self.domain_type and self.domain != other.domain: return False elif not self.xs_tally.can_merge(other.xs_tally): return False elif not self.rxn_rate_tally.can_merge(other.rxn_rate_tally): return False # If all conditionals pass then MGXS are mergeable return True
[docs] def merge(self, other): """Merge another MGXS with this one MGXS are only mergeable if their energy groups and nuclides are either identical or mutually exclusive. If results have been loaded from a statepoint, then MGXS are only mergeable along one and only one of energy groups or nuclides. Parameters ---------- other : openmc.mgxs.MGXS MGXS to merge with this one Returns ------- merged_mgxs : openmc.mgxs.MGXS Merged MGXS """ if not self.can_merge(other): raise ValueError('Unable to merge MGXS') # Create deep copy of tally to return as merged tally merged_mgxs = copy.deepcopy(self) merged_mgxs._derived = True # Merge energy groups if self.energy_groups != other.energy_groups: merged_groups = self.energy_groups.merge(other.energy_groups) merged_mgxs.energy_groups = merged_groups # Merge nuclides if self.nuclides != other.nuclides: # The nuclides must be mutually exclusive for nuclide in self.nuclides: if nuclide in other.nuclides: msg = 'Unable to merge MGXS with shared nuclides' raise ValueError(msg) # Concatenate lists of nuclides for the merged MGXS merged_mgxs.nuclides = self.nuclides + other.nuclides # Null base tallies but merge reaction rate and cross section tallies merged_mgxs._tallies = OrderedDict() merged_mgxs._rxn_rate_tally = self.rxn_rate_tally.merge(other.rxn_rate_tally) merged_mgxs._xs_tally = self.xs_tally.merge(other.xs_tally) return merged_mgxs
[docs] def print_xs(self, subdomains='all', nuclides='all', xs_type='macro'): """Print 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., ['U-235', 'U-238']). 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'. """ # Construct a collection of the subdomains to report if not isinstance(subdomains, basestring): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) else: subdomains = [self.domain.id] # Construct a collection of the nuclides to report if self.by_nuclide: if nuclides == 'all': nuclides = self.get_all_nuclides() elif nuclides == 'sum': nuclides = ['sum'] else: 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' string += '{0: <16}=\t{1}\n'.format('\tReaction Type', self.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) # If cross section data has not been computed, only print string header if self.tallies is None: print(string) return # Loop over all subdomains for subdomain in subdomains: if self.domain_type == 'distribcell': string += '{0: <16}=\t{1}\n'.format('\tSubdomain', subdomain) # Loop over all Nuclides for nuclide in nuclides: # Build header for nuclide type if nuclide != 'sum': string += '{0: <16}=\t{1}\n'.format('\tNuclide', nuclide) # Build header for cross section type if xs_type == 'macro': string += '{0: <16}\n'.format('\tCross Sections [cm^-1]:') else: string += '{0: <16}\n'.format('\tCross Sections [barns]:') template = '{0: <12}Group {1} [{2: <10} - {3: <10}MeV]:\t' # 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]) average = self.get_xs([group], [subdomain], [nuclide], xs_type=xs_type, value='mean') rel_err = self.get_xs([group], [subdomain], [nuclide], xs_type=xs_type, value='rel_err') average = average.flatten()[0] rel_err = rel_err.flatten()[0] * 100. string += '{:.2e} +/- {:1.2e}%'.format(average, rel_err) string += '\n' string += '\n' string += '\n' print(string)
[docs] def build_hdf5_store(self, filename='mgxs.h5', directory='mgxs', subdomains='all', nuclides='all', xs_type='macro', row_column='inout', append=True): """Export the multi-group cross section data to an HDF5 binary file. This method constructs an HDF5 file which stores the multi-group cross section data. The data is stored in a hierarchy of HDF5 groups from the domain type, domain id, subdomain id (for distribcell domains), nuclides and cross section type. Two datasets for the mean and standard deviation are stored for each subdomain entry in the HDF5 file. NOTE: This requires the h5py Python package. Parameters ---------- filename : str Filename for the HDF5 file. Defaults to 'mgxs.h5'. directory : str Directory for the HDF5 file. Defaults to 'mgxs'. 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., ['U-235', 'U-238']). 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'} Store the macro or micro cross section in units of cm^-1 or barns. Defaults to 'macro'. row_column: {'inout', 'outin'} Store scattering matrices indexed first by incoming group and second by outgoing group ('inout'), or vice versa ('outin'). Defaults to 'inout'. append : bool If true, appends to an existing HDF5 file with the same filename directory (if one exists). Defaults to True. Raises ------ ValueError When this method is called before the multi-group cross section is computed from tally data. ImportError When h5py is not installed. """ import h5py # Make directory if it does not exist if not os.path.exists(directory): os.makedirs(directory) filename = os.path.join(directory, filename) filename = filename.replace(' ', '-') if append and os.path.isfile(filename): xs_results = h5py.File(filename, 'a') else: xs_results = h5py.File(filename, 'w') # Construct a collection of the subdomains to report if not isinstance(subdomains, basestring): 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 == 'avg(distribcell)': domain_filter = self.xs_tally.find_filter('avg(distribcell)') subdomains = domain_filter.bins else: subdomains = [self.domain.id] # Construct a collection of the nuclides to report if self.by_nuclide: if nuclides == 'all': nuclides = self.get_all_nuclides() densities = np.zeros(len(nuclides), dtype=np.float) elif nuclides == 'sum': nuclides = ['sum'] else: cv.check_iterable_type('nuclides', nuclides, basestring) else: nuclides = ['sum'] cv.check_value('xs_type', xs_type, ['macro', 'micro']) # Create an HDF5 group within the file for the domain domain_type_group = xs_results.require_group(self.domain_type) domain_group = domain_type_group.require_group(str(self.domain.id)) # Determine number of digits to pad subdomain group keys num_digits = len(str(self.num_subdomains)) # Create a separate HDF5 group for each subdomain for subdomain in subdomains: # Create an HDF5 group for the subdomain if self.domain_type == 'distribcell': group_name = ''.zfill(num_digits) subdomain_group = domain_group.require_group(group_name) else: subdomain_group = domain_group # Create a separate HDF5 group for this cross section rxn_group = subdomain_group.require_group(self.hdf5_key) # Create a separate HDF5 group for each nuclide for j, nuclide in enumerate(nuclides): if nuclide != 'sum': density = densities[j] nuclide_group = rxn_group.require_group(nuclide) nuclide_group.require_dataset('density', dtype=np.float64, data=[density], shape=(1,)) else: nuclide_group = rxn_group # Extract the cross section for this subdomain and nuclide average = self.get_xs(subdomains=[subdomain], nuclides=[nuclide], xs_type=xs_type, value='mean', row_column=row_column) std_dev = self.get_xs(subdomains=[subdomain], nuclides=[nuclide], xs_type=xs_type, value='std_dev', row_column=row_column) average = average.squeeze() std_dev = std_dev.squeeze() # Add MGXS results data to the HDF5 group nuclide_group.require_dataset('average', dtype=np.float64, shape=average.shape, data=average) nuclide_group.require_dataset('std. dev.', dtype=np.float64, shape=std_dev.shape, data=std_dev) # Close the results HDF5 file xs_results.close()
[docs] 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 method leverages the functionality in the Pandas library to export the multi-group cross section data in a variety of output file formats for storage and/or post-processing. Parameters ---------- filename : str Filename for the exported file. Defaults to 'mgxs'. directory : str Directory for the exported file. Defaults to 'mgxs'. format : {'csv', 'excel', 'pickle', 'latex'} The format for the exported data file. Defaults to 'csv'. groups : Iterable of Integral or 'all' Energy groups of interest. Defaults to 'all'. xs_type: {'macro', 'micro'} Store the macro or micro cross section in units of cm^-1 or barns. Defaults to 'macro'. """ 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): os.makedirs(directory) filename = os.path.join(directory, filename) filename = filename.replace(' ', '-') # Get a Pandas DataFrame for the data df = self.get_pandas_dataframe(groups=groups, xs_type=xs_type) # Capitalize column label strings df.columns = df.columns.astype(str) df.columns = map(str.title, df.columns) # Export the data using Pandas IO API if format == 'csv': df.to_csv(filename + '.csv', index=False) elif format == 'excel': df.to_excel(filename + '.xls', index=False) elif format == 'pickle': df.to_pickle(filename + '.pkl') elif format == 'latex': if self.domain_type == 'distribcell': msg = 'Unable to export distribcell multi-group cross section' \ 'data to a LaTeX table' raise NotImplementedError(msg) df.to_latex(filename + '.tex', bold_rows=True, longtable=True, index=False) # Surround LaTeX table with code needed to run pdflatex with open(filename + '.tex', 'r') as original: data = original.read() with open(filename + '.tex', 'w') as modified: modified.write( '\\documentclass[preview, 12pt, border=1mm]{standalone}\n') modified.write('\\usepackage{caption}\n') modified.write('\\usepackage{longtable}\n') modified.write('\\usepackage{booktabs}\n') modified.write('\\begin{document}\n\n') modified.write(data) modified.write('\n\\end{document}')
[docs] def get_pandas_dataframe(self, groups='all', nuclides='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., ['U-235', 'U-238']). 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'. 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. """ if not isinstance(groups, basestring): 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 if self.by_nuclide and nuclides == 'sum': # Use tally summation to sum across all nuclides query_nuclides = self.get_all_nuclides() xs_tally = self.xs_tally.summation(nuclides=query_nuclides) df = xs_tally.get_pandas_dataframe( distribcell_paths=distribcell_paths) # Remove nuclide column since it is homogeneous and redundant df.drop('nuclide', axis=1, inplace=True) # If the user requested a specific set of nuclides elif self.by_nuclide and nuclides != 'all': xs_tally = self.xs_tally.get_slice(nuclides=nuclides) df = xs_tally.get_pandas_dataframe( distribcell_paths=distribcell_paths) # If the user requested all nuclides, keep nuclide column in dataframe else: df = self.xs_tally.get_pandas_dataframe( distribcell_paths=distribcell_paths) # Remove the score column since it is homogeneous and redundant df = df.drop('score', axis=1) # Override energy groups bounds with indices all_groups = np.arange(self.num_groups, 0, -1, dtype=np.int) all_groups = np.repeat(all_groups, self.num_nuclides) if 'energy low [MeV]' in df and 'energyout low [MeV]' in df: df.rename(columns={'energy low [MeV]': 'group in'}, inplace=True) in_groups = np.tile(all_groups, self.num_subdomains) in_groups = np.repeat(in_groups, df.shape[0] / in_groups.size) df['group in'] = in_groups del df['energy high [MeV]'] df.rename(columns={'energyout low [MeV]': 'group out'}, inplace=True) out_groups = np.repeat(all_groups, self.xs_tally.num_scores) out_groups = np.tile(out_groups, df.shape[0] / out_groups.size) df['group out'] = out_groups del df['energyout high [MeV]'] columns = ['group in', 'group out'] elif 'energyout low [MeV]' in df: df.rename(columns={'energyout low [MeV]': 'group out'}, inplace=True) in_groups = np.tile(all_groups, self.num_subdomains) df['group out'] = in_groups del df['energyout high [MeV]'] columns = ['group out'] elif 'energy low [MeV]' in df: df.rename(columns={'energy low [MeV]': 'group in'}, inplace=True) in_groups = np.tile(all_groups, self.num_subdomains) df['group in'] = in_groups del df['energy high [MeV]'] columns = ['group in'] # Select out those groups the user requested if not isinstance(groups, basestring): if 'group in' in df: df = df[df['group in'].isin(groups)] if 'group out' in df: df = df[df['group out'].isin(groups)] # 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') densities = np.repeat(densities, len(self.rxn_rate_tally.scores)) tile_factor = df.shape[0] / len(densities) df['mean'] /= np.tile(densities, tile_factor) df['std. dev.'] /= np.tile(densities, tile_factor) # Sort the dataframe by domain type id (e.g., distribcell id) and # energy groups such that data is from fast to thermal df.sort_values(by=[self.domain_type] + columns, inplace=True) return df
class MatrixMGXS(MGXS): """An abstract multi-group cross section for some energy group structure within some spatial domain. This class is specifically intended for cross sections which depend on both the incoming and outgoing energy groups and are therefore represented by matrices. Examples of this include the scattering and nu-fission matrices. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. NOTE: Users should instantiate the subclasses of this abstract class. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ # This is an abstract class which cannot be instantiated __metaclass__ = abc.ABCMeta @property def filters(self): # Create the non-domain specific Filters for the Tallies group_edges = self.energy_groups.group_edges energy = openmc.Filter('energy', group_edges) energyout = openmc.Filter('energyout', group_edges) return [[energy], [energy, energyout]] @property def estimator(self): return 'analog' def get_xs(self, in_groups='all', out_groups='all', subdomains='all', nuclides='all', xs_type='macro', order_groups='increasing', row_column='inout', value='mean', **kwargs): """Returns an array of multi-group cross sections. This method constructs a 2D NumPy array for the requested multi-group matrix data for one or more energy groups and subdomains. 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., ['U-235', 'U-238']). 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'. 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'. Returns ------- 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']) filters = [] filter_bins = [] # Construct a collection of the domain filter bins if not isinstance(subdomains, basestring): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=2) for subdomain in subdomains: filters.append(self.domain_type) filter_bins.append((subdomain,)) # Construct list of energy group bounds tuples for all requested groups if not isinstance(in_groups, basestring): cv.check_iterable_type('groups', in_groups, Integral) for group in in_groups: filters.append('energy') filter_bins.append(( self.energy_groups.get_group_bounds(group),)) # Construct list of energy group bounds tuples for all requested groups if not isinstance(out_groups, basestring): cv.check_iterable_type('groups', out_groups, Integral) for group in out_groups: filters.append('energyout') filter_bins.append(( self.energy_groups.get_group_bounds(group),)) # 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_all_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(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) xs = np.nan_to_num(xs) # 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] # Reverse data if user requested increasing energy groups since # tally data is stored in order of increasing energies if order_groups == 'increasing': 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) # Reshape tally data array with separate axes for domain and energy num_subdomains = int(xs.shape[0] / (num_in_groups * num_out_groups)) new_shape = (num_subdomains, num_in_groups, num_out_groups) new_shape += xs.shape[1:] xs = np.reshape(xs, new_shape) # Transpose the matrix if requested by user if row_column == 'outin': xs = np.swapaxes(xs, 1, 2) # Reverse energies to align with increasing energy groups xs = xs[:, ::-1, ::-1, :] # Eliminate trivial dimensions xs = np.squeeze(xs) xs = np.atleast_2d(xs) return xs def get_slice(self, nuclides=[], in_groups=[], out_groups=[]): """Build a sliced MatrixMGXS object 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., ['U-235', 'U-238']; 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 []) Returns ------- openmc.mgxs.MatrixMGXS A new MatrixMGXS object 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(MatrixMGXS, self).get_slice(nuclides, in_groups) slice_xs._rxn_rate_tally = None slice_xs._xs_tally = None # 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('energyout'): tally_slice = tally.get_slice(filters=['energyout'], filter_bins=filter_bins) slice_xs.tallies[tally_type] = tally_slice slice_xs.sparse = self.sparse return slice_xs def print_xs(self, subdomains='all', nuclides='all', xs_type='macro'): """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., ['U-235', 'U-238']). 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'. """ # Construct a collection of the subdomains to report if not isinstance(subdomains, basestring): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) else: 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: 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' string += '{0: <16}=\t{1}\n'.format('\tReaction Type', self.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) # 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}MeV]\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]) # Loop over all subdomains for subdomain in subdomains: if self.domain_type == 'distribcell': 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 if xs_type == 'macro': string += '{0: <16}\n'.format('\tCross Sections [cm^-1]:') else: string += '{0: <16}\n'.format('\tCross Sections [barns]:') template = '{0: <12}Group {1} -> Group {2}:\t\t' # 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) average = \ self.get_xs([in_group], [out_group], [subdomain], [nuclide], xs_type=xs_type, value='mean') rel_err = \ self.get_xs([in_group], [out_group], [subdomain], [nuclide], xs_type=xs_type, value='rel_err') average = average.flatten()[0] rel_err = rel_err.flatten()[0] * 100. string += '{:1.2e} +/- {:1.2e}%'.format(average, rel_err) string += '\n' string += '\n' string += '\n' string += '\n' print(string)
[docs]class TotalXS(MGXS): r"""A total multi-group cross section. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group total cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`TotalXS.energy_groups` and :attr:`TotalXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`TotalXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`TotalXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the total cross section is calculated as: .. math:: \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \sigma_t (r, E) \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`TotalXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(TotalXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'total'
[docs]class TransportXS(MGXS): r"""A transport-corrected total multi-group cross section. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`TransportXS.energy_groups` and :attr:`TransportXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`TransportXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`TransportXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the transport-corrected total cross section is calculated as: .. math:: \langle \sigma_t \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \sigma_t (r, E) \psi (r, E, \Omega) \\ \langle \sigma_{s1} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \int_{4\pi} d\Omega' \int_0^\infty dE' \int_{-1}^1 d\mu \; \mu \sigma_s (r, E' \rightarrow E, \Omega' \cdot \Omega) \phi (r, E', \Omega) \\ \langle \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega) \\ \sigma_{tr} &= \frac{\langle \sigma_t \phi \rangle - \langle \sigma_{s1} \phi \rangle}{\langle \phi \rangle} Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`TransportXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(TransportXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'transport' @property def scores(self): return ['flux', 'total', 'scatter-1'] @property def filters(self): group_edges = self.energy_groups.group_edges energy_filter = openmc.Filter('energy', group_edges) energyout_filter = openmc.Filter('energyout', group_edges) return [[energy_filter], [energy_filter], [energyout_filter]] @property def estimator(self): return 'analog' @property def rxn_rate_tally(self): if self._rxn_rate_tally is None: self.tallies['scatter-1'].filters[-1].type = 'energy' self._rxn_rate_tally = \ self.tallies['total'] - self.tallies['scatter-1'] self._rxn_rate_tally.sparse = self.sparse return self._rxn_rate_tally
class NuTransportXS(TransportXS): r"""A transport-corrected total multi-group cross section which accounts for neutron multiplicity in scattering reactions. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`NuTransportXS.energy_groups` and :attr:`NuTransportXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`NuTransportXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`NuTransportXS.xs_tally` property. The calculation of the transport-corrected cross section is the same as that for :class:`TransportXS` except that the scattering multiplicity is accounted for. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`NuTransportXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(NuTransportXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'nu-transport' @property def scores(self): return ['flux', 'total', 'nu-scatter-1'] @property def tally_keys(self): return ['flux', 'total', 'scatter-1']
[docs]class AbsorptionXS(MGXS): r"""An absorption multi-group cross section. Absorption is defined as all reactions that do not produce secondary neutrons (disappearance) plus fission reactions. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group absorption cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`AbsorptionXS.energy_groups` and :attr:`AbsorptionXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`AbsorptionXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`AbsorptionXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the absorption cross section is calculated as: .. math:: \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \sigma_a (r, E) \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`AbsorptionXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(AbsorptionXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'absorption'
[docs]class CaptureXS(MGXS): r"""A capture multi-group cross section. The neutron capture reaction rate is defined as the difference between OpenMC's 'absorption' and 'fission' reaction rate score types. This includes not only radiative capture, but all forms of neutron disappearance aside from fission (i.e., MT > 100). This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group capture cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`CaptureXS.energy_groups` and :attr:`CaptureXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`CaptureXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`CaptureXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the capture cross section is calculated as: .. math:: \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \left [ \sigma_a (r, E) \psi (r, E, \Omega) - \sigma_f (r, E) \psi (r, E, \Omega) \right ]}{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`CaptureXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(CaptureXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'capture' @property def scores(self): return ['flux', 'absorption', 'fission'] @property def rxn_rate_tally(self): if self._rxn_rate_tally is None: self._rxn_rate_tally = \ self.tallies['absorption'] - self.tallies['fission'] self._rxn_rate_tally.sparse = self.sparse return self._rxn_rate_tally
[docs]class FissionXS(MGXS): r"""A fission multi-group cross section. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group fission cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`FissionXS.energy_groups` and :attr:`FissionXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`FissionXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`FissionXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the fission cross section is calculated as: .. math:: \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \sigma_f (r, E) \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`FissionXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(FissionXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'fission'
[docs]class NuFissionXS(MGXS): r"""A fission neutron production multi-group cross section. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group fission neutron production cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`NuFissionXS.energy_groups` and :attr:`NuFissionXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`NuFissionXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`NuFissionXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the fission neutron production cross section is calculated as: .. math:: \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \nu\sigma_f (r, E) \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`NuFissionXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(NuFissionXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'nu-fission'
[docs]class KappaFissionXS(MGXS): r"""A recoverable fission energy production rate multi-group cross section. The recoverable energy per fission, :math:`\kappa`, is defined as the fission product kinetic energy, prompt and delayed neutron kinetic energies, prompt and delayed :math:`\gamma`-ray total energies, and the total energy released by the delayed :math:`\beta` particles. The neutrino energy does not contribute to this response. The prompt and delayed :math:`\gamma`-rays are assumed to deposit their energy locally. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`KappaFissionXS.energy_groups` and :attr:`KappaFissionXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`KappaFissionXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`KappaFissionXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the recoverable fission energy production rate cross section is calculated as: .. math:: \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \kappa\sigma_f (r, E) \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`KappaFissionXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(KappaFissionXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'kappa-fission'
[docs]class ScatterXS(MGXS): r"""A scattering multi-group cross section. The scattering cross section is defined as the difference between the total and absorption cross sections. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`ScatterXS.energy_groups` and :attr:`ScatterXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`ScatterXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`ScatterXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the scattering cross section is calculated as: .. math:: \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \left [ \sigma_t (r, E) \psi (r, E, \Omega) - \sigma_a (r, E) \psi (r, E, \Omega) \right ]}{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`ScatterXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(ScatterXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'scatter'
[docs]class NuScatterXS(MGXS): r"""A scattering neutron production multi-group cross section. The neutron production from scattering is defined as the average number of neutrons produced from all neutron-producing reactions except for fission. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`NuScatterXS.energy_groups` and :attr:`NuScatterXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`NuScatterXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`NuScatterXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the scattering neutron production cross section is calculated as: .. math:: \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \sum_i \upsilon_i \sigma_i (r, E) \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. where :math:`\upsilon_i` is the multiplicity of the :math:`i`-th scattering reaction. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`NuScatterXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(NuScatterXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'nu-scatter' @property def estimator(self): return 'analog'
[docs]class ScatterMatrixXS(MatrixMGXS): r"""A scattering matrix multi-group cross section for one or more Legendre moments. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`ScatterMatrixXS.energy_groups` and :attr:`ScatterMatrixXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`ScatterMatrixXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`ScatterMatrixXS.xs_tally` property. For a spatial domain :math:`V`, incoming energy group :math:`[E_{g'},E_{g'-1}]`, and outgoing energy group :math:`[E_g,E_{g-1}]`, the scattering moments are calculated as: .. math:: \langle \sigma_{s,\ell,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; P_\ell (\Omega \cdot \Omega') \sigma_s (r, E' \rightarrow E, \Omega' \cdot \Omega) \psi(r, E', \Omega')\\ \langle \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega) \\ \sigma_{s,\ell,g'\rightarrow g} &= \frac{\langle \sigma_{s,\ell,g'\rightarrow g} \phi \rangle}{\langle \phi \rangle} If the order is zero and a :math:`P_0` transport-correction is applied (default), the scattering matrix elements are: .. math:: \sigma_{s,g'\rightarrow g} = \frac{\langle \sigma_{s,0,g'\rightarrow g} \phi \rangle - \delta_{gg'} \sum_{g''} \langle \sigma_{s,1,g''\rightarrow g} \phi \rangle}{\langle \phi \rangle} Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- correction : 'P0' or None Apply the P0 correction to scattering matrices if set to 'P0' legendre_order : int The highest Legendre moment in the scattering matrix (default is 0) name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`ScatterMatrixXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(ScatterMatrixXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'scatter' self._correction = 'P0' self._legendre_order = 0 self._hdf5_key = 'scatter matrix' def __deepcopy__(self, memo): clone = super(ScatterMatrixXS, self).__deepcopy__(memo) clone._correction = self.correction clone._legendre_order = self.legendre_order return clone @property def correction(self): return self._correction @property def legendre_order(self): return self._legendre_order @property def scores(self): scores = ['flux'] if self.correction == 'P0' and self.legendre_order == 0: scores += ['{}-0'.format(self.rxn_type), '{}-1'.format(self.rxn_type)] else: scores += ['{}-P{}'.format(self.rxn_type, self.legendre_order)] return scores @property def filters(self): group_edges = self.energy_groups.group_edges energy = openmc.Filter('energy', group_edges) energyout = openmc.Filter('energyout', group_edges) if self.correction == 'P0' and self.legendre_order == 0: filters = [[energy], [energy, energyout], [energyout]] else: filters = [[energy], [energy, energyout]] return filters @property def rxn_rate_tally(self): if self._rxn_rate_tally is None: # If using P0 correction subtract scatter-1 from the diagonal if self.correction == 'P0' and self.legendre_order == 0: scatter_p0 = self.tallies['{}-0'.format(self.rxn_type)] scatter_p1 = self.tallies['{}-1'.format(self.rxn_type)] energy_filter = scatter_p0.find_filter('energy') energy_filter = copy.deepcopy(energy_filter) scatter_p1 = scatter_p1.diagonalize_filter(energy_filter) self._rxn_rate_tally = scatter_p0 - scatter_p1 # Extract scattering moment reaction rate Tally else: tally_key = '{}-P{}'.format(self.rxn_type, self.legendre_order) self._rxn_rate_tally = self.tallies[tally_key] self._rxn_rate_tally.sparse = self.sparse return self._rxn_rate_tally @correction.setter def correction(self, correction): cv.check_value('correction', correction, ('P0', None)) if correction == 'P0' and self.legendre_order > 0: msg = 'The P0 correction will be ignored since the scattering ' \ 'order {} is greater than zero'.format(self.legendre_order) warnings.warn(msg) self._correction = correction @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, 10, equality=True) if self.correction == 'P0' and legendre_order > 0: msg = 'The P0 correction will be ignored since the scattering ' \ 'order {} is greater than zero'.format(self.legendre_order) warnings.warn(msg, RuntimeWarning) self.correction = None self._legendre_order = legendre_order
[docs] 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 first 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 # Expand scores to match the format in the statepoint # e.g., "scatter-P2" -> "scatter-0", "scatter-1", "scatter-2" if self.correction != 'P0' or self.legendre_order != 0: 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)] super(ScatterMatrixXS, self).load_from_statepoint(statepoint)
[docs] def get_slice(self, nuclides=[], in_groups=[], out_groups=[], legendre_order='same'): """Build a sliced ScatterMatrix 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., ['U-235', 'U-238']; 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(ScatterMatrixXS, 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': 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('energyout'): tally_slice = tally.get_slice(filters=['energyout'], filter_bins=filter_bins) slice_xs.tallies[tally_type] = tally_slice slice_xs.sparse = self.sparse return slice_xs
[docs] 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'): r"""Returns an array of multi-group cross sections. This method constructs a 2D NumPy array for the requested scattering matrix data data for one or more energy groups and subdomains. NOTE: The scattering moments are not multiplied by the :math:`(2l+1)/2` prefactor in the expansion of the scattering source into Legendre moments in the neutron transport equation. 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., ['U-235', 'U-238']). 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'. Returns ------- 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']) filters = [] filter_bins = [] # Construct a collection of the domain filter bins if not isinstance(subdomains, basestring): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=2) for subdomain in subdomains: filters.append(self.domain_type) filter_bins.append((subdomain,)) # Construct list of energy group bounds tuples for all requested groups if not isinstance(in_groups, basestring): cv.check_iterable_type('groups', in_groups, Integral) for group in in_groups: filters.append('energy') filter_bins.append((self.energy_groups.get_group_bounds(group),)) # Construct list of energy group bounds tuples for all requested groups if not isinstance(out_groups, basestring): cv.check_iterable_type('groups', out_groups, Integral) for group in out_groups: filters.append('energyout') filter_bins.append((self.energy_groups.get_group_bounds(group),)) # Construct CrossScore for 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) 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_all_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) xs = np.nan_to_num(xs) # 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] # Reverse data if user requested increasing energy groups since # tally data is stored in order of increasing energies if order_groups == 'increasing': 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) # Reshape tally data array with separate axes for domain and energy num_subdomains = int(xs.shape[0] / (num_in_groups * num_out_groups)) 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 energies to align with increasing energy groups xs = xs[:, ::-1, ::-1, :] # Eliminate trivial dimensions xs = np.squeeze(xs) xs = np.atleast_2d(xs) return xs
[docs] 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., ['U-235', 'U-238']). 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(ScatterMatrixXS, self).get_pandas_dataframe( groups, nuclides, xs_type, distribcell_paths) # 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, df.shape[0] / len(moments)) df['moment'] = moments # Place the moment column before the mean column mean_index = df.columns.get_loc('mean') columns = df.columns.tolist() 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
[docs] 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., ['U-235', 'U-238']). 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, basestring): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) else: 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: cv.check_iterable_type('nuclides', nuclides, basestring) else: nuclides = ['sum'] cv.check_value('xs_type', xs_type, ['macro', 'micro']) if self.correction != 'P0': 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) # 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}MeV]\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]) # Loop over all subdomains for subdomain in subdomains: if self.domain_type == 'distribcell': 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 if xs_type == 'macro': string += '{0: <16}\n'.format('\tCross Sections [cm^-1]:') else: string += '{0: <16}\n'.format('\tCross Sections [barns]:') template = '{0: <12}Group {1} -> Group {2}:\t\t' # 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) average = \ self.get_xs([in_group], [out_group], [subdomain], [nuclide], moment=moment, xs_type=xs_type, value='mean') rel_err = \ self.get_xs([in_group], [out_group], [subdomain], [nuclide], moment=moment, xs_type=xs_type, value='rel_err') average = average.flatten()[0] rel_err = rel_err.flatten()[0] * 100. string += '{:1.2e} +/- {:1.2e}%'.format(average, rel_err) string += '\n' string += '\n' string += '\n' string += '\n' print(string)
[docs]class NuScatterMatrixXS(ScatterMatrixXS): """A scattering production matrix multi-group cross section for one or more Legendre moments. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`NuScatterMatrixXS.energy_groups` and :attr:`NuScatterMatrixXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`NuScatterMatrixXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`NuScatterMatrixXS.xs_tally` property. The calculation of the scattering-production matrix is the same as that for :class:`ScatterMatrixXS` except that the scattering multiplicity is accounted for. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- correction : 'P0' or None Apply the P0 correction to scattering matrices if set to 'P0' legendre_order : int The highest legendre moment in the scattering matrix (default is 0) name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`NuScatterMatrixXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(NuScatterMatrixXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'nu-scatter' self._hdf5_key = 'nu-scatter matrix'
[docs]class MultiplicityMatrixXS(MatrixMGXS): r"""The scattering multiplicity matrix. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`MultiplicityMatrixXS.energy_groups` and :attr:`MultiplicityMatrixXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`MultiplicityMatrixXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`MultiplicityMatrixXS.xs_tally` property. For a spatial domain :math:`V`, incoming energy group :math:`[E_{g'},E_{g'-1}]`, and outgoing energy group :math:`[E_g,E_{g-1}]`, the multiplicity is calculated as: .. math:: \langle \upsilon \sigma_{s,g'\rightarrow g} \phi \rangle &= \int_{r \in D} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \sum_i \upsilon_i \sigma_i (r, E' \rightarrow E, \Omega' \cdot \Omega) \psi(r, E', \Omega') \\ \langle \sigma_{s,g'\rightarrow g} \phi \rangle &= \int_{r \in D} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \sum_i \upsilon_i \sigma_i (r, E' \rightarrow E, \Omega' \cdot \Omega) \psi(r, E', \Omega') \\ \upsilon_{g'\rightarrow g} &= \frac{\langle \upsilon \sigma_{s,g'\rightarrow g} \rangle}{\langle \sigma_{s,g'\rightarrow g} \rangle} where :math:`\upsilon_i` is the multiplicity for the :math:`i`-th reaction. Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`MultiplicityMatrixXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(MultiplicityMatrixXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'multiplicity matrix' @property def scores(self): scores = ['nu-scatter', 'scatter'] return scores @property def filters(self): # Create the non-domain specific Filters for the Tallies group_edges = self.energy_groups.group_edges energy = openmc.Filter('energy', group_edges) energyout = openmc.Filter('energyout', group_edges) return [[energy, energyout], [energy, energyout]] @property def rxn_rate_tally(self): if self._rxn_rate_tally is None: self._rxn_rate_tally = self.tallies['nu-scatter'] self._rxn_rate_tally.sparse = self.sparse return self._rxn_rate_tally @property def xs_tally(self): if self._xs_tally is None: scatter = self.tallies['scatter'] # Compute the multiplicity self._xs_tally = self.rxn_rate_tally / scatter super(MultiplicityMatrixXS, self)._compute_xs() return self._xs_tally
[docs]class NuFissionMatrixXS(MatrixMGXS): r"""A fission production matrix multi-group cross section. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`NuFissionMatrixXS.energy_groups` and :attr:`NuFissionMatrixXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`NuFissionMatrixXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`NuFissionMatrixXS.xs_tally` property. For a spatial domain :math:`V`, incoming energy group :math:`[E_{g'},E_{g'-1}]`, and outgoing energy group :math:`[E_g,E_{g-1}]`, the fission production is calculated as: .. math:: \langle \nu\sigma_{f,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{E_g}^{E_{g-1}} dE \; \chi(E) \nu\sigma_f (r, E') \psi(r, E', \Omega')\\ \langle \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega) \\ \nu\sigma_{f,g'\rightarrow g} &= \frac{\langle \nu\sigma_{f,g'\rightarrow g} \phi \rangle}{\langle \phi \rangle} Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`NuFissionMatrixXS.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(NuFissionMatrixXS, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'nu-fission' self._hdf5_key = 'nu-fission matrix'
[docs]class Chi(MGXS): r"""The fission spectrum. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated multi-group cross sections for multi-group neutronics calculations. At a minimum, one needs to set the :attr:`Chi.energy_groups` and :attr:`Chi.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the :attr:`Chi.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section can then be obtained from the :attr:`Chi.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the fission spectrum is calculated as: .. math:: \langle \nu\sigma_{f,\rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_0^\infty dE' \int_{E_g}^{E_{g-1}} dE \; \chi(E) \nu\sigma_f (r, E') \psi(r, E', \Omega')\\ \langle \nu\sigma_f \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_0^\infty dE' \int_0^\infty dE \; \chi(E) \nu\sigma_f (r, E') \psi(r, E', \Omega') \\ \chi_g &= \frac{\langle \nu\sigma_{f,\rightarrow g} \phi \rangle}{\langle \nu\sigma_f \phi \rangle} Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation by_nuclide : bool If true, computes 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. Attributes ---------- name : str, optional Name of the multi-group cross section rxn_type : str Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : Material or Cell or Universe Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section scores : list of str The scores in each tally used to compute the multi-group cross section filters : list of openmc.Filter The filters in each tally used to compute the multi-group cross section tally_keys : list of str The keys into the tallies dictionary for each tally used to compute the multi-group cross section estimator : {'tracklength', 'analog'} The tally estimator used to compute the multi-group cross section tallies : collections.OrderedDict OpenMC tallies needed to compute the multi-group cross section. The keys are strings listed in the :attr:`Chi.tally_keys` property and values are instances of :class:`openmc.Tally`. rxn_rate_tally : openmc.Tally Derived tally for the reaction rate tally used in the numerator to compute the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. xs_tally : openmc.Tally Derived tally for the multi-group cross section. This attribute is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' domain types. When the This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. nuclides : Iterable of str or 'sum' The optional user-specified nuclides for which to compute cross sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides are not specified by the user, all nuclides in the spatial domain are included. This attribute is 'sum' if by_nuclide is false. sparse : bool Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format for compressed data storage loaded_sp : bool Whether or not a statepoint file has been loaded with tally data derived : bool Whether or not the MGXS is merged from one or more other MGXS hdf5_key : str The key used to index multi-group cross sections in an HDF5 data store """ def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name=''): super(Chi, self).__init__(domain, domain_type, groups, by_nuclide, name) self._rxn_type = 'chi' @property def scores(self): return ['nu-fission', 'nu-fission'] @property def filters(self): # Create the non-domain specific Filters for the Tallies group_edges = self.energy_groups.group_edges energyout = openmc.Filter('energyout', group_edges) energyin = openmc.Filter('energy', [group_edges[0], group_edges[-1]]) return [[energyin], [energyout]] @property def tally_keys(self): return ['nu-fission-in', 'nu-fission-out'] @property def estimator(self): return 'analog' @property def rxn_rate_tally(self): if self._rxn_rate_tally is None: self._rxn_rate_tally = self.tallies['nu-fission-out'] self._rxn_rate_tally.sparse = self.sparse return self._rxn_rate_tally @property def xs_tally(self): if self._xs_tally is None: nu_fission_in = self.tallies['nu-fission-in'] # Remove coarse energy filter to keep it out of tally arithmetic energy_filter = nu_fission_in.find_filter('energy') nu_fission_in.remove_filter(energy_filter) # Compute chi self._xs_tally = self.rxn_rate_tally / nu_fission_in super(Chi, self)._compute_xs() # Add the coarse energy filter back to the nu-fission tally nu_fission_in.filters.append(energy_filter) return self._xs_tally
[docs] def get_slice(self, nuclides=[], groups=[]): """Build a sliced Chi 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., ['U-235', 'U-238']; default is []) groups : list of Integral A list of energy group indices starting at 1 for the high energies (e.g., [1, 2, 3]; default is []) Returns ------- openmc.mgxs.MGXS A new MGXS which encapsulates the subset of data requested for the nuclide(s) and/or energy group(s) requested in the parameters. """ # Temporarily remove energy filter from nu-fission-in since its # group structure will work in super MGXS.get_slice(...) method nu_fission_in = self.tallies['nu-fission-in'] energy_filter = nu_fission_in.find_filter('energy') nu_fission_in.remove_filter(energy_filter) # Call super class method and null out derived tallies slice_xs = super(Chi, self).get_slice(nuclides, groups) slice_xs._rxn_rate_tally = None slice_xs._xs_tally = None # Slice energy groups if needed if len(groups) != 0: filter_bins = [] for group in groups: group_bounds = self.energy_groups.get_group_bounds(group) filter_bins.append(group_bounds) filter_bins = [tuple(filter_bins)] # Slice nu-fission-out tally along energyout filter nu_fission_out = slice_xs.tallies['nu-fission-out'] tally_slice = nu_fission_out.get_slice(filters=['energyout'], filter_bins=filter_bins) slice_xs._tallies['nu-fission-out'] = tally_slice # Add energy filter back to nu-fission-in tallies self.tallies['nu-fission-in'].add_filter(energy_filter) slice_xs._tallies['nu-fission-in'].add_filter(energy_filter) slice_xs.sparse = self.sparse return slice_xs
[docs] def merge(self, other): """Merge another Chi with this one If results have been loaded from a statepoint, then Chi are only mergeable along one and only one of energy groups or nuclides. Parameters ---------- other : openmc.mgxs.MGXS MGXS to merge with this one Returns ------- merged_mgxs : openmc.mgxs.MGXS Merged MGXS """ if not self.can_merge(other): raise ValueError('Unable to merge Chi') # Create deep copy of tally to return as merged tally merged_mgxs = copy.deepcopy(self) merged_mgxs._derived = True merged_mgxs._rxn_rate_tally = None merged_mgxs._xs_tally = None # Merge energy groups if self.energy_groups != other.energy_groups: merged_groups = self.energy_groups.merge(other.energy_groups) merged_mgxs.energy_groups = merged_groups # Merge nuclides if self.nuclides != other.nuclides: # The nuclides must be mutually exclusive for nuclide in self.nuclides: if nuclide in other.nuclides: msg = 'Unable to merge Chi with shared nuclides' raise ValueError(msg) # Concatenate lists of nuclides for the merged MGXS merged_mgxs.nuclides = self.nuclides + other.nuclides # Merge tallies for tally_key in self.tallies: merged_tally = self.tallies[tally_key].merge(other.tallies[tally_key]) merged_mgxs.tallies[tally_key] = merged_tally return merged_mgxs
[docs] def get_xs(self, groups='all', subdomains='all', nuclides='all', xs_type='macro', order_groups='increasing', value='mean', **kwargs): """Returns an array of the fission spectrum. This method constructs a 2D NumPy array for the requested multi-group cross section data data for one or more energy groups and subdomains. Parameters ---------- groups : Iterable of Integral or 'all' 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., ['U-235', 'U-238']). 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'. xs_type: {'macro', 'micro'} This parameter is not relevant for chi but is included here to mirror the parent MGXS.get_xs(...) class method order_groups: {'increasing', 'decreasing'} Return the cross section indexed according to increasing or decreasing energy groups (decreasing or increasing energies). Defaults to 'increasing'. value : {'mean', 'std_dev', 'rel_err'} A string for the type of value to return. Defaults to 'mean'. Returns ------- numpy.ndarray A NumPy array of the multi-group cross section indexed in the order each group, subdomain and nuclide 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']) filters = [] filter_bins = [] # Construct a collection of the domain filter bins if not isinstance(subdomains, basestring): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=2) for subdomain in subdomains: filters.append(self.domain_type) filter_bins.append((subdomain,)) # Construct list of energy group bounds tuples for all requested groups if not isinstance(groups, basestring): cv.check_iterable_type('groups', groups, Integral) for group in groups: filters.append('energyout') filter_bins.append((self.energy_groups.get_group_bounds(group),)) # 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' or 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) # Remove coarse energy filter to keep it out of tally arithmetic energy_filter = nu_fission_in.find_filter('energy') nu_fission_in.remove_filter(energy_filter) # Compute chi and store it as the xs_tally attribute so we can # use the generic get_xs(...) method xs_tally = nu_fission_out / nu_fission_in # Add the coarse energy filter back to the nu-fission tally nu_fission_in.filters.append(energy_filter) 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: xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, value=value) # Reverse data if user requested increasing energy groups since # tally data is stored in order of increasing energies if order_groups == 'increasing': # Reshape tally data array with separate axes for domain and energy if groups == 'all': num_groups = self.num_groups else: num_groups = len(groups) num_subdomains = int(xs.shape[0] / num_groups) new_shape = (num_subdomains, num_groups) + xs.shape[1:] xs = np.reshape(xs, new_shape) # Reverse energies to align with increasing energy groups xs = xs[:, ::-1, :] # Eliminate trivial dimensions xs = np.squeeze(xs) xs = np.atleast_1d(xs) xs = np.nan_to_num(xs) return xs
[docs] def get_pandas_dataframe(self, groups='all', nuclides='all', xs_type='macro', distribcell_paths=False): """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., ['U-235', 'U-238']). 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'. 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. """ # Build the dataframe using the parent class method df = super(Chi, self).get_pandas_dataframe( groups, nuclides, xs_type, distribcell_paths=distribcell_paths) # If user requested micro cross sections, multiply by the atom # densities to cancel out division made by the parent class method if xs_type == 'micro': if self.by_nuclide: densities = self.get_nuclide_densities(nuclides) else: densities = self.get_nuclide_densities('sum') tile_factor = df.shape[0] / len(densities) df['mean'] *= np.tile(densities, tile_factor) df['std. dev.'] *= np.tile(densities, tile_factor) return df