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