diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index cc0e1190d..6bb2be31b 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -213,7 +213,7 @@ def check_less_than(name, value, maximum, equality=False): raise ValueError(msg) def check_greater_than(name, value, minimum, equality=False): - """Ensure that an object's value is less than a given value. + """Ensure that an object's value is greater than a given value. Parameters ---------- diff --git a/openmc/filter.py b/openmc/filter.py index 72fb3b14e..4c742352d 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -564,7 +564,7 @@ class Filter(object): # Initialize dictionary to build Pandas Multi-index column filter_dict = {} - # Append Mesh ID as outermost index of mult-index + # Append Mesh ID as outermost index of multi-index mesh_key = 'mesh {0}'.format(self.mesh.id) # Find mesh dimensions - use 3D indices for simplicity diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index c12c61bdf..557660946 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -34,7 +34,10 @@ MGXS_TYPES = ['total', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', - 'chi'] + 'chi', + 'chi-prompt', + 'velocity', + 'prompt-neutron-lifetime'] # Supported domain types @@ -427,7 +430,11 @@ class MGXS(object): 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'} + 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', 'chi-prompt', + 'velocity', 'prompt-neutron-lifetime'} The type of multi-group cross section object to return domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization @@ -482,6 +489,12 @@ class MGXS(object): mgxs = NuFissionMatrixXS(domain, domain_type, energy_groups) elif mgxs_type == 'chi': mgxs = Chi(domain, domain_type, energy_groups) + elif mgxs_type == 'chi-prompt': + mgxs = ChiPrompt(domain, domain_type, energy_groups) + elif mgxs_type == 'velocity': + mgxs = Velocity(domain, domain_type, energy_groups) + elif mgxs_type == 'prompt-neutron-lifetime': + mgxs = PromptNeutronLifetime(domain, domain_type, energy_groups) mgxs.by_nuclide = by_nuclide mgxs.name = name @@ -1935,7 +1948,7 @@ class MatrixMGXS(MGXS): class TotalXS(MGXS): - r"""A total multi-group cross section. + """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 @@ -2044,7 +2057,7 @@ class TotalXS(MGXS): class TransportXS(MGXS): - r"""A transport-corrected total multi-group cross section. + """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 @@ -2187,7 +2200,7 @@ class TransportXS(MGXS): class NuTransportXS(TransportXS): - r"""A transport-corrected total multi-group cross section which + """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 @@ -2300,7 +2313,7 @@ class NuTransportXS(TransportXS): class AbsorptionXS(MGXS): - r"""An absorption multi-group cross section. + """An absorption multi-group cross section. Absorption is defined as all reactions that do not produce secondary neutrons (disappearance) plus fission reactions. @@ -2413,7 +2426,7 @@ class AbsorptionXS(MGXS): class CaptureXS(MGXS): - r"""A capture multi-group cross section. + """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 @@ -2541,7 +2554,7 @@ class CaptureXS(MGXS): class FissionXS(MGXS): - r"""A fission multi-group cross section. + """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 @@ -2651,7 +2664,7 @@ class FissionXS(MGXS): class NuFissionXS(MGXS): - r"""A fission neutron production multi-group cross section. + """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 @@ -2762,7 +2775,7 @@ class NuFissionXS(MGXS): class KappaFissionXS(MGXS): - r"""A recoverable fission energy production rate multi-group cross section. + """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, @@ -2878,7 +2891,7 @@ class KappaFissionXS(MGXS): class ScatterXS(MGXS): - r"""A scattering multi-group cross section. + """A scattering multi-group cross section. The scattering cross section is defined as the difference between the total and absorption cross sections. @@ -2991,7 +3004,7 @@ class ScatterXS(MGXS): class NuScatterXS(MGXS): - r"""A scattering neutron production multi-group cross section. + """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. @@ -3110,7 +3123,7 @@ class NuScatterXS(MGXS): class ScatterMatrixXS(MatrixMGXS): - r"""A scattering matrix multi-group cross section for one or more Legendre + """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 @@ -3444,7 +3457,7 @@ class ScatterMatrixXS(MatrixMGXS): subdomains='all', nuclides='all', moment='all', xs_type='macro', order_groups='increasing', row_column='inout', value='mean', **kwargs): - r"""Returns an array of multi-group cross sections. + """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. @@ -3890,7 +3903,7 @@ class NuScatterMatrixXS(ScatterMatrixXS): class MultiplicityMatrixXS(MatrixMGXS): - r"""The scattering multiplicity matrix. + """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 @@ -4044,7 +4057,7 @@ class MultiplicityMatrixXS(MatrixMGXS): class NuFissionMatrixXS(MatrixMGXS): - r"""A fission production matrix multi-group cross section. + """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 @@ -4159,7 +4172,7 @@ class NuFissionMatrixXS(MatrixMGXS): class Chi(MGXS): - r"""The fission spectrum. + """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 @@ -4615,3 +4628,836 @@ class Chi(MGXS): df['std. dev.'] *= np.tile(densities, tile_factor) return df + + +class ChiPrompt(Chi): + """The prompt 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:`ChiPrompt.energy_groups` and + :attr:`ChiPrompt.domain` properties. Tallies for the flux and appropriate + reaction rates over the specified domain are generated automatically via the + :attr:`ChiPrompt.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:`ChiPrompt.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}^p \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^p \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^p (r, + E') \psi(r, E', \Omega') \\ + \chi_g^p &= \frac{\langle \nu\sigma_{f,\rightarrow g}^p \phi \rangle}{\langle + \nu\sigma_f^p \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:`ChiPrompt.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(ChiPrompt, self).__init__(domain, domain_type, groups, by_nuclide, name) + self._rxn_type = 'chi-prompt' + + @property + def scores(self): + return ['delayed-nu-fission', 'delayed-nu-fission', + '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], [energyin], [energyout]] + + @property + def tally_keys(self): + return ['delayed-nu-fission-in', 'delayed-nu-fission-out', + 'nu-fission-in', 'nu-fission-out'] + + @property + def rxn_rate_tally(self): + if self._rxn_rate_tally is None: + self._rxn_rate_tally = self.tallies['nu-fission-out'] - \ + self.tallies['delayed-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: + delayed_nu_fission_in = self.tallies['delayed-nu-fission-in'] + nu_fission_in = self.tallies['nu-fission-in'] + prompt_nu_fission_in = nu_fission_in - delayed_nu_fission_in + + # Remove coarse energy filter to keep it out of tally arithmetic + energy_filter = prompt_nu_fission_in.find_filter('energy') + prompt_nu_fission_in.remove_filter(energy_filter) + + # Compute chi + self._xs_tally = self.rxn_rate_tally / prompt_nu_fission_in + super(ChiPrompt, self)._compute_xs() + + # Add the coarse energy filter back to the nu-fission tally + prompt_nu_fission_in.filters.append(energy_filter) + + return self._xs_tally + + def get_slice(self, nuclides=[], groups=[]): + """Build a sliced ChiDelayed 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 delayed-nu-fission-in since its + # group structure will work in super MGXS.get_slice(...) method + delayed_nu_fission_in = self.tallies['delayed-nu-fission-in'] + nu_fission_in = self.tallies['nu-fission-in'] + prompt_nu_fission_in = nu_fission_in - delayed_nu_fission_in + energy_filter = prompt_nu_fission_in.find_filter('energy') + prompt_nu_fission_in.remove_filter(energy_filter) + + # Call super class method and null out derived tallies + slice_xs = super(ChiPrompt, 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 + prompt_nu_fission_out = slice_xs.tallies['nu-fission-out'] - \ + slice_xs.tallies['delayed-nu-fission-out'] + tally_slice = prompt_nu_fission_out\ + .get_slice(filters=['energyout'], + filter_bins=filter_bins) + slice_xs._tallies['prompt-nu-fission-out'] = tally_slice + + # Add energy filter back to nu-fission-in tallies + slice_xs._tallies['prompt-nu-fission-in'].add_filter(energy_filter) + + slice_xs.sparse = self.sparse + return slice_xs + + def merge(self, other): + """Merge another ChiPrompt with this one + + If results have been loaded from a statepoint, then ChiPrompt 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 ChiPrompt') + + return super(ChiPrompt, self).merge(other) + + 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 delayed 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 + prompt_nu_fission_in = self.tallies['nu-fission-in'] - \ + self.tallies['delayed-nu-fission-in'] + prompt_nu_fission_out = self.tallies['nu-fission-out'] - \ + self.tallies['delayed-nu-fission-out'] + + # Sum out all nuclides + nuclides = self.get_all_nuclides() + prompt_nu_fission_in = prompt_nu_fission_in.summation\ + (nuclides=nuclides) + prompt_nu_fission_out = prompt_nu_fission_out.summation\ + (nuclides=nuclides) + + # Remove coarse energy filter to keep it out of tally arithmetic + energy_filter = prompt_nu_fission_in.find_filter('energy') + prompt_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 = prompt_nu_fission_out / prompt_nu_fission_in + + # Add the coarse energy filter back to the nu-fission tally + prompt_nu_fission_in.filters.append(energy_filter) + + xs = xs_tally.get_values(filters=filters, + filter_bins=filter_bins, value=value) + + # Get chi delayed 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 prompt 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 prompt 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 + + +class Velocity(MGXS): + """A velocity 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 velocity cross sections for multi-group neutronics + calculations. At a minimum, one needs to set the + :attr:`Velocity.energy_groups` and :attr:`Velocity.domain` + properties. Tallies for the flux and appropriate reaction rates over the + specified domain are generated automatically via the + :attr:`Velocity.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:`Velocity.xs_tally` property. + + For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the + velocity cross section is calculated as: + + .. math:: + + \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; + \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} + d\Omega \int_{E_g}^{E_{g-1}} dE \; \frac{\psi (r, E, \Omega)}{v (r, E)}}. + + 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. 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(Velocity, self).__init__(domain, domain_type, + groups, by_nuclide, name) + self._rxn_type = 'velocity' + + @property + def scores(self): + return ['inverse-velocity', 'flux'] + + @property + def tally_keys(self): + return ['inverse-velocity', 'flux'] + + @property + def rxn_rate_tally(self): + if self._rxn_rate_tally is None: + self._rxn_rate_tally = self.tallies['flux'] + self._rxn_rate_tally.sparse = self.sparse + return self._rxn_rate_tally + + @property + def xs_tally(self): + + if self._xs_tally is None: + inverse_velocity = self.tallies['inverse-velocity'] + + # Compute the velocity + self._xs_tally = self.rxn_rate_tally / inverse_velocity + super(Velocity, self)._compute_xs() + + return self._xs_tally + + 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']) + + # 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 + string += '{0: <16}\n'.format\ + ('\tVelocity [cm/second]:') + + 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) + + +class PromptNeutronLifetime(MGXS): + """The prompt neutron lifetime. + + 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:`PromptNeutronLifetime.energy_groups` + and :attr:`PromptNeutronLifetime.domain` properties. Tallies for the flux + and appropriate reaction rates over the specified domain are generated + automatically via the :attr:`PromptNeutronLifetime.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:`PromptNeutronLifetime.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:: + + \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; + \frac{\psi (r, E, \Omega)}{v (r, E)}}{\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')}. + + 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:`ChiDelayed.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(PromptNeutronLifetime, self).__init__(domain, domain_type, groups, + by_nuclide, name) + self._rxn_type = 'prompt-neutron-lifetime' + + @property + def scores(self): + return ['nu-fission', 'inverse-velocity'] + + @property + def tally_keys(self): + return ['nu-fission', 'inverse-velocity'] + + @property + def rxn_rate_tally(self): + if self._rxn_rate_tally is None: + self._rxn_rate_tally = self.tallies['inverse-velocity'] + 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 = self.tallies['nu-fission'] + + # Compute the prompt neutron lifetime + self._xs_tally = self.rxn_rate_tally / nu_fission + super(PromptNeutronLifetime, self)._compute_xs() + + return self._xs_tally + + 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']) + + # 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 + string += '{0: <16}\n'.format\ + ('\tPrompt Neutron Lifetime [seconds]:') + + 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)