diff --git a/docs/source/pythonapi/examples/mgxs-part-i.ipynb b/docs/source/pythonapi/examples/mgxs-part-i.ipynb index 03195b198..a1946c2b5 100644 --- a/docs/source/pythonapi/examples/mgxs-part-i.ipynb +++ b/docs/source/pythonapi/examples/mgxs-part-i.ipynb @@ -1192,7 +1192,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.0" } }, "nbformat": 4, diff --git a/openmc/filter.py b/openmc/filter.py index dd3e21e5c..623106bcf 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -1453,7 +1453,7 @@ class PolarFilter(RealFilter): msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ 'since it is less than 0'.format(edge, type(self)) raise ValueError(msg) - elif edge > np.pi: + elif not np.isclose(edge, np.pi) and edge > np.pi: msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ 'since it is greater than pi'.format(edge, type(self)) raise ValueError(msg) @@ -1552,11 +1552,11 @@ class AzimuthalFilter(RealFilter): 'since it is a non-integer or floating point ' \ 'value'.format(edge, type(self)) raise ValueError(msg) - elif edge < -np.pi: + elif not np.isclose(edge, -np.pi) and edge < -np.pi: msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ 'since it is less than -pi'.format(edge, type(self)) raise ValueError(msg) - elif edge > np.pi: + elif not np.isclose(edge, np.pi) and edge > np.pi: msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ 'since it is greater than pi'.format(edge, type(self)) raise ValueError(msg) diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index fab4127eb..fdb04e061 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -90,6 +90,12 @@ class MGXS(object): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -105,6 +111,10 @@ class MGXS(object): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -152,7 +162,8 @@ class MGXS(object): """ def __init__(self, domain=None, domain_type=None, - energy_groups=None, by_nuclide=False, name=''): + energy_groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): self._name = '' self._rxn_type = None self._by_nuclide = None @@ -161,6 +172,8 @@ class MGXS(object): self._domain = None self._domain_type = None self._energy_groups = None + self._num_polar = None + self._num_azimuthal = None self._tally_trigger = None self._tallies = None self._rxn_rate_tally = None @@ -180,6 +193,8 @@ class MGXS(object): self.domain = domain if energy_groups is not None: self.energy_groups = energy_groups + self.num_polar = num_polar + self.num_azimuthal = num_azimuthal def __deepcopy__(self, memo): existing = memo.get(id(self)) @@ -194,6 +209,8 @@ class MGXS(object): clone._domain = self.domain clone._domain_type = self.domain_type clone._energy_groups = copy.deepcopy(self.energy_groups, memo) + clone._num_polar = self._num_polar + clone._num_azimuthal = self._num_azimuthal 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) @@ -214,6 +231,28 @@ class MGXS(object): else: return existing + def _add_angle_filters(self, filters): + # Add the azimuthal and polar bins if needed. + if self.num_polar or self.num_azimuthal: + # Then the user has requested angular data, so create the bins + if self.num_polar: + pol_bins = np.linspace(0., np.pi, num=self.num_polar + 1, + endpoint=True) + else: + pol_bins = np.linspace(0., np.pi, num=2, endpoint=True) + if self.num_azimuthal: + azi_bins = np.linspace(-np.pi, np.pi, + num=self.num_azimuthal + 1, + endpoint=True) + else: + azi_bins = np.linspace(-np.pi, np.pi, num=2, endpoint=True) + + for filt in filters: + filt.insert(0, openmc.PolarFilter(pol_bins)) + filt.insert(1, openmc.AzimuthalFilter(azi_bins)) + + return filters + @property def name(self): return self._name @@ -238,6 +277,14 @@ class MGXS(object): def energy_groups(self): return self._energy_groups + @property + def num_polar(self): + return self._num_polar + + @property + def num_azimuthal(self): + return self._num_azimuthal + @property def tally_trigger(self): return self._tally_trigger @@ -254,7 +301,11 @@ class MGXS(object): def filters(self): group_edges = self.energy_groups.group_edges energy_filter = openmc.EnergyFilter(group_edges) - return [[energy_filter]] * len(self.scores) + filters = [] + for i in range(len(self.scores)): + filters.append([energy_filter]) + + return self._add_angle_filters(filters) @property def tally_keys(self): @@ -416,6 +467,18 @@ class MGXS(object): cv.check_type('energy groups', energy_groups, openmc.mgxs.EnergyGroups) self._energy_groups = energy_groups + @num_polar.setter + def num_polar(self, num_polar): + if num_polar: + cv.check_type('num_polar', num_polar, Integral) + self._num_polar = num_polar + + @num_azimuthal.setter + def num_azimuthal(self, num_azimuthal): + if num_azimuthal: + cv.check_type('num_azimuthal', num_azimuthal, Integral) + self._num_azimuthal = num_azimuthal + @tally_trigger.setter def tally_trigger(self, tally_trigger): cv.check_type('tally trigger', tally_trigger, openmc.Trigger) @@ -829,7 +892,8 @@ class MGXS(object): 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, + 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 @@ -851,18 +915,51 @@ class MGXS(object): 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) + # Accomodate the polar and azimuthal bins if needed + if self.num_polar or self.num_azimuthal: + if self.num_polar: + num_pol = self.num_polar + else: + num_pol = 1 + if self.num_azimuthal: + num_azi = self.num_azimuthal + else: + num_azi = 1 + num_subdomains = int(xs.shape[0] / + (num_groups * num_pol * num_azi)) + new_shape = (num_pol, num_azi, num_subdomains, num_groups) + \ + xs.shape[1:] + xs = np.reshape(xs, new_shape) - # 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, :] + # 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, :] - if squeeze: - xs = np.squeeze(xs) - xs = np.atleast_1d(xs) + if squeeze: + # We want to squeeze out everything but the polar, azimuthal, + # and energy group data. + dont_squeeze = (0, 1, 3) + # Squeeze will return a ValueError if the axis has a size + # greater than 1, so try each axis in axes one at a time, + # and do our own check to preclude the ValueError + initial_shape = len(xs.shape) + for axis in range(initial_shape - 1, 0, -1): + if axis not in dont_squeeze and xs.shape[axis] == 1: + xs = np.squeeze(xs, axis=axis) + else: + num_subdomains = int(xs.shape[0] / num_groups) + new_shape = (num_subdomains, num_groups) + xs.shape[1:] + xs = np.reshape(xs, new_shape) + + # 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, :] + + if squeeze: + xs = np.squeeze(xs) + xs = np.atleast_1d(xs) return xs @@ -1318,6 +1415,22 @@ class MGXS(object): print(string) return + # Set polar/azimuthal bins + if self.num_polar or self.num_azimuthal: + if self.num_polar: + pol_bins = np.linspace(0., np.pi, + num=self.num_polar + 1, + endpoint=True) + else: + pol_bins = np.linspace(0., np.pi, num=2, endpoint=True) + if self.num_azimuthal: + azi_bins = np.linspace(-np.pi, np.pi, + num=self.num_azimuthal + 1, + endpoint=True) + else: + azi_bins = np.linspace(-np.pi, np.pi, num=2, + endpoint=True) + # Loop over all subdomains for subdomain in subdomains: @@ -1335,18 +1448,44 @@ class MGXS(object): string += '{0: <16}\n'.format(xs_header) template = '{0: <12}Group {1} [{2: <10} - {3: <10}eV]:\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' + average_xs = self.get_xs(nuclide=[nuclide], + subdomain=[subdomain], + xs_type=xs_type, value='mean') + rel_err_xs = self.get_xs(nuclide=[nuclide], + subdomain=[subdomain], + xs_type=xs_type, value='rel_err') + rel_err_xs = rel_err_xs * 100. + + if self.num_polar or self.num_azimuthal: + # Loop over polar, azimuthal, and 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 group in range(1, self.num_groups + 1): + bounds = \ + self.energy_groups.get_group_bounds(group) + string += '\t' + template.format('', group, + bounds[0], + bounds[1]) + string += '{:.2e} +/- {:1.2e}%'.format( + average_xs[pol, azi, group - 1], + rel_err_xs[pol, azi, group - 1]) + string += '\n' + string += '\n' + else: + # Loop over energy groups + 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]) + string += '{:.2e} +/- {:1.2e}%'.format( + average_xs[group - 1], rel_err_xs[group - 1]) + string += '\n' string += '\n' string += '\n' @@ -1625,14 +1764,12 @@ class MGXS(object): # If the user requested a specific set of nuclides elif self.by_nuclide and nuclides != 'all': - query_nuclides = nuclides 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: - query_nuclides = self.nuclides df = self.xs_tally.get_pandas_dataframe( distribcell_paths=distribcell_paths) @@ -1642,49 +1779,53 @@ class MGXS(object): else: df = df.drop('score', axis=1) - # Determine if change-in-angle bins are included in the MGXS to - # properly tile the group boundaries - if 'mu low' in df: - # Find the length of the mu filters indirectly from the number - # of times the mu bins repeats. - num_mu = int(df.shape[0] / - df[df['mu low'] == df['mu low'][0]].shape[0]) + # Override polar and azimuthal bounds with indices + if self.num_polar or self.num_azimuthal: + # First for polar + del df['polar high'] + df.rename(columns={'polar low': 'polar bin'}, inplace=True) + df_bins = df['polar bin'] + if self.num_polar: + pol_bins = np.linspace(0., np.pi, + num=self.num_polar + 1, + endpoint=True) + else: + pol_bins = np.linspace(0., np.pi, num=2, endpoint=True) + df['polar bin'] = np.searchsorted(pol_bins, df_bins) + 1 + + # Second for azimuthal + del df['azimuthal high'] + df.rename(columns={'azimuthal low': 'azimuthal bin'}, + inplace=True) + df_bins = df['azimuthal bin'] + if self.num_azimuthal: + azi_bins = np.linspace(-np.pi, np.pi, + num=self.num_azimuthal + 1, + endpoint=True) + else: + azi_bins = np.linspace(-np.pi, np.pi, num=2, + endpoint=True) + df['azimuthal bin'] = np.searchsorted(azi_bins, df_bins) + 1 + columns = ['polar bin', 'azimuthal bin'] else: - num_mu = 1 + columns = [] # Override energy groups bounds with indices - all_groups = np.arange(self.num_groups, 0, -1, dtype=np.int) - all_groups = np.repeat(all_groups, len(query_nuclides) * num_mu) - if 'energy low [eV]' in df and 'energyout low [eV]' in df: - df.rename(columns={'energy low [eV]': 'group in'}, - inplace=True) - in_groups = np.tile(all_groups, int(self.num_subdomains)) - in_groups = np.repeat(in_groups, int(df.shape[0] / in_groups.size)) - df['group in'] = in_groups + if 'energy low [eV]' in df: + df.rename(columns={'energy low [eV]': 'group in'}, inplace=True) + df_bins = df['group in'] + df['group in'] = self.energy_groups.num_groups - \ + np.searchsorted(self.energy_groups.group_edges, df_bins) del df['energy high [eV]'] - - df.rename(columns={'energyout low [eV]': 'group out'}, - inplace=True) - out_groups = np.repeat(all_groups, self.xs_tally.num_scores) - out_groups = np.tile(out_groups, int(df.shape[0] / out_groups.size)) - df['group out'] = out_groups - del df['energyout high [eV]'] - columns = ['group in', 'group out'] - + columns += ['group in'] elif 'energyout low [eV]' in df: df.rename(columns={'energyout low [eV]': 'group out'}, inplace=True) - in_groups = np.tile(all_groups, int(df.shape[0] / all_groups.size)) - df['group out'] = in_groups + df_bins = df['group out'] + df['group out'] = self.energy_groups.num_groups - \ + np.searchsorted(self.energy_groups.group_edges, df_bins) del df['energyout high [eV]'] - columns = ['group out'] - - elif 'energy low [eV]' in df: - df.rename(columns={'energy low [eV]': 'group in'}, inplace=True) - in_groups = np.tile(all_groups, int(df.shape[0] / all_groups.size)) - df['group in'] = in_groups - del df['energy high [eV]'] - columns = ['group in'] + columns += ['group out'] # Select out those groups the user requested if not isinstance(groups, string_types): @@ -1712,7 +1853,7 @@ class MGXS(object): # energy groups such that data is from fast to thermal if self.domain_type == 'mesh': mesh_str = 'mesh {0}'.format(self.domain.id) - df.sort_values(by=[(mesh_str, 'x'), (mesh_str, 'y'), \ + df.sort_values(by=[(mesh_str, 'x'), (mesh_str, 'y'), (mesh_str, 'z')] + columns, inplace=True) else: df.sort_values(by=[self.domain_type] + columns, inplace=True) @@ -1767,6 +1908,12 @@ class MatrixMGXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -1782,6 +1929,10 @@ class MatrixMGXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -1834,8 +1985,9 @@ class MatrixMGXS(MGXS): group_edges = self.energy_groups.group_edges energy = openmc.EnergyFilter(group_edges) energyout = openmc.EnergyoutFilter(group_edges) + filters = [[energy], [energy, energyout]] - return [[energy], [energy, energyout]] + return self._add_angle_filters(filters) def get_xs(self, in_groups='all', out_groups='all', subdomains='all', nuclides='all', @@ -1975,23 +2127,62 @@ class MatrixMGXS(MGXS): 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) + # Accomodate the polar and azimuthal bins if needed + if self.num_polar or self.num_azimuthal: + if self.num_polar: + num_pol = self.num_polar + else: + num_pol = 1 + if self.num_azimuthal: + num_azi = self.num_azimuthal + else: + num_azi = 1 + num_subdomains = int(xs.shape[0] / (num_in_groups * + num_out_groups * num_pol * + num_azi)) + new_shape = (num_pol, num_azi, num_subdomains, num_in_groups, + num_out_groups) + 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) + # Transpose the 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, :] + # 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: - xs = np.squeeze(xs) - xs = np.atleast_2d(xs) + if squeeze: + # We want to squeeze out everything but the polar, azimuthal, + # and in/out energy group data. + dont_squeeze = (0, 1, 3, 4) + # Squeeze will return a ValueError if the axis has a size + # greater than 1, so try each axis in axes one at a time, + # and do our own check to preclude the ValueError + initial_shape = len(xs.shape) + for axis in range(initial_shape - 1, 0, -1): + if axis not in dont_squeeze and xs.shape[axis] == 1: + xs = np.squeeze(xs, axis=axis) + else: + 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 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: + xs = np.squeeze(xs) + xs = np.atleast_2d(xs) return xs @@ -2116,12 +2307,27 @@ class MatrixMGXS(MGXS): 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 or self.num_azimuthal: + if self.num_polar: + pol_bins = np.linspace(0., np.pi, + num=self.num_polar + 1, + endpoint=True) + else: + pol_bins = np.linspace(0., np.pi, num=2, endpoint=True) + if self.num_azimuthal: + azi_bins = np.linspace(-np.pi, np.pi, + num=self.num_azimuthal + 1, + endpoint=True) + else: + azi_bins = np.linspace(-np.pi, np.pi, num=2, + endpoint=True) + # Loop over all subdomains for subdomain in subdomains: - if self.domain_type == 'distribcell': - string += \ - '{0: <16}=\t{1}\n'.format('\tSubdomain', subdomain) + 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: @@ -2134,24 +2340,57 @@ class MatrixMGXS(MGXS): string += '{0: <16}\n'.format(xs_header) 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) + average_xs = self.get_xs(nuclide=[nuclide], + subdomain=[subdomain], + xs_type=xs_type, value='mean') + rel_err_xs = self.get_xs(nuclide=[nuclide], + subdomain=[subdomain], + xs_type=xs_type, value='rel_err') + rel_err_xs = rel_err_xs * 100. + + if self.num_polar or self.num_azimuthal: + # 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 += '{1:.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) + 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_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' @@ -2197,6 +2436,12 @@ class TotalXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -2212,6 +2457,10 @@ class TotalXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -2261,9 +2510,11 @@ class TotalXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(TotalXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'total' @@ -2315,6 +2566,12 @@ class TransportXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -2330,6 +2587,10 @@ class TransportXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -2379,9 +2640,11 @@ class TransportXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(TransportXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'transport' self._estimator = 'analog' self._valid_estimators = ['analog'] @@ -2395,7 +2658,9 @@ class TransportXS(MGXS): group_edges = self.energy_groups.group_edges energy_filter = openmc.EnergyFilter(group_edges) energyout_filter = openmc.EnergyoutFilter(group_edges) - return [[energy_filter], [energy_filter], [energyout_filter]] + filters = [[energy_filter], [energy_filter], [energyout_filter]] + + return self._add_angle_filters(filters) @property def rxn_rate_tally(self): @@ -2448,6 +2713,12 @@ class NuTransportXS(TransportXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -2463,6 +2734,10 @@ class NuTransportXS(TransportXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -2512,9 +2787,11 @@ class NuTransportXS(TransportXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(NuTransportXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'nu-transport' @property @@ -2569,6 +2846,12 @@ class AbsorptionXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -2584,6 +2867,10 @@ class AbsorptionXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -2634,9 +2921,11 @@ class AbsorptionXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(AbsorptionXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'absorption' @@ -2686,6 +2975,12 @@ class CaptureXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -2701,6 +2996,10 @@ class CaptureXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -2750,9 +3049,11 @@ class CaptureXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(CaptureXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'capture' @property @@ -2808,6 +3109,12 @@ class FissionXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -2823,6 +3130,10 @@ class FissionXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -2872,9 +3183,11 @@ class FissionXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(FissionXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'fission' @@ -2919,6 +3232,12 @@ class NuFissionXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -2934,6 +3253,10 @@ class NuFissionXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -2983,11 +3306,14 @@ class NuFissionXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(NuFissionXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'nu-fission' + class KappaFissionXS(MGXS): r"""A recoverable fission energy production rate multi-group cross section. @@ -3034,6 +3360,12 @@ class KappaFissionXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -3049,6 +3381,10 @@ class KappaFissionXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -3098,9 +3434,11 @@ class KappaFissionXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(KappaFissionXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'kappa-fission' @@ -3147,6 +3485,12 @@ class ScatterXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -3162,6 +3506,10 @@ class ScatterXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -3211,9 +3559,11 @@ class ScatterXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(ScatterXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'scatter' @@ -3262,6 +3612,12 @@ class NuScatterXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -3277,6 +3633,10 @@ class NuScatterXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -3326,9 +3686,11 @@ class NuScatterXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(NuScatterXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'nu-scatter' self._estimator = 'analog' self._valid_estimators = ['analog'] @@ -3390,6 +3752,12 @@ class ScatterMatrixXS(MatrixMGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -3418,6 +3786,10 @@ class ScatterMatrixXS(MatrixMGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -3467,9 +3839,11 @@ class ScatterMatrixXS(MatrixMGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(ScatterMatrixXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, + num_azimuthal) self._rxn_type = 'scatter' self._correction = 'P0' self._scatter_format = 'legendre' @@ -3534,7 +3908,7 @@ class ScatterMatrixXS(MatrixMGXS): endpoint=True) filters = [[energy], [energy, energyout, openmc.MuFilter(bins)]] - return filters + return self._add_angle_filters(filters) @property def rxn_rate_tally(self): @@ -3891,40 +4265,81 @@ class ScatterMatrixXS(MatrixMGXS): num_mu_bins = 1 # Reshape tally data array with separate axes for domain and energy - num_subdomains = int(xs.shape[0] / - (num_mu_bins * num_in_groups * num_out_groups)) - 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 in_groups, out_groups, - # and, if needed, num_mu_bins dimension. These must not be squeezed - # so 1-group problems have the correct shape. - if self.scatter_format == 'histogram': - axes = (5, 4, 0) + # Accomodate the polar and azimuthal bins if needed + if self.num_polar or self.num_azimuthal: + if self.num_polar: + num_pol = self.num_polar else: - axes = (4, 3, 0) - # Squeeze will return a ValueError if the axis has a size greater - # than 1, so try each axis in axes one at a time, catching the - # ValueError as needed. - for axis in axes: - if xs.shape[axis] == 1: - xs = np.squeeze(xs, axis=axis) + num_pol = 1 + if self.num_azimuthal: + num_azi = self.num_azimuthal + else: + num_azi = 1 + num_subdomains = int(xs.shape[0] / + (num_mu_bins * num_in_groups * + num_out_groups * num_pol * num_azi)) + if self.scatter_format == 'histogram': + new_shape = (num_pol, num_azi, num_subdomains, num_in_groups, + num_out_groups, num_mu_bins) + else: + new_shape = (num_pol, num_azi, 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, ...] + + 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. + if self.scatter_format == 'histogram': + dont_squeeze = (0, 1, 3, 4, 5) + else: + dont_squeeze = (0, 1, 3, 4) + else: + num_subdomains = int(xs.shape[0] / (num_mu_bins * num_in_groups * + num_out_groups)) + 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 in_groups, + # out_groups, and, if needed, num_mu_bins dimension. These must + # not be squeezed so 1-group problems have the correct shape. + if self.scatter_format == 'histogram': + dont_squeeze = (1, 2, 3) + else: + dont_squeeze = (1, 2) + # Squeeze will return a ValueError if the axis has a size + # greater than 1, so try each axis in axes one at a time, + # and do our own check to preclude the ValueError + initial_shape = len(xs.shape) + for axis in range(initial_shape - 1, 0, -1): + if axis not in dont_squeeze and xs.shape[axis] == 1: + xs = np.squeeze(xs, axis=axis) return xs def get_pandas_dataframe(self, groups='all', nuclides='all', moment='all', @@ -4004,11 +4419,12 @@ class ScatterMatrixXS(MatrixMGXS): elif self.scatter_format == 'histogram': # Replace the mu low and mu high columns with a single mu bin - del df['mu high'] df.rename(columns={'mu low': 'mu bins'}, inplace=True) - bins = [i + 1 for i in range(self.histogram_bins)] - bins = np.tile(bins, int(df.shape[0] / len(bins))) - df['mu bins'] = bins + df_bins = df['mu bins'] + mu_bins = np.linspace(-1., 1., num=self.histogram_bins + 1, + endpoint=True) + df['mu bins'] = np.searchsorted(mu_bins, df_bins) + 1 + del df['mu high'] return df @@ -4086,12 +4502,27 @@ class ScatterMatrixXS(MatrixMGXS): 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 or self.num_azimuthal: + if self.num_polar: + pol_bins = np.linspace(0., np.pi, + num=self.num_polar + 1, + endpoint=True) + else: + pol_bins = np.linspace(0., np.pi, num=2, endpoint=True) + if self.num_azimuthal: + azi_bins = np.linspace(-np.pi, np.pi, + num=self.num_azimuthal + 1, + endpoint=True) + else: + azi_bins = np.linspace(-np.pi, np.pi, num=2, + endpoint=True) + # Loop over all subdomains for subdomain in subdomains: - if self.domain_type == 'distribcell': - string += \ - '{0: <16}=\t{1}\n'.format('\tSubdomain', subdomain) + 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: @@ -4104,22 +4535,58 @@ class ScatterMatrixXS(MatrixMGXS): string += '{0: <16}\n'.format(xs_header) 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) + average_xs = self.get_xs(nuclide=[nuclide], + subdomain=[subdomain], + xs_type=xs_type, value='mean', + moment=moment) + rel_err_xs = self.get_xs(nuclide=[nuclide], + subdomain=[subdomain], + xs_type=xs_type, value='rel_err', + moment=moment) + rel_err_xs = rel_err_xs * 100. + + if self.num_polar or self.num_azimuthal: + # 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 += '{:.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) + 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 += '{:.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' @@ -4163,6 +4630,12 @@ class NuScatterMatrixXS(ScatterMatrixXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -4182,6 +4655,10 @@ class NuScatterMatrixXS(ScatterMatrixXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -4231,9 +4708,11 @@ class NuScatterMatrixXS(ScatterMatrixXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(NuScatterMatrixXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'nu-scatter' self._hdf5_key = 'nu-scatter matrix' @@ -4289,6 +4768,12 @@ class MultiplicityMatrixXS(MatrixMGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -4304,6 +4789,10 @@ class MultiplicityMatrixXS(MatrixMGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -4353,9 +4842,11 @@ class MultiplicityMatrixXS(MatrixMGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(MultiplicityMatrixXS, self).__init__(domain, domain_type, groups, - by_nuclide, name) + by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'multiplicity matrix' self._estimator = 'analog' self._valid_estimators = ['analog'] @@ -4371,8 +4862,9 @@ class MultiplicityMatrixXS(MatrixMGXS): group_edges = self.energy_groups.group_edges energy = openmc.EnergyFilter(group_edges) energyout = openmc.EnergyoutFilter(group_edges) + filters = [[energy, energyout], [energy, energyout]] - return [[energy, energyout], [energy, energyout]] + return self._add_angle_filters(filters) @property def rxn_rate_tally(self): @@ -4438,6 +4930,12 @@ class NuFissionMatrixXS(MatrixMGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -4453,6 +4951,10 @@ class NuFissionMatrixXS(MatrixMGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -4502,9 +5004,11 @@ class NuFissionMatrixXS(MatrixMGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(NuFissionMatrixXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'nu-fission' self._hdf5_key = 'nu-fission matrix' self._estimator = 'analog' @@ -4555,6 +5059,12 @@ class Chi(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -4570,6 +5080,10 @@ class Chi(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -4619,8 +5133,10 @@ class Chi(MGXS): """ 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) + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): + super(Chi, self).__init__(domain, domain_type, groups, by_nuclide, + name, num_polar, num_azimuthal) self._rxn_type = 'chi' self._estimator = 'analog' self._valid_estimators = ['analog'] @@ -4635,7 +5151,9 @@ class Chi(MGXS): group_edges = self.energy_groups.group_edges energyout = openmc.EnergyoutFilter(group_edges) energyin = openmc.EnergyFilter([group_edges[0], group_edges[-1]]) - return [[energyin], [energyout]] + filters = [[energyin], [energyout]] + + return self._add_angle_filters(filters) @property def tally_keys(self): @@ -4929,24 +5447,57 @@ class Chi(MGXS): xs = np.squeeze(xs, axis=len(xs.shape) - 1) xs = np.nan_to_num(xs) - # 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) + # Reshape tally data array with separate axes for domain and energy + # Accomodate the polar and azimuthal bins if needed + if self.num_polar or self.num_azimuthal: + if self.num_polar: + num_pol = self.num_polar + else: + num_pol = 1 + if self.num_azimuthal: + num_azi = self.num_azimuthal + else: + num_azi = 1 + num_subdomains = int(xs.shape[0] / + (num_groups * num_pol * num_azi)) + new_shape = (num_pol, num_azi, num_subdomains, num_groups) + \ + xs.shape[1:] + xs = np.reshape(xs, new_shape) - # 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, :] + # 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, :] - if squeeze: - xs = np.squeeze(xs) - xs = np.atleast_1d(xs) + if squeeze: + # We want to squeeze out everything but the polar, azimuthal, + # and energy group data. + dont_squeeze = (0, 1, 3) + # Squeeze will return a ValueError if the axis has a size + # greater than 1, so try each axis in axes one at a time, + # and do our own check to preclude the ValueError + initial_shape = len(xs.shape) + for axis in range(initial_shape - 1, 0, -1): + if axis not in dont_squeeze and xs.shape[axis] == 1: + xs = np.squeeze(xs, axis=axis) + else: + num_subdomains = int(xs.shape[0] / num_groups) + new_shape = (num_subdomains, num_groups) + xs.shape[1:] + xs = np.reshape(xs, new_shape) + + # 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, :] + + if squeeze: + xs = np.squeeze(xs) + xs = np.atleast_1d(xs) return xs @@ -5076,6 +5627,12 @@ class ChiPrompt(Chi): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -5091,6 +5648,10 @@ class ChiPrompt(Chi): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -5140,8 +5701,11 @@ class ChiPrompt(Chi): """ 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) + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): + super(ChiPrompt, self).__init__(domain, domain_type, groups, + by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'chi-prompt' @property @@ -5191,6 +5755,12 @@ class InverseVelocity(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -5206,6 +5776,10 @@ class InverseVelocity(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -5256,9 +5830,11 @@ class InverseVelocity(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(InverseVelocity, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'inverse-velocity' def get_units(self, xs_type='macro'): @@ -5326,6 +5902,12 @@ class PromptNuFissionXS(MGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -5341,6 +5923,10 @@ class PromptNuFissionXS(MGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -5390,9 +5976,11 @@ class PromptNuFissionXS(MGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(PromptNuFissionXS, self).__init__(domain, domain_type, groups, - by_nuclide, name) + by_nuclide, name, num_polar, + num_azimuthal) self._rxn_type = 'prompt-nu-fission' @@ -5440,6 +6028,12 @@ class PromptNuFissionMatrixXS(MatrixMGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angles for angle discretization; defaults to + no discretization + num_azimuthal : Integral, optional + Number of equi-width azimuthal angles for angle discretization; + defaults to no discretization Attributes ---------- @@ -5455,6 +6049,10 @@ class PromptNuFissionMatrixXS(MatrixMGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation + num_polar : None or Integral + Number of equi-width polar angles for angle discretization + num_azimuthal : None or Integral + Number of equi-width azimuthal angles for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to compute the cross section @@ -5504,9 +6102,11 @@ class PromptNuFissionMatrixXS(MatrixMGXS): """ def __init__(self, domain=None, domain_type=None, - groups=None, by_nuclide=False, name=''): + groups=None, by_nuclide=False, name='', num_polar=None, + num_azimuthal=None): super(PromptNuFissionMatrixXS, self).__init__(domain, domain_type, - groups, by_nuclide, name) + groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'prompt-nu-fission' self._hdf5_key = 'prompt-nu-fission matrix' self._estimator = 'analog' diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 4b8092354..3d2dcb8a1 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -3228,7 +3228,7 @@ contains end do filt % bins(Nangle + 1) = PI else - call fatal_error("Number of bins for mu filter must be& + call fatal_error("Number of bins for polar filter must be& & greater than 1 on tally " & // trim(to_str(t % id)) // ".") end if @@ -3262,7 +3262,7 @@ contains end do filt % bins(Nangle + 1) = PI else - call fatal_error("Number of bins for mu filter must be& + call fatal_error("Number of bins for azimuthal filter must be& & greater than 1 on tally " & // trim(to_str(t % id)) // ".") end if