From 3676a4a7c7786e4e9d409a678322a1be0086e14c Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 11:43:57 -0700 Subject: [PATCH 01/39] Added initial Tally arithmetic addition operation --- src/utils/openmc/tallies.py | 148 +++++++++++++++++++++++++++++++++--- 1 file changed, 138 insertions(+), 10 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 59dd03dde..12ca9cf70 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -43,6 +43,144 @@ class Tally(object): self._std_dev = None + def _align_tally_data(self, other): + + self_mean = copy.deepcopy(self.mean) + self_std_dev = copy.deepcopy(self.std_dev) + other_mean = copy.deepcopy(other.mean) + other_std_dev = copy.deepcopy(other.std_dev) + + if self.filters != other.filters: + + # Determine the number of paired combinations of filter bins + # between the two tallies and repeat arrays along filter axes + self_num_filter_bins = self.mean.shape[0] + other_num_filter_bins = other.mean.shape[0] + num_filter_bins = self_num_filter_bins * other_num_filter_bins + self_new_bins = max(num_filter_bins - self_num_filter_bins, 1) + other_new_bins = max(num_filter_bins - other_num_filter_bins, 1) + + # Replicate the data + self_mean = np.repeat(self_mean, self_new_bins, axis=0) + other_mean = np.repeat(other_mean, other_new_bins, axis=0) + self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=0) + other_std_dev = np.repeat(other_std_dev, other_new_bins, axis=0) + + if self.nuclides != other.nuclides: + + # Determine the number of paired combinations of nuclides + # between the two tallies and repeat arrays along nuclide axes + self_num_nuclide_bins = self.mean.shape[1] + other_num_nuclide_bins = other.mean.shape[1] + num_nuclide_bins = self_num_nuclide_bins * other_num_nuclide_bins + self_new_bins = max(num_nuclide_bins - self_num_nuclide_bins, 1) + other_new_bins = max(num_nuclide_bins - other_num_nuclide_bins, 1) + + # Replicate the data + self_mean = np.repeat(self_mean, self_new_bins, axis=1) + other_mean = np.repeat(other_mean, other_new_bins, axis=1) + self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=1) + other_std_dev = np.repeat(other_std_dev, other_new_bins, axis=1) + + if self.scores != other.scores: + + # Determine the number of paired combinations of score bins + # between the two tallies and repeat arrays along score axes + self_num_score_bins = self.mean.shape[2] + other_num_score_bins = other.mean.shape[2] + num_score_bins = self_num_score_bins * other_num_score_bins + self_new_bins = max(num_score_bins - self_num_score_bins, 1) + other_new_bins = max(num_score_bins - other_num_score_bins, 1) + + # Replicate the data + self_mean = np.repeat(self_mean, self_new_bins, axis=2) + other_mean = np.repeat(other_mean, other_new_bins, axis=2) + self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=2) + other_std_dev = np.repeat(other_std_dev, other_new_bins, axis=2) + + data = {} + data['self'] = {} + data['other'] = {} + data['self']['mean'] = self_mean + data['other']['mean'] = other_mean + data['self']['std. dev.'] = self_std_dev + data['other']['std. dev.'] = other_std_dev + return data + + + def __add__(self, other): + + # FIXME: Check that results have been read + if not (self.mean and other.mean): + msg = 'Unable to add Tally ID={0} to {1} since they do not ' \ + 'contain any results.'.format(self.id, other.id) + raise ValueError(msg) + + new_tally = Tally(name='derived') + + if isinstance(other, Tally): + + # FIXME: Need new CrossFilter class + # FIXME: Need way to cross-product Nuclides + # FIXME: Need cross-product scores - just mix/match strings? + + data = self._align_tally_data(other) + + new_tally._mean = data['self']['mean'] + data['other']['mean'] + new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ + data['other']['std. dev.']**2) + + if self.estimator == other.estimator: + new_tally.estimator = self.estimator + + if self.with_summary and other.with_summary: + new_tally.with_summary = self.with_summary + + if self.num_realizations == other.num_realizations: + new_tally.num_realizations = self.num_realizations + + elif is_integer(other) or is_float(other): + + new_tally._mean = self._mean + other + new_tally._std_dev = self._std_dev + + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to add {0} to Tally ID={1}'.format(other, self.id) + raise ValueError(msg) + + return new_tally + + + def __radd__(self, other): + return other + self + + ''' + def __sub__(self, other): + + def __rsub__(self, other): + + def __mul__(self, other): + + def __rmul__(self, other): + + def __div__(self, other): + + def __rdiv__(self, other): + + def __pow__(self, power): + + def sum(self, axis=None): + ''' + + def __deepcopy__(self, memo): existing = memo.get(id(self)) @@ -136,16 +274,6 @@ class Tally(object): return hash(tuple(hashable)) - def __add__(self, other): - - # FIXME: Error checking: must check that results has been - # set and that # bins is the same - - new_tally = Tally() - new_tally._mean = self._mean + other._mean - new_tally._std_dev = np.sqrt(self.std_dev**2 + other.std_dev**2) - - @property def id(self): return self._id From a7c7fad2e6d980930337a2fe213a66cc546ffa9b Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 12:13:59 -0700 Subject: [PATCH 02/39] Implemented tally arithmetic subtract, multiply and divided operations --- src/utils/openmc/tallies.py | 196 ++++++++++++++++++++++++++++++++++-- 1 file changed, 189 insertions(+), 7 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 12ca9cf70..e6968052c 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -110,16 +110,22 @@ class Tally(object): def __add__(self, other): - # FIXME: Check that results have been read - if not (self.mean and other.mean): - msg = 'Unable to add Tally ID={0} to {1} since they do not ' \ - 'contain any results.'.format(self.id, other.id) + # Check that results have been read + if not (self.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) raise ValueError(msg) new_tally = Tally(name='derived') if isinstance(other, Tally): + # Check that results have been read + if not (other.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(other.id) + raise ValueError(msg) + # FIXME: Need new CrossFilter class # FIXME: Need way to cross-product Nuclides # FIXME: Need cross-product scores - just mix/match strings? @@ -160,21 +166,197 @@ class Tally(object): def __radd__(self, other): - return other + self + return self + other + - ''' def __sub__(self, other): + # Check that results have been read + if not (self.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + new_tally = Tally(name='derived') + + if isinstance(other, Tally): + + # Check that results have been read + if not (self.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + # FIXME: Need new CrossFilter class + # FIXME: Need way to cross-product Nuclides + # FIXME: Need cross-product scores - just mix/match strings? + + data = self._align_tally_data(other) + + new_tally._mean = data['self']['mean'] - data['other']['mean'] + new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ + data['other']['std. dev.']**2) + + if self.estimator == other.estimator: + new_tally.estimator = self.estimator + + if self.with_summary and other.with_summary: + new_tally.with_summary = self.with_summary + + if self.num_realizations == other.num_realizations: + new_tally.num_realizations = self.num_realizations + + elif is_integer(other) or is_float(other): + + new_tally._mean = self._mean - other + new_tally._std_dev = self._std_dev + + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to subtract {0} from Tally ID={1}'.format(other, self.id) + raise ValueError(msg) + + return new_tally + + def __rsub__(self, other): + return -1. * self + other + def __mul__(self, other): + # Check that results have been read + if not (self.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + new_tally = Tally(name='derived') + + if isinstance(other, Tally): + + # Check that results have been read + if not (self.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + # FIXME: Need new CrossFilter class + # FIXME: Need way to cross-product Nuclides + # FIXME: Need cross-product scores - just mix/match strings? + + data = self._align_tally_data(other) + + self_rel_err = data['self']['std. dev.'] / data['self']['mean'] + other_rel_err = data['other']['std. dev.'] / data['other']['mean'] + new_tally._mean = data['self']['mean'] * data['other']['mean'] + new_tally._std_dev = np.abs(new_tally.mean) * \ + np.sqrt(self_rel_err**2 + other_rel_err**2) + + if self.estimator == other.estimator: + new_tally.estimator = self.estimator + + if self.with_summary and other.with_summary: + new_tally.with_summary = self.with_summary + + if self.num_realizations == other.num_realizations: + new_tally.num_realizations = self.num_realizations + + elif is_integer(other) or is_float(other): + + new_tally._mean = self._mean * other + new_tally._std_dev = self._std_dev * np.abs(other) + + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to subtract {0} from Tally ID={1}'.format(other, self.id) + raise ValueError(msg) + + return new_tally + + def __rmul__(self, other): + return self * other + def __div__(self, other): - def __rdiv__(self, other): + # Check that results have been read + if not (self.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + new_tally = Tally(name='derived') + + if isinstance(other, Tally): + + # Check that results have been read + if not (self.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + # FIXME: Need new CrossFilter class + # FIXME: Need way to cross-product Nuclides + # FIXME: Need cross-product scores - just mix/match strings? + + data = self._align_tally_data(other) + + self_rel_err = data['self']['std. dev.'] / data['self']['mean'] + other_rel_err = data['other']['std. dev.'] / data['other']['mean'] + new_tally._mean = data['self']['mean'] / data['other']['mean'] + new_tally._std_dev = np.abs(new_tally.mean) * \ + np.sqrt(self_rel_err**2 + other_rel_err**2) + + if self.estimator == other.estimator: + new_tally.estimator = self.estimator + + if self.with_summary and other.with_summary: + new_tally.with_summary = self.with_summary + + if self.num_realizations == other.num_realizations: + new_tally.num_realizations = self.num_realizations + + elif is_integer(other) or is_float(other): + + new_tally._mean = self._mean / other + new_tally._std_dev = self._std_dev * np.abs(1. / other) + + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to subtract {0} from Tally ID={1}'.format(other, self.id) + raise ValueError(msg) + + return new_tally + + + def __rdiv__(self, other): + return self * (1. / other) + + + ''' def __pow__(self, power): def sum(self, axis=None): From a430cd7fd0fd5814d6908f3b867e1c2176a7bf75 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 13:15:02 -0700 Subject: [PATCH 03/39] Implemented tally arithmetic exponent operation --- src/utils/openmc/tallies.py | 84 ++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index e6968052c..e5473ca03 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -182,9 +182,9 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if not (self.mean): + if not (other.mean): msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(self.id) + 'since it does not contain any results.'.format(other.id) raise ValueError(msg) # FIXME: Need new CrossFilter class @@ -220,7 +220,8 @@ class Tally(object): new_tally.add_score(score) else: - msg = 'Unable to subtract {0} from Tally ID={1}'.format(other, self.id) + msg = 'Unable to subtract {0} from Tally ' \ + 'ID={1}'.format(other, self.id) raise ValueError(msg) return new_tally @@ -243,9 +244,9 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if not (self.mean): + if not (other.mean): msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(self.id) + 'since it does not contain any results.'.format(other.id) raise ValueError(msg) # FIXME: Need new CrossFilter class @@ -283,7 +284,8 @@ class Tally(object): new_tally.add_score(score) else: - msg = 'Unable to subtract {0} from Tally ID={1}'.format(other, self.id) + msg = 'Unable to multiply Tally ID={1} ' \ + 'by {0}'.format(self.id, other) raise ValueError(msg) return new_tally @@ -306,9 +308,9 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if not (self.mean): + if not (other.mean): msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(self.id) + 'since it does not contain any results.'.format(other.id) raise ValueError(msg) # FIXME: Need new CrossFilter class @@ -346,7 +348,8 @@ class Tally(object): new_tally.add_score(score) else: - msg = 'Unable to subtract {0} from Tally ID={1}'.format(other, self.id) + msg = 'Unable to divide Tally ID={0} ' \ + 'by {1}'.format(self.id, other) raise ValueError(msg) return new_tally @@ -356,10 +359,71 @@ class Tally(object): return self * (1. / other) - ''' def __pow__(self, power): + # Check that results have been read + if not (self.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + new_tally = Tally(name='derived') + + if isinstance(power, Tally): + + # Check that results have been read + if not (power.mean): + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(power.id) + raise ValueError(msg) + + # FIXME: Need new CrossFilter class + # FIXME: Need way to cross-product Nuclides + # FIXME: Need cross-product scores - just mix/match strings? + + data = self._align_tally_data(power) + + mean_ratio = data['other']['mean'] / data['self']['mean'] + first_term = mean_ratio * data['self']['std. dev.'] + second_term = np.log(data['self']['mean']) * data['other']['std. dev.'] + new_tally._mean = data['self']['mean'] ** data['other']['mean'] + new_tally._std_dev = np.abs(new_tally.mean) * \ + np.sqrt(first_term**2 + second_term**2) + + if self.estimator == power.estimator: + new_tally.estimator = self.estimator + + if self.with_summary and power.with_summary: + new_tally.with_summary = self.with_summary + + if self.num_realizations == power.num_realizations: + new_tally.num_realizations = self.num_realizations + + elif is_integer(power) or is_float(power): + + new_tally._mean = self._mean ** power + self_rel_err = self.std_dev / self.mean + new_tally._std_dev = np.abs(new_tally._mean * power * self_rel_err) + + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to raise Tally ID={0} to ' \ + 'power {1}'.format(self.id, power) + raise ValueError(msg) + + return new_tally + + ''' def sum(self, axis=None): + + def slice(self, filters=[], nuclides=[], scores=[]) ''' From fe7ba998169d3dba1dc3e435aff003a5e19629cf Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 13:26:10 -0700 Subject: [PATCH 04/39] Added tally arithmetic cross-scores --- src/utils/openmc/tallies.py | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index e5473ca03..eb3d5046c 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -145,6 +145,22 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations + # Generate "cross" scores + # NOTE: This only needs to cross the scores if the + if self.scores != other.scores: + + for self_score in self.scores: + for other_score in other.scores: + new_score = '({0} + {1})'.format(self_score, other_score) + new_tally.add_score(new_score) + + else: + + for score in self.scores: + new_score = '({0} + {1})'.format(score, score) + new_tally.add_score(new_score) + + elif is_integer(other) or is_float(other): new_tally._mean = self._mean + other @@ -206,6 +222,21 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations + # Generate "cross" scores + # NOTE: This only needs to cross the scores if the + if self.scores != other.scores: + + for self_score in self.scores: + for other_score in other.scores: + new_score = '({0} - {1})'.format(self_score, other_score) + new_tally.add_score(new_score) + + else: + + for score in self.scores: + new_score = '({0} - {1})'.format(score, score) + new_tally.add_score(new_score) + elif is_integer(other) or is_float(other): new_tally._mean = self._mean - other @@ -270,6 +301,21 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations + # Generate "cross" scores + # NOTE: This only needs to cross the scores if the + if self.scores != other.scores: + + for self_score in self.scores: + for other_score in other.scores: + new_score = '({0} * {1})'.format(self_score, other_score) + new_tally.add_score(new_score) + + else: + + for score in self.scores: + new_score = '({0} * {1})'.format(score, score) + new_tally.add_score(new_score) + elif is_integer(other) or is_float(other): new_tally._mean = self._mean * other @@ -334,6 +380,21 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations + # Generate "cross" scores + # NOTE: This only needs to cross the scores if the + if self.scores != other.scores: + + for self_score in self.scores: + for other_score in other.scores: + new_score = '({0} / {1})'.format(self_score, other_score) + new_tally.add_score(new_score) + + else: + + for score in self.scores: + new_score = '({0} / {1})'.format(score, score) + new_tally.add_score(new_score) + elif is_integer(other) or is_float(other): new_tally._mean = self._mean / other @@ -399,6 +460,21 @@ class Tally(object): if self.num_realizations == power.num_realizations: new_tally.num_realizations = self.num_realizations + # Generate "cross" scores + # NOTE: This only needs to cross the scores if the + if self.scores != power.scores: + + for self_score in self.scores: + for power_score in power.scores: + new_score = '({0} ^ {1})'.format(self_score, power_score) + new_tally.add_score(new_score) + + else: + + for score in self.scores: + new_score = '({0} ^ {1})'.format(score, score) + new_tally.add_score(new_score) + elif is_integer(power) or is_float(power): new_tally._mean = self._mean ** power From 3382a1e5a3d1a63407bf6d69df78bf559a5a70ac Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 13:41:13 -0700 Subject: [PATCH 05/39] Pandas DataFrames now work with tally arithmetic cross-scores --- src/utils/openmc/tallies.py | 44 ++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index eb3d5046c..9aa41be43 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -41,6 +41,7 @@ class Tally(object): self._sum_sq = None self._mean = None self._std_dev = None + self._with_batch_statistics = False def _align_tally_data(self, other): @@ -117,6 +118,7 @@ class Tally(object): raise ValueError(msg) new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -128,7 +130,6 @@ class Tally(object): # FIXME: Need new CrossFilter class # FIXME: Need way to cross-product Nuclides - # FIXME: Need cross-product scores - just mix/match strings? data = self._align_tally_data(other) @@ -194,6 +195,7 @@ class Tally(object): raise ValueError(msg) new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -205,7 +207,6 @@ class Tally(object): # FIXME: Need new CrossFilter class # FIXME: Need way to cross-product Nuclides - # FIXME: Need cross-product scores - just mix/match strings? data = self._align_tally_data(other) @@ -271,6 +272,7 @@ class Tally(object): raise ValueError(msg) new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -282,7 +284,6 @@ class Tally(object): # FIXME: Need new CrossFilter class # FIXME: Need way to cross-product Nuclides - # FIXME: Need cross-product scores - just mix/match strings? data = self._align_tally_data(other) @@ -350,6 +351,7 @@ class Tally(object): raise ValueError(msg) new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -361,7 +363,6 @@ class Tally(object): # FIXME: Need new CrossFilter class # FIXME: Need way to cross-product Nuclides - # FIXME: Need cross-product scores - just mix/match strings? data = self._align_tally_data(other) @@ -429,6 +430,7 @@ class Tally(object): raise ValueError(msg) new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True if isinstance(power, Tally): @@ -440,7 +442,6 @@ class Tally(object): # FIXME: Need new CrossFilter class # FIXME: Need way to cross-product Nuclides - # FIXME: Need cross-product scores - just mix/match strings? data = self._align_tally_data(power) @@ -700,6 +701,11 @@ class Tally(object): return self._std_dev + @property + def with_batch_statistics(self): + return self._with_batch_statistics + + @estimator.setter def estimator(self, estimator): @@ -819,6 +825,17 @@ class Tally(object): self._with_summary = with_summary + @with_batch_statistics.setter + def with_batch_statistics(self, with_batch_statistics): + + if not isinstance(with_batch_statistics, bool): + msg = 'Unable to set with_batch_statistics to a non-boolean ' \ + 'value "{0}"'.format(with_batch_statistics) + raise ValueError(msg) + + self._with_batch_statistics = with_batch_statistics + + def set_results(self, sum, sum_sq): if not isinstance(sum, (tuple, list, np.ndarray)): @@ -874,6 +891,7 @@ class Tally(object): self._std_dev = np.sqrt((self.sum_sq / self.num_realizations - \ self.mean**2) / (self.num_realizations - 1)) self._std_dev *= t_value + self.with_batch_statistics = True def __repr__(self): @@ -1209,7 +1227,11 @@ class Tally(object): """ # Ensure that StatePoint.read_results() was called first - if self._sum is None or self._sum_sq is None: + if (value == 'mean' and self.mean is None) or \ + (value == 'std_dev' and self.std_dev is None) or \ + (value == 'rel_err' and self.mean is None) or \ + (value == 'sum' and self.sum is None) or \ + (value == 'sum_sq' and self.sum_sq is None): msg = 'The Tally ID={0} has no data to return. Call the ' \ 'StatePoint.read_results() routine before using ' \ 'Tally.get_values(...)'.format(self.id) @@ -1217,7 +1239,8 @@ class Tally(object): # Compute batch statistics if not yet computed - self.compute_std_dev() + if not self.with_batch_statistics: + self.compute_std_dev() ############################ FILTERS ######################### # Determine the score indices from any of the requested scores @@ -1358,7 +1381,7 @@ class Tally(object): """ # Ensure that StatePoint.read_results() was called first - if self._sum is None or self._sum_sq is None: + if self.mean is None or self.std_dev is None: msg = 'The Tally ID={0} has no data to return. Call the ' \ 'StatePoint.read_results() routine before using ' \ 'Tally.get_pandas_dataframe(...)'.format(self.id) @@ -1380,13 +1403,14 @@ class Tally(object): raise ImportError(msg) # Compute batch statistics if not yet computed - self.compute_std_dev() + if not self.with_batch_statistics: + self.compute_std_dev() # Initialize a pandas dataframe for the tally data df = pd.DataFrame() # Find the total length of the tally data array - data_size = self.sum.size + data_size = self.mean.size # Build DataFrame columns for filters if user requested them if filters: From b80005e1bd7539f120d6a4c17ff57548b2c5b340 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 13:52:16 -0700 Subject: [PATCH 06/39] Fixed bug in tally arithmetic numpy repeat vs. tile operatoins --- src/utils/openmc/tallies.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 9aa41be43..161f8ca32 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -63,9 +63,9 @@ class Tally(object): # Replicate the data self_mean = np.repeat(self_mean, self_new_bins, axis=0) - other_mean = np.repeat(other_mean, other_new_bins, axis=0) + other_mean = np.tile(other_mean, other_new_bins) self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=0) - other_std_dev = np.repeat(other_std_dev, other_new_bins, axis=0) + other_std_dev = np.tile(other_std_dev, other_new_bins) if self.nuclides != other.nuclides: @@ -79,9 +79,9 @@ class Tally(object): # Replicate the data self_mean = np.repeat(self_mean, self_new_bins, axis=1) - other_mean = np.repeat(other_mean, other_new_bins, axis=1) + other_mean = np.tile(other_mean, other_new_bins) self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=1) - other_std_dev = np.repeat(other_std_dev, other_new_bins, axis=1) + other_std_dev = np.tile(other_std_dev, other_new_bins) if self.scores != other.scores: @@ -95,9 +95,9 @@ class Tally(object): # Replicate the data self_mean = np.repeat(self_mean, self_new_bins, axis=2) - other_mean = np.repeat(other_mean, other_new_bins, axis=2) + other_mean = np.tile(other_mean, other_new_bins) self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=2) - other_std_dev = np.repeat(other_std_dev, other_new_bins, axis=2) + other_std_dev = np.tile(other_std_dev, other_new_bins) data = {} data['self'] = {} @@ -112,7 +112,7 @@ class Tally(object): def __add__(self, other): # Check that results have been read - if not (self.mean): + if self.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) @@ -123,7 +123,7 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if not (other.mean): + if other.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(other.id) raise ValueError(msg) @@ -189,7 +189,7 @@ class Tally(object): def __sub__(self, other): # Check that results have been read - if not (self.mean): + if self.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) @@ -200,7 +200,7 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if not (other.mean): + if other.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(other.id) raise ValueError(msg) @@ -266,7 +266,7 @@ class Tally(object): def __mul__(self, other): # Check that results have been read - if not (self.mean): + if self.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) @@ -277,7 +277,7 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if not (other.mean): + if other.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(other.id) raise ValueError(msg) @@ -345,9 +345,10 @@ class Tally(object): def __div__(self, other): # Check that results have been read - if not (self.mean): + if self.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) + print self.mean raise ValueError(msg) new_tally = Tally(name='derived') @@ -356,7 +357,7 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if not (other.mean): + if other.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(other.id) raise ValueError(msg) @@ -424,7 +425,7 @@ class Tally(object): def __pow__(self, power): # Check that results have been read - if not (self.mean): + if self.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) @@ -435,7 +436,7 @@ class Tally(object): if isinstance(power, Tally): # Check that results have been read - if not (power.mean): + if power.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(power.id) raise ValueError(msg) From 0084f0081f069b971078a39e4380a859cbaf4af6 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 14:22:42 -0700 Subject: [PATCH 07/39] Added tally arithmetic cross-nuclide --- src/utils/openmc/tallies.py | 241 ++++++++++++++++++++++++++++-------- 1 file changed, 191 insertions(+), 50 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 161f8ca32..9528c4901 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -63,9 +63,9 @@ class Tally(object): # Replicate the data self_mean = np.repeat(self_mean, self_new_bins, axis=0) - other_mean = np.tile(other_mean, other_new_bins) + other_mean = np.tile(other_mean, (other_new_bins, 1, 1)) self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=0) - other_std_dev = np.tile(other_std_dev, other_new_bins) + other_std_dev = np.tile(other_std_dev, (other_new_bins, 1, 1)) if self.nuclides != other.nuclides: @@ -79,9 +79,9 @@ class Tally(object): # Replicate the data self_mean = np.repeat(self_mean, self_new_bins, axis=1) - other_mean = np.tile(other_mean, other_new_bins) + other_mean = np.tile(other_mean, (1, other_new_bins, 1)) self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=1) - other_std_dev = np.tile(other_std_dev, other_new_bins) + other_std_dev = np.tile(other_std_dev, (1, other_new_bins, 1)) if self.scores != other.scores: @@ -95,9 +95,9 @@ class Tally(object): # Replicate the data self_mean = np.repeat(self_mean, self_new_bins, axis=2) - other_mean = np.tile(other_mean, other_new_bins) + other_mean = np.tile(other_mean, (1, 1, other_new_bins)) self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=2) - other_std_dev = np.tile(other_std_dev, other_new_bins) + other_std_dev = np.tile(other_std_dev, (1, 1, other_new_bins)) data = {} data['self'] = {} @@ -129,7 +129,6 @@ class Tally(object): raise ValueError(msg) # FIXME: Need new CrossFilter class - # FIXME: Need way to cross-product Nuclides data = self._align_tally_data(other) @@ -146,20 +145,46 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations - # Generate "cross" scores - # NOTE: This only needs to cross the scores if the - if self.scores != other.scores: + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == other.scores: + for score in self.scores: + new_score = '({0} + {1})'.format(score, score) + new_tally.add_score(new_score) + # Generate score "cross product" + else: for self_score in self.scores: for other_score in other.scores: new_score = '({0} + {1})'.format(self_score, other_score) new_tally.add_score(new_score) - else: + # If the two Tallies have same nuclides, replicate them in new Tally + if self.nuclides == other.nuclides: + for nuclide in self.nuclides: - for score in self.scores: - new_score = '({0} + {1})'.format(score, score) - new_tally.add_score(new_score) + if isinstance(nuclide, Nuclide): + name = nuclide.name + new_nuclide = '({0} + {1})'.format(name, name) + else: + new_nuclide = '({0} + {1})'.format(nuclide, nuclide) + + new_tally.add_nuclide(new_nuclide) + + # Generate nuclide "cross product" + else: + for self_nuclide in self.nuclides: + for other_nuclide in other.nuclides: + if isinstance(self_nuclide, Nuclide): + self_name = self_nuclide.name + else: + self_name = self_nuclide + if isinstance(other_nuclide, Nuclide): + other_name = other_nuclide.name + else: + other_name = other_nuclide + + new_nuclide = '({0} + {1})'.format(self_name, other_name) + new_tally.add_nuclide(new_nuclide) elif is_integer(other) or is_float(other): @@ -175,6 +200,9 @@ class Tally(object): for score in self.scores: new_tally.add_score(score) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + else: msg = 'Unable to add {0} to Tally ID={1}'.format(other, self.id) raise ValueError(msg) @@ -206,7 +234,6 @@ class Tally(object): raise ValueError(msg) # FIXME: Need new CrossFilter class - # FIXME: Need way to cross-product Nuclides data = self._align_tally_data(other) @@ -223,20 +250,46 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations - # Generate "cross" scores - # NOTE: This only needs to cross the scores if the - if self.scores != other.scores: + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == other.scores: + for score in self.scores: + new_score = '({0} - {1})'.format(score, score) + new_tally.add_score(new_score) + # Generate score "cross product" + else: for self_score in self.scores: for other_score in other.scores: new_score = '({0} - {1})'.format(self_score, other_score) new_tally.add_score(new_score) - else: + # If the two Tallies have same nuclides, replicate them in new Tally + if self.nuclides == other.nuclides: + for nuclide in self.nuclides: - for score in self.scores: - new_score = '({0} - {1})'.format(score, score) - new_tally.add_score(new_score) + if isinstance(nuclide, Nuclide): + name = nuclide.name + new_nuclide = '({0} - {1})'.format(name, name) + else: + new_nuclide = '({0} - {1})'.format(nuclide, nuclide) + + new_tally.add_nuclide(new_nuclide) + + # Generate nuclide "cross product" + else: + for self_nuclide in self.nuclides: + for other_nuclide in other.nuclides: + if isinstance(self_nuclide, Nuclide): + self_name = self_nuclide.name + else: + self_name = self_nuclide + if isinstance(other_nuclide, Nuclide): + other_name = other_nuclide.name + else: + other_name = other_nuclide + + new_nuclide = '({0} - {1})'.format(self_name, other_name) + new_tally.add_nuclide(new_nuclide) elif is_integer(other) or is_float(other): @@ -251,6 +304,9 @@ class Tally(object): for score in self.scores: new_tally.add_score(score) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + else: msg = 'Unable to subtract {0} from Tally ' \ 'ID={1}'.format(other, self.id) @@ -283,7 +339,6 @@ class Tally(object): raise ValueError(msg) # FIXME: Need new CrossFilter class - # FIXME: Need way to cross-product Nuclides data = self._align_tally_data(other) @@ -302,20 +357,46 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations - # Generate "cross" scores - # NOTE: This only needs to cross the scores if the - if self.scores != other.scores: + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == other.scores: + for score in self.scores: + new_score = '({0} * {1})'.format(score, score) + new_tally.add_score(new_score) + # Generate score "cross product" + else: for self_score in self.scores: for other_score in other.scores: new_score = '({0} * {1})'.format(self_score, other_score) new_tally.add_score(new_score) - else: + # If the two Tallies have same nuclides, replicate them in new Tally + if self.nuclides == other.nuclides: + for nuclide in self.nuclides: - for score in self.scores: - new_score = '({0} * {1})'.format(score, score) - new_tally.add_score(new_score) + if isinstance(nuclide, Nuclide): + name = nuclide.name + new_nuclide = '({0} * {1})'.format(name, name) + else: + new_nuclide = '({0} * {1})'.format(nuclide, nuclide) + + new_tally.add_nuclide(new_nuclide) + + # Generate nuclide "cross product" + else: + for self_nuclide in self.nuclides: + for other_nuclide in other.nuclides: + if isinstance(self_nuclide, Nuclide): + self_name = self_nuclide.name + else: + self_name = self_nuclide + if isinstance(other_nuclide, Nuclide): + other_name = other_nuclide.name + else: + other_name = other_nuclide + + new_nuclide = '({0} * {1})'.format(self_name, other_name) + new_tally.add_nuclide(new_nuclide) elif is_integer(other) or is_float(other): @@ -330,6 +411,9 @@ class Tally(object): for score in self.scores: new_tally.add_score(score) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + else: msg = 'Unable to multiply Tally ID={1} ' \ 'by {0}'.format(self.id, other) @@ -363,7 +447,6 @@ class Tally(object): raise ValueError(msg) # FIXME: Need new CrossFilter class - # FIXME: Need way to cross-product Nuclides data = self._align_tally_data(other) @@ -382,20 +465,47 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations - # Generate "cross" scores - # NOTE: This only needs to cross the scores if the - if self.scores != other.scores: + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == other.scores: + for score in self.scores: + new_score = '({0} / {1})'.format(score, score) + new_tally.add_score(new_score) + # Generate score "cross product" + else: for self_score in self.scores: for other_score in other.scores: new_score = '({0} / {1})'.format(self_score, other_score) new_tally.add_score(new_score) - else: - for score in self.scores: - new_score = '({0} / {1})'.format(score, score) - new_tally.add_score(new_score) + # If the two Tallies have same nuclides, replicate them in new Tally + if self.nuclides == other.nuclides: + for nuclide in self.nuclides: + + if isinstance(nuclide, Nuclide): + name = nuclide.name + new_nuclide = '({0} / {1})'.format(name, name) + else: + new_nuclide = '({0} / {1})'.format(nuclide, nuclide) + + new_tally.add_nuclide(new_nuclide) + + # Generate nuclide "cross product" + else: + for self_nuclide in self.nuclides: + for other_nuclide in other.nuclides: + if isinstance(self_nuclide, Nuclide): + self_name = self_nuclide.name + else: + self_name = self_nuclide + if isinstance(other_nuclide, Nuclide): + other_name = other_nuclide.name + else: + other_name = other_nuclide + + new_nuclide = '({0} / {1})'.format(self_name, other_name) + new_tally.add_nuclide(new_nuclide) elif is_integer(other) or is_float(other): @@ -410,6 +520,9 @@ class Tally(object): for score in self.scores: new_tally.add_score(score) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + else: msg = 'Unable to divide Tally ID={0} ' \ 'by {1}'.format(self.id, other) @@ -442,7 +555,6 @@ class Tally(object): raise ValueError(msg) # FIXME: Need new CrossFilter class - # FIXME: Need way to cross-product Nuclides data = self._align_tally_data(power) @@ -462,21 +574,47 @@ class Tally(object): if self.num_realizations == power.num_realizations: new_tally.num_realizations = self.num_realizations - # Generate "cross" scores - # NOTE: This only needs to cross the scores if the - if self.scores != power.scores: - - for self_score in self.scores: - for power_score in power.scores: - new_score = '({0} ^ {1})'.format(self_score, power_score) - new_tally.add_score(new_score) - - else: - + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == power.scores: for score in self.scores: new_score = '({0} ^ {1})'.format(score, score) new_tally.add_score(new_score) + # Generate score "cross product" + else: + for self_score in self.scores: + for other_score in power.scores: + new_score = '({0} ^ {1})'.format(self_score, other_score) + new_tally.add_score(new_score) + + # If the two Tallies have same nuclides, replicate them in new Tally + if self.nuclides == power.nuclides: + for nuclide in self.nuclides: + + if isinstance(nuclide, Nuclide): + name = nuclide.name + new_nuclide = '({0} ^ {1})'.format(name, name) + else: + new_nuclide = '({0} ^ {1})'.format(nuclide, nuclide) + + new_tally.add_nuclide(new_nuclide) + + # Generate nuclide "cross product" + else: + for self_nuclide in self.nuclides: + for other_nuclide in power.nuclides: + if isinstance(self_nuclide, Nuclide): + self_name = self_nuclide.name + else: + self_name = self_nuclide + if isinstance(other_nuclide, Nuclide): + other_name = other_nuclide.name + else: + other_name = other_nuclide + + new_nuclide = '({0} + {1})'.format(self_name, other_name) + new_tally.add_nuclide(new_nuclide) + elif is_integer(power) or is_float(power): new_tally._mean = self._mean ** power @@ -491,6 +629,9 @@ class Tally(object): for score in self.scores: new_tally.add_score(score) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + else: msg = 'Unable to raise Tally ID={0} to ' \ 'power {1}'.format(self.id, power) From 7094345647a5b94af920d0d13fac5bd837f70c7c Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 15:15:29 -0700 Subject: [PATCH 08/39] Now properly replicating scores and nuclides in tally arithmetic derived tallies --- src/utils/openmc/tallies.py | 166 ++++++++++++++---------------------- 1 file changed, 62 insertions(+), 104 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 9528c4901..3d7f9c38f 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -46,67 +46,67 @@ class Tally(object): def _align_tally_data(self, other): - self_mean = copy.deepcopy(self.mean) - self_std_dev = copy.deepcopy(self.std_dev) - other_mean = copy.deepcopy(other.mean) - other_std_dev = copy.deepcopy(other.std_dev) + self_mean = copy.deepcopy(self.mean) + self_std_dev = copy.deepcopy(self.std_dev) + other_mean = copy.deepcopy(other.mean) + other_std_dev = copy.deepcopy(other.std_dev) - if self.filters != other.filters: + if self.filters != other.filters: - # Determine the number of paired combinations of filter bins - # between the two tallies and repeat arrays along filter axes - self_num_filter_bins = self.mean.shape[0] - other_num_filter_bins = other.mean.shape[0] - num_filter_bins = self_num_filter_bins * other_num_filter_bins - self_new_bins = max(num_filter_bins - self_num_filter_bins, 1) - other_new_bins = max(num_filter_bins - other_num_filter_bins, 1) + # Determine the number of paired combinations of filter bins + # between the two tallies and repeat arrays along filter axes + self_num_filter_bins = self.mean.shape[0] + other_num_filter_bins = other.mean.shape[0] + num_filter_bins = self_num_filter_bins * other_num_filter_bins + self_new_bins = max(num_filter_bins - self_num_filter_bins, 1) + other_new_bins = max(num_filter_bins - other_num_filter_bins, 1) - # Replicate the data - self_mean = np.repeat(self_mean, self_new_bins, axis=0) - other_mean = np.tile(other_mean, (other_new_bins, 1, 1)) - self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=0) - other_std_dev = np.tile(other_std_dev, (other_new_bins, 1, 1)) + # Replicate the data + self_mean = np.repeat(self_mean, self_new_bins, axis=0) + other_mean = np.tile(other_mean, (other_new_bins, 1, 1)) + self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=0) + other_std_dev = np.tile(other_std_dev, (other_new_bins, 1, 1)) - if self.nuclides != other.nuclides: + if self.nuclides != other.nuclides: - # Determine the number of paired combinations of nuclides - # between the two tallies and repeat arrays along nuclide axes - self_num_nuclide_bins = self.mean.shape[1] - other_num_nuclide_bins = other.mean.shape[1] - num_nuclide_bins = self_num_nuclide_bins * other_num_nuclide_bins - self_new_bins = max(num_nuclide_bins - self_num_nuclide_bins, 1) - other_new_bins = max(num_nuclide_bins - other_num_nuclide_bins, 1) + # Determine the number of paired combinations of nuclides + # between the two tallies and repeat arrays along nuclide axes + self_num_nuclide_bins = self.mean.shape[1] + other_num_nuclide_bins = other.mean.shape[1] + num_nuclide_bins = self_num_nuclide_bins * other_num_nuclide_bins + self_new_bins = max(num_nuclide_bins - self_num_nuclide_bins, 1) + other_new_bins = max(num_nuclide_bins - other_num_nuclide_bins, 1) - # Replicate the data - self_mean = np.repeat(self_mean, self_new_bins, axis=1) - other_mean = np.tile(other_mean, (1, other_new_bins, 1)) - self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=1) - other_std_dev = np.tile(other_std_dev, (1, other_new_bins, 1)) + # Replicate the data + self_mean = np.repeat(self_mean, self_new_bins, axis=1) + other_mean = np.tile(other_mean, (1, other_new_bins, 1)) + self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=1) + other_std_dev = np.tile(other_std_dev, (1, other_new_bins, 1)) - if self.scores != other.scores: + if self.scores != other.scores: - # Determine the number of paired combinations of score bins - # between the two tallies and repeat arrays along score axes - self_num_score_bins = self.mean.shape[2] - other_num_score_bins = other.mean.shape[2] - num_score_bins = self_num_score_bins * other_num_score_bins - self_new_bins = max(num_score_bins - self_num_score_bins, 1) - other_new_bins = max(num_score_bins - other_num_score_bins, 1) + # Determine the number of paired combinations of score bins + # between the two tallies and repeat arrays along score axes + self_num_score_bins = self.mean.shape[2] + other_num_score_bins = other.mean.shape[2] + num_score_bins = self_num_score_bins * other_num_score_bins + self_new_bins = max(num_score_bins - self_num_score_bins, 1) + other_new_bins = max(num_score_bins - other_num_score_bins, 1) - # Replicate the data - self_mean = np.repeat(self_mean, self_new_bins, axis=2) - other_mean = np.tile(other_mean, (1, 1, other_new_bins)) - self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=2) - other_std_dev = np.tile(other_std_dev, (1, 1, other_new_bins)) + # Replicate the data + self_mean = np.repeat(self_mean, self_new_bins, axis=2) + other_mean = np.tile(other_mean, (1, 1, other_new_bins)) + self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=2) + other_std_dev = np.tile(other_std_dev, (1, 1, other_new_bins)) - data = {} - data['self'] = {} - data['other'] = {} - data['self']['mean'] = self_mean - data['other']['mean'] = other_mean - data['self']['std. dev.'] = self_std_dev - data['other']['std. dev.'] = other_std_dev - return data + data = {} + data['self'] = {} + data['other'] = {} + data['self']['mean'] = self_mean + data['other']['mean'] = other_mean + data['self']['std. dev.'] = self_std_dev + data['other']['std. dev.'] = other_std_dev + return data def __add__(self, other): @@ -148,8 +148,7 @@ class Tally(object): # If the two Tallies have same scores, replicate them in new Tally if self.scores == other.scores: for score in self.scores: - new_score = '({0} + {1})'.format(score, score) - new_tally.add_score(new_score) + new_tally.add_score(score) # Generate score "cross product" else: @@ -161,14 +160,7 @@ class Tally(object): # If the two Tallies have same nuclides, replicate them in new Tally if self.nuclides == other.nuclides: for nuclide in self.nuclides: - - if isinstance(nuclide, Nuclide): - name = nuclide.name - new_nuclide = '({0} + {1})'.format(name, name) - else: - new_nuclide = '({0} + {1})'.format(nuclide, nuclide) - - new_tally.add_nuclide(new_nuclide) + new_tally.add_nuclide(nuclide) # Generate nuclide "cross product" else: @@ -253,8 +245,7 @@ class Tally(object): # If the two Tallies have same scores, replicate them in new Tally if self.scores == other.scores: for score in self.scores: - new_score = '({0} - {1})'.format(score, score) - new_tally.add_score(new_score) + new_tally.add_score(score) # Generate score "cross product" else: @@ -266,14 +257,7 @@ class Tally(object): # If the two Tallies have same nuclides, replicate them in new Tally if self.nuclides == other.nuclides: for nuclide in self.nuclides: - - if isinstance(nuclide, Nuclide): - name = nuclide.name - new_nuclide = '({0} - {1})'.format(name, name) - else: - new_nuclide = '({0} - {1})'.format(nuclide, nuclide) - - new_tally.add_nuclide(new_nuclide) + new_tally.add_nuclide(nuclide) # Generate nuclide "cross product" else: @@ -360,8 +344,7 @@ class Tally(object): # If the two Tallies have same scores, replicate them in new Tally if self.scores == other.scores: for score in self.scores: - new_score = '({0} * {1})'.format(score, score) - new_tally.add_score(new_score) + new_tally.add_score(score) # Generate score "cross product" else: @@ -373,14 +356,7 @@ class Tally(object): # If the two Tallies have same nuclides, replicate them in new Tally if self.nuclides == other.nuclides: for nuclide in self.nuclides: - - if isinstance(nuclide, Nuclide): - name = nuclide.name - new_nuclide = '({0} * {1})'.format(name, name) - else: - new_nuclide = '({0} * {1})'.format(nuclide, nuclide) - - new_tally.add_nuclide(new_nuclide) + new_tally.add_nuclide(nuclide) # Generate nuclide "cross product" else: @@ -432,7 +408,6 @@ class Tally(object): if self.mean is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) - print self.mean raise ValueError(msg) new_tally = Tally(name='derived') @@ -468,8 +443,7 @@ class Tally(object): # If the two Tallies have same scores, replicate them in new Tally if self.scores == other.scores: for score in self.scores: - new_score = '({0} / {1})'.format(score, score) - new_tally.add_score(new_score) + new_tally.add_score(score) # Generate score "cross product" else: @@ -478,18 +452,10 @@ class Tally(object): new_score = '({0} / {1})'.format(self_score, other_score) new_tally.add_score(new_score) - # If the two Tallies have same nuclides, replicate them in new Tally - if self.nuclides == other.nuclides: + if self.nuclides == power.nuclides: for nuclide in self.nuclides: - - if isinstance(nuclide, Nuclide): - name = nuclide.name - new_nuclide = '({0} / {1})'.format(name, name) - else: - new_nuclide = '({0} / {1})'.format(nuclide, nuclide) - - new_tally.add_nuclide(new_nuclide) + new_tally.add_nuclide(nuclide) # Generate nuclide "cross product" else: @@ -577,8 +543,7 @@ class Tally(object): # If the two Tallies have same scores, replicate them in new Tally if self.scores == power.scores: for score in self.scores: - new_score = '({0} ^ {1})'.format(score, score) - new_tally.add_score(new_score) + new_tally.add_score(score) # Generate score "cross product" else: @@ -590,14 +555,7 @@ class Tally(object): # If the two Tallies have same nuclides, replicate them in new Tally if self.nuclides == power.nuclides: for nuclide in self.nuclides: - - if isinstance(nuclide, Nuclide): - name = nuclide.name - new_nuclide = '({0} ^ {1})'.format(name, name) - else: - new_nuclide = '({0} ^ {1})'.format(nuclide, nuclide) - - new_tally.add_nuclide(new_nuclide) + new_tally.add_nuclide(nuclide) # Generate nuclide "cross product" else: From c032ea8c96f80e3d7452acee8cb51bc9c97638a9 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 15:39:45 -0700 Subject: [PATCH 09/39] Created _CrossScore private class for tally arithmetic --- src/utils/openmc/tallies.py | 87 +++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 3d7f9c38f..0c53436d9 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -154,7 +154,7 @@ class Tally(object): else: for self_score in self.scores: for other_score in other.scores: - new_score = '({0} + {1})'.format(self_score, other_score) + new_score = _CrossScore(self_score, other_score, '+') new_tally.add_score(new_score) # If the two Tallies have same nuclides, replicate them in new Tally @@ -251,7 +251,7 @@ class Tally(object): else: for self_score in self.scores: for other_score in other.scores: - new_score = '({0} - {1})'.format(self_score, other_score) + new_score = _CrossScore(self_score, other_score, '-') new_tally.add_score(new_score) # If the two Tallies have same nuclides, replicate them in new Tally @@ -350,7 +350,7 @@ class Tally(object): else: for self_score in self.scores: for other_score in other.scores: - new_score = '({0} * {1})'.format(self_score, other_score) + new_score = _CrossScore(self_score, other_score, '*') new_tally.add_score(new_score) # If the two Tallies have same nuclides, replicate them in new Tally @@ -449,11 +449,11 @@ class Tally(object): else: for self_score in self.scores: for other_score in other.scores: - new_score = '({0} / {1})'.format(self_score, other_score) + new_score = _CrossScore(self_score, other_score, '/') new_tally.add_score(new_score) # If the two Tallies have same nuclides, replicate them in new Tally - if self.nuclides == power.nuclides: + if self.nuclides == other.nuclides: for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) @@ -548,8 +548,8 @@ class Tally(object): # Generate score "cross product" else: for self_score in self.scores: - for other_score in power.scores: - new_score = '({0} ^ {1})'.format(self_score, other_score) + for other_score in other.scores: + new_score = _CrossScore(self_score, other_score, '^') new_tally.add_score(new_score) # If the two Tallies have same nuclides, replicate them in new Tally @@ -879,7 +879,7 @@ class Tally(object): def add_score(self, score): - if not is_string(score): + if not is_string(score) and not isinstance(score, _CrossScore): msg = 'Unable to add score "{0}" to Tally ID={1} since it is ' \ 'not a string'.format(score, self.id) raise ValueError(msg) @@ -2017,3 +2017,74 @@ class TalliesFile(object): tree = ET.ElementTree(self._tallies_file) tree.write("tallies.xml", xml_declaration=True, encoding='utf-8', method="xml") + + +class _CrossScore(object): + + def __init__(self, left_score=None, right_score=None, binary_op=None): + + self._left_score = None + self._right_score = None + self._binary_op = None + + if left_score is not None: + self.left_score = left_score + + if right_score is not None: + self.right_score = right_score + + if binary_op is not None: + self.binary_op = binary_op + + + @property + def left_score(self): + return self._left_score + + + @property + def right_score(self): + return self._right_score + + + @property + def binary_op(self): + return self._binary_op + + + @left_score.setter + def left_score(self, left_score): + + if not is_string(left_score): + msg = 'Unable to set CrossScore left score to {0} which ' \ + 'is not a string'.format(left_score) + raise ValueError(msg) + + self._left_score = left_score + + @right_score.setter + def right_score(self, right_score): + + if not is_string(right_score): + msg = 'Unable to set CrossScore right score to {0} which ' \ + 'is not a string'.format(right_score) + raise ValueError(msg) + + self._right_score = right_score + + + @binary_op.setter + def binary_op(self, binary_op): + + if not is_string(binary_op): + msg = 'Unable to set CrossScore binary op to {0} which ' \ + 'is not a string'.format(binary_op) + raise ValueError(msg) + + self._binary_op = binary_op + + + def __repr__(self): + string = '({0} {1} {2})'.format(self.left_score, + self.binary_op, self.right_score) + return string \ No newline at end of file From 8ba65c3e01444cc130a86c3877f61fcd15d78645 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 15:53:45 -0700 Subject: [PATCH 10/39] Created _CrossNuclide private class for tally arithmetic --- src/utils/openmc/tallies.py | 153 +++++++++++++++++++++++------------- 1 file changed, 99 insertions(+), 54 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 0c53436d9..419f09959 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -166,19 +166,9 @@ class Tally(object): else: for self_nuclide in self.nuclides: for other_nuclide in other.nuclides: - if isinstance(self_nuclide, Nuclide): - self_name = self_nuclide.name - else: - self_name = self_nuclide - if isinstance(other_nuclide, Nuclide): - other_name = other_nuclide.name - else: - other_name = other_nuclide - - new_nuclide = '({0} + {1})'.format(self_name, other_name) + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '+') new_tally.add_nuclide(new_nuclide) - elif is_integer(other) or is_float(other): new_tally._mean = self._mean + other @@ -263,16 +253,7 @@ class Tally(object): else: for self_nuclide in self.nuclides: for other_nuclide in other.nuclides: - if isinstance(self_nuclide, Nuclide): - self_name = self_nuclide.name - else: - self_name = self_nuclide - if isinstance(other_nuclide, Nuclide): - other_name = other_nuclide.name - else: - other_name = other_nuclide - - new_nuclide = '({0} - {1})'.format(self_name, other_name) + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '-') new_tally.add_nuclide(new_nuclide) elif is_integer(other) or is_float(other): @@ -362,16 +343,7 @@ class Tally(object): else: for self_nuclide in self.nuclides: for other_nuclide in other.nuclides: - if isinstance(self_nuclide, Nuclide): - self_name = self_nuclide.name - else: - self_name = self_nuclide - if isinstance(other_nuclide, Nuclide): - other_name = other_nuclide.name - else: - other_name = other_nuclide - - new_nuclide = '({0} * {1})'.format(self_name, other_name) + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '*') new_tally.add_nuclide(new_nuclide) elif is_integer(other) or is_float(other): @@ -461,18 +433,10 @@ class Tally(object): else: for self_nuclide in self.nuclides: for other_nuclide in other.nuclides: - if isinstance(self_nuclide, Nuclide): - self_name = self_nuclide.name - else: - self_name = self_nuclide - if isinstance(other_nuclide, Nuclide): - other_name = other_nuclide.name - else: - other_name = other_nuclide - - new_nuclide = '({0} / {1})'.format(self_name, other_name) + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '/') new_tally.add_nuclide(new_nuclide) + elif is_integer(other) or is_float(other): new_tally._mean = self._mean / other @@ -548,8 +512,8 @@ class Tally(object): # Generate score "cross product" else: for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '^') + for power_score in power.scores: + new_score = _CrossScore(self_score, power_score, '^') new_tally.add_score(new_score) # If the two Tallies have same nuclides, replicate them in new Tally @@ -560,17 +524,8 @@ class Tally(object): # Generate nuclide "cross product" else: for self_nuclide in self.nuclides: - for other_nuclide in power.nuclides: - if isinstance(self_nuclide, Nuclide): - self_name = self_nuclide.name - else: - self_name = self_nuclide - if isinstance(other_nuclide, Nuclide): - other_name = other_nuclide.name - else: - other_name = other_nuclide - - new_nuclide = '({0} + {1})'.format(self_name, other_name) + for power_nuclide in power.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, power_nuclide, '^') new_tally.add_nuclide(new_nuclide) elif is_integer(power) or is_float(power): @@ -2062,6 +2017,7 @@ class _CrossScore(object): self._left_score = left_score + @right_score.setter def right_score(self, right_score): @@ -2087,4 +2043,93 @@ class _CrossScore(object): def __repr__(self): string = '({0} {1} {2})'.format(self.left_score, self.binary_op, self.right_score) + return string + + +class _CrossNuclide(object): + + def __init__(self, left_nuclide=None, right_nuclide=None, binary_op=None): + + self._left_nuclide = None + self._right_nuclide = None + self._binary_op = None + + if left_nuclide is not None: + self.left_nuclide = left_nuclide + + if right_nuclide is not None: + self.right_nuclide = right_nuclide + + if binary_op is not None: + self.binary_op = binary_op + + + @property + def left_nuclide(self): + return self._left_nuclide + + + @property + def right_nuclide(self): + return self._right_nuclide + + + @property + def binary_op(self): + return self._binary_op + + + @left_nuclide.setter + def left_nuclide(self, left_nuclide): + + if not isinstance(left_nuclide, Nuclide) or is_integer(left_nuclide): + msg = 'Unable to set CrossNuclide left nuclide to {0} which ' \ + 'is not an integer or Nuclide'.format(left_nuclide) + raise ValueError(msg) + + self._left_nuclide = left_nuclide + + + @right_nuclide.setter + def right_nuclide(self, right_nuclide): + + if not isinstance(right_nuclide, Nuclide) or is_integer(right_nuclide): + msg = 'Unable to set CrossNuclide right nuclide to {0} which ' \ + 'is not an integer or Nuclide'.format(right_nuclide) + raise ValueError(msg) + + self._right_nuclide = right_nuclide + + + @binary_op.setter + def binary_op(self, binary_op): + + if not is_string(binary_op): + msg = 'Unable to set CrossNuclide binary op to {0} which ' \ + 'is not a string'.format(binary_op) + raise ValueError(msg) + + self._binary_op = binary_op + + + def __repr__(self): + + string = '' + + # If the Summary was linked, the left nuclide is a Nuclide object + if isinstance(self.left_nuclide, Nuclide): + string += '(' + self.left_nuclide.name + # If the Summary was not linked, the left nuclide is the ZAID + else: + string += '(' + str(self.left_nuclide) + + string += ' ' + self.binary_op + ' ' + + # If the Summary was linked, the right nuclide is a Nuclide object + if isinstance(self.right_nuclide, Nuclide): + string += self.right_nuclide.name + ')' + # If the Summary was not linked, the right nuclide is the ZAID + else: + string += str(self.right_nuclide) + ')' + return string \ No newline at end of file From b51197773f5e63c22fa6cb722cb1cda1dd3225a7 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 16:30:15 -0700 Subject: [PATCH 11/39] Created _CrossFilter private class for tally arithmetic --- src/utils/openmc/tallies.py | 288 ++++++++++++++++++++++++++++-------- 1 file changed, 229 insertions(+), 59 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 419f09959..59fb3910f 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -145,17 +145,17 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == other.scores: - for score in self.scores: - new_tally.add_score(score) + # If the two Tallies have same filters, replicate them in new Tally + if self.filters == other.filters: + for filter in self.filters: + new_tally.add_filter(filter) - # Generate score "cross product" + # Generate filter "cross product" else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '+') - new_tally.add_score(new_score) + for self_filter in self.filters: + for other_filter in other.filters: + new_filter = _CrossFilter(self_filter, other_filter, '+') + new_tally.add_filter(new_filter) # If the two Tallies have same nuclides, replicate them in new Tally if self.nuclides == other.nuclides: @@ -169,6 +169,18 @@ class Tally(object): new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '+') new_tally.add_nuclide(new_nuclide) + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == other.scores: + for score in self.scores: + new_tally.add_score(score) + + # Generate score "cross product" + else: + for self_score in self.scores: + for other_score in other.scores: + new_score = _CrossScore(self_score, other_score, '+') + new_tally.add_score(new_score) + elif is_integer(other) or is_float(other): new_tally._mean = self._mean + other @@ -179,12 +191,15 @@ class Tally(object): new_tally.num_realization = self.num_realizations new_tally.num_score_bins = self.num_score_bins - for score in self.scores: - new_tally.add_score(score) + for filter in self.filters: + new_tally.add_filter(filter) for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + else: msg = 'Unable to add {0} to Tally ID={1}'.format(other, self.id) raise ValueError(msg) @@ -232,17 +247,17 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == other.scores: - for score in self.scores: - new_tally.add_score(score) + # If the two Tallies have same filters, replicate them in new Tally + if self.filters == other.filters: + for filter in self.filters: + new_tally.add_filter(filter) - # Generate score "cross product" + # Generate filter "cross product" else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '-') - new_tally.add_score(new_score) + for self_filter in self.filters: + for other_filter in other.filters: + new_filter = _CrossFilter(self_filter, other_filter, '-') + new_tally.add_filter(new_filter) # If the two Tallies have same nuclides, replicate them in new Tally if self.nuclides == other.nuclides: @@ -256,6 +271,18 @@ class Tally(object): new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '-') new_tally.add_nuclide(new_nuclide) + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == other.scores: + for score in self.scores: + new_tally.add_score(score) + + # Generate score "cross product" + else: + for self_score in self.scores: + for other_score in other.scores: + new_score = _CrossScore(self_score, other_score, '-') + new_tally.add_score(new_score) + elif is_integer(other) or is_float(other): new_tally._mean = self._mean - other @@ -266,12 +293,15 @@ class Tally(object): new_tally.num_realization = self.num_realizations new_tally.num_score_bins = self.num_score_bins - for score in self.scores: - new_tally.add_score(score) + for filter in self.filters: + new_tally.add_filter(filter) for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + else: msg = 'Unable to subtract {0} from Tally ' \ 'ID={1}'.format(other, self.id) @@ -303,8 +333,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need new CrossFilter class - data = self._align_tally_data(other) self_rel_err = data['self']['std. dev.'] / data['self']['mean'] @@ -322,17 +350,17 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == other.scores: - for score in self.scores: - new_tally.add_score(score) + # If the two Tallies have same filters, replicate them in new Tally + if self.filters == other.filters: + for filter in self.filters: + new_tally.add_filter(filter) - # Generate score "cross product" + # Generate filter "cross product" else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '*') - new_tally.add_score(new_score) + for self_filter in self.filters: + for other_filter in other.filters: + new_filter = _CrossFilter(self_filter, other_filter, '*') + new_tally.add_filter(new_filter) # If the two Tallies have same nuclides, replicate them in new Tally if self.nuclides == other.nuclides: @@ -346,6 +374,18 @@ class Tally(object): new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '*') new_tally.add_nuclide(new_nuclide) + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == other.scores: + for score in self.scores: + new_tally.add_score(score) + + # Generate score "cross product" + else: + for self_score in self.scores: + for other_score in other.scores: + new_score = _CrossScore(self_score, other_score, '*') + new_tally.add_score(new_score) + elif is_integer(other) or is_float(other): new_tally._mean = self._mean * other @@ -356,12 +396,15 @@ class Tally(object): new_tally.num_realization = self.num_realizations new_tally.num_score_bins = self.num_score_bins - for score in self.scores: - new_tally.add_score(score) + for filter in self.filters: + new_tally.add_filter(filter) for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + else: msg = 'Unable to multiply Tally ID={1} ' \ 'by {0}'.format(self.id, other) @@ -412,17 +455,17 @@ class Tally(object): if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == other.scores: - for score in self.scores: - new_tally.add_score(score) + # If the two Tallies have same filters, replicate them in new Tally + if self.filters == other.filters: + for filter in self.filters: + new_tally.add_filter(filter) - # Generate score "cross product" + # Generate filter "cross product" else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '/') - new_tally.add_score(new_score) + for self_filter in self.filters: + for other_filter in other.filters: + new_filter = _CrossFilter(self_filter, other_filter, '/') + new_tally.add_filter(new_filter) # If the two Tallies have same nuclides, replicate them in new Tally if self.nuclides == other.nuclides: @@ -436,6 +479,17 @@ class Tally(object): new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '/') new_tally.add_nuclide(new_nuclide) + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == other.scores: + for score in self.scores: + new_tally.add_score(score) + + # Generate score "cross product" + else: + for self_score in self.scores: + for other_score in other.scores: + new_score = _CrossScore(self_score, other_score, '/') + new_tally.add_score(new_score) elif is_integer(other) or is_float(other): @@ -447,12 +501,15 @@ class Tally(object): new_tally.num_realization = self.num_realizations new_tally.num_score_bins = self.num_score_bins - for score in self.scores: - new_tally.add_score(score) + for filter in self.filters: + new_tally.add_filter(filter) for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + else: msg = 'Unable to divide Tally ID={0} ' \ 'by {1}'.format(self.id, other) @@ -504,17 +561,17 @@ class Tally(object): if self.num_realizations == power.num_realizations: new_tally.num_realizations = self.num_realizations - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == power.scores: - for score in self.scores: - new_tally.add_score(score) + # If the two Tallies have same filters, replicate them in new Tally + if self.filters == power.filters: + for filter in self.filters: + new_tally.add_filter(filter) - # Generate score "cross product" + # Generate filter "cross product" else: - for self_score in self.scores: - for power_score in power.scores: - new_score = _CrossScore(self_score, power_score, '^') - new_tally.add_score(new_score) + for self_filter in self.filters: + for power_filter in power.filters: + new_filter = _CrossFilter(self_filter, power_filter, '^') + new_tally.add_filter(new_filter) # If the two Tallies have same nuclides, replicate them in new Tally if self.nuclides == power.nuclides: @@ -528,6 +585,18 @@ class Tally(object): new_nuclide = _CrossNuclide(self_nuclide, power_nuclide, '^') new_tally.add_nuclide(new_nuclide) + # If the two Tallies have same scores, replicate them in new Tally + if self.scores == power.scores: + for score in self.scores: + new_tally.add_score(score) + + # Generate score "cross product" + else: + for self_score in self.scores: + for power_score in power.scores: + new_score = _CrossScore(self_score, power_score, '^') + new_tally.add_score(new_score) + elif is_integer(power) or is_float(power): new_tally._mean = self._mean ** power @@ -539,12 +608,15 @@ class Tally(object): new_tally.num_realization = self.num_realizations new_tally.num_score_bins = self.num_score_bins - for score in self.scores: - new_tally.add_score(score) + for filter in self.filters: + new_tally.add_filter(filter) for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + else: msg = 'Unable to raise Tally ID={0} to ' \ 'power {1}'.format(self.id, power) @@ -820,7 +892,7 @@ class Tally(object): global filters - if not isinstance(filter, Filter): + if not isinstance(filter, (Filter, _CrossFilter)): msg = 'Unable to add Filter "{0}" to Tally ID={1} since it is ' \ 'not a Filter object'.format(filter, self.id) raise ValueError(msg) @@ -2132,4 +2204,102 @@ class _CrossNuclide(object): else: string += str(self.right_nuclide) + ')' - return string \ No newline at end of file + return string + + +class _CrossFilter(object): + + def __init__(self, left_filter=None, right_filter=None, binary_op=None): + + self._left_filter = None + self._right_filter = None + self._binary_op = None + + if left_filter is not None: + self.left_filter = left_filter + + if right_filter is not None: + self.right_filter = right_filter + + if binary_op is not None: + self.binary_op = binary_op + + + @property + def left_filter(self): + return self._left_filter + + + @property + def right_filter(self): + return self._right_filter + + + @property + def binary_op(self): + return self._binary_op + + + @property + def type(self): + return (self.right_filter.type, self.left_filter.type) + + + @property + def bins(self): + return (self.right_filter.bins, self.left_filter.bins) + + + @property + def stride(self): + return self.left_filter.stride * self.right_filter.stride + + + @left_filter.setter + def left_filter(self, left_filter): + + if not isinstance(left_filter, Filter): + msg = 'Unable to set CrossFilter left filter to {0} which ' \ + 'is not a Filter'.format(left_filter) + raise ValueError(msg) + + self._left_filter = left_filter + + + @right_filter.setter + def right_filter(self, right_filter): + + if not isinstance(right_filter, Filter): + msg = 'Unable to set CrossFilter right filter to {0} which ' \ + 'is not a Filter'.format(right_filter) + raise ValueError(msg) + + self._right_filter = right_filter + + + @binary_op.setter + def binary_op(self, binary_op): + + if not is_string(binary_op): + msg = 'Unable to set CrossFilter binary op to {0} which ' \ + 'is not a string'.format(binary_op) + raise ValueError(msg) + + self._binary_op = binary_op + + + def __repr__(self): + + string = '_CrossFilter\n' + + filter_type = '({0} {1} {2})'.format(self.left_filter.type, + self.binary_op, + self.right_filter.type) + + filter_bins = '({0} {1} {2})'.format(self.left_filter.bins, + self.binary_op, + self.right_filter.bins) + + string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', filter_type) + string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', filter_bins) + return string From 6c23391309c46aacddd5cfcdb9dec7fc39f13649 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 16:38:09 -0700 Subject: [PATCH 12/39] Added unary +/- operators for Tally arithmetic --- src/utils/openmc/tallies.py | 48 ++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 59fb3910f..ba9185668 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -128,7 +128,10 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need new CrossFilter class + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize data = self._align_tally_data(other) @@ -230,7 +233,10 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need new CrossFilter class + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize data = self._align_tally_data(other) @@ -333,6 +339,11 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize + data = self._align_tally_data(other) self_rel_err = data['self']['std. dev.'] / data['self']['mean'] @@ -436,7 +447,10 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need new CrossFilter class + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize data = self._align_tally_data(other) @@ -541,7 +555,10 @@ class Tally(object): 'since it does not contain any results.'.format(power.id) raise ValueError(msg) - # FIXME: Need new CrossFilter class + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize data = self._align_tally_data(power) @@ -624,6 +641,21 @@ class Tally(object): return new_tally + + def __pos__(self): + + new_tally = copy.deepcopy(self) + new_tally._mean = np.abs(new_tally.mean) + return new_tally + + + def __neg__(self): + + new_tally = copy.deepcopy(self) + new_tally._mean *= -1 + return new_tally + + ''' def sum(self, axis=None): @@ -649,19 +681,19 @@ class Tally(object): clone._mean = copy.deepcopy(self.mean, memo) clone._std_dev = copy.deepcopy(self.std_dev, memo) - clone.filters = [] + clone._filters = [] for filter in self.filters: clone.add_filter(copy.deepcopy(filter, memo)) - clone.nuclides = [] + clone._nuclides = [] for nuclide in self.nuclides: clone.add_nuclide(copy.deepcopy(nuclide, memo)) - clone.scores = [] + clone._scores = [] for score in self.scores: clone.add_score(score) - clone.triggers = [] + clone._triggers = [] for trigger in self.triggers: clone.add_trigger(trigger) From e5b1cae87445d3ad967651f7d7ca46e3ff8db254 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 17:12:06 -0700 Subject: [PATCH 13/39] Fixed some bugs in tally arithmetic tiling / repeats of NumPy data arrays --- src/utils/openmc/filter.py | 63 ++++++++++++++++++------------------- src/utils/openmc/tallies.py | 61 ++++++++++++++++++++--------------- 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/src/utils/openmc/filter.py b/src/utils/openmc/filter.py index 089f9f18a..742c91d37 100644 --- a/src/utils/openmc/filter.py +++ b/src/utils/openmc/filter.py @@ -21,15 +21,15 @@ class Filter(object): def __eq__(self, filter2): # Check type - if self._type != filter2._type: + if self.type != filter2.type: return False # Check number of bins - elif len(self._bins) != len(filter2._bins): + elif len(self.bins) != len(filter2.bins): return False # Check bin edges - elif not np.allclose(self._bins, filter2._bins): + elif not np.allclose(self.bins, filter2.bins): return False else: @@ -38,8 +38,8 @@ class Filter(object): def __hash__(self): hashable = [] - hashable.append(self._type) - hashable.append(self._bins) + hashable.append(self.type) + hashable.append(self.bins) return hash(tuple(hashable)) @@ -51,12 +51,12 @@ class Filter(object): if existing is None: clone = type(self).__new__(type(self)) - clone._type = self._type - clone._bins = copy.deepcopy(self._bins, memo) - clone._num_bins = self._num_bins - clone._mesh = copy.deepcopy(self._mesh, memo) - clone._offset = self._offset - clone._stride = self._stride + clone._type = self.type + clone._bins = copy.deepcopy(self.bins, memo) + clone._num_bins = self.num_bins + clone._mesh = copy.deepcopy(self.mesh, memo) + clone._offset = self.offset + clone._stride = self.stride memo[id(self)] = clone @@ -117,7 +117,7 @@ class Filter(object): if bins is None: self.num_bins = 0 - elif self._type is None: + elif self.type is None: msg = 'Unable to set bins for Filter to "{0}" since ' \ 'the Filter type has not yet been set'.format(bins) raise ValueError(msg) @@ -130,35 +130,35 @@ class Filter(object): else: bins = list(bins) - if self._type in ['cell', 'cellborn', 'surface', 'material', + if self.type in ['cell', 'cellborn', 'surface', 'material', 'universe', 'distribcell']: for edge in bins: if not is_integer(edge): msg = 'Unable to add bin "{0}" to a {1} Filter since ' \ - 'it is a non-integer'.format(edge, self._type) + 'it is a non-integer'.format(edge, self.type) raise ValueError(msg) elif edge < 0: msg = 'Unable to add bin "{0}" to a {1} Filter since ' \ - 'it is a negative integer'.format(edge, self._type) + 'it is a negative integer'.format(edge, self.type) raise ValueError(msg) - elif self._type in ['energy', 'energyout']: + elif self.type in ['energy', 'energyout']: for edge in bins: if not is_integer(edge) and not is_float(edge): msg = 'Unable to add bin edge "{0}" to a {1} Filter ' \ 'since it is a non-integer or floating point ' \ - 'value'.format(edge, self._type) + 'value'.format(edge, self.type) raise ValueError(msg) elif edge < 0.: msg = 'Unable to add bin edge "{0}" to a {1} Filter ' \ - 'since it is a negative value'.format(edge, self._type) + 'since it is a negative value'.format(edge, self.type) raise ValueError(msg) # Check that bin edges are monotonically increasing @@ -167,12 +167,12 @@ class Filter(object): if index > 0 and bins[index] < bins[index-1]: msg = 'Unable to add bin edges "{0}" to a {1} Filter ' \ 'since they are not monotonically ' \ - 'increasing'.format(bins, self._type) + 'increasing'.format(bins, self.type) raise ValueError(msg) # mesh filters - elif self._type == 'mesh': + elif self.type == 'mesh': if not len(bins) == 1: msg = 'Unable to add bins "{0}" to a mesh Filter since ' \ @@ -193,14 +193,13 @@ class Filter(object): self._bins = bins - # FIXME @num_bins.setter def num_bins(self, num_bins): if not is_integer(num_bins) or num_bins < 0: msg = 'Unable to set the number of bins "{0}" for a {1} Filter ' \ 'since it is not a positive ' \ - 'integer'.format(num_bins, self._type) + 'integer'.format(num_bins, self.type) raise ValueError(msg) self._num_bins = num_bins @@ -216,7 +215,7 @@ class Filter(object): self._mesh = mesh self.type = 'mesh' - self.bins = self._mesh._id + self.bins = self.mesh.id @offset.setter @@ -224,7 +223,7 @@ class Filter(object): if not is_integer(offset): msg = 'Unable to set offset "{0}" for a {1} Filter since it is a ' \ - 'non-integer value'.format(offset, self._type) + 'non-integer value'.format(offset, self.type) raise ValueError(msg) self._offset = offset @@ -235,12 +234,12 @@ class Filter(object): if not is_integer(stride): msg = 'Unable to set stride "{0}" for a {1} Filter since it is a ' \ - 'non-integer value'.format(stride, self._type) + 'non-integer value'.format(stride, self.type) raise ValueError(msg) if stride < 0: msg = 'Unable to set stride "{0}" for a {1} Filter since it is a ' \ - 'negative value'.format(stride, self._type) + 'negative value'.format(stride, self.type) raise ValueError(msg) self._stride = stride @@ -274,14 +273,14 @@ class Filter(object): def merge(self, filter): if not self.can_merge(filter): - msg = 'Unable to merge {0} with {1} filters'.format(self._type, filter._type) + msg = 'Unable to merge {0} with {1} filters'.format(self.type, filter.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 = list(set(self._bins + filter._bins)) + merged_bins = list(set(self.bins + filter.bins)) merged_filter.bins = merged_bins merged_filter.num_bins = len(merged_bins) @@ -335,7 +334,7 @@ class Filter(object): # Filter bins for distribcell are the "IDs" of each unique placement # of the Cell in the Geometry (integers starting at 0) - elif self._type == 'distribcell': + elif self.type == 'distribcell': filter_index = filter_bin # Use ID for all other Filters (e.g., material, cell, etc.) @@ -354,7 +353,7 @@ class Filter(object): def __repr__(self): string = 'Filter\n' - string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self._type) - string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', self._bins) - string += '{0: <16}{1}{2}\n'.format('\tOffset', '=\t', self._offset) + string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self.type) + string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', self.bins) + string += '{0: <16}{1}{2}\n'.format('\tOffset', '=\t', self.offset) return string diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index ba9185668..dc0565081 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -58,14 +58,14 @@ class Tally(object): self_num_filter_bins = self.mean.shape[0] other_num_filter_bins = other.mean.shape[0] num_filter_bins = self_num_filter_bins * other_num_filter_bins - self_new_bins = max(num_filter_bins - self_num_filter_bins, 1) - other_new_bins = max(num_filter_bins - other_num_filter_bins, 1) + self_repeat_factor = num_filter_bins / self_num_filter_bins + other_tile_factor = num_filter_bins / other_num_filter_bins # Replicate the data - self_mean = np.repeat(self_mean, self_new_bins, axis=0) - other_mean = np.tile(other_mean, (other_new_bins, 1, 1)) - self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=0) - other_std_dev = np.tile(other_std_dev, (other_new_bins, 1, 1)) + self_mean = np.repeat(self_mean, self_repeat_factor, axis=0) + other_mean = np.tile(other_mean, (other_tile_factor, 1, 1)) + self_std_dev = np.repeat(self_std_dev, self_repeat_factor, axis=0) + other_std_dev = np.tile(other_std_dev, (other_tile_factor, 1, 1)) if self.nuclides != other.nuclides: @@ -74,14 +74,14 @@ class Tally(object): self_num_nuclide_bins = self.mean.shape[1] other_num_nuclide_bins = other.mean.shape[1] num_nuclide_bins = self_num_nuclide_bins * other_num_nuclide_bins - self_new_bins = max(num_nuclide_bins - self_num_nuclide_bins, 1) - other_new_bins = max(num_nuclide_bins - other_num_nuclide_bins, 1) + self_repeat_factor = num_nuclide_bins / self_num_nuclide_bins + other_tile_factor = num_nuclide_bins / other_num_nuclide_bins # Replicate the data - self_mean = np.repeat(self_mean, self_new_bins, axis=1) - other_mean = np.tile(other_mean, (1, other_new_bins, 1)) - self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=1) - other_std_dev = np.tile(other_std_dev, (1, other_new_bins, 1)) + self_mean = np.repeat(self_mean, self_repeat_factor, axis=1) + other_mean = np.tile(other_mean, (1, other_tile_factor, 1)) + self_std_dev = np.repeat(self_std_dev, self_repeat_factor, axis=1) + other_std_dev = np.tile(other_std_dev, (1, other_tile_factor, 1)) if self.scores != other.scores: @@ -90,14 +90,14 @@ class Tally(object): self_num_score_bins = self.mean.shape[2] other_num_score_bins = other.mean.shape[2] num_score_bins = self_num_score_bins * other_num_score_bins - self_new_bins = max(num_score_bins - self_num_score_bins, 1) - other_new_bins = max(num_score_bins - other_num_score_bins, 1) + self_repeat_factor = num_score_bins / self_num_score_bins + other_tile_factor = num_score_bins / other_num_score_bins # Replicate the data - self_mean = np.repeat(self_mean, self_new_bins, axis=2) - other_mean = np.tile(other_mean, (1, 1, other_new_bins)) - self_std_dev = np.repeat(self_std_dev, self_new_bins, axis=2) - other_std_dev = np.tile(other_std_dev, (1, 1, other_new_bins)) + self_mean = np.repeat(self_mean, self_repeat_factor, axis=2) + other_mean = np.tile(other_mean, (1, 1, other_tile_factor)) + self_std_dev = np.repeat(self_std_dev, self_repeat_factor, axis=2) + other_std_dev = np.tile(other_std_dev, (1, 1, other_tile_factor)) data = {} data['self'] = {} @@ -1571,10 +1571,21 @@ class Tally(object): # Find the total length of the tally data array data_size = self.mean.size + # Split CrossFilters into separate filters + split_filters = [] + + for filter in self.filters: + + if isinstance(filter, _CrossFilter): + split_filters.append(filter.left_filter) + split_filters.append(filter.right_filter) + else: + split_filters.append(filter) + # Build DataFrame columns for filters if user requested them if filters: - for filter in self.filters: + for filter in split_filters: # mesh filters if filter.type == 'mesh': @@ -2114,7 +2125,7 @@ class _CrossScore(object): @left_score.setter def left_score(self, left_score): - if not is_string(left_score): + if not isinstance(left_score, (_CrossScore, str)): msg = 'Unable to set CrossScore left score to {0} which ' \ 'is not a string'.format(left_score) raise ValueError(msg) @@ -2125,7 +2136,7 @@ class _CrossScore(object): @right_score.setter def right_score(self, right_score): - if not is_string(right_score): + if not isinstance(right_score, (_CrossScore, str)): msg = 'Unable to set CrossScore right score to {0} which ' \ 'is not a string'.format(right_score) raise ValueError(msg) @@ -2186,7 +2197,7 @@ class _CrossNuclide(object): @left_nuclide.setter def left_nuclide(self, left_nuclide): - if not isinstance(left_nuclide, Nuclide) or is_integer(left_nuclide): + if not isinstance(left_nuclide, (Nuclide, _CrossNuclide, int)): msg = 'Unable to set CrossNuclide left nuclide to {0} which ' \ 'is not an integer or Nuclide'.format(left_nuclide) raise ValueError(msg) @@ -2197,7 +2208,7 @@ class _CrossNuclide(object): @right_nuclide.setter def right_nuclide(self, right_nuclide): - if not isinstance(right_nuclide, Nuclide) or is_integer(right_nuclide): + if not isinstance(right_nuclide, (Nuclide, _CrossNuclide, int)): msg = 'Unable to set CrossNuclide right nuclide to {0} which ' \ 'is not an integer or Nuclide'.format(right_nuclide) raise ValueError(msg) @@ -2290,7 +2301,7 @@ class _CrossFilter(object): @left_filter.setter def left_filter(self, left_filter): - if not isinstance(left_filter, Filter): + if not isinstance(left_filter, (Filter, _CrossFilter)): msg = 'Unable to set CrossFilter left filter to {0} which ' \ 'is not a Filter'.format(left_filter) raise ValueError(msg) @@ -2301,7 +2312,7 @@ class _CrossFilter(object): @right_filter.setter def right_filter(self, right_filter): - if not isinstance(right_filter, Filter): + if not isinstance(right_filter, (Filter, _CrossFilter)): msg = 'Unable to set CrossFilter right filter to {0} which ' \ 'is not a Filter'.format(right_filter) raise ValueError(msg) From 3969ec0dcd248017f9e13a881ea94a3aa3d498fa Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 31 May 2015 21:09:14 -0700 Subject: [PATCH 14/39] Added Tally min, max and summation routines --- src/utils/openmc/tallies.py | 199 ++++++++++++++++++++++++++++++++---- 1 file changed, 177 insertions(+), 22 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index dc0565081..5bb42f3d9 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -132,6 +132,7 @@ class Tally(object): # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value # FIXME: Modularize + # FIXME: Redundant filters data = self._align_tally_data(other) @@ -656,9 +657,135 @@ class Tally(object): return new_tally - ''' - def sum(self, axis=None): + def min(self, scores=[], filters=[], filter_bins=[], + nuclides=[], value='mean'): + """Returns the minimum of a slice of the Tally's data. + Parameters + ---------- + scores : list + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) + + filters : list + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) + + filter_bins : list + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin + in the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for + the cell instance ID for 'distribcell Filters. Each bin is + a 2-tuple of floats for 'energy' and 'energyout' filters + corresponding to the energy boundaries of the bin of interest. + The bin is a (x,y,z) 3-tuple for 'mesh' filters corresponding + to the mesh cell of interest. The order of the bins in the list + must correspond of the filter_types parameter. + + nuclides : list + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) + + value : str + A string for the type of value to return - 'mean' (default), + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + + Returns + ------- + The minimum value of the requested slice of Tally data. + """ + + data = self.get_values(scores, filters, filter_bins, nuclides, value) + return np.min(data) + + + def max(self, scores=[], filters=[], filter_bins=[], + nuclides=[], value='mean'): + """Returns the maximum of a slice of the Tally's data. + + Parameters + ---------- + scores : list + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) + + filters : list + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) + + filter_bins : list + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin + in the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for + the cell instance ID for 'distribcell Filters. Each bin is + a 2-tuple of floats for 'energy' and 'energyout' filters + corresponding to the energy boundaries of the bin of interest. + The bin is a (x,y,z) 3-tuple for 'mesh' filters corresponding + to the mesh cell of interest. The order of the bins in the list + must correspond of the filter_types parameter. + + nuclides : list + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) + + value : str + A string for the type of value to return - 'mean' (default), + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + + Returns + ------- + The maximum value of the requested slice of Tally data. + """ + + data = self.get_values(scores, filters, filter_bins, nuclides, value) + return np.max(data) + + + def summation(self, scores=[], filters=[], filter_bins=[], + nuclides=[], value='mean'): + """Returns the sum of a slice of the Tally's data. + + Parameters + ---------- + scores : list + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) + + filters : list + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) + + filter_bins : list + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin + in the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for + the cell instance ID for 'distribcell Filters. Each bin is + a 2-tuple of floats for 'energy' and 'energyout' filters + corresponding to the energy boundaries of the bin of interest. + The bin is a (x,y,z) 3-tuple for 'mesh' filters corresponding + to the mesh cell of interest. The order of the bins in the list + must correspond of the filter_types parameter. + + nuclides : list + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) + + value : str + A string for the type of value to return - 'mean' (default), + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + + Returns + ------- + The sum of the requested slice of Tally data. + """ + + data = self.get_values(scores, filters, filter_bins, nuclides, value) + return np.sum(data) + + ''' def slice(self, filters=[], nuclides=[], scores=[]) ''' @@ -1235,12 +1362,12 @@ class Tally(object): def get_filter_index(self, filter_type, filter_bin): - """Returns the index in the Tally's results array for a Filter bin + """Returns the index in the Tally's results array for a Filter bin. Parameters ---------- filter_type : str - The type of Filter (e.g., 'cell', 'energy', etc.) + The type of Filter (e.g., 'cell', 'energy', etc.). filter_bin : int, list The bin is an integer ID for 'material', 'surface', 'cell', @@ -1265,12 +1392,12 @@ class Tally(object): def get_nuclide_index(self, nuclide): - """Returns the index in the Tally's results array for a Nuclide bin + """Returns the index in the Tally's results array for a Nuclide bin. Parameters ---------- nuclide : str - The name of the Nuclide (e.g., 'H-1', 'U-238') + The name of the Nuclide (e.g., 'H-1', 'U-238'). Returns ------- @@ -1308,12 +1435,12 @@ class Tally(object): def get_score_index(self, score): - """Returns the index in the Tally's results array for a score bin + """Returns the index in the Tally's results array for a score bin. Parameters ---------- score : str - The score string (e.g., 'absorption', 'nu-fission') + The score string (e.g., 'absorption', 'nu-fission'). Returns ------- @@ -1342,17 +1469,17 @@ class Tally(object): This routine constructs a 3D NumPy array for the requested Tally data indexed by filter bin, nuclide bin, and score index. The routine will - order the data in the array + order the data in the array as specified in the parameter lists. Parameters ---------- scores : list A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) + (e.g., ['absorption', 'nu-fission']; default is []). filters : list A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) + (e.g., ['mesh', 'energy']; default is []). filter_bins : list A list of the filter bins corresponding to the filter_types @@ -1368,11 +1495,11 @@ class Tally(object): nuclides : list A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) + (e.g., ['U-235', 'U-238']; default is []). value : str A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted. Returns ------- @@ -1505,8 +1632,8 @@ class Tally(object): This routine constructs a Pandas DataFrame object for the Tally data with columns annotated by filter, nuclide and score bin information. - This capability has been tested for Pandas >=v0.13.1. However, if p - possible, it is recommended to use the v0.16 or newer versions of + This capability has been tested for Pandas >=v0.13.1. However, if + possible, it is recommended to use the v0.16 or newer versions of Pandas since this this routine uses the Multi-index Pandas feature. Parameters @@ -1577,8 +1704,7 @@ class Tally(object): for filter in self.filters: if isinstance(filter, _CrossFilter): - split_filters.append(filter.left_filter) - split_filters.append(filter.right_filter) + split_filters.extend(filter.split_filters()) else: split_filters.append(filter) @@ -1773,13 +1899,16 @@ class Tally(object): # energy, energyout filters elif 'energy' in filter.type: + + print filter + bins = filter.bins num_bins = filter.num_bins # Create strings for template = '{0:.1e} - {1:.1e}' filter_bins = [] - for i in range(num_bins): + for i in range(num_bins-1): filter_bins.append(template.format(bins[i], bins[i+1])) # Tile the energy bins into a DataFrame column @@ -1832,17 +1961,17 @@ class Tally(object): Parameters ---------- filename : str - The name of the file for the results (default is 'tally-results') + The name of the file for the results (default is 'tally-results'). directory : str - The name of the directory for the results (default is '.') + The name of the directory for the results (default is '.'). format : str The format for the exported file - HDF5 ('hdf5', default) and - Python pickle ('pkl') files are supported + Python pickle ('pkl') files are supported. append : bool - Whether or not to append the results to the file (default is True) + Whether or not to append the results to the file (default is True). Raises ------ @@ -2293,6 +2422,11 @@ class _CrossFilter(object): return (self.right_filter.bins, self.left_filter.bins) + @property + def num_bins(self): + return self.left_filter.num_bins * self.right_filter.num_bins + + @property def stride(self): return self.left_filter.stride * self.right_filter.stride @@ -2346,3 +2480,24 @@ class _CrossFilter(object): string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', filter_type) string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', filter_bins) return string + + + def split_filters(self): + + split_filters = [] + + # If left Filter is not a CrossFilter, simply append to list + if isinstance(self.left_filter, Filter): + split_filters.append(self.left_filter) + # Recursively descend CrossFilter tree to collect all Filters + else: + split_filters.extend(self.left_filter.split_filters()) + + # If right Filter is not a CrossFilter, simply append to list + if isinstance(self.right_filter, Filter): + split_filters.append(self.right_filter) + # Recursively descend CrossFilter tree to collect all Filters + else: + split_filters.extend(self.right_filter.split_filters()) + + return split_filters \ No newline at end of file From 0092432a8c5acda1ec7d0c928d001da3c941e47f Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Mon, 1 Jun 2015 05:58:10 -0700 Subject: [PATCH 15/39] initial implementation for tally slice operator --- src/utils/openmc/tallies.py | 113 +++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 7 deletions(-) diff --git a/src/utils/openmc/tallies.py b/src/utils/openmc/tallies.py index 5bb42f3d9..edf136b83 100644 --- a/src/utils/openmc/tallies.py +++ b/src/utils/openmc/tallies.py @@ -657,8 +657,8 @@ class Tally(object): return new_tally - def min(self, scores=[], filters=[], filter_bins=[], - nuclides=[], value='mean'): + def minimum(self, scores=[], filters=[], filter_bins=[], + nuclides=[], value='mean'): """Returns the minimum of a slice of the Tally's data. Parameters @@ -700,8 +700,8 @@ class Tally(object): return np.min(data) - def max(self, scores=[], filters=[], filter_bins=[], - nuclides=[], value='mean'): + def maximum(self, scores=[], filters=[], filter_bins=[], + nuclides=[], value='mean'): """Returns the maximum of a slice of the Tally's data. Parameters @@ -785,9 +785,106 @@ class Tally(object): data = self.get_values(scores, filters, filter_bins, nuclides, value) return np.sum(data) - ''' - def slice(self, filters=[], nuclides=[], scores=[]) - ''' + + + + def slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]): + """ + """ + + # Ensure that StatePoint.read_results() was called first + if (self.mean is None) or (self.std_dev is None): + msg = 'The Tally ID={0} has no data to slice. Call the ' \ + 'StatePoint.read_results() routine before using ' \ + 'Tally.slice(...)'.format(self.id) + raise ValueError(msg) + + # Compute batch statistics if not yet computed + if not self.with_batch_statistics: + self.compute_std_dev() + + new_tally = copy.deepcopy(self) + new_sum = self.get_values(scores, filters, filter_bins, + nuclides, 'sum') + new_sum_sq = self.get_values(scores, filters, filter_bins, + nuclides, 'sum_sq') + new_tally.set_results(new_sum, new_sum_sq) + + ############################ FILTERS ######################### + # Determine the score indices from any of the requested scores + if filters: + + # Initialize list of indices to Filters to exclude from slice + filter_indices = [] + + # Loop over all of the Tally's Filters + for i, filter in enumerate(self.filters): + + # FIXME: sum over unused filter bins + # FIXME: neglect bins not selected for selected filters + # NOTE: We must store filter indices here since they are strings + + if filter.type not in filters: + filter_index = self.get_filter_index(filters[i], filter_bins[i]) + filter_indices.append(filter_index) + + # Loop over indices in reverse to remove excluded Filters + for filter_index in filter_indices[::-1]: + new_tally.remove_filter(self.filters[filter_index]) + + for i, filter_type in enumerate(filters): + + if 'energy' in filter_type: + # Create a list of the first energy edge + bins = [filter_bin[0] for filter_bin in filter_bins] + + + + ############################ NUCLIDES ######################## + # Determine the score indices from any of the requested scores + if nuclides: + + # Initialize list of indices to Nuclides to exclude from slice + nuclide_indices = [] + + for nuclide in self.nuclides: + + if isinstance(nuclide, Nuclide): + if nuclide.name not in nuclides: + nuclide_index = self.get_nuclide_index(nuclide) + nuclide_indices.append(nuclide_index) + else: + if nuclide not in nuclides: + nuclide_index = self.get_nuclide_index(nuclide) + nuclide_indices.append(nuclide_index) + + # Loop over indices in reverse to remove excluded Nuclides + for nuclide_index in nuclide_indices[::-1]: + new_tally.remove_nuclide(self.nuclides[nuclide_index]) + + ############################# SCORES ######################### + # Determine the score indices from any of the requested scores + if scores: + + # Initialize list of indices to Nuclides to exclude from slice + score_indices = [] + + for score in self.scores: + + if score not in scores: + score_index = self.get_score_index(score) + score_indices.append(score_index) + + # Loop over indices in reverse to remove excluded scores + for score_index in score_indices[::-1]: + new_tally.remove_score(self.scores[score_index]) + new_tally.num_score_bins -= 1 + + + + return new_tally + + def __deepcopy__(self, memo): @@ -807,6 +904,8 @@ class Tally(object): clone._sum_sq = copy.deepcopy(self.sum_sq, memo) clone._mean = copy.deepcopy(self.mean, memo) clone._std_dev = copy.deepcopy(self.std_dev, memo) + clone._with_summary = self.with_summary + clone._with_batch_statistics = self.with_batch_statistics clone._filters = [] for filter in self.filters: From da4f6a797cd825151d5dee73a18f01a789519422 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 26 Jul 2015 20:06:22 -0700 Subject: [PATCH 16/39] Cleaned up tallies.py to use new checkvalue API --- openmc/tallies.py | 303 ++++++++++++++-------------------------------- 1 file changed, 88 insertions(+), 215 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 06c5fd439..2b58a1c2a 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3,7 +3,7 @@ import copy import os import pickle import itertools -from numbers import Integral +from numbers import Integral, Rational from xml.etree import ElementTree as ET import sys @@ -200,10 +200,8 @@ class Tally(object): if self.estimator == other.estimator: new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations @@ -243,11 +241,10 @@ class Tally(object): new_score = _CrossScore(self_score, other_score, '+') new_tally.add_score(new_score) - elif is_integer(other) or is_float(other): + elif isinstance(other, Integral) or isinstance(other): new_tally._mean = self._mean + other new_tally._std_dev = self._std_dev - new_tally.estimator = self.estimator new_tally.with_summary = self.with_summary new_tally.num_realization = self.num_realizations @@ -255,10 +252,8 @@ class Tally(object): for filter in self.filters: new_tally.add_filter(filter) - for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) - for score in self.scores: new_tally.add_score(score) @@ -305,10 +300,8 @@ class Tally(object): if self.estimator == other.estimator: new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations @@ -348,11 +341,10 @@ class Tally(object): new_score = _CrossScore(self_score, other_score, '-') new_tally.add_score(new_score) - elif is_integer(other) or is_float(other): + elif isinstance(other, (Integral, Rational)): new_tally._mean = self._mean - other new_tally._std_dev = self._std_dev - new_tally.estimator = self.estimator new_tally.with_summary = self.with_summary new_tally.num_realization = self.num_realizations @@ -360,10 +352,8 @@ class Tally(object): for filter in self.filters: new_tally.add_filter(filter) - for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) - for score in self.scores: new_tally.add_score(score) @@ -413,10 +403,8 @@ class Tally(object): if self.estimator == other.estimator: new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations @@ -456,11 +444,10 @@ class Tally(object): new_score = _CrossScore(self_score, other_score, '*') new_tally.add_score(new_score) - elif is_integer(other) or is_float(other): + elif isinstance(other, (Integral, Rational)): new_tally._mean = self._mean * other new_tally._std_dev = self._std_dev * np.abs(other) - new_tally.estimator = self.estimator new_tally.with_summary = self.with_summary new_tally.num_realization = self.num_realizations @@ -468,10 +455,8 @@ class Tally(object): for filter in self.filters: new_tally.add_filter(filter) - for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) - for score in self.scores: new_tally.add_score(score) @@ -521,10 +506,8 @@ class Tally(object): if self.estimator == other.estimator: new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations @@ -564,11 +547,10 @@ class Tally(object): new_score = _CrossScore(self_score, other_score, '/') new_tally.add_score(new_score) - elif is_integer(other) or is_float(other): + elif isinstance(other, (Integral, Rational)): new_tally._mean = self._mean / other new_tally._std_dev = self._std_dev * np.abs(1. / other) - new_tally.estimator = self.estimator new_tally.with_summary = self.with_summary new_tally.num_realization = self.num_realizations @@ -576,10 +558,8 @@ class Tally(object): for filter in self.filters: new_tally.add_filter(filter) - for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) - for score in self.scores: new_tally.add_score(score) @@ -630,10 +610,8 @@ class Tally(object): if self.estimator == power.estimator: new_tally.estimator = self.estimator - if self.with_summary and power.with_summary: new_tally.with_summary = self.with_summary - if self.num_realizations == power.num_realizations: new_tally.num_realizations = self.num_realizations @@ -673,12 +651,11 @@ class Tally(object): new_score = _CrossScore(self_score, power_score, '^') new_tally.add_score(new_score) - elif is_integer(power) or is_float(power): + elif isinstance(power, (Integral, Rational)): new_tally._mean = self._mean ** power self_rel_err = self.std_dev / self.mean new_tally._std_dev = np.abs(new_tally._mean * power * self_rel_err) - new_tally.estimator = self.estimator new_tally.with_summary = self.with_summary new_tally.num_realization = self.num_realizations @@ -686,10 +663,8 @@ class Tally(object): for filter in self.filters: new_tally.add_filter(filter) - for nuclide in self.nuclides: new_tally.add_nuclide(nuclide) - for score in self.scores: new_tally.add_score(score) @@ -702,19 +677,14 @@ class Tally(object): def __pos__(self): - new_tally = copy.deepcopy(self) new_tally._mean = np.abs(new_tally.mean) return new_tally - def __neg__(self): - - new_tally = copy.deepcopy(self) - new_tally._mean *= -1 + new_tally = self * -1 return new_tally - def minimum(self, scores=[], filters=[], filter_bins=[], nuclides=[], value='mean'): """Returns the minimum of a slice of the Tally's data. @@ -722,42 +692,43 @@ class Tally(object): Parameters ---------- scores : list - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) filters : list - A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) filter_bins : list - A list of the filter bins corresponding to the filter_types - parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin - in the list is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. Each bin is an integer for - the cell instance ID for 'distribcell Filters. Each bin is - a 2-tuple of floats for 'energy' and 'energyout' filters - corresponding to the energy boundaries of the bin of interest. - The bin is a (x,y,z) 3-tuple for 'mesh' filters corresponding - to the mesh cell of interest. The order of the bins in the list - must correspond of the filter_types parameter. + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in + the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for the + cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of + floats for 'energy' and 'energyout' filters corresponding to the + energy boundaries of the bin of interest. The bin is a (x,y,z) + 3-tuple for 'mesh' filters corresponding to the mesh cell of + interest. The order of the bins in the list must correspond of the + filter_types parameter. nuclides : list - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) value : str - A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + A string for the type of value to return - 'mean' (default), + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted Returns ------- - The minimum value of the requested slice of Tally data. + float + A scalar float of the minimum value in the Tally data indexed by + each filter, nuclide and score as listed in the parameters. """ data = self.get_values(scores, filters, filter_bins, nuclides, value) return np.min(data) - def maximum(self, scores=[], filters=[], filter_bins=[], nuclides=[], value='mean'): """Returns the maximum of a slice of the Tally's data. @@ -765,42 +736,43 @@ class Tally(object): Parameters ---------- scores : list - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) filters : list - A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) filter_bins : list - A list of the filter bins corresponding to the filter_types - parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin - in the list is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. Each bin is an integer for - the cell instance ID for 'distribcell Filters. Each bin is - a 2-tuple of floats for 'energy' and 'energyout' filters - corresponding to the energy boundaries of the bin of interest. - The bin is a (x,y,z) 3-tuple for 'mesh' filters corresponding - to the mesh cell of interest. The order of the bins in the list - must correspond of the filter_types parameter. + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in + the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for the + cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of + floats for 'energy' and 'energyout' filters corresponding to the + energy boundaries of the bin of interest. The bin is a (x,y,z) + 3-tuple for 'mesh' filters corresponding to the mesh cell of + interest. The order of the bins in the list must correspond of the + filter_types parameter. nuclides : list - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) value : str - A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + A string for the type of value to return - 'mean' (default), + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted Returns ------- - The maximum value of the requested slice of Tally data. + float + A scalar float of the maximum value in the Tally data indexed by + each filter, nuclide and score as listed in the parameters. """ data = self.get_values(scores, filters, filter_bins, nuclides, value) return np.max(data) - def summation(self, scores=[], filters=[], filter_bins=[], nuclides=[], value='mean'): """Returns the sum of a slice of the Tally's data. @@ -808,44 +780,43 @@ class Tally(object): Parameters ---------- scores : list - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) filters : list - A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) filter_bins : list - A list of the filter bins corresponding to the filter_types - parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin - in the list is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. Each bin is an integer for - the cell instance ID for 'distribcell Filters. Each bin is - a 2-tuple of floats for 'energy' and 'energyout' filters - corresponding to the energy boundaries of the bin of interest. - The bin is a (x,y,z) 3-tuple for 'mesh' filters corresponding - to the mesh cell of interest. The order of the bins in the list - must correspond of the filter_types parameter. + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in + the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for the + cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of + floats for 'energy' and 'energyout' filters corresponding to the + energy boundaries of the bin of interest. The bin is a (x,y,z) + 3-tuple for 'mesh' filters corresponding to the mesh cell of + interest. The order of the bins in the list must correspond of the + filter_types parameter. nuclides : list - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) value : str - A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + A string for the type of value to return - 'mean' (default), + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted Returns ------- - The sum of the requested slice of Tally data. + float + A scalar float of the sum of the Tally data indexed by + each filter, nuclide and score as listed in the parameters. """ data = self.get_values(scores, filters, filter_bins, nuclides, value) return np.sum(data) - - - def slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]): """ """ @@ -866,7 +837,8 @@ class Tally(object): nuclides, 'sum') new_sum_sq = self.get_values(scores, filters, filter_bins, nuclides, 'sum_sq') - new_tally.set_results(new_sum, new_sum_sq) + new_tally.sum = new_sum + new_tally.sum_sq = new_sum_sq ############################ FILTERS ######################### # Determine the score indices from any of the requested scores @@ -1236,29 +1208,9 @@ class Tally(object): @with_batch_statistics.setter def with_batch_statistics(self, with_batch_statistics): - - if not isinstance(with_batch_statistics, bool): - msg = 'Unable to set with_batch_statistics to a non-boolean ' \ - 'value "{0}"'.format(with_batch_statistics) - raise ValueError(msg) - + check_type('with_batch_statistics', with_batch_statistics, bool) self._with_batch_statistics = with_batch_statistics - - def set_results(self, sum, sum_sq): - - if not isinstance(sum, (tuple, list, np.ndarray)): - msg = 'Unable to set the sum to "{0}" for Tally ID={1} since ' \ - 'it is not a Python tuple/list or NumPy ' \ - 'array'.format(sum, self.id) - raise ValueError(msg) - - if not isinstance(sum_sq, (tuple, list, np.ndarray)): - msg = 'Unable to set the sum to "{0}" for Tally ID={1} since ' \ - 'it is not a Python tuple/list or NumPy ' \ - 'array'.format(sum_sq, self.id) - raise ValueError(msg) - @sum.setter def sum(self, sum): check_type('sum', sum, Iterable) @@ -1564,7 +1516,7 @@ class Tally(object): return filter def get_filter_index(self, filter_type, filter_bin): - """Returns the index in the Tally's results array for a Filter bin. + """Returns the index in the Tally's results array for a Filter bin Parameters ---------- @@ -1582,7 +1534,7 @@ class Tally(object): Returns ------- - The index in the Tally data array for this filter bin. + The index in the Tally data array for this filter bin """ @@ -1594,7 +1546,7 @@ class Tally(object): return filter_index def get_nuclide_index(self, nuclide): - """Returns the index in the Tally's results array for a Nuclide bin. + """Returns the index in the Tally's results array for a Nuclide bin Parameters ---------- @@ -1639,7 +1591,7 @@ class Tally(object): return nuclide_index def get_score_index(self, score): - """Returns the index in the Tally's results array for a score bin. + """Returns the index in the Tally's results array for a score bin Parameters ---------- @@ -1675,7 +1627,7 @@ class Tally(object): This routine constructs a 3D NumPy array for the requested Tally data indexed by filter bin, nuclide bin, and score index. The routine will - order the data in the array as specified in the parameter lists. + order the data in the array as specified in the parameter lists Parameters ---------- @@ -2461,62 +2413,38 @@ class _CrossScore(object): if left_score is not None: self.left_score = left_score - if right_score is not None: self.right_score = right_score - if binary_op is not None: self.binary_op = binary_op - @property def left_score(self): return self._left_score - @property def right_score(self): return self._right_score - @property def binary_op(self): return self._binary_op - @left_score.setter def left_score(self, left_score): - - if not isinstance(left_score, (_CrossScore, str)): - msg = 'Unable to set CrossScore left score to {0} which ' \ - 'is not a string'.format(left_score) - raise ValueError(msg) - + check_type('left score', left_score, (str, _CrossScore)) self._left_score = left_score - @right_score.setter def right_score(self, right_score): - - if not isinstance(right_score, (_CrossScore, str)): - msg = 'Unable to set CrossScore right score to {0} which ' \ - 'is not a string'.format(right_score) - raise ValueError(msg) - + check_type('right score', right_score, (str, _CrossScore)) self._right_score = right_score - @binary_op.setter def binary_op(self, binary_op): - - if not is_string(binary_op): - msg = 'Unable to set CrossScore binary op to {0} which ' \ - 'is not a string'.format(binary_op) - raise ValueError(msg) - + check_type('binary op', binary_op, str) self._binary_op = binary_op - def __repr__(self): string = '({0} {1} {2})'.format(self.left_score, self.binary_op, self.right_score) @@ -2533,62 +2461,38 @@ class _CrossNuclide(object): if left_nuclide is not None: self.left_nuclide = left_nuclide - if right_nuclide is not None: self.right_nuclide = right_nuclide - if binary_op is not None: self.binary_op = binary_op - @property def left_nuclide(self): return self._left_nuclide - @property def right_nuclide(self): return self._right_nuclide - @property def binary_op(self): return self._binary_op - @left_nuclide.setter def left_nuclide(self, left_nuclide): - - if not isinstance(left_nuclide, (Nuclide, _CrossNuclide, int)): - msg = 'Unable to set CrossNuclide left nuclide to {0} which ' \ - 'is not an integer or Nuclide'.format(left_nuclide) - raise ValueError(msg) - + check_type('left nuclide', left_nuclide, (Nuclide, _CrossNuclide)) self._left_nuclide = left_nuclide - @right_nuclide.setter def right_nuclide(self, right_nuclide): - - if not isinstance(right_nuclide, (Nuclide, _CrossNuclide, int)): - msg = 'Unable to set CrossNuclide right nuclide to {0} which ' \ - 'is not an integer or Nuclide'.format(right_nuclide) - raise ValueError(msg) - + check_type('right nuclide', right_nuclide, (Nuclide, _CrossNuclide)) self._right_nuclide = right_nuclide - @binary_op.setter def binary_op(self, binary_op): - - if not is_string(binary_op): - msg = 'Unable to set CrossNuclide binary op to {0} which ' \ - 'is not a string'.format(binary_op) - raise ValueError(msg) - + check_type('binary op', binary_op, str) self._binary_op = binary_op - def __repr__(self): string = '' @@ -2622,94 +2526,63 @@ class _CrossFilter(object): if left_filter is not None: self.left_filter = left_filter - if right_filter is not None: self.right_filter = right_filter - if binary_op is not None: self.binary_op = binary_op - @property def left_filter(self): return self._left_filter - @property def right_filter(self): return self._right_filter - @property def binary_op(self): return self._binary_op - @property def type(self): return (self.right_filter.type, self.left_filter.type) - @property def bins(self): return (self.right_filter.bins, self.left_filter.bins) - @property def num_bins(self): return self.left_filter.num_bins * self.right_filter.num_bins - @property def stride(self): return self.left_filter.stride * self.right_filter.stride - @left_filter.setter def left_filter(self, left_filter): - - if not isinstance(left_filter, (Filter, _CrossFilter)): - msg = 'Unable to set CrossFilter left filter to {0} which ' \ - 'is not a Filter'.format(left_filter) - raise ValueError(msg) - + check_type('left filter', left_filter, (Filter, _CrossFilter)) self._left_filter = left_filter - @right_filter.setter def right_filter(self, right_filter): - - if not isinstance(right_filter, (Filter, _CrossFilter)): - msg = 'Unable to set CrossFilter right filter to {0} which ' \ - 'is not a Filter'.format(right_filter) - raise ValueError(msg) - + check_type('right filter', right_filter, (Filter, _CrossFilter)) self._right_filter = right_filter - @binary_op.setter def binary_op(self, binary_op): - - if not is_string(binary_op): - msg = 'Unable to set CrossFilter binary op to {0} which ' \ - 'is not a string'.format(binary_op) - raise ValueError(msg) - + check_type('binary op', binary_op, str) self._binary_op = binary_op - def __repr__(self): string = '_CrossFilter\n' - filter_type = '({0} {1} {2})'.format(self.left_filter.type, self.binary_op, self.right_filter.type) - filter_bins = '({0} {1} {2})'.format(self.left_filter.bins, self.binary_op, self.right_filter.bins) - string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', filter_type) string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', filter_bins) return string @@ -2733,4 +2606,4 @@ class _CrossFilter(object): else: split_filters.extend(self.right_filter.split_filters()) - return split_filters + return split_filters \ No newline at end of file From c36ea824ace917d90e8d8fdbe9b33c32654cf8de Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 26 Jul 2015 23:14:17 -0700 Subject: [PATCH 17/39] Fixed Tally.add_score to accept _CrossScore types --- openmc/tallies.py | 76 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 2b58a1c2a..b1a391c0c 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1180,8 +1180,8 @@ class Tally(object): """ - if not isinstance(score, basestring): - msg = 'Unable to add score "{0}" to Tally ID={1} since it is ' \ + if not isinstance(score, (basestring, _CrossScore)): + msg = 'Unable to add score "{0}" to Tally ID={1} since it is ' \ 'not a string'.format(score, self.id) raise ValueError(msg) @@ -2404,6 +2404,30 @@ class TalliesFile(object): class _CrossScore(object): + """A special-purpose tally score used to encapsulate all combinations of two + tally's scores as a cross product for tally arithmetic. + + Parameters + ---------- + left_score : str or _CrossScore + The left score in the cross product. + right_score : str or _CrossScore + The right score in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's scores with this _CrossNuclide. + + Attributes + ---------- + left_score : str or _CrossScore + The left score in the cross product. + right_score : str or _CrossScore + The right score in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's scores with this _CrossNuclide. + + """ def __init__(self, left_score=None, right_score=None, binary_op=None): @@ -2452,6 +2476,30 @@ class _CrossScore(object): class _CrossNuclide(object): + """A special-purpose nuclide used to encapsulate all combinations of two + tally's nuclides as a cross product for tally arithmetic. + + Parameters + ---------- + left_nuclide : Nuclide or _CrossNuclide + The left nuclide in the cross product. + right_nuclide : Nuclide or _CrossNuclide + The right nuclide in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's nuclides with this _CrossNuclide. + + Attributes + ---------- + left_nuclide : Nuclide or _CrossNuclide + The left nuclide in the cross product. + right_nuclide : Nuclide or _CrossNuclide + The right nuclide in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's nuclides with this _CrossNuclide. + + """ def __init__(self, left_nuclide=None, right_nuclide=None, binary_op=None): @@ -2517,6 +2565,30 @@ class _CrossNuclide(object): class _CrossFilter(object): + """A special-purpose filter used to encapsulate all combinations of two + tally's filter bins as a cross product for tally arithmetic. + + Parameters + ---------- + left_filter : Filter or _CrossFilter + The left filter in the cross product. + right_filter : Filter or _CrossFilter + The right filter in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's filter bins with this _CrossFilter. + + Attributes + ---------- + left_filter : Filter or _CrossFilter + The left filter in the cross product. + right_filter : Filter or _CrossFilter + The right filter in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's filter bins with this _CrossFilter. + + """ def __init__(self, left_filter=None, right_filter=None, binary_op=None): From 65383dbce45dc6f34814e5b4d26efdcb6c13e47a Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 26 Jul 2015 23:17:59 -0700 Subject: [PATCH 18/39] Abstracted cross product datatypes into new cross.py --- openmc/cross.py | 280 +++++++++++++++++++++++++++++++++++++++++++++ openmc/tallies.py | 283 +--------------------------------------------- 2 files changed, 283 insertions(+), 280 deletions(-) create mode 100644 openmc/cross.py diff --git a/openmc/cross.py b/openmc/cross.py new file mode 100644 index 000000000..60a524b51 --- /dev/null +++ b/openmc/cross.py @@ -0,0 +1,280 @@ +from openmc import Filter, Nuclide +from openmc.checkvalue import check_type + + +class _CrossScore(object): + """A special-purpose tally score used to encapsulate all combinations of two + tally's scores as a cross product for tally arithmetic. + + Parameters + ---------- + left_score : str or _CrossScore + The left score in the cross product. + right_score : str or _CrossScore + The right score in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's scores with this _CrossNuclide. + + Attributes + ---------- + left_score : str or _CrossScore + The left score in the cross product. + right_score : str or _CrossScore + The right score in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's scores with this _CrossNuclide. + + """ + + def __init__(self, left_score=None, right_score=None, binary_op=None): + + self._left_score = None + self._right_score = None + self._binary_op = None + + if left_score is not None: + self.left_score = left_score + if right_score is not None: + self.right_score = right_score + if binary_op is not None: + self.binary_op = binary_op + + @property + def left_score(self): + return self._left_score + + @property + def right_score(self): + return self._right_score + + @property + def binary_op(self): + return self._binary_op + + @left_score.setter + def left_score(self, left_score): + check_type('left score', left_score, (str, _CrossScore)) + self._left_score = left_score + + @right_score.setter + def right_score(self, right_score): + check_type('right score', right_score, (str, _CrossScore)) + self._right_score = right_score + + @binary_op.setter + def binary_op(self, binary_op): + check_type('binary op', binary_op, str) + self._binary_op = binary_op + + def __repr__(self): + string = '({0} {1} {2})'.format(self.left_score, + self.binary_op, self.right_score) + return string + + +class _CrossNuclide(object): + """A special-purpose nuclide used to encapsulate all combinations of two + tally's nuclides as a cross product for tally arithmetic. + + Parameters + ---------- + left_nuclide : Nuclide or _CrossNuclide + The left nuclide in the cross product. + right_nuclide : Nuclide or _CrossNuclide + The right nuclide in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's nuclides with this _CrossNuclide. + + Attributes + ---------- + left_nuclide : Nuclide or _CrossNuclide + The left nuclide in the cross product. + right_nuclide : Nuclide or _CrossNuclide + The right nuclide in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's nuclides with this _CrossNuclide. + + """ + + def __init__(self, left_nuclide=None, right_nuclide=None, binary_op=None): + + self._left_nuclide = None + self._right_nuclide = None + self._binary_op = None + + if left_nuclide is not None: + self.left_nuclide = left_nuclide + if right_nuclide is not None: + self.right_nuclide = right_nuclide + if binary_op is not None: + self.binary_op = binary_op + + @property + def left_nuclide(self): + return self._left_nuclide + + @property + def right_nuclide(self): + return self._right_nuclide + + @property + def binary_op(self): + return self._binary_op + + @left_nuclide.setter + def left_nuclide(self, left_nuclide): + check_type('left nuclide', left_nuclide, (Nuclide, _CrossNuclide)) + self._left_nuclide = left_nuclide + + @right_nuclide.setter + def right_nuclide(self, right_nuclide): + check_type('right nuclide', right_nuclide, (Nuclide, _CrossNuclide)) + self._right_nuclide = right_nuclide + + @binary_op.setter + def binary_op(self, binary_op): + check_type('binary op', binary_op, str) + self._binary_op = binary_op + + def __repr__(self): + + string = '' + + # If the Summary was linked, the left nuclide is a Nuclide object + if isinstance(self.left_nuclide, Nuclide): + string += '(' + self.left_nuclide.name + # If the Summary was not linked, the left nuclide is the ZAID + else: + string += '(' + str(self.left_nuclide) + + string += ' ' + self.binary_op + ' ' + + # If the Summary was linked, the right nuclide is a Nuclide object + if isinstance(self.right_nuclide, Nuclide): + string += self.right_nuclide.name + ')' + # If the Summary was not linked, the right nuclide is the ZAID + else: + string += str(self.right_nuclide) + ')' + + return string + + +class _CrossFilter(object): + """A special-purpose filter used to encapsulate all combinations of two + tally's filter bins as a cross product for tally arithmetic. + + Parameters + ---------- + left_filter : Filter or _CrossFilter + The left filter in the cross product. + right_filter : Filter or _CrossFilter + The right filter in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's filter bins with this _CrossFilter. + + Attributes + ---------- + left_filter : Filter or _CrossFilter + The left filter in the cross product. + right_filter : Filter or _CrossFilter + The right filter in the cross product. + binary_op : str + The tally arithmetic binary operator (e.g., '+', '-', etc.) used to + combine two tally's filter bins with this _CrossFilter. + + """ + + def __init__(self, left_filter=None, right_filter=None, binary_op=None): + + self._left_filter = None + self._right_filter = None + self._binary_op = None + + if left_filter is not None: + self.left_filter = left_filter + if right_filter is not None: + self.right_filter = right_filter + if binary_op is not None: + self.binary_op = binary_op + + @property + def left_filter(self): + return self._left_filter + + @property + def right_filter(self): + return self._right_filter + + @property + def binary_op(self): + return self._binary_op + + @property + def type(self): + return (self.right_filter.type, self.left_filter.type) + + @property + def bins(self): + return (self.right_filter.bins, self.left_filter.bins) + + @property + def num_bins(self): + return self.left_filter.num_bins * self.right_filter.num_bins + + @property + def stride(self): + return self.left_filter.stride * self.right_filter.stride + + @left_filter.setter + def left_filter(self, left_filter): + check_type('left filter', left_filter, (Filter, _CrossFilter)) + self._left_filter = left_filter + + @right_filter.setter + def right_filter(self, right_filter): + check_type('right filter', right_filter, (Filter, _CrossFilter)) + self._right_filter = right_filter + + @binary_op.setter + def binary_op(self, binary_op): + check_type('binary op', binary_op, str) + self._binary_op = binary_op + + def __repr__(self): + + string = '_CrossFilter\n' + filter_type = '({0} {1} {2})'.format(self.left_filter.type, + self.binary_op, + self.right_filter.type) + filter_bins = '({0} {1} {2})'.format(self.left_filter.bins, + self.binary_op, + self.right_filter.bins) + string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', filter_type) + string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', filter_bins) + return string + + + def split_filters(self): + + split_filters = [] + + # If left Filter is not a CrossFilter, simply append to list + if isinstance(self.left_filter, Filter): + split_filters.append(self.left_filter) + # Recursively descend CrossFilter tree to collect all Filters + else: + split_filters.extend(self.left_filter.split_filters()) + + # If right Filter is not a CrossFilter, simply append to list + if isinstance(self.right_filter, Filter): + split_filters.append(self.right_filter) + # Recursively descend CrossFilter tree to collect all Filters + else: + split_filters.extend(self.right_filter.split_filters()) + + return split_filters \ No newline at end of file diff --git a/openmc/tallies.py b/openmc/tallies.py index b1a391c0c..0faa9d59e 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -10,6 +10,7 @@ import sys import numpy as np from openmc import Mesh, Filter, Trigger, Nuclide +from openmc.cross import _CrossScore, _CrossNuclide, _CrossFilter from openmc.summary import Summary from openmc.checkvalue import check_type, check_value, check_greater_than from openmc.clean_xml import * @@ -1181,7 +1182,7 @@ class Tally(object): """ if not isinstance(score, (basestring, _CrossScore)): - msg = 'Unable to add score "{0}" to Tally ID={1} since it is ' \ + msg = 'Unable to add score "{0}" to Tally ID={1} since it is ' \ 'not a string'.format(score, self.id) raise ValueError(msg) @@ -2400,282 +2401,4 @@ class TalliesFile(object): # Write the XML Tree to the tallies.xml file tree = ET.ElementTree(self._tallies_file) tree.write("tallies.xml", xml_declaration=True, - encoding='utf-8', method="xml") - - -class _CrossScore(object): - """A special-purpose tally score used to encapsulate all combinations of two - tally's scores as a cross product for tally arithmetic. - - Parameters - ---------- - left_score : str or _CrossScore - The left score in the cross product. - right_score : str or _CrossScore - The right score in the cross product. - binary_op : str - The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's scores with this _CrossNuclide. - - Attributes - ---------- - left_score : str or _CrossScore - The left score in the cross product. - right_score : str or _CrossScore - The right score in the cross product. - binary_op : str - The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's scores with this _CrossNuclide. - - """ - - def __init__(self, left_score=None, right_score=None, binary_op=None): - - self._left_score = None - self._right_score = None - self._binary_op = None - - if left_score is not None: - self.left_score = left_score - if right_score is not None: - self.right_score = right_score - if binary_op is not None: - self.binary_op = binary_op - - @property - def left_score(self): - return self._left_score - - @property - def right_score(self): - return self._right_score - - @property - def binary_op(self): - return self._binary_op - - @left_score.setter - def left_score(self, left_score): - check_type('left score', left_score, (str, _CrossScore)) - self._left_score = left_score - - @right_score.setter - def right_score(self, right_score): - check_type('right score', right_score, (str, _CrossScore)) - self._right_score = right_score - - @binary_op.setter - def binary_op(self, binary_op): - check_type('binary op', binary_op, str) - self._binary_op = binary_op - - def __repr__(self): - string = '({0} {1} {2})'.format(self.left_score, - self.binary_op, self.right_score) - return string - - -class _CrossNuclide(object): - """A special-purpose nuclide used to encapsulate all combinations of two - tally's nuclides as a cross product for tally arithmetic. - - Parameters - ---------- - left_nuclide : Nuclide or _CrossNuclide - The left nuclide in the cross product. - right_nuclide : Nuclide or _CrossNuclide - The right nuclide in the cross product. - binary_op : str - The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's nuclides with this _CrossNuclide. - - Attributes - ---------- - left_nuclide : Nuclide or _CrossNuclide - The left nuclide in the cross product. - right_nuclide : Nuclide or _CrossNuclide - The right nuclide in the cross product. - binary_op : str - The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's nuclides with this _CrossNuclide. - - """ - - def __init__(self, left_nuclide=None, right_nuclide=None, binary_op=None): - - self._left_nuclide = None - self._right_nuclide = None - self._binary_op = None - - if left_nuclide is not None: - self.left_nuclide = left_nuclide - if right_nuclide is not None: - self.right_nuclide = right_nuclide - if binary_op is not None: - self.binary_op = binary_op - - @property - def left_nuclide(self): - return self._left_nuclide - - @property - def right_nuclide(self): - return self._right_nuclide - - @property - def binary_op(self): - return self._binary_op - - @left_nuclide.setter - def left_nuclide(self, left_nuclide): - check_type('left nuclide', left_nuclide, (Nuclide, _CrossNuclide)) - self._left_nuclide = left_nuclide - - @right_nuclide.setter - def right_nuclide(self, right_nuclide): - check_type('right nuclide', right_nuclide, (Nuclide, _CrossNuclide)) - self._right_nuclide = right_nuclide - - @binary_op.setter - def binary_op(self, binary_op): - check_type('binary op', binary_op, str) - self._binary_op = binary_op - - def __repr__(self): - - string = '' - - # If the Summary was linked, the left nuclide is a Nuclide object - if isinstance(self.left_nuclide, Nuclide): - string += '(' + self.left_nuclide.name - # If the Summary was not linked, the left nuclide is the ZAID - else: - string += '(' + str(self.left_nuclide) - - string += ' ' + self.binary_op + ' ' - - # If the Summary was linked, the right nuclide is a Nuclide object - if isinstance(self.right_nuclide, Nuclide): - string += self.right_nuclide.name + ')' - # If the Summary was not linked, the right nuclide is the ZAID - else: - string += str(self.right_nuclide) + ')' - - return string - - -class _CrossFilter(object): - """A special-purpose filter used to encapsulate all combinations of two - tally's filter bins as a cross product for tally arithmetic. - - Parameters - ---------- - left_filter : Filter or _CrossFilter - The left filter in the cross product. - right_filter : Filter or _CrossFilter - The right filter in the cross product. - binary_op : str - The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's filter bins with this _CrossFilter. - - Attributes - ---------- - left_filter : Filter or _CrossFilter - The left filter in the cross product. - right_filter : Filter or _CrossFilter - The right filter in the cross product. - binary_op : str - The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's filter bins with this _CrossFilter. - - """ - - def __init__(self, left_filter=None, right_filter=None, binary_op=None): - - self._left_filter = None - self._right_filter = None - self._binary_op = None - - if left_filter is not None: - self.left_filter = left_filter - if right_filter is not None: - self.right_filter = right_filter - if binary_op is not None: - self.binary_op = binary_op - - @property - def left_filter(self): - return self._left_filter - - @property - def right_filter(self): - return self._right_filter - - @property - def binary_op(self): - return self._binary_op - - @property - def type(self): - return (self.right_filter.type, self.left_filter.type) - - @property - def bins(self): - return (self.right_filter.bins, self.left_filter.bins) - - @property - def num_bins(self): - return self.left_filter.num_bins * self.right_filter.num_bins - - @property - def stride(self): - return self.left_filter.stride * self.right_filter.stride - - @left_filter.setter - def left_filter(self, left_filter): - check_type('left filter', left_filter, (Filter, _CrossFilter)) - self._left_filter = left_filter - - @right_filter.setter - def right_filter(self, right_filter): - check_type('right filter', right_filter, (Filter, _CrossFilter)) - self._right_filter = right_filter - - @binary_op.setter - def binary_op(self, binary_op): - check_type('binary op', binary_op, str) - self._binary_op = binary_op - - def __repr__(self): - - string = '_CrossFilter\n' - filter_type = '({0} {1} {2})'.format(self.left_filter.type, - self.binary_op, - self.right_filter.type) - filter_bins = '({0} {1} {2})'.format(self.left_filter.bins, - self.binary_op, - self.right_filter.bins) - string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', filter_type) - string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', filter_bins) - return string - - - def split_filters(self): - - split_filters = [] - - # If left Filter is not a CrossFilter, simply append to list - if isinstance(self.left_filter, Filter): - split_filters.append(self.left_filter) - # Recursively descend CrossFilter tree to collect all Filters - else: - split_filters.extend(self.left_filter.split_filters()) - - # If right Filter is not a CrossFilter, simply append to list - if isinstance(self.right_filter, Filter): - split_filters.append(self.right_filter) - # Recursively descend CrossFilter tree to collect all Filters - else: - split_filters.extend(self.right_filter.split_filters()) - - return split_filters \ No newline at end of file + encoding='utf-8', method="xml") \ No newline at end of file From 8ecc3c115c0a71b9cfd7eb783dc18e367e6b0bea Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Wed, 29 Jul 2015 00:06:07 -0700 Subject: [PATCH 19/39] Continued cleanup and modularization of tally arithmetic routines --- openmc/tallies.py | 1608 ++++++++++++++++++++++----------------------- 1 file changed, 785 insertions(+), 823 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 0faa9d59e..66361a3e9 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -102,821 +102,6 @@ class Tally(object): self._std_dev = None self._with_batch_statistics = False - - def _align_tally_data(self, other): - - self_mean = copy.deepcopy(self.mean) - self_std_dev = copy.deepcopy(self.std_dev) - other_mean = copy.deepcopy(other.mean) - other_std_dev = copy.deepcopy(other.std_dev) - - if self.filters != other.filters: - - # Determine the number of paired combinations of filter bins - # between the two tallies and repeat arrays along filter axes - self_num_filter_bins = self.mean.shape[0] - other_num_filter_bins = other.mean.shape[0] - num_filter_bins = self_num_filter_bins * other_num_filter_bins - self_repeat_factor = num_filter_bins / self_num_filter_bins - other_tile_factor = num_filter_bins / other_num_filter_bins - - # Replicate the data - self_mean = np.repeat(self_mean, self_repeat_factor, axis=0) - other_mean = np.tile(other_mean, (other_tile_factor, 1, 1)) - self_std_dev = np.repeat(self_std_dev, self_repeat_factor, axis=0) - other_std_dev = np.tile(other_std_dev, (other_tile_factor, 1, 1)) - - if self.nuclides != other.nuclides: - - # Determine the number of paired combinations of nuclides - # between the two tallies and repeat arrays along nuclide axes - self_num_nuclide_bins = self.mean.shape[1] - other_num_nuclide_bins = other.mean.shape[1] - num_nuclide_bins = self_num_nuclide_bins * other_num_nuclide_bins - self_repeat_factor = num_nuclide_bins / self_num_nuclide_bins - other_tile_factor = num_nuclide_bins / other_num_nuclide_bins - - # Replicate the data - self_mean = np.repeat(self_mean, self_repeat_factor, axis=1) - other_mean = np.tile(other_mean, (1, other_tile_factor, 1)) - self_std_dev = np.repeat(self_std_dev, self_repeat_factor, axis=1) - other_std_dev = np.tile(other_std_dev, (1, other_tile_factor, 1)) - - if self.scores != other.scores: - - # Determine the number of paired combinations of score bins - # between the two tallies and repeat arrays along score axes - self_num_score_bins = self.mean.shape[2] - other_num_score_bins = other.mean.shape[2] - num_score_bins = self_num_score_bins * other_num_score_bins - self_repeat_factor = num_score_bins / self_num_score_bins - other_tile_factor = num_score_bins / other_num_score_bins - - # Replicate the data - self_mean = np.repeat(self_mean, self_repeat_factor, axis=2) - other_mean = np.tile(other_mean, (1, 1, other_tile_factor)) - self_std_dev = np.repeat(self_std_dev, self_repeat_factor, axis=2) - other_std_dev = np.tile(other_std_dev, (1, 1, other_tile_factor)) - - data = {} - data['self'] = {} - data['other'] = {} - data['self']['mean'] = self_mean - data['other']['mean'] = other_mean - data['self']['std. dev.'] = self_std_dev - data['other']['std. dev.'] = other_std_dev - return data - - - def __add__(self, other): - - # Check that results have been read - if self.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(self.id) - raise ValueError(msg) - - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - - if isinstance(other, Tally): - - # Check that results have been read - if other.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(other.id) - raise ValueError(msg) - - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters - # FIXME: Need to be able to use StatePoint.get_tally - # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize - # FIXME: Redundant filters - - data = self._align_tally_data(other) - - new_tally._mean = data['self']['mean'] + data['other']['mean'] - new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ - data['other']['std. dev.']**2) - - if self.estimator == other.estimator: - new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: - new_tally.num_realizations = self.num_realizations - - # If the two Tallies have same filters, replicate them in new Tally - if self.filters == other.filters: - for filter in self.filters: - new_tally.add_filter(filter) - - # Generate filter "cross product" - else: - for self_filter in self.filters: - for other_filter in other.filters: - new_filter = _CrossFilter(self_filter, other_filter, '+') - new_tally.add_filter(new_filter) - - # If the two Tallies have same nuclides, replicate them in new Tally - if self.nuclides == other.nuclides: - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - - # Generate nuclide "cross product" - else: - for self_nuclide in self.nuclides: - for other_nuclide in other.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '+') - new_tally.add_nuclide(new_nuclide) - - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == other.scores: - for score in self.scores: - new_tally.add_score(score) - - # Generate score "cross product" - else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '+') - new_tally.add_score(new_score) - - elif isinstance(other, Integral) or isinstance(other): - - new_tally._mean = self._mean + other - new_tally._std_dev = self._std_dev - new_tally.estimator = self.estimator - new_tally.with_summary = self.with_summary - new_tally.num_realization = self.num_realizations - new_tally.num_score_bins = self.num_score_bins - - for filter in self.filters: - new_tally.add_filter(filter) - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - for score in self.scores: - new_tally.add_score(score) - - else: - msg = 'Unable to add {0} to Tally ID={1}'.format(other, self.id) - raise ValueError(msg) - - return new_tally - - - def __radd__(self, other): - return self + other - - - def __sub__(self, other): - - # Check that results have been read - if self.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(self.id) - raise ValueError(msg) - - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - - if isinstance(other, Tally): - - # Check that results have been read - if other.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(other.id) - raise ValueError(msg) - - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters - # FIXME: Need to be able to use StatePoint.get_tally - # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize - - data = self._align_tally_data(other) - - new_tally._mean = data['self']['mean'] - data['other']['mean'] - new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ - data['other']['std. dev.']**2) - - if self.estimator == other.estimator: - new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: - new_tally.num_realizations = self.num_realizations - - # If the two Tallies have same filters, replicate them in new Tally - if self.filters == other.filters: - for filter in self.filters: - new_tally.add_filter(filter) - - # Generate filter "cross product" - else: - for self_filter in self.filters: - for other_filter in other.filters: - new_filter = _CrossFilter(self_filter, other_filter, '-') - new_tally.add_filter(new_filter) - - # If the two Tallies have same nuclides, replicate them in new Tally - if self.nuclides == other.nuclides: - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - - # Generate nuclide "cross product" - else: - for self_nuclide in self.nuclides: - for other_nuclide in other.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '-') - new_tally.add_nuclide(new_nuclide) - - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == other.scores: - for score in self.scores: - new_tally.add_score(score) - - # Generate score "cross product" - else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '-') - new_tally.add_score(new_score) - - elif isinstance(other, (Integral, Rational)): - - new_tally._mean = self._mean - other - new_tally._std_dev = self._std_dev - new_tally.estimator = self.estimator - new_tally.with_summary = self.with_summary - new_tally.num_realization = self.num_realizations - new_tally.num_score_bins = self.num_score_bins - - for filter in self.filters: - new_tally.add_filter(filter) - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - for score in self.scores: - new_tally.add_score(score) - - else: - msg = 'Unable to subtract {0} from Tally ' \ - 'ID={1}'.format(other, self.id) - raise ValueError(msg) - - return new_tally - - - def __rsub__(self, other): - return -1. * self + other - - - def __mul__(self, other): - - # Check that results have been read - if self.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(self.id) - raise ValueError(msg) - - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - - if isinstance(other, Tally): - - # Check that results have been read - if other.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(other.id) - raise ValueError(msg) - - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters - # FIXME: Need to be able to use StatePoint.get_tally - # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize - - data = self._align_tally_data(other) - - self_rel_err = data['self']['std. dev.'] / data['self']['mean'] - other_rel_err = data['other']['std. dev.'] / data['other']['mean'] - new_tally._mean = data['self']['mean'] * data['other']['mean'] - new_tally._std_dev = np.abs(new_tally.mean) * \ - np.sqrt(self_rel_err**2 + other_rel_err**2) - - if self.estimator == other.estimator: - new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: - new_tally.num_realizations = self.num_realizations - - # If the two Tallies have same filters, replicate them in new Tally - if self.filters == other.filters: - for filter in self.filters: - new_tally.add_filter(filter) - - # Generate filter "cross product" - else: - for self_filter in self.filters: - for other_filter in other.filters: - new_filter = _CrossFilter(self_filter, other_filter, '*') - new_tally.add_filter(new_filter) - - # If the two Tallies have same nuclides, replicate them in new Tally - if self.nuclides == other.nuclides: - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - - # Generate nuclide "cross product" - else: - for self_nuclide in self.nuclides: - for other_nuclide in other.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '*') - new_tally.add_nuclide(new_nuclide) - - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == other.scores: - for score in self.scores: - new_tally.add_score(score) - - # Generate score "cross product" - else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '*') - new_tally.add_score(new_score) - - elif isinstance(other, (Integral, Rational)): - - new_tally._mean = self._mean * other - new_tally._std_dev = self._std_dev * np.abs(other) - new_tally.estimator = self.estimator - new_tally.with_summary = self.with_summary - new_tally.num_realization = self.num_realizations - new_tally.num_score_bins = self.num_score_bins - - for filter in self.filters: - new_tally.add_filter(filter) - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - for score in self.scores: - new_tally.add_score(score) - - else: - msg = 'Unable to multiply Tally ID={1} ' \ - 'by {0}'.format(self.id, other) - raise ValueError(msg) - - return new_tally - - - def __rmul__(self, other): - return self * other - - - def __div__(self, other): - - # Check that results have been read - if self.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(self.id) - raise ValueError(msg) - - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - - if isinstance(other, Tally): - - # Check that results have been read - if other.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(other.id) - raise ValueError(msg) - - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters - # FIXME: Need to be able to use StatePoint.get_tally - # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize - - data = self._align_tally_data(other) - - self_rel_err = data['self']['std. dev.'] / data['self']['mean'] - other_rel_err = data['other']['std. dev.'] / data['other']['mean'] - new_tally._mean = data['self']['mean'] / data['other']['mean'] - new_tally._std_dev = np.abs(new_tally.mean) * \ - np.sqrt(self_rel_err**2 + other_rel_err**2) - - if self.estimator == other.estimator: - new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: - new_tally.num_realizations = self.num_realizations - - # If the two Tallies have same filters, replicate them in new Tally - if self.filters == other.filters: - for filter in self.filters: - new_tally.add_filter(filter) - - # Generate filter "cross product" - else: - for self_filter in self.filters: - for other_filter in other.filters: - new_filter = _CrossFilter(self_filter, other_filter, '/') - new_tally.add_filter(new_filter) - - # If the two Tallies have same nuclides, replicate them in new Tally - if self.nuclides == other.nuclides: - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - - # Generate nuclide "cross product" - else: - for self_nuclide in self.nuclides: - for other_nuclide in other.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '/') - new_tally.add_nuclide(new_nuclide) - - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == other.scores: - for score in self.scores: - new_tally.add_score(score) - - # Generate score "cross product" - else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '/') - new_tally.add_score(new_score) - - elif isinstance(other, (Integral, Rational)): - - new_tally._mean = self._mean / other - new_tally._std_dev = self._std_dev * np.abs(1. / other) - new_tally.estimator = self.estimator - new_tally.with_summary = self.with_summary - new_tally.num_realization = self.num_realizations - new_tally.num_score_bins = self.num_score_bins - - for filter in self.filters: - new_tally.add_filter(filter) - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - for score in self.scores: - new_tally.add_score(score) - - else: - msg = 'Unable to divide Tally ID={0} ' \ - 'by {1}'.format(self.id, other) - raise ValueError(msg) - - return new_tally - - - def __rdiv__(self, other): - return self * (1. / other) - - - def __pow__(self, power): - - # Check that results have been read - if self.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(self.id) - raise ValueError(msg) - - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - - if isinstance(power, Tally): - - # Check that results have been read - if power.mean is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(power.id) - raise ValueError(msg) - - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters - # FIXME: Need to be able to use StatePoint.get_tally - # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize - - data = self._align_tally_data(power) - - mean_ratio = data['other']['mean'] / data['self']['mean'] - first_term = mean_ratio * data['self']['std. dev.'] - second_term = np.log(data['self']['mean']) * data['other']['std. dev.'] - new_tally._mean = data['self']['mean'] ** data['other']['mean'] - new_tally._std_dev = np.abs(new_tally.mean) * \ - np.sqrt(first_term**2 + second_term**2) - - if self.estimator == power.estimator: - new_tally.estimator = self.estimator - if self.with_summary and power.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == power.num_realizations: - new_tally.num_realizations = self.num_realizations - - # If the two Tallies have same filters, replicate them in new Tally - if self.filters == power.filters: - for filter in self.filters: - new_tally.add_filter(filter) - - # Generate filter "cross product" - else: - for self_filter in self.filters: - for power_filter in power.filters: - new_filter = _CrossFilter(self_filter, power_filter, '^') - new_tally.add_filter(new_filter) - - # If the two Tallies have same nuclides, replicate them in new Tally - if self.nuclides == power.nuclides: - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - - # Generate nuclide "cross product" - else: - for self_nuclide in self.nuclides: - for power_nuclide in power.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, power_nuclide, '^') - new_tally.add_nuclide(new_nuclide) - - # If the two Tallies have same scores, replicate them in new Tally - if self.scores == power.scores: - for score in self.scores: - new_tally.add_score(score) - - # Generate score "cross product" - else: - for self_score in self.scores: - for power_score in power.scores: - new_score = _CrossScore(self_score, power_score, '^') - new_tally.add_score(new_score) - - elif isinstance(power, (Integral, Rational)): - - new_tally._mean = self._mean ** power - self_rel_err = self.std_dev / self.mean - new_tally._std_dev = np.abs(new_tally._mean * power * self_rel_err) - new_tally.estimator = self.estimator - new_tally.with_summary = self.with_summary - new_tally.num_realization = self.num_realizations - new_tally.num_score_bins = self.num_score_bins - - for filter in self.filters: - new_tally.add_filter(filter) - for nuclide in self.nuclides: - new_tally.add_nuclide(nuclide) - for score in self.scores: - new_tally.add_score(score) - - else: - msg = 'Unable to raise Tally ID={0} to ' \ - 'power {1}'.format(self.id, power) - raise ValueError(msg) - - return new_tally - - - def __pos__(self): - new_tally = copy.deepcopy(self) - new_tally._mean = np.abs(new_tally.mean) - return new_tally - - def __neg__(self): - new_tally = self * -1 - return new_tally - - def minimum(self, scores=[], filters=[], filter_bins=[], - nuclides=[], value='mean'): - """Returns the minimum of a slice of the Tally's data. - - Parameters - ---------- - scores : list - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) - - filters : list - A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) - - filter_bins : list - A list of the filter bins corresponding to the filter_types - parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in - the list is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. Each bin is an integer for the - cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of - floats for 'energy' and 'energyout' filters corresponding to the - energy boundaries of the bin of interest. The bin is a (x,y,z) - 3-tuple for 'mesh' filters corresponding to the mesh cell of - interest. The order of the bins in the list must correspond of the - filter_types parameter. - - nuclides : list - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) - - value : str - A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted - - Returns - ------- - float - A scalar float of the minimum value in the Tally data indexed by - each filter, nuclide and score as listed in the parameters. - """ - - data = self.get_values(scores, filters, filter_bins, nuclides, value) - return np.min(data) - - def maximum(self, scores=[], filters=[], filter_bins=[], - nuclides=[], value='mean'): - """Returns the maximum of a slice of the Tally's data. - - Parameters - ---------- - scores : list - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) - - filters : list - A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) - - filter_bins : list - A list of the filter bins corresponding to the filter_types - parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in - the list is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. Each bin is an integer for the - cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of - floats for 'energy' and 'energyout' filters corresponding to the - energy boundaries of the bin of interest. The bin is a (x,y,z) - 3-tuple for 'mesh' filters corresponding to the mesh cell of - interest. The order of the bins in the list must correspond of the - filter_types parameter. - - nuclides : list - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) - - value : str - A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted - - Returns - ------- - float - A scalar float of the maximum value in the Tally data indexed by - each filter, nuclide and score as listed in the parameters. - """ - - data = self.get_values(scores, filters, filter_bins, nuclides, value) - return np.max(data) - - def summation(self, scores=[], filters=[], filter_bins=[], - nuclides=[], value='mean'): - """Returns the sum of a slice of the Tally's data. - - Parameters - ---------- - scores : list - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) - - filters : list - A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) - - filter_bins : list - A list of the filter bins corresponding to the filter_types - parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in - the list is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. Each bin is an integer for the - cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of - floats for 'energy' and 'energyout' filters corresponding to the - energy boundaries of the bin of interest. The bin is a (x,y,z) - 3-tuple for 'mesh' filters corresponding to the mesh cell of - interest. The order of the bins in the list must correspond of the - filter_types parameter. - - nuclides : list - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) - - value : str - A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted - - Returns - ------- - float - A scalar float of the sum of the Tally data indexed by - each filter, nuclide and score as listed in the parameters. - """ - - data = self.get_values(scores, filters, filter_bins, nuclides, value) - return np.sum(data) - - def slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]): - """ - """ - - # Ensure that StatePoint.read_results() was called first - if (self.mean is None) or (self.std_dev is None): - msg = 'The Tally ID={0} has no data to slice. Call the ' \ - 'StatePoint.read_results() routine before using ' \ - 'Tally.slice(...)'.format(self.id) - raise ValueError(msg) - - # Compute batch statistics if not yet computed - if not self.with_batch_statistics: - self.compute_std_dev() - - new_tally = copy.deepcopy(self) - new_sum = self.get_values(scores, filters, filter_bins, - nuclides, 'sum') - new_sum_sq = self.get_values(scores, filters, filter_bins, - nuclides, 'sum_sq') - new_tally.sum = new_sum - new_tally.sum_sq = new_sum_sq - - ############################ FILTERS ######################### - # Determine the score indices from any of the requested scores - if filters: - - # Initialize list of indices to Filters to exclude from slice - filter_indices = [] - - # Loop over all of the Tally's Filters - for i, filter in enumerate(self.filters): - - # FIXME: sum over unused filter bins - # FIXME: neglect bins not selected for selected filters - # NOTE: We must store filter indices here since they are strings - - if filter.type not in filters: - filter_index = self.get_filter_index(filters[i], filter_bins[i]) - filter_indices.append(filter_index) - - # Loop over indices in reverse to remove excluded Filters - for filter_index in filter_indices[::-1]: - new_tally.remove_filter(self.filters[filter_index]) - - for i, filter_type in enumerate(filters): - - if 'energy' in filter_type: - # Create a list of the first energy edge - bins = [filter_bin[0] for filter_bin in filter_bins] - - - - ############################ NUCLIDES ######################## - # Determine the score indices from any of the requested scores - if nuclides: - - # Initialize list of indices to Nuclides to exclude from slice - nuclide_indices = [] - - for nuclide in self.nuclides: - - if isinstance(nuclide, Nuclide): - if nuclide.name not in nuclides: - nuclide_index = self.get_nuclide_index(nuclide) - nuclide_indices.append(nuclide_index) - else: - if nuclide not in nuclides: - nuclide_index = self.get_nuclide_index(nuclide) - nuclide_indices.append(nuclide_index) - - # Loop over indices in reverse to remove excluded Nuclides - for nuclide_index in nuclide_indices[::-1]: - new_tally.remove_nuclide(self.nuclides[nuclide_index]) - - ############################# SCORES ######################### - # Determine the score indices from any of the requested scores - if scores: - - # Initialize list of indices to Nuclides to exclude from slice - score_indices = [] - - for score in self.scores: - - if score not in scores: - score_index = self.get_score_index(score) - score_indices.append(score_index) - - # Loop over indices in reverse to remove excluded scores - for score_index in score_indices[::-1]: - new_tally.remove_score(self.scores[score_index]) - new_tally.num_score_bins -= 1 - - - - return new_tally - - - def __deepcopy__(self, memo): existing = memo.get(id(self)) @@ -1006,14 +191,6 @@ class Tally(object): return hash(tuple(hashable)) - def __add__(self, other): - # FIXME: Error checking: must check that results has been - # set and that # bins is the same - - new_tally = Tally() - new_tally._mean = self._mean + other._mean - new_tally._std_dev = np.sqrt(self.std_dev**2 + other.std_dev**2) - @property def id(self): return self._id @@ -2259,6 +1436,791 @@ class Tally(object): # Pickle the Tally results to a file pickle.dump(tally_results, open(filename, 'wb')) + def _align_tally_data(self, other): + + self_mean = copy.deepcopy(self.mean) + self_std_dev = copy.deepcopy(self.std_dev) + other_mean = copy.deepcopy(other.mean) + other_std_dev = copy.deepcopy(other.std_dev) + + if self.filters != other.filters: + + # Determine the number of paired combinations of filter bins + # between the two tallies and repeat arrays along filter axes + self_num_filter_bins = self.mean.shape[0] + other_num_filter_bins = other.mean.shape[0] + num_filter_bins = self_num_filter_bins * other_num_filter_bins + self_repeat_factor = num_filter_bins / self_num_filter_bins + other_tile_factor = num_filter_bins / other_num_filter_bins + + # Replicate the data + self_mean = np.repeat(self_mean, self_repeat_factor, axis=0) + other_mean = np.tile(other_mean, (other_tile_factor, 1, 1)) + self_std_dev = np.repeat(self_std_dev, self_repeat_factor, axis=0) + other_std_dev = np.tile(other_std_dev, (other_tile_factor, 1, 1)) + + if self.nuclides != other.nuclides: + + # Determine the number of paired combinations of nuclides + # between the two tallies and repeat arrays along nuclide axes + self_num_nuclide_bins = self.mean.shape[1] + other_num_nuclide_bins = other.mean.shape[1] + num_nuclide_bins = self_num_nuclide_bins * other_num_nuclide_bins + self_repeat_factor = num_nuclide_bins / self_num_nuclide_bins + other_tile_factor = num_nuclide_bins / other_num_nuclide_bins + + # Replicate the data + self_mean = np.repeat(self_mean, self_repeat_factor, axis=1) + other_mean = np.tile(other_mean, (1, other_tile_factor, 1)) + self_std_dev = np.repeat(self_std_dev, self_repeat_factor, axis=1) + other_std_dev = np.tile(other_std_dev, (1, other_tile_factor, 1)) + + if self.scores != other.scores: + + # Determine the number of paired combinations of score bins + # between the two tallies and repeat arrays along score axes + self_num_score_bins = self.mean.shape[2] + other_num_score_bins = other.mean.shape[2] + num_score_bins = self_num_score_bins * other_num_score_bins + self_repeat_factor = num_score_bins / self_num_score_bins + other_tile_factor = num_score_bins / other_num_score_bins + + # Replicate the data + self_mean = np.repeat(self_mean, self_repeat_factor, axis=2) + other_mean = np.tile(other_mean, (1, 1, other_tile_factor)) + self_std_dev = np.repeat(self_std_dev, self_repeat_factor, axis=2) + other_std_dev = np.tile(other_std_dev, (1, 1, other_tile_factor)) + + data = {} + data['self'] = {} + data['other'] = {} + data['self']['mean'] = self_mean + data['other']['mean'] = other_mean + data['self']['std. dev.'] = self_std_dev + data['other']['std. dev.'] = other_std_dev + return data + + def __add__(self, other): + + # Check that results have been read + if self.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True + + if isinstance(other, Tally): + + # Check that results have been read + if other.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(other.id) + raise ValueError(msg) + + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize + # FIXME: Redundant filters + + data = self._align_tally_data(other) + + new_tally._mean = data['self']['mean'] + data['other']['mean'] + new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ + data['other']['std. dev.']**2) + + if self.estimator == other.estimator: + new_tally.estimator = self.estimator + if self.with_summary and other.with_summary: + new_tally.with_summary = self.with_summary + if self.num_realizations == other.num_realizations: + new_tally.num_realizations = self.num_realizations + + # Generate filter "cross products" + if self.filters == other.filters: + for self_filter in self.filters: + new_filter = _CrossFilter(self_filter, self_filter, '+') + new_tally.add_filter(new_filter) + else: + for self_filter in self.filters: + for other_filter in other.filters: + new_filter = _CrossFilter(self_filter, other_filter, '+') + new_tally.add_filter(new_filter) + + # Generate nuclide "cross products" + if self.nuclides == other.nuclides: + for self_nuclide in self.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '+') + new_tally.add_nuclide(new_nuclide) + else: + for self_nuclide in self.nuclides: + for other_nuclide in other.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '+') + new_tally.add_nuclide(new_nuclide) + + # Generate score "cross products" + if self.scores == other.scores: + for self_score in self.scores: + new_score = _CrossScore(self_score, self_score, '+') + new_tally.add_score(new_score) + else: + for self_score in self.scores: + for other_score in other.scores: + new_score = _CrossScore(self_score, other_score, '+') + new_tally.add_score(new_score) + + elif isinstance(other, Integral) or isinstance(other): + + new_tally._mean = self._mean + other + new_tally._std_dev = self._std_dev + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for filter in self.filters: + new_tally.add_filter(filter) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to add {0} to Tally ID={1}'.format(other, self.id) + raise ValueError(msg) + + return new_tally + + def __sub__(self, other): + + # Check that results have been read + if self.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True + + if isinstance(other, Tally): + + # Check that results have been read + if other.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(other.id) + raise ValueError(msg) + + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize + + data = self._align_tally_data(other) + + new_tally._mean = data['self']['mean'] - data['other']['mean'] + new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ + data['other']['std. dev.']**2) + + if self.estimator == other.estimator: + new_tally.estimator = self.estimator + if self.with_summary and other.with_summary: + new_tally.with_summary = self.with_summary + if self.num_realizations == other.num_realizations: + new_tally.num_realizations = self.num_realizations + + # Generate filter "cross products" + if self.filters == other.filters: + for self_filter in self.filters: + new_filter = _CrossFilter(self_filter, self_filter, '-') + new_tally.add_filter(new_filter) + else: + for self_filter in self.filters: + for other_filter in other.filters: + new_filter = _CrossFilter(self_filter, other_filter, '-') + new_tally.add_filter(new_filter) + + # Generate nuclide "cross products" + if self.nuclides == other.nuclides: + for self_nuclide in self.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '-') + new_tally.add_nuclide(new_nuclide) + else: + for self_nuclide in self.nuclides: + for other_nuclide in other.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '-') + new_tally.add_nuclide(new_nuclide) + + # Generate score "cross products" + if self.scores == other.scores: + for self_score in self.scores: + new_score = _CrossScore(self_score, self_score, '-') + new_tally.add_score(new_score) + else: + for self_score in self.scores: + for other_score in other.scores: + new_score = _CrossScore(self_score, other_score, '-') + new_tally.add_score(new_score) + + elif isinstance(other, (Integral, Rational)): + + new_tally._mean = self._mean - other + new_tally._std_dev = self._std_dev + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for filter in self.filters: + new_tally.add_filter(filter) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to subtract {0} from Tally ' \ + 'ID={1}'.format(other, self.id) + raise ValueError(msg) + + return new_tally + + def __mul__(self, other): + + # Check that results have been read + if self.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True + + if isinstance(other, Tally): + + # Check that results have been read + if other.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(other.id) + raise ValueError(msg) + + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize + + data = self._align_tally_data(other) + + self_rel_err = data['self']['std. dev.'] / data['self']['mean'] + other_rel_err = data['other']['std. dev.'] / data['other']['mean'] + new_tally._mean = data['self']['mean'] * data['other']['mean'] + new_tally._std_dev = np.abs(new_tally.mean) * \ + np.sqrt(self_rel_err**2 + other_rel_err**2) + + if self.estimator == other.estimator: + new_tally.estimator = self.estimator + if self.with_summary and other.with_summary: + new_tally.with_summary = self.with_summary + if self.num_realizations == other.num_realizations: + new_tally.num_realizations = self.num_realizations + + # Generate filter "cross products" + if self.filters == other.filters: + for self_filter in self.filters: + new_filter = _CrossFilter(self_filter, self_filter, '*') + new_tally.add_filter(new_filter) + else: + for self_filter in self.filters: + for other_filter in other.filters: + new_filter = _CrossFilter(self_filter, other_filter, '*') + new_tally.add_filter(new_filter) + + # Generate nuclide "cross products" + if self.nuclides == other.nuclides: + for self_nuclide in self.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '*') + new_tally.add_nuclide(new_nuclide) + else: + for self_nuclide in self.nuclides: + for other_nuclide in other.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '*') + new_tally.add_nuclide(new_nuclide) + + # Generate score "cross products" + if self.scores == other.scores: + for self_score in self.scores: + new_score = _CrossScore(self_score, self_score, '*') + new_tally.add_score(new_score) + else: + for self_score in self.scores: + for other_score in other.scores: + new_score = _CrossScore(self_score, other_score, '*') + new_tally.add_score(new_score) + + elif isinstance(other, (Integral, Rational)): + + new_tally._mean = self._mean * other + new_tally._std_dev = self._std_dev * np.abs(other) + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for filter in self.filters: + new_tally.add_filter(filter) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to multiply Tally ID={1} ' \ + 'by {0}'.format(self.id, other) + raise ValueError(msg) + + return new_tally + + def __div__(self, other): + + # Check that results have been read + if self.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True + + if isinstance(other, Tally): + + # Check that results have been read + if other.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(other.id) + raise ValueError(msg) + + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize + + data = self._align_tally_data(other) + + self_rel_err = data['self']['std. dev.'] / data['self']['mean'] + other_rel_err = data['other']['std. dev.'] / data['other']['mean'] + new_tally._mean = data['self']['mean'] / data['other']['mean'] + new_tally._std_dev = np.abs(new_tally.mean) * \ + np.sqrt(self_rel_err**2 + other_rel_err**2) + + if self.estimator == other.estimator: + new_tally.estimator = self.estimator + if self.with_summary and other.with_summary: + new_tally.with_summary = self.with_summary + if self.num_realizations == other.num_realizations: + new_tally.num_realizations = self.num_realizations + + # Generate filter "cross products" + if self.filters == other.filters: + for self_filter in self.filters: + new_filter = _CrossFilter(self_filter, self_filter, '/') + new_tally.add_filter(new_filter) + else: + for self_filter in self.filters: + for other_filter in other.filters: + new_filter = _CrossFilter(self_filter, other_filter, '/') + new_tally.add_filter(new_filter) + + # Generate nuclide "cross products" + if self.nuclides == other.nuclides: + for self_nuclide in self.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '/') + new_tally.add_nuclide(new_nuclide) + else: + for self_nuclide in self.nuclides: + for other_nuclide in other.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '/') + new_tally.add_nuclide(new_nuclide) + + # Generate score "cross products" + if self.scores == other.scores: + for self_score in self.scores: + new_score = _CrossScore(self_score, self_score, '/') + new_tally.add_score(new_score) + else: + for self_score in self.scores: + for other_score in other.scores: + new_score = _CrossScore(self_score, other_score, '/') + new_tally.add_score(new_score) + + elif isinstance(other, (Integral, Rational)): + + new_tally._mean = self._mean / other + new_tally._std_dev = self._std_dev * np.abs(1. / other) + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for filter in self.filters: + new_tally.add_filter(filter) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to divide Tally ID={0} ' \ + 'by {1}'.format(self.id, other) + raise ValueError(msg) + + return new_tally + + def __pow__(self, power): + + # Check that results have been read + if self.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) + raise ValueError(msg) + + new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True + + if isinstance(power, Tally): + + # Check that results have been read + if power.mean is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(power.id) + raise ValueError(msg) + + # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters + # FIXME: Need to be able to use StatePoint.get_tally + # FIXME: Need to be able to use Tally.get_value + # FIXME: Modularize + + data = self._align_tally_data(power) + + mean_ratio = data['other']['mean'] / data['self']['mean'] + first_term = mean_ratio * data['self']['std. dev.'] + second_term = np.log(data['self']['mean']) * data['other']['std. dev.'] + new_tally._mean = data['self']['mean'] ** data['other']['mean'] + new_tally._std_dev = np.abs(new_tally.mean) * \ + np.sqrt(first_term**2 + second_term**2) + + if self.estimator == power.estimator: + new_tally.estimator = self.estimator + if self.with_summary and power.with_summary: + new_tally.with_summary = self.with_summary + if self.num_realizations == power.num_realizations: + new_tally.num_realizations = self.num_realizations + + # Generate nuclide "cross products" + if self.nuclides == power.nuclides: + for self_nuclide in self.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '^') + new_tally.add_nuclide(new_nuclide) + else: + for self_nuclide in self.nuclides: + for other_nuclide in power.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '^') + new_tally.add_nuclide(new_nuclide) + + # Generate score "cross products" + if self.scores == power.scores: + for self_score in self.scores: + new_score = _CrossScore(self_score, self_score, '^') + new_tally.add_score(new_score) + else: + for self_score in self.scores: + for other_score in power.scores: + new_score = _CrossScore(self_score, other_score, '^') + new_tally.add_score(new_score) + + # Generate score "cross products" + if self.scores == power.scores: + for self_score in self.scores: + new_score = _CrossScore(self_score, self_score, '^') + new_tally.add_score(new_score) + else: + for self_score in self.scores: + for other_score in power.scores: + new_score = _CrossScore(self_score, other_score, '^') + new_tally.add_score(new_score) + + elif isinstance(power, (Integral, Rational)): + + new_tally._mean = self._mean ** power + self_rel_err = self.std_dev / self.mean + new_tally._std_dev = np.abs(new_tally._mean * power * self_rel_err) + new_tally.estimator = self.estimator + new_tally.with_summary = self.with_summary + new_tally.num_realization = self.num_realizations + new_tally.num_score_bins = self.num_score_bins + + for filter in self.filters: + new_tally.add_filter(filter) + for nuclide in self.nuclides: + new_tally.add_nuclide(nuclide) + for score in self.scores: + new_tally.add_score(score) + + else: + msg = 'Unable to raise Tally ID={0} to ' \ + 'power {1}'.format(self.id, power) + raise ValueError(msg) + + return new_tally + + def __radd__(self, other): + return self + other + + def __rsub__(self, other): + return -1. * self + other + + def __rmul__(self, other): + return self * other + + def __rdiv__(self, other): + return self * (1. / other) + + def __pos__(self): + new_tally = copy.deepcopy(self) + new_tally._mean = np.abs(new_tally.mean) + return new_tally + + def __neg__(self): + new_tally = self * -1 + return new_tally + + def minimum(self, scores=[], filters=[], filter_bins=[], + nuclides=[], value='mean'): + """Returns the minimum of a slice of the Tally's data. + + Parameters + ---------- + scores : list + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) + + filters : list + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) + + filter_bins : list + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in + the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for the + cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of + floats for 'energy' and 'energyout' filters corresponding to the + energy boundaries of the bin of interest. The bin is a (x,y,z) + 3-tuple for 'mesh' filters corresponding to the mesh cell of + interest. The order of the bins in the list must correspond of the + filter_types parameter. + + nuclides : list + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) + + value : str + A string for the type of value to return - 'mean' (default), + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + + Returns + ------- + float + A scalar float of the minimum value in the Tally data indexed by + each filter, nuclide and score as listed in the parameters. + """ + + data = self.get_values(scores, filters, filter_bins, nuclides, value) + return np.min(data) + + def maximum(self, scores=[], filters=[], filter_bins=[], + nuclides=[], value='mean'): + """Returns the maximum of a slice of the Tally's data. + + Parameters + ---------- + scores : list + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) + + filters : list + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) + + filter_bins : list + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in + the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for the + cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of + floats for 'energy' and 'energyout' filters corresponding to the + energy boundaries of the bin of interest. The bin is a (x,y,z) + 3-tuple for 'mesh' filters corresponding to the mesh cell of + interest. The order of the bins in the list must correspond of the + filter_types parameter. + + nuclides : list + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) + + value : str + A string for the type of value to return - 'mean' (default), + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + + Returns + ------- + float + A scalar float of the maximum value in the Tally data indexed by + each filter, nuclide and score as listed in the parameters. + """ + + data = self.get_values(scores, filters, filter_bins, nuclides, value) + return np.max(data) + + def summation(self, scores=[], filters=[], filter_bins=[], + nuclides=[], value='mean'): + """Returns the sum of a slice of the Tally's data. + + Parameters + ---------- + scores : list + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) + + filters : list + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) + + filter_bins : list + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in + the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for the + cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of + floats for 'energy' and 'energyout' filters corresponding to the + energy boundaries of the bin of interest. The bin is a (x,y,z) + 3-tuple for 'mesh' filters corresponding to the mesh cell of + interest. The order of the bins in the list must correspond of the + filter_types parameter. + + nuclides : list + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) + + value : str + A string for the type of value to return - 'mean' (default), + 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted + + Returns + ------- + float + A scalar float of the sum of the Tally data indexed by + each filter, nuclide and score as listed in the parameters. + """ + + data = self.get_values(scores, filters, filter_bins, nuclides, value) + return np.sum(data) + + def slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]): + """ + """ + + # Ensure that StatePoint.read_results() was called first + if (self.mean is None) or (self.std_dev is None): + msg = 'The Tally ID={0} has no data to slice. Call the ' \ + 'StatePoint.read_results() routine before using ' \ + 'Tally.slice(...)'.format(self.id) + raise ValueError(msg) + + # Compute batch statistics if not yet computed + if not self.with_batch_statistics: + self.compute_std_dev() + + new_tally = copy.deepcopy(self) + new_sum = self.get_values(scores, filters, filter_bins, + nuclides, 'sum') + new_sum_sq = self.get_values(scores, filters, filter_bins, + nuclides, 'sum_sq') + new_tally.sum = new_sum + new_tally.sum_sq = new_sum_sq + + ############################ FILTERS ######################### + # Determine the score indices from any of the requested scores + if filters: + + # Initialize list of indices to Filters to exclude from slice + filter_indices = [] + + # Loop over all of the Tally's Filters + for i, filter in enumerate(self.filters): + + # FIXME: sum over unused filter bins + # FIXME: neglect bins not selected for selected filters + # NOTE: We must store filter indices here since they are strings + + if filter.type not in filters: + filter_index = self.get_filter_index(filters[i], filter_bins[i]) + filter_indices.append(filter_index) + + # Loop over indices in reverse to remove excluded Filters + for filter_index in filter_indices[::-1]: + new_tally.remove_filter(self.filters[filter_index]) + + for i, filter_type in enumerate(filters): + + if 'energy' in filter_type: + # Create a list of the first energy edge + bins = [filter_bin[0] for filter_bin in filter_bins] + + + + ############################ NUCLIDES ######################## + # Determine the score indices from any of the requested scores + if nuclides: + + # Initialize list of indices to Nuclides to exclude from slice + nuclide_indices = [] + + for nuclide in self.nuclides: + + if isinstance(nuclide, Nuclide): + if nuclide.name not in nuclides: + nuclide_index = self.get_nuclide_index(nuclide) + nuclide_indices.append(nuclide_index) + else: + if nuclide not in nuclides: + nuclide_index = self.get_nuclide_index(nuclide) + nuclide_indices.append(nuclide_index) + + # Loop over indices in reverse to remove excluded Nuclides + for nuclide_index in nuclide_indices[::-1]: + new_tally.remove_nuclide(self.nuclides[nuclide_index]) + + ############################# SCORES ######################### + # Determine the score indices from any of the requested scores + if scores: + + # Initialize list of indices to Nuclides to exclude from slice + score_indices = [] + + for score in self.scores: + + if score not in scores: + score_index = self.get_score_index(score) + score_indices.append(score_index) + + # Loop over indices in reverse to remove excluded scores + for score_index in score_indices[::-1]: + new_tally.remove_score(self.scores[score_index]) + new_tally.num_score_bins -= 1 + + return new_tally + class TalliesFile(object): """Tallies file used for an OpenMC simulation. Corresponds directly to the From dea42d7208991b7a489f4f07ce529da4f72c0392 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Wed, 29 Jul 2015 00:39:34 -0700 Subject: [PATCH 20/39] Created new Tally._cross_product(...) routine --- openmc/tallies.py | 276 ++++++++++------------------------------------ 1 file changed, 60 insertions(+), 216 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 66361a3e9..6e9f06fee 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1235,9 +1235,6 @@ class Tally(object): # energy, energyout filters elif 'energy' in filter.type: - - print filter - bins = filter.bins num_bins = filter.num_bins @@ -1296,7 +1293,7 @@ class Tally(object): Parameters ---------- filename : str - The name of the file for the results (default is 'tally-results') + The name of the file for the results (default is 'tally-results') directory : str The name of the directory for the results (default is '.') @@ -1436,6 +1433,59 @@ class Tally(object): # Pickle the Tally results to a file pickle.dump(tally_results, open(filename, 'wb')) + def _cross_product(self, other_tally, new_tally, binary_op): + """ + + Parameters + ---------- + other_tally : Tally + The tally on the right hand side of the cross-product + new_tally: Tally + The new tally to represent the cross-product + op : str + The binary operation in the cross product (+,-,*,/,^) + """ + + if self.estimator == other_tally.estimator: + new_tally.estimator = self.estimator + if self.with_summary and other_tally.with_summary: + new_tally.with_summary = self.with_summary + if self.num_realizations == other_tally.num_realizations: + new_tally.num_realizations = self.num_realizations + + # Generate nuclide "cross products" + if self.nuclides == other_tally.nuclides: + for self_nuclide in self.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, binary_op) + new_tally.add_nuclide(new_nuclide) + else: + all_nuclides = [self.nuclides, other_tally.nuclides] + for self_nuclide, other_nuclide in itertools.product(*all_nuclides): + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, binary_op) + new_tally.add_nuclide(new_nuclide) + + # Generate filter "cross products" + if self.filters == other_tally.filters: + for self_filter in self.filters: + new_filter = _CrossScore(self_filter, self_filter, binary_op) + new_tally.add_filter(new_filter) + else: + all_filters = [self.filters, other_tally.filters] + for self_filter, other_filter in itertools.product(*all_filters): + new_filter = _CrossScore(self_filter, other_filter, binary_op) + new_tally.add_filter(new_filter) + + # Generate score "cross products" + if self.scores == other_tally.scores: + for self_score in self.scores: + new_score = _CrossScore(self_score, self_score, binary_op) + new_tally.add_score(new_score) + else: + all_scores = [self.scores, other_tally.scores] + for self_score, other_score in itertools.product(*all_scores): + new_score = _CrossScore(self_score, other_score, binary_op) + new_tally.add_score(new_score) + def _align_tally_data(self, other): self_mean = copy.deepcopy(self.mean) @@ -1522,56 +1572,14 @@ class Tally(object): # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize - # FIXME: Redundant filters + self._cross_product(other, new_tally, binary_op='+') data = self._align_tally_data(other) - new_tally._mean = data['self']['mean'] + data['other']['mean'] new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ data['other']['std. dev.']**2) - if self.estimator == other.estimator: - new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: - new_tally.num_realizations = self.num_realizations - - # Generate filter "cross products" - if self.filters == other.filters: - for self_filter in self.filters: - new_filter = _CrossFilter(self_filter, self_filter, '+') - new_tally.add_filter(new_filter) - else: - for self_filter in self.filters: - for other_filter in other.filters: - new_filter = _CrossFilter(self_filter, other_filter, '+') - new_tally.add_filter(new_filter) - - # Generate nuclide "cross products" - if self.nuclides == other.nuclides: - for self_nuclide in self.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '+') - new_tally.add_nuclide(new_nuclide) - else: - for self_nuclide in self.nuclides: - for other_nuclide in other.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '+') - new_tally.add_nuclide(new_nuclide) - - # Generate score "cross products" - if self.scores == other.scores: - for self_score in self.scores: - new_score = _CrossScore(self_score, self_score, '+') - new_tally.add_score(new_score) - else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '+') - new_tally.add_score(new_score) - - elif isinstance(other, Integral) or isinstance(other): + elif isinstance(other, (Integral, Rational)): new_tally._mean = self._mean + other new_tally._std_dev = self._std_dev @@ -1615,54 +1623,13 @@ class Tally(object): # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize + self._cross_product(other, new_tally, binary_op='-') data = self._align_tally_data(other) - new_tally._mean = data['self']['mean'] - data['other']['mean'] new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ data['other']['std. dev.']**2) - if self.estimator == other.estimator: - new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: - new_tally.num_realizations = self.num_realizations - - # Generate filter "cross products" - if self.filters == other.filters: - for self_filter in self.filters: - new_filter = _CrossFilter(self_filter, self_filter, '-') - new_tally.add_filter(new_filter) - else: - for self_filter in self.filters: - for other_filter in other.filters: - new_filter = _CrossFilter(self_filter, other_filter, '-') - new_tally.add_filter(new_filter) - - # Generate nuclide "cross products" - if self.nuclides == other.nuclides: - for self_nuclide in self.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '-') - new_tally.add_nuclide(new_nuclide) - else: - for self_nuclide in self.nuclides: - for other_nuclide in other.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '-') - new_tally.add_nuclide(new_nuclide) - - # Generate score "cross products" - if self.scores == other.scores: - for self_score in self.scores: - new_score = _CrossScore(self_score, self_score, '-') - new_tally.add_score(new_score) - else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '-') - new_tally.add_score(new_score) - elif isinstance(other, (Integral, Rational)): new_tally._mean = self._mean - other @@ -1708,56 +1675,15 @@ class Tally(object): # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize + self._cross_product(other, new_tally, binary_op='*') data = self._align_tally_data(other) - self_rel_err = data['self']['std. dev.'] / data['self']['mean'] other_rel_err = data['other']['std. dev.'] / data['other']['mean'] new_tally._mean = data['self']['mean'] * data['other']['mean'] new_tally._std_dev = np.abs(new_tally.mean) * \ np.sqrt(self_rel_err**2 + other_rel_err**2) - if self.estimator == other.estimator: - new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: - new_tally.num_realizations = self.num_realizations - - # Generate filter "cross products" - if self.filters == other.filters: - for self_filter in self.filters: - new_filter = _CrossFilter(self_filter, self_filter, '*') - new_tally.add_filter(new_filter) - else: - for self_filter in self.filters: - for other_filter in other.filters: - new_filter = _CrossFilter(self_filter, other_filter, '*') - new_tally.add_filter(new_filter) - - # Generate nuclide "cross products" - if self.nuclides == other.nuclides: - for self_nuclide in self.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '*') - new_tally.add_nuclide(new_nuclide) - else: - for self_nuclide in self.nuclides: - for other_nuclide in other.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '*') - new_tally.add_nuclide(new_nuclide) - - # Generate score "cross products" - if self.scores == other.scores: - for self_score in self.scores: - new_score = _CrossScore(self_score, self_score, '*') - new_tally.add_score(new_score) - else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '*') - new_tally.add_score(new_score) - elif isinstance(other, (Integral, Rational)): new_tally._mean = self._mean * other @@ -1803,56 +1729,15 @@ class Tally(object): # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize + self._cross_product(other, new_tally, binary_op='/') data = self._align_tally_data(other) - self_rel_err = data['self']['std. dev.'] / data['self']['mean'] other_rel_err = data['other']['std. dev.'] / data['other']['mean'] new_tally._mean = data['self']['mean'] / data['other']['mean'] new_tally._std_dev = np.abs(new_tally.mean) * \ np.sqrt(self_rel_err**2 + other_rel_err**2) - if self.estimator == other.estimator: - new_tally.estimator = self.estimator - if self.with_summary and other.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == other.num_realizations: - new_tally.num_realizations = self.num_realizations - - # Generate filter "cross products" - if self.filters == other.filters: - for self_filter in self.filters: - new_filter = _CrossFilter(self_filter, self_filter, '/') - new_tally.add_filter(new_filter) - else: - for self_filter in self.filters: - for other_filter in other.filters: - new_filter = _CrossFilter(self_filter, other_filter, '/') - new_tally.add_filter(new_filter) - - # Generate nuclide "cross products" - if self.nuclides == other.nuclides: - for self_nuclide in self.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '/') - new_tally.add_nuclide(new_nuclide) - else: - for self_nuclide in self.nuclides: - for other_nuclide in other.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '/') - new_tally.add_nuclide(new_nuclide) - - # Generate score "cross products" - if self.scores == other.scores: - for self_score in self.scores: - new_score = _CrossScore(self_score, self_score, '/') - new_tally.add_score(new_score) - else: - for self_score in self.scores: - for other_score in other.scores: - new_score = _CrossScore(self_score, other_score, '/') - new_tally.add_score(new_score) - elif isinstance(other, (Integral, Rational)): new_tally._mean = self._mean / other @@ -1898,10 +1783,9 @@ class Tally(object): # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - # FIXME: Modularize + self._cross_product(power, new_tally, binary_op='^') data = self._align_tally_data(power) - mean_ratio = data['other']['mean'] / data['self']['mean'] first_term = mean_ratio * data['self']['std. dev.'] second_term = np.log(data['self']['mean']) * data['other']['std. dev.'] @@ -1909,46 +1793,6 @@ class Tally(object): new_tally._std_dev = np.abs(new_tally.mean) * \ np.sqrt(first_term**2 + second_term**2) - if self.estimator == power.estimator: - new_tally.estimator = self.estimator - if self.with_summary and power.with_summary: - new_tally.with_summary = self.with_summary - if self.num_realizations == power.num_realizations: - new_tally.num_realizations = self.num_realizations - - # Generate nuclide "cross products" - if self.nuclides == power.nuclides: - for self_nuclide in self.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, '^') - new_tally.add_nuclide(new_nuclide) - else: - for self_nuclide in self.nuclides: - for other_nuclide in power.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, '^') - new_tally.add_nuclide(new_nuclide) - - # Generate score "cross products" - if self.scores == power.scores: - for self_score in self.scores: - new_score = _CrossScore(self_score, self_score, '^') - new_tally.add_score(new_score) - else: - for self_score in self.scores: - for other_score in power.scores: - new_score = _CrossScore(self_score, other_score, '^') - new_tally.add_score(new_score) - - # Generate score "cross products" - if self.scores == power.scores: - for self_score in self.scores: - new_score = _CrossScore(self_score, self_score, '^') - new_tally.add_score(new_score) - else: - for self_score in self.scores: - for other_score in power.scores: - new_score = _CrossScore(self_score, other_score, '^') - new_tally.add_score(new_score) - elif isinstance(power, (Integral, Rational)): new_tally._mean = self._mean ** power From 67fb220ada24372d29a8c72b63b37e382cf2c52f Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Wed, 29 Jul 2015 18:04:39 -0700 Subject: [PATCH 21/39] Renamed Tally._cross_product as Tally._outer_product --- openmc/tallies.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 6e9f06fee..2f7a80335 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1433,7 +1433,7 @@ class Tally(object): # Pickle the Tally results to a file pickle.dump(tally_results, open(filename, 'wb')) - def _cross_product(self, other_tally, new_tally, binary_op): + def _outer_product(self, other_tally, new_tally, binary_op): """ Parameters @@ -1573,7 +1573,7 @@ class Tally(object): # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - self._cross_product(other, new_tally, binary_op='+') + self._outer_product(other, new_tally, binary_op='+') data = self._align_tally_data(other) new_tally._mean = data['self']['mean'] + data['other']['mean'] new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ @@ -1624,7 +1624,7 @@ class Tally(object): # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - self._cross_product(other, new_tally, binary_op='-') + self._outer_product(other, new_tally, binary_op='-') data = self._align_tally_data(other) new_tally._mean = data['self']['mean'] - data['other']['mean'] new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ @@ -1676,7 +1676,7 @@ class Tally(object): # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - self._cross_product(other, new_tally, binary_op='*') + self._outer_product(other, new_tally, binary_op='*') data = self._align_tally_data(other) self_rel_err = data['self']['std. dev.'] / data['self']['mean'] other_rel_err = data['other']['std. dev.'] / data['other']['mean'] @@ -1730,7 +1730,7 @@ class Tally(object): # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - self._cross_product(other, new_tally, binary_op='/') + self._outer_product(other, new_tally, binary_op='/') data = self._align_tally_data(other) self_rel_err = data['self']['std. dev.'] / data['self']['mean'] other_rel_err = data['other']['std. dev.'] / data['other']['mean'] @@ -1784,7 +1784,7 @@ class Tally(object): # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value - self._cross_product(power, new_tally, binary_op='^') + self._outer_product(power, new_tally, binary_op='^') data = self._align_tally_data(power) mean_ratio = data['other']['mean'] / data['self']['mean'] first_term = mean_ratio * data['self']['std. dev.'] From f6128ff387bbf4c0a4df690f2fd08438f25e56f5 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Wed, 29 Jul 2015 18:47:42 -0700 Subject: [PATCH 22/39] Added docstrings to tally arithmetic routines --- openmc/tallies.py | 345 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 294 insertions(+), 51 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 2f7a80335..db2f897ff 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -803,8 +803,8 @@ class Tally(object): nuclides=[], value='mean'): """Returns a tally score value given a list of filters to satisfy. - This routine constructs a 3D NumPy array for the requested Tally data - indexed by filter bin, nuclide bin, and score index. The routine will + This method constructs a 3D NumPy array for the requested Tally data + indexed by filter bin, nuclide bin, and score index. The method will order the data in the array as specified in the parameter lists Parameters @@ -846,8 +846,8 @@ class Tally(object): Raises ------ ValueError - When this routine is called before the Tally is populated with data - by the StatePoint.read_results() routine. ValueError is also thrown + When this method is called before the Tally is populated with data + by the StatePoint.read_results() method. ValueError is also thrown if the input parameters do not correspond to the Tally's attributes, e.g., if the score(s) do not match those in the Tally. @@ -860,7 +860,7 @@ class Tally(object): (value == 'sum' and self.sum is None) or \ (value == 'sum_sq' and self.sum_sq is None): msg = 'The Tally ID={0} has no data to return. Call the ' \ - 'StatePoint.read_results() routine before using ' \ + 'StatePoint.read_results() method before using ' \ 'Tally.get_values(...)'.format(self.id) raise ValueError(msg) @@ -911,7 +911,7 @@ class Tally(object): filter_indices[i].append( self.get_filter_index(filter.type, bin)) - # Apply cross-product sum between all filter bin indices + # Apply outer product sum between all filter bin indices filter_indices = list(map(sum, itertools.product(*filter_indices))) # If user did not specify any specific Filters, use them all @@ -940,7 +940,7 @@ class Tally(object): else: score_indices = np.arange(self.num_scores) - # Construct cross-product of all three index types with each other + # Construct outer product of all three index types with each other indices = np.ix_(filter_indices, nuclide_indices, score_indices) # Return the desired result from Tally @@ -966,11 +966,11 @@ class Tally(object): scores=True, summary=None): """Build a Pandas DataFrame for the Tally data. - This routine constructs a Pandas DataFrame object for the Tally data + This method constructs a Pandas DataFrame object for the Tally data with columns annotated by filter, nuclide and score bin information. This capability has been tested for Pandas >=v0.13.1. However, if possible, it is recommended to use the v0.16 or newer versions of - Pandas since this this routine uses the Multi-index Pandas feature. + Pandas since this this method uses the Multi-index Pandas feature. Parameters ---------- @@ -1000,22 +1000,22 @@ class Tally(object): Raises ------ KeyError - When this routine is called before the Tally is populated with data - by the StatePoint.read_results() routine. + When this method is called before the Tally is populated with data + by the StatePoint.read_results() method. """ # Ensure that StatePoint.read_results() was called first if self.mean is None or self.std_dev is None: msg = 'The Tally ID={0} has no data to return. Call the ' \ - 'StatePoint.read_results() routine before using ' \ + 'StatePoint.read_results() method before using ' \ 'Tally.get_pandas_dataframe(...)'.format(self.id) raise KeyError(msg) # If using Summary, ensure StatePoint.link_with_summary(...) was called if summary and not self.with_summary: msg = 'The Tally ID={0} has not been linked with the Summary. ' \ - 'Call the StatePoint.link_with_summary(...) routine ' \ + 'Call the StatePoint.link_with_summary(...) method ' \ 'before using Tally.get_pandas_dataframe(...) with ' \ 'Summary info'.format(self.id) raise KeyError(msg) @@ -1308,15 +1308,15 @@ class Tally(object): Raises ------ KeyError - When this routine is called before the Tally is populated with data - by the StatePoint.read_results() routine. + When this method is called before the Tally is populated with data + by the StatePoint.read_results() method. """ # Ensure that StatePoint.read_results() was called first if self._sum is None or self._sum_sq is None: msg = 'The Tally ID={0} has no data to export. Call the ' \ - 'StatePoint.read_results() routine before using ' \ + 'StatePoint.read_results() method before using ' \ 'Tally.export_results(...)'.format(self.id) raise KeyError(msg) @@ -1433,60 +1433,91 @@ class Tally(object): # Pickle the Tally results to a file pickle.dump(tally_results, open(filename, 'wb')) - def _outer_product(self, other_tally, new_tally, binary_op): - """ + def _outer_product(self, other, new_tally, binary_op): + """Combines filters, scores and nuclides with another tally. + + This is a helper method for the tally arithmetic methods. The ilters, + scores and nuclides from both tallies are enumerated into all possible + combinations and expressed as _CrossFilter, _CrossScore and + _CrossNuclide objects in the new derived tally. Parameters ---------- - other_tally : Tally - The tally on the right hand side of the cross-product + other : Tally + The tally on the right hand side of the outer product new_tally: Tally - The new tally to represent the cross-product + The new tally to represent the outer product op : str - The binary operation in the cross product (+,-,*,/,^) + The binary operation in the outer product ('+', '-', '*', '/', '^') + """ - if self.estimator == other_tally.estimator: + new_tally.name = '({0} {1} {2})'.format(self.name, other.name, binary_op) + + if self.estimator == other.estimator: new_tally.estimator = self.estimator - if self.with_summary and other_tally.with_summary: + if self.with_summary and other.with_summary: new_tally.with_summary = self.with_summary - if self.num_realizations == other_tally.num_realizations: + if self.num_realizations == other.num_realizations: new_tally.num_realizations = self.num_realizations - # Generate nuclide "cross products" - if self.nuclides == other_tally.nuclides: - for self_nuclide in self.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, binary_op) - new_tally.add_nuclide(new_nuclide) - else: - all_nuclides = [self.nuclides, other_tally.nuclides] - for self_nuclide, other_nuclide in itertools.product(*all_nuclides): - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, binary_op) - new_tally.add_nuclide(new_nuclide) - - # Generate filter "cross products" - if self.filters == other_tally.filters: + # Generate filter "outer products" + if self.filters == other.filters: for self_filter in self.filters: new_filter = _CrossScore(self_filter, self_filter, binary_op) new_tally.add_filter(new_filter) else: - all_filters = [self.filters, other_tally.filters] + all_filters = [self.filters, other.filters] for self_filter, other_filter in itertools.product(*all_filters): new_filter = _CrossScore(self_filter, other_filter, binary_op) new_tally.add_filter(new_filter) - # Generate score "cross products" - if self.scores == other_tally.scores: + # Generate score "outer products" + if self.scores == other.scores: for self_score in self.scores: new_score = _CrossScore(self_score, self_score, binary_op) new_tally.add_score(new_score) else: - all_scores = [self.scores, other_tally.scores] + all_scores = [self.scores, other.scores] for self_score, other_score in itertools.product(*all_scores): new_score = _CrossScore(self_score, other_score, binary_op) new_tally.add_score(new_score) + # Generate nuclide "outer products" + if self.nuclides == other.nuclides: + for self_nuclide in self.nuclides: + new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, binary_op) + new_tally.add_nuclide(new_nuclide) + else: + all_nuclides = [self.nuclides, other.nuclides] + for self_nuclide, other_nuclide in itertools.product(*all_nuclides): + new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, binary_op) + new_tally.add_nuclide(new_nuclide) + def _align_tally_data(self, other): + """Aligns data from two tallies for tally arithmetic. + + This is a helper method to construct a dict of dicts of the "aligned" + data arrays from each tally for tally arithmetic. The method analyzes + the filters, scores and nuclides in both tally's and determines how to + appropriately align the data for vectorized arithmetic. For example, + if the two tallies have different filters, this method will use NumPy + 'tile' and 'repeat' operations to the new data arrays such that all + possible combinations of the data in each tally's bins will be made + when the arithmetic operation is applied to the arrays. + + Parameters + ---------- + other : Tally + The tally to outer product with this tally + + Returns + ------- + dict + A dictionary of dictionaries to "aligned" 'mean' and 'std. dev' + NumPy arrays for each tally's data. + + """ self_mean = copy.deepcopy(self.mean) self_std_dev = copy.deepcopy(self.std_dev) @@ -1551,6 +1582,34 @@ class Tally(object): return data def __add__(self, other): + """Adds this tally to another tally or scalar value. + + This method builds a new tally with data that is the sum of this + tally's data and that from the other tally or scalar value. If the + filters, scores and nuclides in the two tallies are not the same, then + they are combined in all possible ways in the new derived tally. + + Uncertainty propagation is used to compute the standard deviation + for the new tally's data. It is important to note that this makes + the assumption that the tally data is independently distributed. + In most use cases, this is *not* true and may lead to under-prediction + of the uncertainty. The uncertainty propagation model is from the + following source: + + https://en.wikipedia.org/wiki/Propagation_of_uncertainty + + Parameters + ---------- + other : Tally or Integer or Rational + The tally or scalar value to add to this tally + + Returns + ------- + Tally + A new derived tally which is the sum of this tally and the other + tally or scalar value in the addition. + + """ # Check that results have been read if self.mean is None: @@ -1558,7 +1617,7 @@ class Tally(object): 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally(name='derived') + new_tally = Tally() new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -1569,7 +1628,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value @@ -1581,6 +1639,7 @@ class Tally(object): elif isinstance(other, (Integral, Rational)): + new_tally.name = self.name new_tally._mean = self._mean + other new_tally._std_dev = self._std_dev new_tally.estimator = self.estimator @@ -1602,6 +1661,34 @@ class Tally(object): return new_tally def __sub__(self, other): + """Subtracts another tally or scalar value from this tally. + + This method builds a new tally with data that is the difference of + this tally's data and that from the other tally or scalar value. If the + filters, scores and nuclides in the two tallies are not the same, then + they are combined in all possible ways in the new derived tally. + + Uncertainty propagation is used to compute the standard deviation + for the new tally's data. It is important to note that this makes + the assumption that the tally data is independently distributed. + In most use cases, this is *not* true and may lead to under-prediction + of the uncertainty. The uncertainty propagation model is from the + following source: + + https://en.wikipedia.org/wiki/Propagation_of_uncertainty + + Parameters + ---------- + other : Tally or Integer or Rational + The tally or scalar value to subtract from this tally + + Returns + ------- + Tally + A new derived tally which is the difference of this tally and the + other tally or scalar value in the subtraction. + + """ # Check that results have been read if self.mean is None: @@ -1609,7 +1696,7 @@ class Tally(object): 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally(name='derived') + new_tally = Tally() new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -1620,7 +1707,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value @@ -1632,6 +1718,7 @@ class Tally(object): elif isinstance(other, (Integral, Rational)): + new_tally.name = self.name new_tally._mean = self._mean - other new_tally._std_dev = self._std_dev new_tally.estimator = self.estimator @@ -1654,6 +1741,34 @@ class Tally(object): return new_tally def __mul__(self, other): + """Multiplies this tally with another tally or scalar value. + + This method builds a new tally with data that is the product of + this tally's data and that from the other tally or scalar value. If the + filters, scores and nuclides in the two tallies are not the same, then + they are combined in all possible ways in the new derived tally. + + Uncertainty propagation is used to compute the standard deviation + for the new tally's data. It is important to note that this makes + the assumption that the tally data is independently distributed. + In most use cases, this is *not* true and may lead to under-prediction + of the uncertainty. The uncertainty propagation model is from the + following source: + + https://en.wikipedia.org/wiki/Propagation_of_uncertainty + + Parameters + ---------- + other : Tally or Integer or Rational + The tally or scalar value to multiply with this tally + + Returns + ------- + Tally + A new derived tally which is the product of this tally and the + other tally or scalar value in the multiplication. + + """ # Check that results have been read if self.mean is None: @@ -1661,7 +1776,7 @@ class Tally(object): 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally(name='derived') + new_tally = Tally() new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -1672,7 +1787,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value @@ -1686,6 +1800,7 @@ class Tally(object): elif isinstance(other, (Integral, Rational)): + new_tally.name = self.name new_tally._mean = self._mean * other new_tally._std_dev = self._std_dev * np.abs(other) new_tally.estimator = self.estimator @@ -1708,6 +1823,34 @@ class Tally(object): return new_tally def __div__(self, other): + """Divides this tally by another tally or scalar value. + + This method builds a new tally with data that is the dividend of + this tally's data and that from the other tally or scalar value. If the + filters, scores and nuclides in the two tallies are not the same, then + they are combined in all possible ways in the new derived tally. + + Uncertainty propagation is used to compute the standard deviation + for the new tally's data. It is important to note that this makes + the assumption that the tally data is independently distributed. + In most use cases, this is *not* true and may lead to under-prediction + of the uncertainty. The uncertainty propagation model is from the + following source: + + https://en.wikipedia.org/wiki/Propagation_of_uncertainty + + Parameters + ---------- + other : Tally or Integer or Rational + The tally or scalar value to divide this tally by + + Returns + ------- + Tally + A new derived tally which is the dividend of this tally and the + other tally or scalar value in the division. + + """ # Check that results have been read if self.mean is None: @@ -1726,7 +1869,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value @@ -1740,6 +1882,7 @@ class Tally(object): elif isinstance(other, (Integral, Rational)): + new_tally.name = self.name new_tally._mean = self._mean / other new_tally._std_dev = self._std_dev * np.abs(1. / other) new_tally.estimator = self.estimator @@ -1762,6 +1905,34 @@ class Tally(object): return new_tally def __pow__(self, power): + """Raises this tally to another tally or scalar value power. + + This method builds a new tally with data that is the power of + this tally's data to that from the other tally or scalar value. If the + filters, scores and nuclides in the two tallies are not the same, then + they are combined in all possible ways in the new derived tally. + + Uncertainty propagation is used to compute the standard deviation + for the new tally's data. It is important to note that this makes + the assumption that the tally data is independently distributed. + In most use cases, this is *not* true and may lead to under-prediction + of the uncertainty. The uncertainty propagation model is from the + following source: + + https://en.wikipedia.org/wiki/Propagation_of_uncertainty + + Parameters + ---------- + other : Tally or Integer or Rational + The tally or scalar value exponent + + Returns + ------- + Tally + A new derived tally which is this tally raised to the power of the + other tally or scalar value in the exponentiation. + + """ # Check that results have been read if self.mean is None: @@ -1780,7 +1951,6 @@ class Tally(object): 'since it does not contain any results.'.format(power.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_pandas_dataframe - filters # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value @@ -1795,6 +1965,7 @@ class Tally(object): elif isinstance(power, (Integral, Rational)): + new_tally.name = self.name new_tally._mean = self._mean ** power self_rel_err = self.std_dev / self.mean new_tally._std_dev = np.abs(new_tally._mean * power * self_rel_err) @@ -1818,18 +1989,90 @@ class Tally(object): return new_tally def __radd__(self, other): + """Right addition with a scalar value. + + This reverses the operands and calls the __add__ method. + + Parameters + ---------- + other : Integer or Rational + The scalar value to add to this tally + + Returns + ------- + Tally + A new derived tally of this tally added with the scalar value. + + """ + return self + other def __rsub__(self, other): + """Right subtraction from a scalar value. + + This reverses the operands and calls the __sub__ method. + + Parameters + ---------- + other : Integer or Rational + The scalar value to subtract this tally from + + Returns + ------- + Tally + A new derived tally of this tally subtracted from the scalar value. + + """ + return -1. * self + other def __rmul__(self, other): + """Right multiplication with a scalar value. + + This reverses the operands and calls the __mul__ method. + + Parameters + ---------- + other : Integer or Rational + The scalar value to multiply with this tally + + Returns + ------- + Tally + A new derived tally of this tally multiplied by the scalar value. + + """ + return self * other def __rdiv__(self, other): + """Right division with a scalar value. + + This reverses the operands and calls the __div__ method. + + Parameters + ---------- + other : Integer or Rational + The scalar value to divide by this tally + + Returns + ------- + Tally + A new derived tally of the scalar value divided by this tally. + + """ + return self * (1. / other) def __pos__(self): + """The absolute value of this tally. + + Returns + ------- + Tally + A new derived tally which is the absolute value of this tally. + + """ new_tally = copy.deepcopy(self) new_tally._mean = np.abs(new_tally.mean) return new_tally @@ -1977,7 +2220,7 @@ class Tally(object): # Ensure that StatePoint.read_results() was called first if (self.mean is None) or (self.std_dev is None): msg = 'The Tally ID={0} has no data to slice. Call the ' \ - 'StatePoint.read_results() routine before using ' \ + 'StatePoint.read_results() method before using ' \ 'Tally.slice(...)'.format(self.id) raise ValueError(msg) From e8ab4b973106d0c030305fd249d4438c17033e49 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Wed, 29 Jul 2015 23:18:59 -0700 Subject: [PATCH 23/39] Removed Tally summation, minimum and maximum routines --- openmc/cross.py | 71 +++++++++-------- openmc/tallies.py | 192 ++++++---------------------------------------- 2 files changed, 59 insertions(+), 204 deletions(-) diff --git a/openmc/cross.py b/openmc/cross.py index 60a524b51..45c7ce2d8 100644 --- a/openmc/cross.py +++ b/openmc/cross.py @@ -1,30 +1,29 @@ from openmc import Filter, Nuclide -from openmc.checkvalue import check_type class _CrossScore(object): """A special-purpose tally score used to encapsulate all combinations of two - tally's scores as a cross product for tally arithmetic. + tally's scores as a outer product for tally arithmetic. Parameters ---------- left_score : str or _CrossScore - The left score in the cross product. + The left score in the outer product right_score : str or _CrossScore - The right score in the cross product. + The right score in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's scores with this _CrossNuclide. + combine two tally's scores with this _CrossNuclide Attributes ---------- left_score : str or _CrossScore - The left score in the cross product. + The left score in the outer product right_score : str or _CrossScore - The right score in the cross product. + The right score in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's scores with this _CrossNuclide. + combine two tally's scores with this _CrossNuclide """ @@ -55,17 +54,14 @@ class _CrossScore(object): @left_score.setter def left_score(self, left_score): - check_type('left score', left_score, (str, _CrossScore)) self._left_score = left_score @right_score.setter def right_score(self, right_score): - check_type('right score', right_score, (str, _CrossScore)) self._right_score = right_score @binary_op.setter def binary_op(self, binary_op): - check_type('binary op', binary_op, str) self._binary_op = binary_op def __repr__(self): @@ -76,27 +72,27 @@ class _CrossScore(object): class _CrossNuclide(object): """A special-purpose nuclide used to encapsulate all combinations of two - tally's nuclides as a cross product for tally arithmetic. + tally's nuclides as a outer product for tally arithmetic. Parameters ---------- left_nuclide : Nuclide or _CrossNuclide - The left nuclide in the cross product. + The left nuclide in the outer product right_nuclide : Nuclide or _CrossNuclide - The right nuclide in the cross product. + The right nuclide in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's nuclides with this _CrossNuclide. + combine two tally's nuclides with this _CrossNuclide Attributes ---------- left_nuclide : Nuclide or _CrossNuclide - The left nuclide in the cross product. + The left nuclide in the outer product right_nuclide : Nuclide or _CrossNuclide - The right nuclide in the cross product. + The right nuclide in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's nuclides with this _CrossNuclide. + combine two tally's nuclides with this _CrossNuclide """ @@ -127,17 +123,14 @@ class _CrossNuclide(object): @left_nuclide.setter def left_nuclide(self, left_nuclide): - check_type('left nuclide', left_nuclide, (Nuclide, _CrossNuclide)) self._left_nuclide = left_nuclide @right_nuclide.setter def right_nuclide(self, right_nuclide): - check_type('right nuclide', right_nuclide, (Nuclide, _CrossNuclide)) self._right_nuclide = right_nuclide @binary_op.setter def binary_op(self, binary_op): - check_type('binary op', binary_op, str) self._binary_op = binary_op def __repr__(self): @@ -165,32 +158,41 @@ class _CrossNuclide(object): class _CrossFilter(object): """A special-purpose filter used to encapsulate all combinations of two - tally's filter bins as a cross product for tally arithmetic. + tally's filter bins as a outer product for tally arithmetic. Parameters ---------- left_filter : Filter or _CrossFilter - The left filter in the cross product. + The left filter in the outer product right_filter : Filter or _CrossFilter - The right filter in the cross product. + The right filter in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's filter bins with this _CrossFilter. + combine two tally's filter bins with this _CrossFilter Attributes ---------- left_filter : Filter or _CrossFilter - The left filter in the cross product. + The left filter in the outer product right_filter : Filter or _CrossFilter - The right filter in the cross product. + The right filter in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's filter bins with this _CrossFilter. + combine two tally's filter bins with this _CrossFilter """ def __init__(self, left_filter=None, right_filter=None, binary_op=None): + left_type = left_filter.type + right_type = right_filter.type + self.type = '({0} {1} {2})'.format(left_type, binary_op, right_type) + + self._bins = {} + self._bins['left'] = left_filter.bins + self._bins['right'] = right_filter.bins + self._num_bins = left_filter.num_bins * right_filter.num_bins + self._left_filter = None self._right_filter = None self._binary_op = None @@ -216,33 +218,34 @@ class _CrossFilter(object): @property def type(self): - return (self.right_filter.type, self.left_filter.type) + return self._type @property def bins(self): - return (self.right_filter.bins, self.left_filter.bins) + return (self._bins['left'], self._bins['right']) @property def num_bins(self): - return self.left_filter.num_bins * self.right_filter.num_bins + return self._num_bins @property def stride(self): return self.left_filter.stride * self.right_filter.stride + @type.setter + def type(self, filter_type): + self._type = filter_type + @left_filter.setter def left_filter(self, left_filter): - check_type('left filter', left_filter, (Filter, _CrossFilter)) self._left_filter = left_filter @right_filter.setter def right_filter(self, right_filter): - check_type('right filter', right_filter, (Filter, _CrossFilter)) self._right_filter = right_filter @binary_op.setter def binary_op(self, binary_op): - check_type('binary op', binary_op, str) self._binary_op = binary_op def __repr__(self): diff --git a/openmc/tallies.py b/openmc/tallies.py index db2f897ff..85f2ecbd6 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -479,7 +479,7 @@ class Tally(object): string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self.id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self.name) - string += '{0: <16}\n'.format('\tFilters') + string += '{0: <16}{1}\n'.format('\tFilters', '=\t') for filter in self.filters: string += '{0: <16}\t\t{1}\t{2}\n'.format('', filter.type, @@ -864,10 +864,6 @@ class Tally(object): 'Tally.get_values(...)'.format(self.id) raise ValueError(msg) - # Compute batch statistics if not yet computed - if not self.with_batch_statistics: - self.compute_std_dev() - ############################ FILTERS ######################### # Determine the score indices from any of the requested scores if filters: @@ -1027,10 +1023,6 @@ class Tally(object): msg = 'The pandas Python package must be installed on your system' raise ImportError(msg) - # Compute batch statistics if not yet computed - if not self.with_batch_statistics: - self.compute_std_dev() - # Initialize a pandas dataframe for the tally data df = pd.DataFrame() @@ -1452,7 +1444,7 @@ class Tally(object): """ - new_tally.name = '({0} {1} {2})'.format(self.name, other.name, binary_op) + new_tally.name = '({0} {1} {2})'.format(self.name, binary_op, other.name) if self.estimator == other.estimator: new_tally.estimator = self.estimator @@ -1464,12 +1456,12 @@ class Tally(object): # Generate filter "outer products" if self.filters == other.filters: for self_filter in self.filters: - new_filter = _CrossScore(self_filter, self_filter, binary_op) + new_filter = _CrossFilter(self_filter, self_filter, binary_op) new_tally.add_filter(new_filter) else: all_filters = [self.filters, other.filters] for self_filter, other_filter in itertools.product(*all_filters): - new_filter = _CrossScore(self_filter, other_filter, binary_op) + new_filter = _CrossFilter(self_filter, other_filter, binary_op) new_tally.add_filter(new_filter) # Generate score "outer products" @@ -1487,7 +1479,7 @@ class Tally(object): if self.nuclides == other.nuclides: for self_nuclide in self.nuclides: new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, binary_op) - new_tally.add_nuclide(new_nuclide) + new_tally.nuclides.append(new_nuclide) else: all_nuclides = [self.nuclides, other.nuclides] for self_nuclide, other_nuclide in itertools.product(*all_nuclides): @@ -1612,7 +1604,7 @@ class Tally(object): """ # Check that results have been read - if self.mean is None: + if self.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) @@ -1623,7 +1615,7 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if other.mean is None: + if other.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(other.id) raise ValueError(msg) @@ -1691,7 +1683,7 @@ class Tally(object): """ # Check that results have been read - if self.mean is None: + if self.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) @@ -1702,7 +1694,7 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if other.mean is None: + if other.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(other.id) raise ValueError(msg) @@ -1771,7 +1763,7 @@ class Tally(object): """ # Check that results have been read - if self.mean is None: + if self.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) @@ -1782,7 +1774,7 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if other.mean is None: + if other.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(other.id) raise ValueError(msg) @@ -1853,7 +1845,7 @@ class Tally(object): """ # Check that results have been read - if self.mean is None: + if self.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) @@ -1864,7 +1856,7 @@ class Tally(object): if isinstance(other, Tally): # Check that results have been read - if other.mean is None: + if other.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(other.id) raise ValueError(msg) @@ -1935,7 +1927,7 @@ class Tally(object): """ # Check that results have been read - if self.mean is None: + if self.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) @@ -1946,7 +1938,7 @@ class Tally(object): if isinstance(power, Tally): # Check that results have been read - if power.mean is None: + if power.sum is None: msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ 'since it does not contain any results.'.format(power.id) raise ValueError(msg) @@ -2081,153 +2073,16 @@ class Tally(object): new_tally = self * -1 return new_tally - def minimum(self, scores=[], filters=[], filter_bins=[], - nuclides=[], value='mean'): - """Returns the minimum of a slice of the Tally's data. - - Parameters - ---------- - scores : list - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) - - filters : list - A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) - - filter_bins : list - A list of the filter bins corresponding to the filter_types - parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in - the list is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. Each bin is an integer for the - cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of - floats for 'energy' and 'energyout' filters corresponding to the - energy boundaries of the bin of interest. The bin is a (x,y,z) - 3-tuple for 'mesh' filters corresponding to the mesh cell of - interest. The order of the bins in the list must correspond of the - filter_types parameter. - - nuclides : list - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) - - value : str - A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted - - Returns - ------- - float - A scalar float of the minimum value in the Tally data indexed by - each filter, nuclide and score as listed in the parameters. - """ - - data = self.get_values(scores, filters, filter_bins, nuclides, value) - return np.min(data) - - def maximum(self, scores=[], filters=[], filter_bins=[], - nuclides=[], value='mean'): - """Returns the maximum of a slice of the Tally's data. - - Parameters - ---------- - scores : list - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) - - filters : list - A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) - - filter_bins : list - A list of the filter bins corresponding to the filter_types - parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in - the list is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. Each bin is an integer for the - cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of - floats for 'energy' and 'energyout' filters corresponding to the - energy boundaries of the bin of interest. The bin is a (x,y,z) - 3-tuple for 'mesh' filters corresponding to the mesh cell of - interest. The order of the bins in the list must correspond of the - filter_types parameter. - - nuclides : list - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) - - value : str - A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted - - Returns - ------- - float - A scalar float of the maximum value in the Tally data indexed by - each filter, nuclide and score as listed in the parameters. - """ - - data = self.get_values(scores, filters, filter_bins, nuclides, value) - return np.max(data) - - def summation(self, scores=[], filters=[], filter_bins=[], - nuclides=[], value='mean'): - """Returns the sum of a slice of the Tally's data. - - Parameters - ---------- - scores : list - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) - - filters : list - A list of filter type strings - (e.g., ['mesh', 'energy']; default is []) - - filter_bins : list - A list of the filter bins corresponding to the filter_types - parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in - the list is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. Each bin is an integer for the - cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of - floats for 'energy' and 'energyout' filters corresponding to the - energy boundaries of the bin of interest. The bin is a (x,y,z) - 3-tuple for 'mesh' filters corresponding to the mesh cell of - interest. The order of the bins in the list must correspond of the - filter_types parameter. - - nuclides : list - A list of nuclide name strings - (e.g., ['U-235', 'U-238']; default is []) - - value : str - A string for the type of value to return - 'mean' (default), - 'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted - - Returns - ------- - float - A scalar float of the sum of the Tally data indexed by - each filter, nuclide and score as listed in the parameters. - """ - - data = self.get_values(scores, filters, filter_bins, nuclides, value) - return np.sum(data) - def slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]): """ """ # Ensure that StatePoint.read_results() was called first - if (self.mean is None) or (self.std_dev is None): - msg = 'The Tally ID={0} has no data to slice. Call the ' \ - 'StatePoint.read_results() method before using ' \ - 'Tally.slice(...)'.format(self.id) + if self.sum is None: + msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - # Compute batch statistics if not yet computed - if not self.with_batch_statistics: - self.compute_std_dev() - new_tally = copy.deepcopy(self) new_sum = self.get_values(scores, filters, filter_bins, nuclides, 'sum') @@ -2264,10 +2119,8 @@ class Tally(object): # Create a list of the first energy edge bins = [filter_bin[0] for filter_bin in filter_bins] - - - ############################ NUCLIDES ######################## - # Determine the score indices from any of the requested scores + ############################ NUCLIDES ######################### + # Determine the nuclide indices from any of the requested nuclides if nuclides: # Initialize list of indices to Nuclides to exclude from slice @@ -2288,15 +2141,14 @@ class Tally(object): for nuclide_index in nuclide_indices[::-1]: new_tally.remove_nuclide(self.nuclides[nuclide_index]) - ############################# SCORES ######################### + ############################ SCORES ########################## # Determine the score indices from any of the requested scores if scores: - # Initialize list of indices to Nuclides to exclude from slice + # Initialize list of indices to scores to exclude from slice score_indices = [] for score in self.scores: - if score not in scores: score_index = self.get_score_index(score) score_indices.append(score_index) From 48a3ffef8d2f82737b48d6bc0d493b79b08310da Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Wed, 29 Jul 2015 23:49:12 -0700 Subject: [PATCH 24/39] Removed Cross class objects for Filters, Nuclides and scores --- openmc/filter.py | 2 +- openmc/tallies.py | 61 ++++++++++++++++++----------------------------- 2 files changed, 24 insertions(+), 39 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index 68511771c..6c4311291 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -180,7 +180,7 @@ class Filter(object): raise ValueError(msg) # If all error checks passed, add bin edges - self._bins = bins + self._bins = np.array(bins) # FIXME @num_bins.setter diff --git a/openmc/tallies.py b/openmc/tallies.py index 85f2ecbd6..3695885b0 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -983,8 +983,8 @@ class Tally(object): An optional Summary object to be used to construct columns for distribcell tally filters (default is None). The geometric information in the Summary object is embedded into a Multi-index - column with a geometric "path" to each distribcell intance. NOTE: - This option requires the OpenCG Python package. + column with a geometric "path" to each distribcell intance. + NOTE: This option requires the OpenCG Python package. Returns ------- @@ -1428,7 +1428,7 @@ class Tally(object): def _outer_product(self, other, new_tally, binary_op): """Combines filters, scores and nuclides with another tally. - This is a helper method for the tally arithmetic methods. The ilters, + This is a helper method for the tally arithmetic methods. The filters, scores and nuclides from both tallies are enumerated into all possible combinations and expressed as _CrossFilter, _CrossScore and _CrossNuclide objects in the new derived tally. @@ -1456,8 +1456,7 @@ class Tally(object): # Generate filter "outer products" if self.filters == other.filters: for self_filter in self.filters: - new_filter = _CrossFilter(self_filter, self_filter, binary_op) - new_tally.add_filter(new_filter) + new_tally.add_filter(self_filter) else: all_filters = [self.filters, other.filters] for self_filter, other_filter in itertools.product(*all_filters): @@ -1467,8 +1466,7 @@ class Tally(object): # Generate score "outer products" if self.scores == other.scores: for self_score in self.scores: - new_score = _CrossScore(self_score, self_score, binary_op) - new_tally.add_score(new_score) + new_tally.add_score(self_score) else: all_scores = [self.scores, other.scores] for self_score, other_score in itertools.product(*all_scores): @@ -1478,8 +1476,7 @@ class Tally(object): # Generate nuclide "outer products" if self.nuclides == other.nuclides: for self_nuclide in self.nuclides: - new_nuclide = _CrossNuclide(self_nuclide, self_nuclide, binary_op) - new_tally.nuclides.append(new_nuclide) + new_tally.nuclides.append(self_nuclide) else: all_nuclides = [self.nuclides, other.nuclides] for self_nuclide, other_nuclide in itertools.product(*all_nuclides): @@ -1620,7 +1617,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='+') @@ -1699,7 +1695,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='-') @@ -1779,7 +1774,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='*') @@ -1861,7 +1855,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='/') @@ -1943,7 +1936,6 @@ class Tally(object): 'since it does not contain any results.'.format(power.id) raise ValueError(msg) - # FIXME: Need to be able to use StatePoint.get_tally # FIXME: Need to be able to use Tally.get_value self._outer_product(power, new_tally, binary_op='^') @@ -2091,41 +2083,36 @@ class Tally(object): new_tally.sum = new_sum new_tally.sum_sq = new_sum_sq - ############################ FILTERS ######################### - # Determine the score indices from any of the requested scores + ############################ FILTERS ########################## if filters: - - # Initialize list of indices to Filters to exclude from slice filter_indices = [] - # Loop over all of the Tally's Filters + # Determine the filter indices from any of the requested filters for i, filter in enumerate(self.filters): - - # FIXME: sum over unused filter bins - # FIXME: neglect bins not selected for selected filters - # NOTE: We must store filter indices here since they are strings - if filter.type not in filters: - filter_index = self.get_filter_index(filters[i], filter_bins[i]) + filter_index = self.get_filter_index(filters[i], filter_bins[i][0]) filter_indices.append(filter_index) - # Loop over indices in reverse to remove excluded Filters - for filter_index in filter_indices[::-1]: - new_tally.remove_filter(self.filters[filter_index]) + # Remove and/or reorder filter bins to user specifications + else: + bin_indices = [] - for i, filter_type in enumerate(filters): + for filter_bin in filter_bins[i]: + bin_index = self.filters[filter_index].get_bin_index(filter_bin) + bin_indices.append(bin_index) - if 'energy' in filter_type: - # Create a list of the first energy edge - bins = [filter_bin[0] for filter_bin in filter_bins] + new_bins = self.filters[filter_index].bins[bin_indices] + self.filters[filter_index].bins = new_bins + + # Loop over indices in reverse to remove excluded Filters + for filter_index in filter_indices[::-1]: + new_tally.remove_filter(self.filters[filter_index]) ############################ NUCLIDES ######################### - # Determine the nuclide indices from any of the requested nuclides if nuclides: - - # Initialize list of indices to Nuclides to exclude from slice nuclide_indices = [] + # Determine the nuclide indices from any of the requested nuclides for nuclide in self.nuclides: if isinstance(nuclide, Nuclide): @@ -2142,12 +2129,10 @@ class Tally(object): new_tally.remove_nuclide(self.nuclides[nuclide_index]) ############################ SCORES ########################## - # Determine the score indices from any of the requested scores if scores: - - # Initialize list of indices to scores to exclude from slice score_indices = [] + # Determine the score indices from any of the requested scores for score in self.scores: if score not in scores: score_index = self.get_score_index(score) From 2db8bbe060867f1a0194ec4e5f0b2edea0055faf Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Wed, 29 Jul 2015 23:55:17 -0700 Subject: [PATCH 25/39] Now initialize mean and std_dev to None in Tally.slice(...) --- openmc/tallies.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openmc/tallies.py b/openmc/tallies.py index 3695885b0..afdc19048 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -2082,6 +2082,8 @@ class Tally(object): nuclides, 'sum_sq') new_tally.sum = new_sum new_tally.sum_sq = new_sum_sq + new_tally._mean = None + new_tally._std_dev = None ############################ FILTERS ########################## if filters: From 1252dc8689e0ba31b53a48e7feb43afe980df538 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Thu, 30 Jul 2015 07:57:52 -0700 Subject: [PATCH 26/39] Fixed split filter loop --- openmc/tallies.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index afdc19048..91777dcb0 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1031,9 +1031,7 @@ class Tally(object): # Split CrossFilters into separate filters split_filters = [] - for filter in self.filters: - if isinstance(filter, _CrossFilter): split_filters.extend(filter.split_filters()) else: @@ -1041,6 +1039,7 @@ class Tally(object): # Build DataFrame columns for filters if user requested them if filters: + for filter in split_filters: # mesh filters @@ -1233,7 +1232,7 @@ class Tally(object): # Create strings for template = '{0:.1e} - {1:.1e}' filter_bins = [] - for i in range(num_bins-1): + for i in range(num_bins): filter_bins.append(template.format(bins[i], bins[i+1])) # Tile the energy bins into a DataFrame column From ce147bd68dd804b5dc3efadf32725ed6ff0ed9d6 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sat, 1 Aug 2015 22:49:53 -0700 Subject: [PATCH 27/39] Tally slice routine now corrects new tally filter strides --- openmc/filter.py | 4 +-- openmc/tallies.py | 79 +++++++++++++++++++++++++++-------------------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index 6c4311291..b46144f6c 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -322,7 +322,7 @@ class Filter(object): # Use lower energy bound to find index for energy Filters elif self.type in ['energy', 'energyout']: - val = self.bins.index(filter_bin[0]) + val = np.where(self.bins == filter_bin[0])[0][0] filter_index = val # Filter bins for distribcell are the "IDs" of each unique placement @@ -332,7 +332,7 @@ class Filter(object): # Use ID for all other Filters (e.g., material, cell, etc.) else: - val = self.bins.index(filter_bin) + val = np.where(self.bins == filter_bin)[0][0] filter_index = val except ValueError: diff --git a/openmc/tallies.py b/openmc/tallies.py index 91777dcb0..40023c99a 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1616,6 +1616,7 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) + # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='+') @@ -1694,6 +1695,7 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) + # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='-') @@ -1773,6 +1775,7 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) + # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='*') @@ -1854,6 +1857,7 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) + # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='/') @@ -1935,6 +1939,7 @@ class Tally(object): 'since it does not contain any results.'.format(power.id) raise ValueError(msg) + # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(power, new_tally, binary_op='^') @@ -2084,6 +2089,41 @@ class Tally(object): new_tally._mean = None new_tally._std_dev = None + ############################ SCORES ########################## + if scores: + score_indices = [] + + # Determine the score indices from any of the requested scores + for score in self.scores: + if score not in scores: + score_index = self.get_score_index(score) + score_indices.append(score_index) + + # Loop over indices in reverse to remove excluded scores + for score_index in score_indices[::-1]: + new_tally.remove_score(self.scores[score_index]) + new_tally.num_score_bins -= 1 + + ############################ NUCLIDES ######################### + if nuclides: + nuclide_indices = [] + + # Determine the nuclide indices from any of the requested nuclides + for nuclide in self.nuclides: + + if isinstance(nuclide, Nuclide): + if nuclide.name not in nuclides: + nuclide_index = self.get_nuclide_index(nuclide) + nuclide_indices.append(nuclide_index) + else: + if nuclide not in nuclides: + nuclide_index = self.get_nuclide_index(nuclide) + nuclide_indices.append(nuclide_index) + + # Loop over indices in reverse to remove excluded Nuclides + for nuclide_index in nuclide_indices[::-1]: + new_tally.remove_nuclide(self.nuclides[nuclide_index]) + ############################ FILTERS ########################## if filters: filter_indices = [] @@ -2109,40 +2149,11 @@ class Tally(object): for filter_index in filter_indices[::-1]: new_tally.remove_filter(self.filters[filter_index]) - ############################ NUCLIDES ######################### - if nuclides: - nuclide_indices = [] - - # Determine the nuclide indices from any of the requested nuclides - for nuclide in self.nuclides: - - if isinstance(nuclide, Nuclide): - if nuclide.name not in nuclides: - nuclide_index = self.get_nuclide_index(nuclide) - nuclide_indices.append(nuclide_index) - else: - if nuclide not in nuclides: - nuclide_index = self.get_nuclide_index(nuclide) - nuclide_indices.append(nuclide_index) - - # Loop over indices in reverse to remove excluded Nuclides - for nuclide_index in nuclide_indices[::-1]: - new_tally.remove_nuclide(self.nuclides[nuclide_index]) - - ############################ SCORES ########################## - if scores: - score_indices = [] - - # Determine the score indices from any of the requested scores - for score in self.scores: - if score not in scores: - score_index = self.get_score_index(score) - score_indices.append(score_index) - - # Loop over indices in reverse to remove excluded scores - for score_index in score_indices[::-1]: - new_tally.remove_score(self.scores[score_index]) - new_tally.num_score_bins -= 1 + # Correct each Filter's stride + stride = new_tally.num_nuclides * new_tally.num_score_bins + for filter in reversed(new_tally.filters): + filter.stride = stride + stride *= filter.num_bins return new_tally From 7ba69d893bdb8cfc7c66163b76f3394e580b25f2 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 2 Aug 2015 01:20:30 -0700 Subject: [PATCH 28/39] Made Tally.get_values(...) not NumPy squeeze data. Fixed bug in doubly used loop iterator i in Tally.slice(...) --- openmc/tallies.py | 56 +++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 40023c99a..8b259c592 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -895,8 +895,8 @@ class Tally(object): # Create list of 2-tuples for energy boundary bins elif filter.type in ['energy', 'energyout']: bins = [] - for i in range(filter.num_bins): - bins.append((filter.bins[i], filter.bins[i+1])) + for k in range(filter.num_bins): + bins.append((filter.bins[k], filter.bins[k+1])) # Create list of IDs for bins for all other Filter types else: @@ -956,7 +956,7 @@ class Tally(object): '\rel_err\', \'sum\', or \'sum_sq\''.format(self.id, value) raise LookupError(msg) - return data.squeeze() + return data def get_pandas_dataframe(self, filters=True, nuclides=True, scores=True, summary=None): @@ -1516,8 +1516,8 @@ class Tally(object): # Determine the number of paired combinations of filter bins # between the two tallies and repeat arrays along filter axes - self_num_filter_bins = self.mean.shape[0] - other_num_filter_bins = other.mean.shape[0] + self_num_filter_bins = self.num_filter_bins + other_num_filter_bins = other.num_filter_bins num_filter_bins = self_num_filter_bins * other_num_filter_bins self_repeat_factor = num_filter_bins / self_num_filter_bins other_tile_factor = num_filter_bins / other_num_filter_bins @@ -1532,8 +1532,8 @@ class Tally(object): # Determine the number of paired combinations of nuclides # between the two tallies and repeat arrays along nuclide axes - self_num_nuclide_bins = self.mean.shape[1] - other_num_nuclide_bins = other.mean.shape[1] + self_num_nuclide_bins = self.num_nuclides + other_num_nuclide_bins = other.num_nuclides num_nuclide_bins = self_num_nuclide_bins * other_num_nuclide_bins self_repeat_factor = num_nuclide_bins / self_num_nuclide_bins other_tile_factor = num_nuclide_bins / other_num_nuclide_bins @@ -1548,8 +1548,8 @@ class Tally(object): # Determine the number of paired combinations of score bins # between the two tallies and repeat arrays along score axes - self_num_score_bins = self.mean.shape[2] - other_num_score_bins = other.mean.shape[2] + self_num_score_bins = self.num_score_bins + other_num_score_bins = other.num_score_bins num_score_bins = self_num_score_bins * other_num_score_bins self_repeat_factor = num_score_bins / self_num_score_bins other_tile_factor = num_score_bins / other_num_score_bins @@ -1616,7 +1616,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='+') @@ -1695,7 +1694,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='-') @@ -1775,7 +1773,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='*') @@ -1857,7 +1854,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(other, new_tally, binary_op='/') @@ -1939,7 +1935,6 @@ class Tally(object): 'since it does not contain any results.'.format(power.id) raise ValueError(msg) - # FIXME: New tally IDs collide with existing IDs # FIXME: Need to be able to use Tally.get_value self._outer_product(power, new_tally, binary_op='^') @@ -2084,12 +2079,15 @@ class Tally(object): nuclides, 'sum') new_sum_sq = self.get_values(scores, filters, filter_bins, nuclides, 'sum_sq') + new_tally.sum = new_sum new_tally.sum_sq = new_sum_sq new_tally._mean = None new_tally._std_dev = None - ############################ SCORES ########################## + # FIXME: Reorder nuclides, scores, filters + + # SCORES if scores: score_indices = [] @@ -2104,7 +2102,7 @@ class Tally(object): new_tally.remove_score(self.scores[score_index]) new_tally.num_score_bins -= 1 - ############################ NUCLIDES ######################### + # NUCLIDES if nuclides: nuclide_indices = [] @@ -2124,30 +2122,22 @@ class Tally(object): for nuclide_index in nuclide_indices[::-1]: new_tally.remove_nuclide(self.nuclides[nuclide_index]) - ############################ FILTERS ########################## + # FILTERS if filters: - filter_indices = [] # Determine the filter indices from any of the requested filters - for i, filter in enumerate(self.filters): - if filter.type not in filters: - filter_index = self.get_filter_index(filters[i], filter_bins[i][0]) - filter_indices.append(filter_index) + for i, filter_type in enumerate(filters): + filter = new_tally.find_filter(filter_type) # Remove and/or reorder filter bins to user specifications - else: - bin_indices = [] + bin_indices = [] - for filter_bin in filter_bins[i]: - bin_index = self.filters[filter_index].get_bin_index(filter_bin) - bin_indices.append(bin_index) + for filter_bin in filter_bins[i]: + bin_index = filter.get_bin_index(filter_bin) + bin_indices.append(bin_index) - new_bins = self.filters[filter_index].bins[bin_indices] - self.filters[filter_index].bins = new_bins - - # Loop over indices in reverse to remove excluded Filters - for filter_index in filter_indices[::-1]: - new_tally.remove_filter(self.filters[filter_index]) + new_bins = filter.bins[bin_indices] + filter.bins = new_bins # Correct each Filter's stride stride = new_tally.num_nuclides * new_tally.num_score_bins From 587062ad39df850c4b8cd838d78c6a73061323c6 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 2 Aug 2015 23:19:46 -0700 Subject: [PATCH 29/39] Fixed Tally.get_values(...) bug with filter strides --- openmc/tallies.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 8b259c592..ea4975173 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -904,8 +904,16 @@ class Tally(object): # Add indices for each bin in this Filter to the list for bin in bins: - filter_indices[i].append( - self.get_filter_index(filter.type, bin)) + if user_filter: + filter_indices[i].append( + self.get_filter_index(filter.type, bin)) + else: + filter_indices[i].append( + self.get_filter_index(filter.type, bin)) + + # Account for stride in each of the previous filters + reverse_mult = lambda x: [xi * filter.num_bins for xi in x] + filter_indices[:i] = map(reverse_mult, filter_indices[:i]) # Apply outer product sum between all filter bin indices filter_indices = list(map(sum, itertools.product(*filter_indices))) @@ -2111,7 +2119,7 @@ class Tally(object): if isinstance(nuclide, Nuclide): if nuclide.name not in nuclides: - nuclide_index = self.get_nuclide_index(nuclide) + nuclide_index = self.get_nuclide_index(nuclide.name) nuclide_indices.append(nuclide_index) else: if nuclide not in nuclides: From 79055493e00dcc6d12f8578ed8b9c61e22d0f8a2 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Sun, 2 Aug 2015 23:52:36 -0700 Subject: [PATCH 30/39] Shortened nuclide slice block and added docstring to Tally.slice --- openmc/tallies.py | 70 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index ea4975173..75830d8f8 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -2064,16 +2064,70 @@ class Tally(object): A new derived tally which is the absolute value of this tally. """ + new_tally = copy.deepcopy(self) new_tally._mean = np.abs(new_tally.mean) return new_tally def __neg__(self): + """The negated value of this tally. + + Returns + ------- + Tally + A new derived tally which is the negated value of this tally. + + """ + new_tally = self * -1 return new_tally - def slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]): - """ + def get_slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]): + """Build a sliced tally for the specified filters, scores and nuclides. + + This method constructs a new tally to encapsulate a subset of the data + represented by this tally. The subset of data to included in the tally + slice is determined by the scores, filters and nuclides specified in + the input parameters. + + Parameters + ---------- + scores : list + A list of one or more score strings + (e.g., ['absorption', 'nu-fission']; default is []) + + filters : list + A list of filter type strings + (e.g., ['mesh', 'energy']; default is []) + + filter_bins : list + A list of the filter bins corresponding to the filter_types + parameter (e.g., [1, (0., 0.625e-6)]; default is []). Each bin in + the list is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. Each bin is an integer for the + cell instance ID for 'distribcell Filters. Each bin is a 2-tuple of + floats for 'energy' and 'energyout' filters corresponding to the + energy boundaries of the bin of interest. The bin is a (x,y,z) + 3-tuple for 'mesh' filters corresponding to the mesh cell of + interest. The order of the bins in the list must correspond of the + filter_types parameter. + + nuclides : list + A list of nuclide name strings + (e.g., ['U-235', 'U-238']; default is []) + + Returns + ------- + Tally + A new tally which encapsulates the subset of data requested in the + order each filter, nuclide and score is listed in the parameters. + + Raises + ------ + ValueError + When this method is called before the Tally is populated with data + by the StatePoint.read_results() method. + """ # Ensure that StatePoint.read_results() was called first @@ -2116,15 +2170,9 @@ class Tally(object): # Determine the nuclide indices from any of the requested nuclides for nuclide in self.nuclides: - - if isinstance(nuclide, Nuclide): - if nuclide.name not in nuclides: - nuclide_index = self.get_nuclide_index(nuclide.name) - nuclide_indices.append(nuclide_index) - else: - if nuclide not in nuclides: - nuclide_index = self.get_nuclide_index(nuclide) - nuclide_indices.append(nuclide_index) + if nuclide.name not in nuclides: + nuclide_index = self.get_nuclide_index(nuclide.name) + nuclide_indices.append(nuclide_index) # Loop over indices in reverse to remove excluded Nuclides for nuclide_index in nuclide_indices[::-1]: From fa57c16e7e63a55315e577f719973f9d69e2aed7 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Mon, 3 Aug 2015 00:00:40 -0700 Subject: [PATCH 31/39] Renamed _CrossXXX class types to CrossXXX --- openmc/cross.py | 44 ++++++++++++++++++++++---------------------- openmc/tallies.py | 18 +++++++++--------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/openmc/cross.py b/openmc/cross.py index 45c7ce2d8..16b8bd573 100644 --- a/openmc/cross.py +++ b/openmc/cross.py @@ -1,29 +1,29 @@ from openmc import Filter, Nuclide -class _CrossScore(object): +class CrossScore(object): """A special-purpose tally score used to encapsulate all combinations of two tally's scores as a outer product for tally arithmetic. Parameters ---------- - left_score : str or _CrossScore + left_score : str or CrossScore The left score in the outer product - right_score : str or _CrossScore + right_score : str or CrossScore The right score in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's scores with this _CrossNuclide + combine two tally's scores with this CrossNuclide Attributes ---------- - left_score : str or _CrossScore + left_score : str or CrossScore The left score in the outer product - right_score : str or _CrossScore + right_score : str or CrossScore The right score in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's scores with this _CrossNuclide + combine two tally's scores with this CrossNuclide """ @@ -70,29 +70,29 @@ class _CrossScore(object): return string -class _CrossNuclide(object): +class CrossNuclide(object): """A special-purpose nuclide used to encapsulate all combinations of two tally's nuclides as a outer product for tally arithmetic. Parameters ---------- - left_nuclide : Nuclide or _CrossNuclide + left_nuclide : Nuclide or CrossNuclide The left nuclide in the outer product - right_nuclide : Nuclide or _CrossNuclide + right_nuclide : Nuclide or CrossNuclide The right nuclide in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's nuclides with this _CrossNuclide + combine two tally's nuclides with this CrossNuclide Attributes ---------- - left_nuclide : Nuclide or _CrossNuclide + left_nuclide : Nuclide or CrossNuclide The left nuclide in the outer product - right_nuclide : Nuclide or _CrossNuclide + right_nuclide : Nuclide or CrossNuclide The right nuclide in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's nuclides with this _CrossNuclide + combine two tally's nuclides with this CrossNuclide """ @@ -156,29 +156,29 @@ class _CrossNuclide(object): return string -class _CrossFilter(object): +class CrossFilter(object): """A special-purpose filter used to encapsulate all combinations of two tally's filter bins as a outer product for tally arithmetic. Parameters ---------- - left_filter : Filter or _CrossFilter + left_filter : Filter or CrossFilter The left filter in the outer product - right_filter : Filter or _CrossFilter + right_filter : Filter or CrossFilter The right filter in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's filter bins with this _CrossFilter + combine two tally's filter bins with this CrossFilter Attributes ---------- - left_filter : Filter or _CrossFilter + left_filter : Filter or CrossFilter The left filter in the outer product - right_filter : Filter or _CrossFilter + right_filter : Filter or CrossFilter The right filter in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to - combine two tally's filter bins with this _CrossFilter + combine two tally's filter bins with this CrossFilter """ @@ -250,7 +250,7 @@ class _CrossFilter(object): def __repr__(self): - string = '_CrossFilter\n' + string = 'CrossFilter\n' filter_type = '({0} {1} {2})'.format(self.left_filter.type, self.binary_op, self.right_filter.type) diff --git a/openmc/tallies.py b/openmc/tallies.py index 75830d8f8..4f30a9487 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -10,7 +10,7 @@ import sys import numpy as np from openmc import Mesh, Filter, Trigger, Nuclide -from openmc.cross import _CrossScore, _CrossNuclide, _CrossFilter +from openmc.cross import CrossScore, CrossNuclide, CrossFilter from openmc.summary import Summary from openmc.checkvalue import check_type, check_value, check_greater_than from openmc.clean_xml import * @@ -329,7 +329,7 @@ class Tally(object): """ - if not isinstance(filter, (Filter, _CrossFilter)): + if not isinstance(filter, (Filter, CrossFilter)): msg = 'Unable to add Filter "{0}" to Tally ID={1} since it is ' \ 'not a Filter object'.format(filter, self.id) raise ValueError(msg) @@ -358,7 +358,7 @@ class Tally(object): """ - if not isinstance(score, (basestring, _CrossScore)): + if not isinstance(score, (basestring, CrossScore)): msg = 'Unable to add score "{0}" to Tally ID={1} since it is ' \ 'not a string'.format(score, self.id) raise ValueError(msg) @@ -1040,7 +1040,7 @@ class Tally(object): # Split CrossFilters into separate filters split_filters = [] for filter in self.filters: - if isinstance(filter, _CrossFilter): + if isinstance(filter, CrossFilter): split_filters.extend(filter.split_filters()) else: split_filters.append(filter) @@ -1437,8 +1437,8 @@ class Tally(object): This is a helper method for the tally arithmetic methods. The filters, scores and nuclides from both tallies are enumerated into all possible - combinations and expressed as _CrossFilter, _CrossScore and - _CrossNuclide objects in the new derived tally. + combinations and expressed as CrossFilter, CrossScore and + CrossNuclide objects in the new derived tally. Parameters ---------- @@ -1467,7 +1467,7 @@ class Tally(object): else: all_filters = [self.filters, other.filters] for self_filter, other_filter in itertools.product(*all_filters): - new_filter = _CrossFilter(self_filter, other_filter, binary_op) + new_filter = CrossFilter(self_filter, other_filter, binary_op) new_tally.add_filter(new_filter) # Generate score "outer products" @@ -1477,7 +1477,7 @@ class Tally(object): else: all_scores = [self.scores, other.scores] for self_score, other_score in itertools.product(*all_scores): - new_score = _CrossScore(self_score, other_score, binary_op) + new_score = CrossScore(self_score, other_score, binary_op) new_tally.add_score(new_score) # Generate nuclide "outer products" @@ -1487,7 +1487,7 @@ class Tally(object): else: all_nuclides = [self.nuclides, other.nuclides] for self_nuclide, other_nuclide in itertools.product(*all_nuclides): - new_nuclide = _CrossNuclide(self_nuclide, other_nuclide, binary_op) + new_nuclide = CrossNuclide(self_nuclide, other_nuclide, binary_op) new_tally.add_nuclide(new_nuclide) def _align_tally_data(self, other): From 0625350869223ed5247e88348ad1f9b7c1cc2de4 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Mon, 3 Aug 2015 00:26:42 -0700 Subject: [PATCH 32/39] Added __eq__ methods to CrossXXX classes to allow for Tally.get_values with derived Tallies --- openmc/cross.py | 9 +++++++++ openmc/tallies.py | 10 ---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/openmc/cross.py b/openmc/cross.py index 16b8bd573..123886ddf 100644 --- a/openmc/cross.py +++ b/openmc/cross.py @@ -64,6 +64,9 @@ class CrossScore(object): def binary_op(self, binary_op): self._binary_op = binary_op + def __eq__(self, other): + return str(other) == str(self) + def __repr__(self): string = '({0} {1} {2})'.format(self.left_score, self.binary_op, self.right_score) @@ -133,6 +136,9 @@ class CrossNuclide(object): def binary_op(self, binary_op): self._binary_op = binary_op + def __eq__(self, other): + return str(other) == str(self) + def __repr__(self): string = '' @@ -248,6 +254,9 @@ class CrossFilter(object): def binary_op(self, binary_op): self._binary_op = binary_op + def __eq__(self, other): + return str(other) == str(self) + def __repr__(self): string = 'CrossFilter\n' diff --git a/openmc/tallies.py b/openmc/tallies.py index 4f30a9487..6ac1e3769 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1624,8 +1624,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_value - self._outer_product(other, new_tally, binary_op='+') data = self._align_tally_data(other) new_tally._mean = data['self']['mean'] + data['other']['mean'] @@ -1702,8 +1700,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_value - self._outer_product(other, new_tally, binary_op='-') data = self._align_tally_data(other) new_tally._mean = data['self']['mean'] - data['other']['mean'] @@ -1781,8 +1777,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_value - self._outer_product(other, new_tally, binary_op='*') data = self._align_tally_data(other) self_rel_err = data['self']['std. dev.'] / data['self']['mean'] @@ -1862,8 +1856,6 @@ class Tally(object): 'since it does not contain any results.'.format(other.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_value - self._outer_product(other, new_tally, binary_op='/') data = self._align_tally_data(other) self_rel_err = data['self']['std. dev.'] / data['self']['mean'] @@ -1943,8 +1935,6 @@ class Tally(object): 'since it does not contain any results.'.format(power.id) raise ValueError(msg) - # FIXME: Need to be able to use Tally.get_value - self._outer_product(power, new_tally, binary_op='^') data = self._align_tally_data(power) mean_ratio = data['other']['mean'] / data['self']['mean'] From f38c3bccfacbc347ad1de3b6a8102aaf73086c36 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Mon, 3 Aug 2015 00:28:13 -0700 Subject: [PATCH 33/39] Removed extraneous README comment --- openmc/tallies.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 6ac1e3769..ff44f3135 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -2137,8 +2137,6 @@ class Tally(object): new_tally._mean = None new_tally._std_dev = None - # FIXME: Reorder nuclides, scores, filters - # SCORES if scores: score_indices = [] From a11c1993c67a6abe604ef3d9e8abd7c7562a09aa Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Mon, 3 Aug 2015 22:02:44 -0700 Subject: [PATCH 34/39] Added get_bin_index, __hash__ and __deepcopy__ methods to CrossFilter --- openmc/cross.py | 78 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/openmc/cross.py b/openmc/cross.py index 123886ddf..735bb4cd2 100644 --- a/openmc/cross.py +++ b/openmc/cross.py @@ -210,6 +210,28 @@ class CrossFilter(object): if binary_op is not None: self.binary_op = binary_op + def __hash__(self): + return hash((self.type, self.bins)) + + def __deepcopy__(self, memo): + existing = memo.get(id(self)) + + # If this is the first time we have tried to copy this object, create a copy + if existing is None: + clone = type(self).__new__(type(self)) + clone._left_filter = self.left_filter + clone._right_filter = self.right_filter + clone._type = self.type + clone._bins = self.bins + clone._num_bins = self.num_bins + memo[id(self)] = clone + + return clone + + # If this object has been copied before, return the first copy made + else: + return existing + @property def left_filter(self): return self._left_filter @@ -257,20 +279,6 @@ class CrossFilter(object): def __eq__(self, other): return str(other) == str(self) - def __repr__(self): - - string = 'CrossFilter\n' - filter_type = '({0} {1} {2})'.format(self.left_filter.type, - self.binary_op, - self.right_filter.type) - filter_bins = '({0} {1} {2})'.format(self.left_filter.bins, - self.binary_op, - self.right_filter.bins) - string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', filter_type) - string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', filter_bins) - return string - - def split_filters(self): split_filters = [] @@ -289,4 +297,44 @@ class CrossFilter(object): else: split_filters.extend(self.right_filter.split_filters()) - return split_filters \ No newline at end of file + return split_filters + + def get_bin_index(self, filter_bin): + """Returns the index in the CrossFilter for some bin. + + Parameters + ---------- + filter_bin : 2-tuple + A 2-tuple where each value corresponds to the bin of interest + in the left and right filter, respectively. A bin is the integer + ID for 'material', 'surface', 'cell', 'cellborn', and 'universe' + Filters. The bin is an integer for the cell instance ID for + 'distribcell' Filters. The bin is a 2-tuple of floats for 'energy' + and 'energyout' filters corresponding to the energy boundaries of + the bin of interest. The bin is a (x,y,z) 3-tuple for 'mesh' + filters corresponding to the mesh cell of interest. + + Returns + ------- + filter_index : int + The index in the Tally data array for this filter bin. + + """ + + left_index = self.left_filter.get_bin_index(filter_bin[0]) + right_index = self.right_filter.get_bin_index(filter_bin[0]) + filter_index = left_index * self.right_filter.num_bins + right_index + return filter_index + + def __repr__(self): + + string = 'CrossFilter\n' + filter_type = '({0} {1} {2})'.format(self.left_filter.type, + self.binary_op, + self.right_filter.type) + filter_bins = '({0} {1} {2})'.format(self.left_filter.bins, + self.binary_op, + self.right_filter.bins) + string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', filter_type) + string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', filter_bins) + return string From 39ecaa225b0c3da04709b34b33f02ead7cdfe5b3 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Thu, 6 Aug 2015 22:53:48 -0700 Subject: [PATCH 35/39] Simplified filter index building in Tally.get_values(...) per @paulromanos comments --- openmc/tallies.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index bb9aaf06d..ff32ba7b7 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -872,9 +872,6 @@ class Tally(object): # Loop over all of the Tally's Filters for i, filter in enumerate(self.filters): - # Initialize empty list of indices for this Filter's bins - filter_indices.append([]) - user_filter = False # If a user-requested Filter, get the user-requested bins @@ -902,18 +899,16 @@ class Tally(object): else: bins = filter.bins + # Initialize a NumPy array for the Filter bin indices + filter_indices.append(np.zeros(len(bins), dtype=np.int)) + # Add indices for each bin in this Filter to the list - for bin in bins: - if user_filter: - filter_indices[i].append( - self.get_filter_index(filter.type, bin)) - else: - filter_indices[i].append( - self.get_filter_index(filter.type, bin)) + for j, bin in enumerate(bins): + filter_index = self.get_filter_index(filter.type, bin) + filter_indices[i][j] = filter_index # Account for stride in each of the previous filters - reverse_mult = lambda x: [xi * filter.num_bins for xi in x] - filter_indices[:i] = map(reverse_mult, filter_indices[:i]) + filter_indices[:i] *= filter.num_bins # Apply outer product sum between all filter bin indices filter_indices = list(map(sum, itertools.product(*filter_indices))) @@ -1010,7 +1005,7 @@ class Tally(object): """ # Ensure that StatePoint.read_results() was called first - if self._sum is None or self._sum_sq is None: + if self.mean is None or self.std_dev is None: msg = 'The Tally ID={0} has no data to return. Call the ' \ 'StatePoint.read_results() method before using ' \ 'Tally.get_pandas_dataframe(...)'.format(self.id) From 3890ed4d673c18fd18ace5746a824c21f9de6c89 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Thu, 6 Aug 2015 23:03:50 -0700 Subject: [PATCH 36/39] Simplified tile and repeat factors in Tally._align_tally_data() --- openmc/tallies.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index ff32ba7b7..4689da014 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1441,8 +1441,8 @@ class Tally(object): The tally on the right hand side of the outer product new_tally: Tally The new tally to represent the outer product - op : str - The binary operation in the outer product ('+', '-', '*', '/', '^') + binary_op : {'+', '-', '*', '/', '^'} + The binary operation in the outer product """ @@ -1519,11 +1519,8 @@ class Tally(object): # Determine the number of paired combinations of filter bins # between the two tallies and repeat arrays along filter axes - self_num_filter_bins = self.num_filter_bins - other_num_filter_bins = other.num_filter_bins - num_filter_bins = self_num_filter_bins * other_num_filter_bins - self_repeat_factor = num_filter_bins / self_num_filter_bins - other_tile_factor = num_filter_bins / other_num_filter_bins + self_repeat_factor = other.num_filter_bins + other_tile_factor = self.num_filter_bins # Replicate the data self_mean = np.repeat(self_mean, self_repeat_factor, axis=0) @@ -1535,11 +1532,8 @@ class Tally(object): # Determine the number of paired combinations of nuclides # between the two tallies and repeat arrays along nuclide axes - self_num_nuclide_bins = self.num_nuclides - other_num_nuclide_bins = other.num_nuclides - num_nuclide_bins = self_num_nuclide_bins * other_num_nuclide_bins - self_repeat_factor = num_nuclide_bins / self_num_nuclide_bins - other_tile_factor = num_nuclide_bins / other_num_nuclide_bins + self_repeat_factor = other.num_nuclides + other_tile_factor = self.num_nuclides # Replicate the data self_mean = np.repeat(self_mean, self_repeat_factor, axis=1) @@ -1551,11 +1545,8 @@ class Tally(object): # Determine the number of paired combinations of score bins # between the two tallies and repeat arrays along score axes - self_num_score_bins = self.num_score_bins - other_num_score_bins = other.num_score_bins - num_score_bins = self_num_score_bins * other_num_score_bins - self_repeat_factor = num_score_bins / self_num_score_bins - other_tile_factor = num_score_bins / other_num_score_bins + self_repeat_factor = other.num_score_bins + other_tile_factor = self.num_score_bins # Replicate the data self_mean = np.repeat(self_mean, self_repeat_factor, axis=2) From 9db3a343cab6ca41a1b48fb67af130b48a45e9fb Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Thu, 6 Aug 2015 23:12:22 -0700 Subject: [PATCH 37/39] Made all tallies created with tally arithmetic have name "derived" --- openmc/tallies.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 4689da014..325eca925 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3,7 +3,7 @@ import copy import os import pickle import itertools -from numbers import Integral, Rational +from numbers import Integral, Real from xml.etree import ElementTree as ET import sys @@ -1582,7 +1582,7 @@ class Tally(object): Parameters ---------- - other : Tally or Integer or Rational + other : Tally or Real The tally or scalar value to add to this tally Returns @@ -1599,7 +1599,7 @@ class Tally(object): 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally() + new_tally = Tally(name='derived') new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -1613,10 +1613,10 @@ class Tally(object): self._outer_product(other, new_tally, binary_op='+') data = self._align_tally_data(other) new_tally._mean = data['self']['mean'] + data['other']['mean'] - new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ + new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + data['other']['std. dev.']**2) - elif isinstance(other, (Integral, Rational)): + elif isinstance(other, Real): new_tally.name = self.name new_tally._mean = self._mean + other @@ -1658,7 +1658,7 @@ class Tally(object): Parameters ---------- - other : Tally or Integer or Rational + other : Tally or Real The tally or scalar value to subtract from this tally Returns @@ -1675,7 +1675,7 @@ class Tally(object): 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally() + new_tally = Tally(name='derived') new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -1689,10 +1689,10 @@ class Tally(object): self._outer_product(other, new_tally, binary_op='-') data = self._align_tally_data(other) new_tally._mean = data['self']['mean'] - data['other']['mean'] - new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + \ + new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + data['other']['std. dev.']**2) - elif isinstance(other, (Integral, Rational)): + elif isinstance(other, Real): new_tally.name = self.name new_tally._mean = self._mean - other @@ -1735,7 +1735,7 @@ class Tally(object): Parameters ---------- - other : Tally or Integer or Rational + other : Tally or Real The tally or scalar value to multiply with this tally Returns @@ -1752,7 +1752,7 @@ class Tally(object): 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally() + new_tally = Tally(name='derived') new_tally.with_batch_statistics = True if isinstance(other, Tally): @@ -1771,7 +1771,7 @@ class Tally(object): new_tally._std_dev = np.abs(new_tally.mean) * \ np.sqrt(self_rel_err**2 + other_rel_err**2) - elif isinstance(other, (Integral, Rational)): + elif isinstance(other, Real): new_tally.name = self.name new_tally._mean = self._mean * other @@ -1814,7 +1814,7 @@ class Tally(object): Parameters ---------- - other : Tally or Integer or Rational + other : Tally or Real The tally or scalar value to divide this tally by Returns @@ -1850,7 +1850,7 @@ class Tally(object): new_tally._std_dev = np.abs(new_tally.mean) * \ np.sqrt(self_rel_err**2 + other_rel_err**2) - elif isinstance(other, (Integral, Rational)): + elif isinstance(other, Real): new_tally.name = self.name new_tally._mean = self._mean / other @@ -1893,7 +1893,7 @@ class Tally(object): Parameters ---------- - other : Tally or Integer or Rational + power : Tally or Real The tally or scalar value exponent Returns @@ -1930,7 +1930,7 @@ class Tally(object): new_tally._std_dev = np.abs(new_tally.mean) * \ np.sqrt(first_term**2 + second_term**2) - elif isinstance(power, (Integral, Rational)): + elif isinstance(power, Real): new_tally.name = self.name new_tally._mean = self._mean ** power @@ -1962,7 +1962,7 @@ class Tally(object): Parameters ---------- - other : Integer or Rational + other : Integer or Real The scalar value to add to this tally Returns @@ -1981,7 +1981,7 @@ class Tally(object): Parameters ---------- - other : Integer or Rational + other : Integer or Real The scalar value to subtract this tally from Returns @@ -2000,7 +2000,7 @@ class Tally(object): Parameters ---------- - other : Integer or Rational + other : Integer or Real The scalar value to multiply with this tally Returns @@ -2019,7 +2019,7 @@ class Tally(object): Parameters ---------- - other : Integer or Rational + other : Integer or Real The scalar value to divide by this tally Returns @@ -2029,7 +2029,7 @@ class Tally(object): """ - return self * (1. / other) + return other * self**-1 def __pos__(self): """The absolute value of this tally. From 1154c07438cc5e66530464b6bf7ee5177d543ba5 Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Thu, 6 Aug 2015 23:41:38 -0700 Subject: [PATCH 38/39] Refactored Tally._outer_product(...) and operator overloads per @paulromano suggestion --- openmc/tallies.py | 239 ++++++++++++++++++++++++---------------------- 1 file changed, 125 insertions(+), 114 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 325eca925..733d05372 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -101,6 +101,7 @@ class Tally(object): self._mean = None self._std_dev = None self._with_batch_statistics = False + self._derived = False def __deepcopy__(self, memo): existing = memo.get(id(self)) @@ -119,6 +120,7 @@ class Tally(object): clone._std_dev = copy.deepcopy(self.std_dev, memo) clone._with_summary = self.with_summary clone._with_batch_statistics = self.with_batch_statistics + clone._derived = self.derived clone._filters = [] for filter in self.filters: @@ -281,6 +283,10 @@ class Tally(object): def with_batch_statistics(self): return self._with_batch_statistics + @property + def derived(self): + return self._derived + @estimator.setter def estimator(self, estimator): check_value('estimator', estimator, ['analog', 'tracklength']) @@ -410,8 +416,8 @@ class Tally(object): """ if score not in self.scores: - msg = 'Unable to remove score "{0}" from Tally ID="{1}" since the ' \ - 'Tally does not contain this score'.format(score, self.id) + msg = 'Unable to remove score "{0}" from Tally ID="{1}" since ' \ + 'the Tally does not contain this score'.format(score, self.id) ValueError(msg) self._scores.remove(score) @@ -1006,7 +1012,7 @@ class Tally(object): # Ensure that StatePoint.read_results() was called first if self.mean is None or self.std_dev is None: - msg = 'The Tally ID={0} has no data to return. Call the ' \ + msg = 'The Tally ID="{0}" has no data to return. Call the ' \ 'StatePoint.read_results() method before using ' \ 'Tally.get_pandas_dataframe(...)'.format(self.id) raise KeyError(msg) @@ -1427,7 +1433,7 @@ class Tally(object): # Pickle the Tally results to a file pickle.dump(tally_results, open(filename, 'wb')) - def _outer_product(self, other, new_tally, binary_op): + def _outer_product(self, other, binary_op): """Combines filters, scores and nuclides with another tally. This is a helper method for the tally arithmetic methods. The filters, @@ -1439,14 +1445,66 @@ class Tally(object): ---------- other : Tally The tally on the right hand side of the outer product - new_tally: Tally - The new tally to represent the outer product binary_op : {'+', '-', '*', '/', '^'} The binary operation in the outer product + Returns + ------- + Tally + A new Tally outer that is the outer product with this one. + + Raises + ------ + ValueError + When this method is called before the other tally is populated + with data by the StatePoint.read_results() method. + """ - new_tally.name = '({0} {1} {2})'.format(self.name, binary_op, other.name) + # Check that results have been read + if not other.derived and other.sum is None: + msg = 'Unable to use tally arithmetic with Tally ID="{0}" ' \ + 'since it does not contain any results.'.format(other.id) + raise ValueError(msg) + + new_name = '({0} {1} {2})'.format(self.name, binary_op, other.name) + new_tally = Tally(name=new_name) + new_tally.with_batch_statistics = True + + data = self._align_tally_data(other) + + if binary_op == '+': + new_tally._mean = data['self']['mean'] + data['other']['mean'] + new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + + data['other']['std. dev.']**2) + elif binary_op == '-': + data = self._align_tally_data(other) + new_tally._mean = data['self']['mean'] - data['other']['mean'] + new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + + data['other']['std. dev.']**2) + elif binary_op == '*': + data = self._align_tally_data(other) + self_rel_err = data['self']['std. dev.'] / data['self']['mean'] + other_rel_err = data['other']['std. dev.'] / data['other']['mean'] + new_tally._mean = data['self']['mean'] * data['other']['mean'] + new_tally._std_dev = np.abs(new_tally.mean) * \ + np.sqrt(self_rel_err**2 + other_rel_err**2) + elif binary_op == '/': + data = self._align_tally_data(other) + self_rel_err = data['self']['std. dev.'] / data['self']['mean'] + other_rel_err = data['other']['std. dev.'] / data['other']['mean'] + new_tally._mean = data['self']['mean'] / data['other']['mean'] + new_tally._std_dev = np.abs(new_tally.mean) * \ + np.sqrt(self_rel_err**2 + other_rel_err**2) + elif binary_op == '^': + data = self._align_tally_data(power) + mean_ratio = data['other']['mean'] / data['self']['mean'] + first_term = mean_ratio * data['self']['std. dev.'] + second_term = \ + np.log(data['self']['mean']) * data['other']['std. dev.'] + new_tally._mean = data['self']['mean'] ** data['other']['mean'] + new_tally._std_dev = np.abs(new_tally.mean) * \ + np.sqrt(first_term**2 + second_term**2) if self.estimator == other.estimator: new_tally.estimator = self.estimator @@ -1485,6 +1543,8 @@ class Tally(object): new_nuclide = CrossNuclide(self_nuclide, other_nuclide, binary_op) new_tally.add_nuclide(new_nuclide) + return new_tally + def _align_tally_data(self, other): """Aligns data from two tallies for tally arithmetic. @@ -1508,6 +1568,7 @@ class Tally(object): A dictionary of dictionaries to "aligned" 'mean' and 'std. dev' NumPy arrays for each tally's data. + """ self_mean = copy.deepcopy(self.mean) @@ -1591,33 +1652,26 @@ class Tally(object): A new derived tally which is the sum of this tally and the other tally or scalar value in the addition. + Raises + ------ + ValueError + When this method is called before the Tally is populated with data + by the StatePoint.read_results() method. + """ # Check that results have been read - if self.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + if not self.derived and self.sum is None: + msg = 'Unable to use tally arithmetic with Tally ID="{0}" ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - if isinstance(other, Tally): - - # Check that results have been read - if other.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(other.id) - raise ValueError(msg) - - self._outer_product(other, new_tally, binary_op='+') - data = self._align_tally_data(other) - new_tally._mean = data['self']['mean'] + data['other']['mean'] - new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + - data['other']['std. dev.']**2) + new_tally = self._outer_product(other, binary_op='+') elif isinstance(other, Real): - + new_tally = Tally(name='derived') + new_tally.with_batch_statistics = True new_tally.name = self.name new_tally._mean = self._mean + other new_tally._std_dev = self._std_dev @@ -1634,7 +1688,7 @@ class Tally(object): new_tally.add_score(score) else: - msg = 'Unable to add {0} to Tally ID={1}'.format(other, self.id) + msg = 'Unable to add "{0}" to Tally ID="{1}"'.format(other, self.id) raise ValueError(msg) return new_tally @@ -1667,33 +1721,24 @@ class Tally(object): A new derived tally which is the difference of this tally and the other tally or scalar value in the subtraction. + Raises + ------ + ValueError + When this method is called before the Tally is populated with data + by the StatePoint.read_results() method. + """ # Check that results have been read - if self.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + if not self.derived and self.sum is None: + msg = 'Unable to use tally arithmetic with Tally ID="{0}" ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - if isinstance(other, Tally): - - # Check that results have been read - if other.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(other.id) - raise ValueError(msg) - - self._outer_product(other, new_tally, binary_op='-') - data = self._align_tally_data(other) - new_tally._mean = data['self']['mean'] - data['other']['mean'] - new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + - data['other']['std. dev.']**2) + new_tally = self._outer_product(other, binary_op='-') elif isinstance(other, Real): - new_tally.name = self.name new_tally._mean = self._mean - other new_tally._std_dev = self._std_dev @@ -1710,8 +1755,8 @@ class Tally(object): new_tally.add_score(score) else: - msg = 'Unable to subtract {0} from Tally ' \ - 'ID={1}'.format(other, self.id) + msg = 'Unable to subtract "{0}" from Tally ' \ + 'ID="{1}"'.format(other, self.id) raise ValueError(msg) return new_tally @@ -1744,35 +1789,24 @@ class Tally(object): A new derived tally which is the product of this tally and the other tally or scalar value in the multiplication. + Raises + ------ + ValueError + When this method is called before the Tally is populated with data + by the StatePoint.read_results() method. + """ # Check that results have been read - if self.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + if not self.derived and self.sum is None: + msg = 'Unable to use tally arithmetic with Tally ID="{0}" ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - if isinstance(other, Tally): - - # Check that results have been read - if other.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(other.id) - raise ValueError(msg) - - self._outer_product(other, new_tally, binary_op='*') - data = self._align_tally_data(other) - self_rel_err = data['self']['std. dev.'] / data['self']['mean'] - other_rel_err = data['other']['std. dev.'] / data['other']['mean'] - new_tally._mean = data['self']['mean'] * data['other']['mean'] - new_tally._std_dev = np.abs(new_tally.mean) * \ - np.sqrt(self_rel_err**2 + other_rel_err**2) + new_tally = self._outer_product(other, binary_op='*') elif isinstance(other, Real): - new_tally.name = self.name new_tally._mean = self._mean * other new_tally._std_dev = self._std_dev * np.abs(other) @@ -1789,8 +1823,8 @@ class Tally(object): new_tally.add_score(score) else: - msg = 'Unable to multiply Tally ID={1} ' \ - 'by {0}'.format(self.id, other) + msg = 'Unable to multiply Tally ID="{0}" ' \ + 'by "{1}"'.format(self.id, other) raise ValueError(msg) return new_tally @@ -1823,35 +1857,24 @@ class Tally(object): A new derived tally which is the dividend of this tally and the other tally or scalar value in the division. + Raises + ------ + ValueError + When this method is called before the Tally is populated with data + by the StatePoint.read_results() method. + """ # Check that results have been read - if self.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + if not self.derived and self.sum is None: + msg = 'Unable to use tally arithmetic with Tally ID="{0}" ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - if isinstance(other, Tally): - - # Check that results have been read - if other.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(other.id) - raise ValueError(msg) - - self._outer_product(other, new_tally, binary_op='/') - data = self._align_tally_data(other) - self_rel_err = data['self']['std. dev.'] / data['self']['mean'] - other_rel_err = data['other']['std. dev.'] / data['other']['mean'] - new_tally._mean = data['self']['mean'] / data['other']['mean'] - new_tally._std_dev = np.abs(new_tally.mean) * \ - np.sqrt(self_rel_err**2 + other_rel_err**2) + new_tally = self._outer_product(other, binary_op='/') elif isinstance(other, Real): - new_tally.name = self.name new_tally._mean = self._mean / other new_tally._std_dev = self._std_dev * np.abs(1. / other) @@ -1868,8 +1891,8 @@ class Tally(object): new_tally.add_score(score) else: - msg = 'Unable to divide Tally ID={0} ' \ - 'by {1}'.format(self.id, other) + msg = 'Unable to divide Tally ID="{0}" ' \ + 'by "{1}"'.format(self.id, other) raise ValueError(msg) return new_tally @@ -1902,36 +1925,24 @@ class Tally(object): A new derived tally which is this tally raised to the power of the other tally or scalar value in the exponentiation. + Raises + ------ + ValueError + When this method is called before the Tally is populated with data + by the StatePoint.read_results() method. + """ # Check that results have been read - if self.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + if not self.derived and self.sum is None: + msg = 'Unable to use tally arithmetic with Tally ID="{0}" ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) - new_tally = Tally(name='derived') - new_tally.with_batch_statistics = True - if isinstance(power, Tally): - - # Check that results have been read - if power.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ - 'since it does not contain any results.'.format(power.id) - raise ValueError(msg) - - self._outer_product(power, new_tally, binary_op='^') - data = self._align_tally_data(power) - mean_ratio = data['other']['mean'] / data['self']['mean'] - first_term = mean_ratio * data['self']['std. dev.'] - second_term = np.log(data['self']['mean']) * data['other']['std. dev.'] - new_tally._mean = data['self']['mean'] ** data['other']['mean'] - new_tally._std_dev = np.abs(new_tally.mean) * \ - np.sqrt(first_term**2 + second_term**2) + new_tally = self._outer_product(power, binary_op='^') elif isinstance(power, Real): - new_tally.name = self.name new_tally._mean = self._mean ** power self_rel_err = self.std_dev / self.mean @@ -1949,8 +1960,8 @@ class Tally(object): new_tally.add_score(score) else: - msg = 'Unable to raise Tally ID={0} to ' \ - 'power {1}'.format(self.id, power) + msg = 'Unable to raise Tally ID="{0}" to ' \ + 'power "{1}"'.format(self.id, power) raise ValueError(msg) return new_tally @@ -2108,7 +2119,7 @@ class Tally(object): # Ensure that StatePoint.read_results() was called first if self.sum is None: - msg = 'Unable to use tally arithmetic with Tally ID={0} ' \ + msg = 'Unable to use tally arithmetic with Tally ID="{0}" ' \ 'since it does not contain any results.'.format(self.id) raise ValueError(msg) From d0dde6f5a8dbc3d37ffe916224dd01d61e8cc7ae Mon Sep 17 00:00:00 2001 From: Will Boyd Date: Fri, 7 Aug 2015 07:31:10 -0700 Subject: [PATCH 39/39] Changed reverse iteration of filter bins, indices to use reversed(...) --- openmc/tallies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 733d05372..fa41f5c7f 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -2145,7 +2145,7 @@ class Tally(object): score_indices.append(score_index) # Loop over indices in reverse to remove excluded scores - for score_index in score_indices[::-1]: + for score_index in reversed(score_indices): new_tally.remove_score(self.scores[score_index]) new_tally.num_score_bins -= 1 @@ -2160,7 +2160,7 @@ class Tally(object): nuclide_indices.append(nuclide_index) # Loop over indices in reverse to remove excluded Nuclides - for nuclide_index in nuclide_indices[::-1]: + for nuclide_index in reversed(nuclide_indices): new_tally.remove_nuclide(self.nuclides[nuclide_index]) # FILTERS