Refactored Tally._outer_product(...) and operator overloads per @paulromano suggestion

This commit is contained in:
Will Boyd 2015-08-06 23:41:38 -07:00
parent 9db3a343ca
commit 1154c07438

View file

@ -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)