diff --git a/openmc/arithmetic.py b/openmc/arithmetic.py index f6f701d095..13f300968a 100644 --- a/openmc/arithmetic.py +++ b/openmc/arithmetic.py @@ -1,5 +1,7 @@ import sys +import copy from numbers import Integral +from collections import Iterable import numpy as np @@ -430,7 +432,7 @@ class CrossFilter(object): filter_index = left_index * self.right_filter.num_bins + right_index return filter_index - def get_pandas_dataframe(self, datasize, summary=None): + def get_pandas_dataframe(self, data_size, summary=None): """Builds a Pandas DataFrame for the CrossFilter's bins. This method constructs a Pandas DataFrame object for the CrossFilter @@ -445,7 +447,7 @@ class CrossFilter(object): Parameters ---------- - datasize : Integral + data_size : Integral The total number of bins in the tally corresponding to this filter summary : None or Summary An optional Summary object to be used to construct columns for @@ -472,12 +474,12 @@ class CrossFilter(object): # If left and right filters are identical, do not combine bins if self.left_filter == self.right_filter: - df = self.left_filter.get_pandas_dataframe(datasize, summary) + df = self.left_filter.get_pandas_dataframe(data_size, summary) # If left and right filters are different, combine their bins else: - left_df = self.left_filter.get_pandas_dataframe(datasize, summary) - right_df = self.right_filter.get_pandas_dataframe(datasize, summary) + left_df = self.left_filter.get_pandas_dataframe(data_size, summary) + right_df = self.right_filter.get_pandas_dataframe(data_size, summary) left_df = left_df.astype(str) right_df = right_df.astype(str) df = '(' + left_df + ' ' + self.binary_op + ' ' + right_df + ')' @@ -713,6 +715,13 @@ class AggregateFilter(object): def __ne__(self, other): return not self == other + def __gt__(self, other): + # FIXME + return False + + def __lt__(self, other): + return not self > other + def __repr__(self): string = 'AggregateFilter\n' string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self.type) @@ -757,7 +766,7 @@ class AggregateFilter(object): @property def num_bins(self): - return 1 if self.aggregate_filter else 0 + return len(self.bins) if self.aggregate_filter else 0 @property def stride(self): @@ -779,8 +788,10 @@ class AggregateFilter(object): @bins.setter def bins(self, bins): - cv.check_iterable_type('bins', bins, (Integral, tuple)) - self._bins = bins + cv.check_iterable_type('bins', bins, Iterable) + self._bins = [] + for bin in bins: + self._bins.append(tuple(bin)) @aggregate_op.setter def aggregate_op(self, aggregate_op): @@ -825,9 +836,9 @@ class AggregateFilter(object): '"{0}" is not one of the bins'.format(filter_bin) raise ValueError(msg) else: - return 0 + return self.bins.index(filter_bin) - def get_pandas_dataframe(self, datasize, summary=None): + def get_pandas_dataframe(self, data_size, summary=None): """Builds a Pandas DataFrame for the AggregateFilter's bins. This method constructs a Pandas DataFrame object for the AggregateFilter @@ -836,7 +847,7 @@ class AggregateFilter(object): Parameters ---------- - datasize : Integral + data_size : Integral The total number of bins in the tally corresponding to this filter summary : None or Summary An optional Summary object to be used to construct columns for @@ -864,14 +875,85 @@ class AggregateFilter(object): import pandas as pd - # Construct a sring representing the filter aggregation - aggregate_bin = '{0}('.format(self.aggregate_op) - aggregate_bin += ', '.join(map(str, self.bins)) + ')' + # Create NumPy array of the bin tuples for repeating / tiling + filter_bins = np.empty(self.num_bins, dtype=tuple) + for i, bin in enumerate(self.bins): + filter_bins[i] = bin - # Construct NumPy array of bin repeated for each element in dataframe - aggregate_bin_array = np.array([aggregate_bin]) - aggregate_bin_array = np.repeat(aggregate_bin_array, datasize) + # Repeat and tile bins as needed for DataFrame + filter_bins = np.repeat(filter_bins, self.stride) + tile_factor = data_size / len(filter_bins) + filter_bins = np.tile(filter_bins, tile_factor) - # Construct Pandas DataFrame for the AggregateFilter - df = pd.DataFrame({self.type: aggregate_bin_array}) + # Create DataFrame with aggregated bins + df = pd.DataFrame({self.type: filter_bins}) return df + + def can_merge(self, other): + """Determine if AggregateFilter can be merged with another. + + Parameters + ---------- + other : AggregateFilter + Filter to compare with + + Returns + ------- + bool + Whether the filter can be merged + + """ + + if not isinstance(other, AggregateFilter): + return False + + # Filters must be of the same type + elif self.type != other.type: + return False + + # None of the bins in this filter should match in the other filter + for bin in self.bins: + if bin in other.bins: + return False + + # None of the bins in the other filter should match in this filter + for bin in other.bins: + if bin in self.bins: + return False + + # If all conditional checks passed then filters are mergeable + return True + + def merge(self, other): + """Merge this aggregatefilter with another. + + Parameters + ---------- + other : AggregateFilter + Filter to merge with + + Returns + ------- + merged_filter : AggregateFilter + Filter resulting from the merge + + """ + + if not self.can_merge(other): + msg = 'Unable to merge "{0}" with "{1}" ' \ + 'filters'.format(self.type, other.type) + raise ValueError(msg) + + # Create deep copy of filter to return as merged filter + merged_filter = copy.deepcopy(self) + + # Merge unique filter bins + merged_bins = self.bins + other.bins + + # Sort energy bin edges + if 'energy' in self.type: + merged_bins = sorted(merged_bins) + + # Assign merged bins to merged filter + merged_filter.bins = list(merged_bins) + return merged_filter \ No newline at end of file diff --git a/openmc/filter.py b/openmc/filter.py index 21797264b3..f8484ec767 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -263,11 +263,11 @@ class Filter(object): return False # Filters must be of the same type - elif self.type != other.type: + if self.type != other.type: return False # Distribcell filters cannot have more than one bin - elif self.type == 'distribcell': + if self.type == 'distribcell': return False # Mesh filters cannot have more than one bin @@ -289,6 +289,24 @@ class Filter(object): else: return True + ''' + # FIXME: Should all bins be completely separate??? + # FIMXE: This is necessary if merging will choose out unique bins + else: + # None of the bins in this filter should match in the other filter + for bin in self.bins: + if bin in other.bins: + return False + + # None of the bins in the other filter should match in this filter + for bin in other.bins: + if bin in self.bins: + return False + + # If all conditional checks pass then filters are mergeable + return True + ''' + def merge(self, other): """Merge this filter with another. @@ -313,7 +331,8 @@ class Filter(object): merged_filter = copy.deepcopy(self) # Merge unique filter bins - merged_bins = set(np.concatenate((self.bins, other.bins))) + merged_bins = np.concatenate((self.bins, other.bins)) + merged_bins = np.unique(merged_bins) # Sort energy bin edges if 'energy' in self.type: @@ -557,14 +576,8 @@ class Filter(object): """ - # Attempt to import Pandas - try: - import pandas as pd - except ImportError: - msg = 'The Pandas Python package must be installed on your system' - raise ImportError(msg) - # Initialize Pandas DataFrame + import pandas as pd df = pd.DataFrame() # mesh filters @@ -743,7 +756,6 @@ class Filter(object): filter_bins = np.repeat(filter_bins, self.stride) tile_factor = data_size / len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) - filter_bins = filter_bins df = pd.DataFrame({self.type : filter_bins}) # If OpenCG level info DataFrame was created, concatenate diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index a382c629b1..b82426e7df 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -1368,7 +1368,6 @@ class MGXS(object): # Sort the dataframe by domain type id (e.g., distribcell id) and # energy groups such that data is from fast to thermal df.sort([self.domain_type] + columns, inplace=True) - return df diff --git a/openmc/tallies.py b/openmc/tallies.py index d88519ba3b..c04ee001a7 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -301,7 +301,7 @@ class Tally(object): @property def sum(self): - if not self._sp_filename: + if not self._sp_filename or self.derived: return None if not self._results_read: @@ -924,6 +924,17 @@ class Tally(object): if score not in merged_tally.scores: merged_tally.add_score(score) + # Add triggers from other tally to merged tally + for trigger in other.triggers: + merged_tally.add_trigger(trigger) + + # If results have not been read, then return tally for input generation + if self._sp_filename is None: + return merged_tally + #Otherwise, this is a derived tally which needs merged results arrays + else: + self._derived = True + # Update filter strides in merged tally merged_tally._update_filter_strides() @@ -986,10 +997,6 @@ class Tally(object): # Sparsify merged tally if both tallies are sparse merged_tally.sparse = self.sparse and other.sparse - # Add triggers from other tally to merged tally - for trigger in other.triggers: - merged_tally.add_trigger(trigger) - return merged_tally def get_tally_xml(self): @@ -1538,14 +1545,8 @@ class Tally(object): 'Summary info'.format(self.id) raise KeyError(msg) - # Attempt to import Pandas - try: - import pandas as pd - except ImportError: - msg = 'The Pandas Python package must be installed on your system' - raise ImportError(msg) - # Initialize a pandas dataframe for the tally data + import pandas as pd df = pd.DataFrame() # Find the total length of the tally data array @@ -2189,8 +2190,8 @@ class Tally(object): # 'since it does not contain any results.'.format(self.id) # raise ValueError(msg) - cv.check_type('filter1', filter1, Filter) - cv.check_type('filter2', filter2, Filter) + cv.check_type('filter1', filter1, (Filter, CrossFilter, AggregateFilter)) + cv.check_type('filter2', filter2, (Filter, CrossFilter, AggregateFilter)) # Check that the filters exist in the tally and are not the same if filter1 == filter2: @@ -2923,6 +2924,7 @@ class Tally(object): # Create deep copy of tally to return as sliced tally new_tally = copy.deepcopy(self) + new_tally._derived = True # Differentiate Tally with a new auto-generated Tally ID new_tally.id = None @@ -3094,7 +3096,7 @@ class Tally(object): # Add AggregateFilter to the tally sum if not remove_filter: filter_sum = \ - AggregateFilter(self_filter, filter_bins, 'sum') + AggregateFilter(self_filter, [tuple(filter_bins)], 'sum') tally_sum.add_filter(filter_sum) # Add a copy of each filter not summed across to the tally sum @@ -3243,7 +3245,7 @@ class Tally(object): # Add AggregateFilter to the tally avg if not remove_filter: filter_sum = \ - AggregateFilter(self_filter, filter_bins, 'avg') + AggregateFilter(self_filter, [tuple(filter_bins)], 'avg') tally_avg.add_filter(filter_sum) # Add a copy of each filter not averaged across to the tally avg diff --git a/tests/test_mgxs_library_distribcell/results_true.dat b/tests/test_mgxs_library_distribcell/results_true.dat index 4936da4cec..7b4be0f468 100644 --- a/tests/test_mgxs_library_distribcell/results_true.dat +++ b/tests/test_mgxs_library_distribcell/results_true.dat @@ -1,5 +1,5 @@ sum(distribcell) group in nuclide mean std. dev. -0 sum(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ... 1 total 0.720213 1.424323 sum(distribcell) group in nuclide mean std. dev. -0 sum(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ... 1 total 0 0 sum(distribcell) group in group out nuclide mean std. dev. -0 sum(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ... 1 1 total 0.70466 1.403916 sum(distribcell) group out nuclide mean std. dev. -0 sum(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ... 1 total 0 0 \ No newline at end of file +0 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,... 1 total 0.720213 1.424323 sum(distribcell) group in nuclide mean std. dev. +0 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,... 1 total 0 0 sum(distribcell) group in group out nuclide mean std. dev. +0 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,... 1 1 total 0.70466 1.403916 sum(distribcell) group out nuclide mean std. dev. +0 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,... 1 total 0 0 \ No newline at end of file