mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-28 22:26:08 -04:00
Merge pull request #550 from wbinventor/distribcell-tally-sum
Fixed Distribcell Tally Summation
This commit is contained in:
commit
bd89b423bd
11 changed files with 818 additions and 219 deletions
409
openmc/aggregate.py
Normal file
409
openmc/aggregate.py
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
import sys
|
||||
from numbers import Integral
|
||||
|
||||
import numpy as np
|
||||
|
||||
from openmc import Filter, Nuclide
|
||||
from openmc.cross import CrossScore, CrossNuclide, CrossFilter
|
||||
from openmc.filter import _FILTER_TYPES
|
||||
import openmc.checkvalue as cv
|
||||
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
basestring = str
|
||||
|
||||
# Acceptable tally aggregation operations
|
||||
_TALLY_AGGREGATE_OPS = ['sum', 'mean']
|
||||
|
||||
|
||||
class AggregateScore(object):
|
||||
"""A special-purpose tally score used to encapsulate an aggregate of a
|
||||
subset or all of tally's scores for tally aggregation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
scores : Iterable of str or CrossScore
|
||||
The scores included in the aggregation
|
||||
aggregate_op : str
|
||||
The tally aggregation operator (e.g., 'sum', 'mean', etc.) used
|
||||
to aggregate across a tally's scores with this AggregateScore
|
||||
|
||||
Attributes
|
||||
----------
|
||||
scores : Iterable of str or CrossScore
|
||||
The scores included in the aggregation
|
||||
aggregate_op : str
|
||||
The tally aggregation operator (e.g., 'sum', 'mean', etc.) used
|
||||
to aggregate across a tally's scores with this AggregateScore
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, scores=None, aggregate_op=None):
|
||||
|
||||
self._scores = None
|
||||
self._aggregate_op = None
|
||||
|
||||
if scores is not None:
|
||||
self.scores = scores
|
||||
if aggregate_op is not None:
|
||||
self.aggregate_op = aggregate_op
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
def __eq__(self, other):
|
||||
return str(other) == str(self)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
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._scores = self.scores
|
||||
clone._aggregate_op = self.aggregate_op
|
||||
|
||||
memo[id(self)] = clone
|
||||
|
||||
return clone
|
||||
|
||||
# If this object has been copied before, return the first copy made
|
||||
else:
|
||||
return existing
|
||||
|
||||
def __repr__(self):
|
||||
string = ', '.join(map(str, self.scores))
|
||||
string = '{0}({1})'.format(self.aggregate_op, string)
|
||||
return string
|
||||
|
||||
@property
|
||||
def scores(self):
|
||||
return self._scores
|
||||
|
||||
@property
|
||||
def aggregate_op(self):
|
||||
return self._aggregate_op
|
||||
|
||||
@scores.setter
|
||||
def scores(self, scores):
|
||||
cv.check_iterable_type('scores', scores, basestring)
|
||||
self._scores = scores
|
||||
|
||||
@aggregate_op.setter
|
||||
def aggregate_op(self, aggregate_op):
|
||||
cv.check_type('aggregate_op', aggregate_op, (basestring, CrossScore))
|
||||
cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS)
|
||||
self._aggregate_op = aggregate_op
|
||||
|
||||
|
||||
class AggregateNuclide(object):
|
||||
"""A special-purpose tally nuclide used to encapsulate an aggregate of a
|
||||
subset or all of tally's nuclides for tally aggregation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nuclides : Iterable of str or Nuclide or CrossNuclide
|
||||
The nuclides included in the aggregation
|
||||
aggregate_op : str
|
||||
The tally aggregation operator (e.g., 'sum', 'mean', etc.) used
|
||||
to aggregate across a tally's nuclides with this AggregateNuclide
|
||||
|
||||
Attributes
|
||||
----------
|
||||
nuclides : Iterable of str or Nuclide or CrossNuclide
|
||||
The nuclides included in the aggregation
|
||||
aggregate_op : str
|
||||
The tally aggregation operator (e.g., 'sum', 'mean', etc.) used
|
||||
to aggregate across a tally's nuclides with this AggregateNuclide
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, nuclides=None, aggregate_op=None):
|
||||
|
||||
self._nuclides = None
|
||||
self._aggregate_op = None
|
||||
|
||||
if nuclides is not None:
|
||||
self.nuclides = nuclides
|
||||
if aggregate_op is not None:
|
||||
self.aggregate_op = aggregate_op
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
def __eq__(self, other):
|
||||
return str(other) == str(self)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
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._nuclides = self.nuclides
|
||||
clone._aggregate_op = self._aggregate_op
|
||||
|
||||
memo[id(self)] = clone
|
||||
|
||||
return clone
|
||||
|
||||
# If this object has been copied before, return the first copy made
|
||||
else:
|
||||
return existing
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
# Append each nuclide in the aggregate to the string
|
||||
string = '{0}('.format(self.aggregate_op)
|
||||
names = [nuclide.name if isinstance(nuclide, Nuclide) else str(nuclide)
|
||||
for nuclide in self.nuclides]
|
||||
string += ', '.join(map(str, names)) + ')'
|
||||
return string
|
||||
|
||||
@property
|
||||
def nuclides(self):
|
||||
return self._nuclides
|
||||
|
||||
@property
|
||||
def aggregate_op(self):
|
||||
return self._aggregate_op
|
||||
|
||||
@nuclides.setter
|
||||
def nuclides(self, nuclides):
|
||||
cv.check_iterable_type('nuclides', nuclides,
|
||||
(basestring, Nuclide, CrossNuclide))
|
||||
self._nuclides = nuclides
|
||||
|
||||
@aggregate_op.setter
|
||||
def aggregate_op(self, aggregate_op):
|
||||
cv.check_type('aggregate_op', aggregate_op, basestring)
|
||||
cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS)
|
||||
self._aggregate_op = aggregate_op
|
||||
|
||||
|
||||
class AggregateFilter(object):
|
||||
"""A special-purpose tally filter used to encapsulate an aggregate of a
|
||||
subset or all of a tally filter's bins for tally aggregation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aggregate_filter : Filter or CrossFilter
|
||||
The filter included in the aggregation
|
||||
bins : Iterable of tuple
|
||||
The filter bins included in the aggregation
|
||||
aggregate_op : str
|
||||
The tally aggregation operator (e.g., 'sum', 'mean', etc.) used
|
||||
to aggregate across a tally filter's bins with this AggregateFilter
|
||||
|
||||
Attributes
|
||||
----------
|
||||
type : str
|
||||
The type of the aggregatefilter (e.g., 'sum(energy)', 'sum(cell)')
|
||||
aggregate_filter : filter
|
||||
The filter included in the aggregation
|
||||
aggregate_op : str
|
||||
The tally aggregation operator (e.g., 'sum', 'mean', etc.) used
|
||||
to aggregate across a tally filter's bins with this AggregateFilter
|
||||
bins : Iterable of tuple
|
||||
The filter bins included in the aggregation
|
||||
num_bins : Integral
|
||||
The number of filter bins (always 1 if aggregate_filter is defined)
|
||||
stride : Integral
|
||||
The number of filter, nuclide and score bins within each of this
|
||||
aggregatefilter's bins.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, aggregate_filter=None, bins=None, aggregate_op=None):
|
||||
|
||||
self._type = '{0}({1})'.format(aggregate_op, aggregate_filter.type)
|
||||
self._bins = None
|
||||
self._stride = None
|
||||
|
||||
self._aggregate_filter = None
|
||||
self._aggregate_op = None
|
||||
|
||||
if aggregate_filter is not None:
|
||||
self.aggregate_filter = aggregate_filter
|
||||
if bins is not None:
|
||||
self.bins = bins
|
||||
if aggregate_op is not None:
|
||||
self.aggregate_op = aggregate_op
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.type, self.bins, self.aggregate_op))
|
||||
|
||||
def __eq__(self, other):
|
||||
return str(other) == str(self)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
string = 'AggregateFilter\n'
|
||||
string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self.type)
|
||||
string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', self.bins)
|
||||
return string
|
||||
|
||||
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._type = self.type
|
||||
clone._aggregate_filter = self.aggregate_filter
|
||||
clone._aggregate_op = self.aggregate_op
|
||||
clone._bins = self._bins
|
||||
clone._stride = self.stride
|
||||
|
||||
memo[id(self)] = clone
|
||||
|
||||
return clone
|
||||
|
||||
# If this object has been copied before, return the first copy made
|
||||
else:
|
||||
return existing
|
||||
|
||||
@property
|
||||
def aggregate_filter(self):
|
||||
return self._aggregate_filter
|
||||
|
||||
@property
|
||||
def aggregate_op(self):
|
||||
return self._aggregate_op
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def bins(self):
|
||||
return self._bins
|
||||
|
||||
@property
|
||||
def num_bins(self):
|
||||
return 1 if self.aggregate_filter else 0
|
||||
|
||||
@property
|
||||
def stride(self):
|
||||
return self._stride
|
||||
|
||||
@type.setter
|
||||
def type(self, filter_type):
|
||||
if filter_type not in _FILTER_TYPES.values():
|
||||
msg = 'Unable to set AggregateFilter type to "{0}" since it ' \
|
||||
'is not one of the supported types'.format(filter_type)
|
||||
raise ValueError(msg)
|
||||
|
||||
self._type = filter_type
|
||||
|
||||
@aggregate_filter.setter
|
||||
def aggregate_filter(self, aggregate_filter):
|
||||
cv.check_type('aggregate_filter', aggregate_filter, (Filter, CrossFilter))
|
||||
self._aggregate_filter = aggregate_filter
|
||||
|
||||
@bins.setter
|
||||
def bins(self, bins):
|
||||
cv.check_iterable_type('bins', bins, (Integral, tuple))
|
||||
self._bins = bins
|
||||
|
||||
@aggregate_op.setter
|
||||
def aggregate_op(self, aggregate_op):
|
||||
cv.check_type('aggregate_op', aggregate_op, basestring)
|
||||
cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS)
|
||||
self._aggregate_op = aggregate_op
|
||||
|
||||
@stride.setter
|
||||
def stride(self, stride):
|
||||
self._stride = stride
|
||||
|
||||
def get_bin_index(self, filter_bin):
|
||||
"""Returns the index in the AggregateFilter for some bin.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filter_bin : Integral or tuple of Real
|
||||
A tuple of value(s) corresponding to the bin of interest in
|
||||
the aggregated filter. The bin is the integer ID for 'material',
|
||||
'surface', 'cell', 'cellborn', and 'universe' Filters. The bin
|
||||
is the integer 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 : Integral
|
||||
The index in the Tally data array for this filter bin. For an
|
||||
AggregateTally the filter bin index is always unity.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
When the filter_bin is not part of the aggregated filter's bins
|
||||
|
||||
"""
|
||||
|
||||
if filter_bin not in self.bins:
|
||||
msg = 'Unable to get the bin index for AggregateFilter since ' \
|
||||
'"{0}" is not one of the bins'.format(filter_bin)
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
return 0
|
||||
|
||||
def get_pandas_dataframe(self, datasize, summary=None):
|
||||
"""Builds a Pandas DataFrame for the AggregateFilter's bins.
|
||||
|
||||
This method constructs a Pandas DataFrame object for the AggregateFilter
|
||||
with columns annotated by filter bin information. This is a helper
|
||||
method for the Tally.get_pandas_dataframe(...) method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
datasize : Integral
|
||||
The total number of bins in the tally corresponding to this filter
|
||||
summary : None or Summary
|
||||
An optional Summary object to be used to construct columns for
|
||||
distribcell tally filters (default is None). NOTE: This parameter
|
||||
is not used by the AggregateFilter and simply mirrors the method
|
||||
signature for the CrossFilter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pandas.DataFrame
|
||||
A Pandas DataFrame with columns of strings that characterize the
|
||||
aggregatefilter's bins. Each entry in the DataFrame will include
|
||||
one or more aggregation operations used to construct the
|
||||
aggregatefilter's bins. The number of rows in the DataFrame is the
|
||||
same as the total number of bins in the corresponding tally, with
|
||||
the filter bins appropriately tiled to map to the corresponding
|
||||
tally bins.
|
||||
|
||||
See also
|
||||
--------
|
||||
Tally.get_pandas_dataframe(), Filter.get_pandas_dataframe(),
|
||||
CrossFilter.get_pandas_dataframe()
|
||||
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
|
||||
# Construct a sring representing the filter aggregation
|
||||
aggregate_bin = '{0}('.format(self.aggregate_op)
|
||||
aggregate_bin += ', '.join(map(str, self.bins)) + ')'
|
||||
|
||||
# Construct NumPy array of bin repeated for each element in dataframe
|
||||
aggregate_bin_array = np.array([aggregate_bin])
|
||||
aggregate_bin_array = np.repeat(aggregate_bin_array, datasize)
|
||||
|
||||
# Construct Pandas DataFrame for the AggregateFilter
|
||||
df = pd.DataFrame({self.aggregate_filter.type: aggregate_bin_array})
|
||||
return df
|
||||
|
|
@ -34,7 +34,7 @@ class CrossScore(object):
|
|||
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 CrossScore
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -245,6 +245,8 @@ class CrossFilter(object):
|
|||
|
||||
Attributes
|
||||
----------
|
||||
type : str
|
||||
The type of the crossfilter (e.g., 'energy / energy')
|
||||
left_filter : Filter or CrossFilter
|
||||
The left filter in the outer product
|
||||
right_filter : Filter or CrossFilter
|
||||
|
|
@ -252,6 +254,14 @@ class CrossFilter(object):
|
|||
binary_op : str
|
||||
The tally arithmetic binary operator (e.g., '+', '-', etc.) used to
|
||||
combine two tally's filter bins with this CrossFilter
|
||||
bins : dict of Iterable
|
||||
A dictionary of the bins from each filter keyed by the types of the
|
||||
left / right filters
|
||||
num_bins : Integral
|
||||
The number of filter bins (always 1 if aggregate_filter is defined)
|
||||
stride : Integral
|
||||
The number of filter, nuclide and score bins within each of this
|
||||
crossfilter's bins.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -310,7 +320,6 @@ class CrossFilter(object):
|
|||
clone._binary_op = self.binary_op
|
||||
clone._type = self.type
|
||||
clone._bins = self._bins
|
||||
clone._num_bins = self.num_bins
|
||||
clone._stride = self.stride
|
||||
|
||||
memo[id(self)] = clone
|
||||
|
|
@ -355,8 +364,8 @@ class CrossFilter(object):
|
|||
@type.setter
|
||||
def type(self, filter_type):
|
||||
if filter_type not in _FILTER_TYPES.values():
|
||||
msg = 'Unable to set Filter type to "{0}" since it is not one ' \
|
||||
'of the supported types'.format(filter_type)
|
||||
msg = 'Unable to set CrossFilter type to "{0}" since it ' \
|
||||
'is not one of the supported types'.format(filter_type)
|
||||
raise ValueError(msg)
|
||||
|
||||
self._type = filter_type
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ class Filter(object):
|
|||
Attributes
|
||||
----------
|
||||
type : str
|
||||
The type of the tally filter.
|
||||
bins : Integral or Iterable of Integral or Iterable of Real
|
||||
The type of the tally filter
|
||||
bins : Integral or Iterable of Real
|
||||
The bins for the filter
|
||||
num_bins : Integral
|
||||
The number of filter bins
|
||||
|
|
@ -227,12 +227,12 @@ class Filter(object):
|
|||
|
||||
self._stride = stride
|
||||
|
||||
def can_merge(self, filter):
|
||||
def can_merge(self, other):
|
||||
"""Determine if filter can be merged with another.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filter : Filter
|
||||
other : Filter
|
||||
Filter to compare with
|
||||
|
||||
Returns
|
||||
|
|
@ -242,11 +242,11 @@ class Filter(object):
|
|||
|
||||
"""
|
||||
|
||||
if not isinstance(filter, Filter):
|
||||
if not isinstance(other, Filter):
|
||||
return False
|
||||
|
||||
# Filters must be of the same type
|
||||
elif self.type != filter.type:
|
||||
elif self.type != other.type:
|
||||
return False
|
||||
|
||||
# Distribcell filters cannot have more than one bin
|
||||
|
|
@ -264,12 +264,12 @@ class Filter(object):
|
|||
else:
|
||||
return True
|
||||
|
||||
def merge(self, filter):
|
||||
def merge(self, other):
|
||||
"""Merge this filter with another.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filter : Filter
|
||||
other : Filter
|
||||
Filter to merge with
|
||||
|
||||
Returns
|
||||
|
|
@ -279,16 +279,16 @@ class Filter(object):
|
|||
|
||||
"""
|
||||
|
||||
if not self.can_merge(filter):
|
||||
if not self.can_merge(other):
|
||||
msg = 'Unable to merge "{0}" with "{1}" ' \
|
||||
'filters'.format(self.type, filter.type)
|
||||
'filters'.format(self.type, other.type)
|
||||
raise ValueError(msg)
|
||||
|
||||
# Create deep copy of filter to return as merged filter
|
||||
merged_filter = copy.deepcopy(self)
|
||||
|
||||
# Merge unique filter bins
|
||||
merged_bins = list(set(np.concatenate((self.bins, filter.bins))))
|
||||
merged_bins = list(set(np.concatenate((self.bins, other.bins))))
|
||||
merged_filter.bins = merged_bins
|
||||
merged_filter.num_bins = len(merged_bins)
|
||||
|
||||
|
|
|
|||
|
|
@ -521,8 +521,8 @@ class MGXS(object):
|
|||
self.tallies[key].add_trigger(trigger_clone)
|
||||
|
||||
# Add all non-domain specific Filters (e.g., 'energy') to the Tally
|
||||
for filter in filters:
|
||||
self.tallies[key].add_filter(filter)
|
||||
for add_filter in filters:
|
||||
self.tallies[key].add_filter(add_filter)
|
||||
|
||||
# If this is a by-nuclide cross-section, add all nuclides to Tally
|
||||
if self.by_nuclide and score != 'flux':
|
||||
|
|
@ -787,15 +787,15 @@ class MGXS(object):
|
|||
std_dev = tally.get_reshaped_data(value='std_dev')
|
||||
|
||||
# Sum across all applicable fine energy group filters
|
||||
for i, filter in enumerate(tally.filters):
|
||||
if 'energy' not in filter.type:
|
||||
for i, tally_filter in enumerate(tally.filters):
|
||||
if 'energy' not in tally_filter.type:
|
||||
continue
|
||||
elif len(filter.bins) != len(fine_edges):
|
||||
elif len(tally_filter.bins) != len(fine_edges):
|
||||
continue
|
||||
elif not np.allclose(filter.bins, fine_edges):
|
||||
elif not np.allclose(tally_filter.bins, fine_edges):
|
||||
continue
|
||||
else:
|
||||
filter.bins = coarse_groups.group_edges
|
||||
tally_filter.bins = coarse_groups.group_edges
|
||||
mean = np.add.reduceat(mean, energy_indices, axis=i)
|
||||
std_dev = np.add.reduceat(std_dev**2, energy_indices, axis=i)
|
||||
std_dev = np.sqrt(std_dev)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import re
|
|||
import numpy as np
|
||||
|
||||
import openmc
|
||||
import openmc.checkvalue as cv
|
||||
|
||||
if sys.version > '3':
|
||||
long = int
|
||||
|
|
@ -377,18 +378,18 @@ class StatePoint(object):
|
|||
bins = self._f['{0}{1}/bins'.format(subbase, j)].value
|
||||
|
||||
# Create Filter object
|
||||
filter = openmc.Filter(filter_type, bins)
|
||||
filter.num_bins = n_bins
|
||||
new_filter = openmc.Filter(filter_type, bins)
|
||||
new_filter.num_bins = n_bins
|
||||
|
||||
if filter_type == 'mesh':
|
||||
mesh_ids = self._f['tallies/meshes/ids'].value
|
||||
mesh_keys = self._f['tallies/meshes/keys'].value
|
||||
|
||||
key = mesh_keys[mesh_ids == bins][0]
|
||||
filter.mesh = self.meshes[key]
|
||||
new_filter.mesh = self.meshes[key]
|
||||
|
||||
# Add Filter to the Tally
|
||||
tally.add_filter(filter)
|
||||
tally.add_filter(new_filter)
|
||||
|
||||
# Read Nuclide bins
|
||||
nuclide_names = \
|
||||
|
|
@ -406,11 +407,11 @@ class StatePoint(object):
|
|||
|
||||
# Compute and set the filter strides
|
||||
for i in range(n_filters):
|
||||
filter = tally.filters[i]
|
||||
filter.stride = n_score_bins * len(nuclide_names)
|
||||
tally_filter = tally.filters[i]
|
||||
tally_filter.stride = n_score_bins * len(nuclide_names)
|
||||
|
||||
for j in range(i+1, n_filters):
|
||||
filter.stride *= tally.filters[j].num_bins
|
||||
tally_filter.stride *= tally.filters[j].num_bins
|
||||
|
||||
# Read scattering moment order strings (e.g., P3, Y1,2, etc.)
|
||||
moments = self._f['{0}{1}/moment_orders'.format(
|
||||
|
|
@ -544,13 +545,13 @@ class StatePoint(object):
|
|||
contains_filters = True
|
||||
|
||||
# Iterate over the Filters requested by the user
|
||||
for filter in filters:
|
||||
for outer_filter in filters:
|
||||
contains_filters = False
|
||||
|
||||
# Test if requested filter is a subset of any of the test
|
||||
# tally's filters and if so continue to next filter
|
||||
for test_filter in test_tally.filters:
|
||||
if test_filter.is_subset(filter):
|
||||
for inner_filter in test_tally.filters:
|
||||
if inner_filter.is_subset(outer_filter):
|
||||
contains_filters = True
|
||||
break
|
||||
|
||||
|
|
@ -616,29 +617,29 @@ class StatePoint(object):
|
|||
tally.name = summary.tallies[tally_id].name
|
||||
tally.with_summary = True
|
||||
|
||||
for filter in tally.filters:
|
||||
if filter.type == 'surface':
|
||||
for tally_filter in tally.filters:
|
||||
if tally_filter.type == 'surface':
|
||||
surface_ids = []
|
||||
for bin in filter.bins:
|
||||
for bin in tally_filter.bins:
|
||||
surface_ids.append(summary.surfaces[bin].id)
|
||||
filter.bins = surface_ids
|
||||
tally_filter.bins = surface_ids
|
||||
|
||||
if filter.type in ['cell', 'distribcell']:
|
||||
if tally_filter.type in ['cell', 'distribcell']:
|
||||
distribcell_ids = []
|
||||
for bin in filter.bins:
|
||||
for bin in tally_filter.bins:
|
||||
distribcell_ids.append(summary.cells[bin].id)
|
||||
filter.bins = distribcell_ids
|
||||
tally_filter.bins = distribcell_ids
|
||||
|
||||
if filter.type == 'universe':
|
||||
if tally_filter.type == 'universe':
|
||||
universe_ids = []
|
||||
for bin in filter.bins:
|
||||
for bin in tally_filter.bins:
|
||||
universe_ids.append(summary.universes[bin].id)
|
||||
filter.bins = universe_ids
|
||||
tally_filter.bins = universe_ids
|
||||
|
||||
if filter.type == 'material':
|
||||
if tally_filter.type == 'material':
|
||||
material_ids = []
|
||||
for bin in filter.bins:
|
||||
for bin in tally_filter.bins:
|
||||
material_ids.append(summary.materials[bin].id)
|
||||
filter.bins = material_ids
|
||||
tally_filter.bins = material_ids
|
||||
|
||||
self._summary = summary
|
||||
|
|
|
|||
|
|
@ -556,11 +556,11 @@ class Summary(object):
|
|||
bins = self._f['{0}/bins'.format(subsubbase)][...]
|
||||
|
||||
# Create Filter object
|
||||
filter = openmc.Filter(filter_type, bins)
|
||||
filter.num_bins = num_bins
|
||||
new_filter = openmc.Filter(filter_type, bins)
|
||||
new_filter.num_bins = num_bins
|
||||
|
||||
# Add Filter to the Tally
|
||||
tally.add_filter(filter)
|
||||
tally.add_filter(new_filter)
|
||||
|
||||
# Add Tally to the global dictionary of all Tallies
|
||||
self.tallies[tally_id] = tally
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import numpy as np
|
|||
|
||||
from openmc import Mesh, Filter, Trigger, Nuclide
|
||||
from openmc.cross import CrossScore, CrossNuclide, CrossFilter
|
||||
from openmc.aggregate import AggregateScore, AggregateNuclide, AggregateFilter
|
||||
from openmc.filter import _FILTER_TYPES
|
||||
import openmc.checkvalue as cv
|
||||
from openmc.clean_xml import *
|
||||
|
|
@ -143,8 +144,8 @@ class Tally(object):
|
|||
clone._results_read = self._results_read
|
||||
|
||||
clone._filters = []
|
||||
for filter in self.filters:
|
||||
clone.add_filter(copy.deepcopy(filter, memo))
|
||||
for self_filter in self.filters:
|
||||
clone.add_filter(copy.deepcopy(self_filter, memo))
|
||||
|
||||
clone._nuclides = []
|
||||
for nuclide in self.nuclides:
|
||||
|
|
@ -174,8 +175,8 @@ class Tally(object):
|
|||
if len(self.filters) != len(other.filters):
|
||||
return False
|
||||
|
||||
for filter in self.filters:
|
||||
if filter not in other.filters:
|
||||
for self_filter in self.filters:
|
||||
if self_filter not in other.filters:
|
||||
return False
|
||||
|
||||
# Check all nuclides
|
||||
|
|
@ -212,9 +213,9 @@ class Tally(object):
|
|||
|
||||
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,
|
||||
filter.bins)
|
||||
for self_filter in self.filters:
|
||||
string += '{0: <16}\t\t{1}\t{2}\n'.format('', self_filter.type,
|
||||
self_filter.bins)
|
||||
|
||||
string += '{0: <16}{1}'.format('\tNuclides', '=\t')
|
||||
|
||||
|
|
@ -259,12 +260,16 @@ class Tally(object):
|
|||
def num_scores(self):
|
||||
return len(self._scores)
|
||||
|
||||
@property
|
||||
def num_filters(self):
|
||||
return len(self.filters)
|
||||
|
||||
@property
|
||||
def num_filter_bins(self):
|
||||
num_bins = 1
|
||||
|
||||
for filter in self.filters:
|
||||
num_bins *= filter.num_bins
|
||||
for self_filter in self.filters:
|
||||
num_bins *= self_filter.num_bins
|
||||
|
||||
return num_bins
|
||||
|
||||
|
|
@ -455,33 +460,63 @@ class Tally(object):
|
|||
else:
|
||||
self._name = ''
|
||||
|
||||
def add_filter(self, filter):
|
||||
def add_filter(self, new_filter):
|
||||
"""Add a filter to the tally
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filter : openmc.filter.Filter
|
||||
Filter to add
|
||||
new_filter : Filter, CrossFilter or AggregateFilter
|
||||
A filter to specify a discretization of the tally across some
|
||||
dimension (e.g., 'energy', 'cell'). The filter should be a Filter
|
||||
object when a user is adding filters to a Tally for input file
|
||||
generation or when the Tally is created from a StatePoint. The
|
||||
filter may be a CrossFilter or AggregateFilter for derived tallies
|
||||
created by tally arithmetic.
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(filter, (Filter, CrossFilter)):
|
||||
if not isinstance(new_filter, (Filter, CrossFilter, AggregateFilter)):
|
||||
msg = 'Unable to add Filter "{0}" to Tally ID="{1}" since it is ' \
|
||||
'not a Filter object'.format(filter, self.id)
|
||||
'not a Filter object'.format(new_filter, self.id)
|
||||
raise ValueError(msg)
|
||||
|
||||
self._filters.append(filter)
|
||||
# If the filter is already in the Tally, raise an error
|
||||
if new_filter in self.filters:
|
||||
msg = 'Unable to add a duplicate filter "{0}" to Tally ID="{1}" ' \
|
||||
'since duplicate filters are not supported in the OpenMC ' \
|
||||
'Python API'.format(new_filter, self.id)
|
||||
raise ValueError(msg)
|
||||
|
||||
self._filters.append(new_filter)
|
||||
|
||||
def add_nuclide(self, nuclide):
|
||||
"""Specify that scores for a particular nuclide should be accumulated
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nuclide : openmc.nuclide.Nuclide
|
||||
Nuclide to add
|
||||
nuclide : str, Nuclide, CrossNuclide or AggregateNuclide
|
||||
Nuclide to add to the tally. The nuclide should be a Nuclide object
|
||||
when a user is adding nuclides to a Tally for input file generation.
|
||||
The nuclide is a str when a Tally is created from a StatePoint file
|
||||
(e.g., 'H-1', 'U-235') unless a Summary has been linked with the
|
||||
StatePoint. The nuclide may be a CrossNuclide or AggregateNuclide
|
||||
for derived tallies created by tally arithmetic.
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(nuclide, (basestring, Nuclide,
|
||||
CrossNuclide, AggregateNuclide)):
|
||||
msg = 'Unable to add nuclide "{0}" to Tally ID="{1}" since it is ' \
|
||||
'not a Nuclide object'.format(nuclide)
|
||||
raise ValueError(msg)
|
||||
|
||||
# If the nuclide is already in the Tally, raise an error
|
||||
if nuclide in self.nuclides:
|
||||
msg = 'Unable to add a duplicate nuclide "{0}" to Tally ID="{1}" ' \
|
||||
'since duplicate nuclides are not supported in the OpenMC ' \
|
||||
'Python API'.format(nuclide, self.id)
|
||||
raise ValueError(msg)
|
||||
|
||||
self._nuclides.append(nuclide)
|
||||
|
||||
def add_score(self, score):
|
||||
|
|
@ -489,19 +524,23 @@ class Tally(object):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
score : str
|
||||
Score to be accumulated, e.g. 'flux'
|
||||
score : str, CrossScore or AggregateScore
|
||||
A score to be accumulated (e.g., 'flux', 'nu-fission'). The score
|
||||
should be a str when a user is adding scores to a Tally for input
|
||||
file generation or when the Tally is created from a StatePoint. The
|
||||
score may be a CrossScore or AggregateScore for derived tallies
|
||||
created by tally arithmetic.
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(score, (basestring, CrossScore)):
|
||||
if not isinstance(score, (basestring, CrossScore, AggregateScore)):
|
||||
msg = 'Unable to add score "{0}" to Tally ID="{1}" since it is ' \
|
||||
'not a string'.format(score, self.id)
|
||||
raise ValueError(msg)
|
||||
|
||||
# If the score is already in the Tally, raise an error
|
||||
if score in self.scores:
|
||||
msg = 'Unable to add a duplicate score {0} to Tally ID="{1}" ' \
|
||||
msg = 'Unable to add a duplicate score "{0}" to Tally ID="{1}" ' \
|
||||
'since duplicate scores are not supported in the OpenMC ' \
|
||||
'Python API'.format(score, self.id)
|
||||
raise ValueError(msg)
|
||||
|
|
@ -509,7 +548,7 @@ class Tally(object):
|
|||
# Normal score strings
|
||||
if isinstance(score, basestring):
|
||||
self._scores.append(score.strip())
|
||||
# CrossScores
|
||||
# CrossScores and AggrgateScore
|
||||
else:
|
||||
self._scores.append(score)
|
||||
|
||||
|
|
@ -601,22 +640,22 @@ class Tally(object):
|
|||
|
||||
self._scores.remove(score)
|
||||
|
||||
def remove_filter(self, filter):
|
||||
def remove_filter(self, old_filter):
|
||||
"""Remove a filter from the tally
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filter : openmc.filter.Filter
|
||||
old_filter : openmc.filter.Filter
|
||||
Filter to remove
|
||||
|
||||
"""
|
||||
|
||||
if filter not in self.filters:
|
||||
if old_filter not in self.filters:
|
||||
msg = 'Unable to remove filter "{0}" from Tally ID="{1}" since the ' \
|
||||
'Tally does not contain this filter'.format(filter, self.id)
|
||||
'Tally does not contain this filter'.format(old_filter, self.id)
|
||||
ValueError(msg)
|
||||
|
||||
self._filters.remove(filter)
|
||||
self._filters.remove(old_filter)
|
||||
|
||||
def remove_nuclide(self, nuclide):
|
||||
"""Remove a nuclide from the tally
|
||||
|
|
@ -760,13 +799,13 @@ class Tally(object):
|
|||
element.set("name", self.name)
|
||||
|
||||
# Optional Tally filters
|
||||
for filter in self.filters:
|
||||
for self_filter in self.filters:
|
||||
subelement = ET.SubElement(element, "filter")
|
||||
subelement.set("type", str(filter.type))
|
||||
subelement.set("type", str(self_filter.type))
|
||||
|
||||
if filter.bins is not None:
|
||||
if self_filter.bins is not None:
|
||||
bins = ''
|
||||
for bin in filter.bins:
|
||||
for bin in self_filter.bins:
|
||||
bins += '{0} '.format(bin)
|
||||
|
||||
subelement.set("bins", bins.rstrip(' '))
|
||||
|
|
@ -818,7 +857,7 @@ class Tally(object):
|
|||
|
||||
Returns
|
||||
-------
|
||||
filter : openmc.filter.Filter
|
||||
filter_found : openmc.filter.Filter
|
||||
Filter from this tally with matching type, or None if no matching
|
||||
Filter is found
|
||||
|
||||
|
|
@ -829,21 +868,21 @@ class Tally(object):
|
|||
|
||||
"""
|
||||
|
||||
filter = None
|
||||
filter_found = None
|
||||
|
||||
# Look through all of this Tally's Filters for the type requested
|
||||
for test_filter in self.filters:
|
||||
if test_filter.type == filter_type:
|
||||
filter = test_filter
|
||||
filter_found = test_filter
|
||||
break
|
||||
|
||||
# If we did not find the Filter, throw an Exception
|
||||
if filter is None:
|
||||
if filter_found is None:
|
||||
msg = 'Unable to find filter type "{0}" in ' \
|
||||
'Tally ID="{1}"'.format(filter_type, self.id)
|
||||
raise ValueError(msg)
|
||||
|
||||
return filter
|
||||
return filter_found
|
||||
|
||||
def get_filter_index(self, filter_type, filter_bin):
|
||||
"""Returns the index in the Tally's results array for a Filter bin
|
||||
|
|
@ -868,10 +907,10 @@ class Tally(object):
|
|||
"""
|
||||
|
||||
# Find the equivalent Filter in this Tally's list of Filters
|
||||
filter = self.find_filter(filter_type)
|
||||
filter_found = self.find_filter(filter_type)
|
||||
|
||||
# Get the index for the requested bin from the Filter and return it
|
||||
filter_index = filter.get_bin_index(filter_bin)
|
||||
filter_index = filter_found.get_bin_index(filter_bin)
|
||||
return filter_index
|
||||
|
||||
def get_nuclide_index(self, nuclide):
|
||||
|
|
@ -991,12 +1030,12 @@ class Tally(object):
|
|||
filter_indices = []
|
||||
|
||||
# Loop over all of the Tally's Filters
|
||||
for i, filter in enumerate(self.filters):
|
||||
for i, self_filter in enumerate(self.filters):
|
||||
user_filter = False
|
||||
|
||||
# If a user-requested Filter, get the user-requested bins
|
||||
for j, test_filter in enumerate(filters):
|
||||
if filter.type == test_filter:
|
||||
if self_filter.type == test_filter:
|
||||
bins = filter_bins[j]
|
||||
user_filter = True
|
||||
break
|
||||
|
|
@ -1004,36 +1043,36 @@ class Tally(object):
|
|||
# If not a user-requested Filter, get all bins
|
||||
if not user_filter:
|
||||
# Create list of 2- or 3-tuples tuples for mesh cell bins
|
||||
if filter.type == 'mesh':
|
||||
dimension = filter.mesh.dimension
|
||||
if self_filter.type == 'mesh':
|
||||
dimension = self_filter.mesh.dimension
|
||||
xyz = map(lambda x: np.arange(1, x+1), dimension)
|
||||
bins = list(itertools.product(*xyz))
|
||||
|
||||
# Create list of 2-tuples for energy boundary bins
|
||||
elif filter.type in ['energy', 'energyout']:
|
||||
elif self_filter.type in ['energy', 'energyout']:
|
||||
bins = []
|
||||
for k in range(filter.num_bins):
|
||||
bins.append((filter.bins[k], filter.bins[k+1]))
|
||||
for k in range(self_filter.num_bins):
|
||||
bins.append((self_filter.bins[k], self_filter.bins[k+1]))
|
||||
|
||||
# Create list of cell instance IDs for distribcell Filters
|
||||
elif filter.type == 'distribcell':
|
||||
bins = np.arange(filter.num_bins)
|
||||
elif self_filter.type == 'distribcell':
|
||||
bins = np.arange(self_filter.num_bins)
|
||||
|
||||
# Create list of IDs for bins for all other filter types
|
||||
else:
|
||||
bins = filter.bins
|
||||
bins = self_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 j, bin in enumerate(bins):
|
||||
filter_index = self.get_filter_index(filter.type, bin)
|
||||
filter_index = self.get_filter_index(self_filter.type, bin)
|
||||
filter_indices[i][j] = filter_index
|
||||
|
||||
# Account for stride in each of the previous filters
|
||||
for indices in filter_indices[:i]:
|
||||
indices *= filter.num_bins
|
||||
indices *= self_filter.num_bins
|
||||
|
||||
# Apply outer product sum between all filter bin indices
|
||||
filter_indices = list(map(sum, itertools.product(*filter_indices)))
|
||||
|
|
@ -1275,8 +1314,8 @@ class Tally(object):
|
|||
if filters:
|
||||
|
||||
# Append each Filter's DataFrame to the overall DataFrame
|
||||
for filter in self.filters:
|
||||
filter_df = filter.get_pandas_dataframe(data_size, summary)
|
||||
for self_filter in self.filters:
|
||||
filter_df = self_filter.get_pandas_dataframe(data_size, summary)
|
||||
df = pd.concat([df, filter_df], axis=1)
|
||||
|
||||
# Include DataFrame column for nuclides if user requested it
|
||||
|
|
@ -1365,8 +1404,8 @@ class Tally(object):
|
|||
|
||||
# Build a new array shape with one dimension per filter
|
||||
new_shape = ()
|
||||
for filter in self.filters:
|
||||
new_shape += (filter.num_bins, )
|
||||
for self_filter in self.filters:
|
||||
new_shape += (self_filter.num_bins, )
|
||||
new_shape += (self.num_nuclides,)
|
||||
new_shape += (self.num_scores,)
|
||||
|
||||
|
|
@ -1459,8 +1498,9 @@ class Tally(object):
|
|||
# Create an HDF5 sub-group for the Filters
|
||||
filter_group = tally_group.create_group('filters')
|
||||
|
||||
for filter in self.filters:
|
||||
filter_group.create_dataset(filter.type, data=filter.bins)
|
||||
for self_filter in self.filters:
|
||||
filter_group.create_dataset(self_filter.type,
|
||||
filter=self_filter.bins)
|
||||
|
||||
# Add all results to the main HDF5 group for the Tally
|
||||
tally_group.create_dataset('sum', data=self.sum)
|
||||
|
|
@ -1503,8 +1543,8 @@ class Tally(object):
|
|||
tally_group['filters'] = {}
|
||||
filter_group = tally_group['filters']
|
||||
|
||||
for filter in self.filters:
|
||||
filter_group[filter.type] = filter.bins
|
||||
for self_filter in self.filters:
|
||||
filter_group[self_filter.type] = self_filter.bins
|
||||
|
||||
# Add all results to the main sub-dictionary for the Tally
|
||||
tally_group['sum'] = self.sum
|
||||
|
|
@ -1599,8 +1639,12 @@ class Tally(object):
|
|||
raise ValueError(msg)
|
||||
|
||||
new_tally = Tally()
|
||||
new_tally.with_batch_statistics = True
|
||||
new_tally._derived = True
|
||||
new_tally.with_batch_statistics = True
|
||||
new_tally._num_realizations = self.num_realizations
|
||||
new_tally._estimator = self.estimator
|
||||
new_tally._with_summary = self.with_summary
|
||||
new_tally._sp_filename = self._sp_filename
|
||||
|
||||
# Construct a combined derived name from the two tally operands
|
||||
if self.name != '' and other.name != '':
|
||||
|
|
@ -1698,14 +1742,21 @@ class Tally(object):
|
|||
new_score = CrossScore(self_score, other_score, binary_op)
|
||||
new_tally.add_score(new_score)
|
||||
|
||||
# Correct each Filter's stride
|
||||
stride = new_tally.num_nuclides * new_tally.num_scores
|
||||
for filter in reversed(new_tally.filters):
|
||||
filter.stride = stride
|
||||
stride *= filter.num_bins
|
||||
# Update the new tally's filter strides
|
||||
new_tally._update_filter_strides()
|
||||
|
||||
return new_tally
|
||||
|
||||
def _update_filter_strides(self):
|
||||
"""Update each filter's stride based on the tally's nuclides and scores
|
||||
for derived tallies created by tally arithmetic.
|
||||
"""
|
||||
|
||||
stride = self.num_nuclides * self.num_scores
|
||||
for self_filter in reversed(self.filters):
|
||||
self_filter.stride = stride
|
||||
stride *= self_filter.num_bins
|
||||
|
||||
def _align_tally_data(self, other, filter_product, nuclide_product,
|
||||
score_product):
|
||||
"""Aligns data from two tallies for tally arithmetic.
|
||||
|
|
@ -1748,26 +1799,26 @@ class Tally(object):
|
|||
set(other.filters).difference(set(self.filters))
|
||||
|
||||
# Add filters present in self but not in other to other
|
||||
for filter in other_missing_filters:
|
||||
filter = copy.deepcopy(filter)
|
||||
other._mean = np.repeat(other.mean, filter.num_bins, axis=0)
|
||||
other._std_dev = np.repeat(other.std_dev, filter.num_bins, axis=0)
|
||||
other.add_filter(filter)
|
||||
for other_filter in other_missing_filters:
|
||||
filter_copy = copy.deepcopy(other_filter)
|
||||
other._mean = np.repeat(other.mean, filter_copy.num_bins, axis=0)
|
||||
other._std_dev = np.repeat(other.std_dev, filter_copy.num_bins, axis=0)
|
||||
other.add_filter(filter_copy)
|
||||
|
||||
# Add filters present in other but not in self to self
|
||||
for filter in self_missing_filters:
|
||||
filter = copy.deepcopy(filter)
|
||||
self._mean = np.repeat(self.mean, filter.num_bins, axis=0)
|
||||
self._std_dev = np.repeat(self.std_dev, filter.num_bins, axis=0)
|
||||
self.add_filter(filter)
|
||||
for self_filter in self_missing_filters:
|
||||
filter_copy = copy.deepcopy(self_filter)
|
||||
self._mean = np.repeat(self.mean, filter_copy.num_bins, axis=0)
|
||||
self._std_dev = np.repeat(self.std_dev, filter_copy.num_bins, axis=0)
|
||||
self.add_filter(filter_copy)
|
||||
|
||||
# Align other filters with self filters
|
||||
for i, filter in enumerate(self.filters):
|
||||
other_index = other.filters.index(filter)
|
||||
for i, self_filter in enumerate(self.filters):
|
||||
other_index = other.filters.index(self_filter)
|
||||
|
||||
# If necessary, swap other filter
|
||||
if other_index != i:
|
||||
other._swap_filters(filter, other.filters[i])
|
||||
other._swap_filters(self_filter, other.filters[i])
|
||||
|
||||
# Repeat and tile the data by nuclide in preparation for performing
|
||||
# the tensor product across nuclides.
|
||||
|
|
@ -1855,17 +1906,9 @@ class Tally(object):
|
|||
if other_index != i:
|
||||
other._swap_scores(score, other.scores[i])
|
||||
|
||||
# Correct the stride for other filters
|
||||
stride = other.num_nuclides * other.num_scores
|
||||
for filter in reversed(other.filters):
|
||||
filter.stride = stride
|
||||
stride *= filter.num_bins
|
||||
|
||||
# Correct the stride for self filters
|
||||
stride = self.num_nuclides * self.num_scores
|
||||
for filter in reversed(self.filters):
|
||||
filter.stride = stride
|
||||
stride *= filter.num_bins
|
||||
# Update the tallies' filter strides
|
||||
other._update_filter_strides()
|
||||
self._update_filter_strides()
|
||||
|
||||
data = {}
|
||||
data['self'] = {}
|
||||
|
|
@ -1927,16 +1970,13 @@ class Tally(object):
|
|||
self.filters[filter1_index] = filter2
|
||||
self.filters[filter2_index] = filter1
|
||||
|
||||
# Update the strides for each of the filters
|
||||
stride = self.num_nuclides * self.num_scores
|
||||
for filter in reversed(self.filters):
|
||||
filter.stride = stride
|
||||
stride *= filter.num_bins
|
||||
# Update the tally's filter strides
|
||||
self._update_filter_strides()
|
||||
|
||||
# Construct lists of tuples for the bins in each of the two filters
|
||||
filters = [filter1.type, filter2.type]
|
||||
if filter1.type == 'distribcell':
|
||||
filter1_bins = np.arange(filter.num_bins)
|
||||
filter1_bins = np.arange(filter1.num_bins)
|
||||
else:
|
||||
filter1_bins = [(filter1.get_bin(i)) for i in range(filter1.num_bins)]
|
||||
|
||||
|
|
@ -2161,8 +2201,8 @@ class Tally(object):
|
|||
new_tally.with_summary = self.with_summary
|
||||
new_tally.num_realization = self.num_realizations
|
||||
|
||||
for filter in self.filters:
|
||||
new_tally.add_filter(filter)
|
||||
for self_filter in self.filters:
|
||||
new_tally.add_filter(self_filter)
|
||||
for nuclide in self.nuclides:
|
||||
new_tally.add_nuclide(nuclide)
|
||||
for score in self.scores:
|
||||
|
|
@ -2235,8 +2275,8 @@ class Tally(object):
|
|||
new_tally.with_summary = self.with_summary
|
||||
new_tally.num_realization = self.num_realizations
|
||||
|
||||
for filter in self.filters:
|
||||
new_tally.add_filter(filter)
|
||||
for self_filter in self.filters:
|
||||
new_tally.add_filter(self_filter)
|
||||
for nuclide in self.nuclides:
|
||||
new_tally.add_nuclide(nuclide)
|
||||
for score in self.scores:
|
||||
|
|
@ -2310,8 +2350,8 @@ class Tally(object):
|
|||
new_tally.with_summary = self.with_summary
|
||||
new_tally.num_realization = self.num_realizations
|
||||
|
||||
for filter in self.filters:
|
||||
new_tally.add_filter(filter)
|
||||
for self_filter in self.filters:
|
||||
new_tally.add_filter(self_filter)
|
||||
for nuclide in self.nuclides:
|
||||
new_tally.add_nuclide(nuclide)
|
||||
for score in self.scores:
|
||||
|
|
@ -2385,8 +2425,8 @@ class Tally(object):
|
|||
new_tally.with_summary = self.with_summary
|
||||
new_tally.num_realization = self.num_realizations
|
||||
|
||||
for filter in self.filters:
|
||||
new_tally.add_filter(filter)
|
||||
for self_filter in self.filters:
|
||||
new_tally.add_filter(self_filter)
|
||||
for nuclide in self.nuclides:
|
||||
new_tally.add_nuclide(nuclide)
|
||||
for score in self.scores:
|
||||
|
|
@ -2449,8 +2489,8 @@ class Tally(object):
|
|||
if isinstance(power, Tally):
|
||||
new_tally = self.hybrid_product(power, binary_op='^')
|
||||
|
||||
# If original tally operands were sparse, sparsify the new tally
|
||||
if self.sparse and other.sparse:
|
||||
# If original tally operand was sparse, sparsify the new tally
|
||||
if self.sparse:
|
||||
new_tally.sparse = True
|
||||
|
||||
elif isinstance(power, Real):
|
||||
|
|
@ -2464,8 +2504,8 @@ class Tally(object):
|
|||
new_tally.with_summary = self.with_summary
|
||||
new_tally.num_realization = self.num_realizations
|
||||
|
||||
for filter in self.filters:
|
||||
new_tally.add_filter(filter)
|
||||
for self_filter in self.filters:
|
||||
new_tally.add_filter(self_filter)
|
||||
for nuclide in self.nuclides:
|
||||
new_tally.add_nuclide(nuclide)
|
||||
for score in self.scores:
|
||||
|
|
@ -2605,7 +2645,7 @@ class Tally(object):
|
|||
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
|
||||
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
|
||||
|
|
@ -2687,29 +2727,29 @@ class Tally(object):
|
|||
|
||||
# Determine the filter indices from any of the requested filters
|
||||
for i, filter_type in enumerate(filters):
|
||||
filter = new_tally.find_filter(filter_type)
|
||||
find_filter = new_tally.find_filter(filter_type)
|
||||
|
||||
# Remove and/or reorder filter bins to user specifications
|
||||
bin_indices = []
|
||||
num_bins = 0
|
||||
|
||||
for filter_bin in filter_bins[i]:
|
||||
bin_index = filter.get_bin_index(filter_bin)
|
||||
bin_index = find_filter.get_bin_index(filter_bin)
|
||||
if filter_type in ['energy', 'energyout']:
|
||||
bin_indices.extend([bin_index, bin_index+1])
|
||||
num_bins += 1
|
||||
elif filter_type == 'distribcell':
|
||||
indices = [(bin,) for bin in range(filter.num_bins)]
|
||||
bin_indices.extend(indices)
|
||||
bin_indices = [0]
|
||||
num_bins = find_filter.num_bins
|
||||
else:
|
||||
bin_indices.append(bin_index)
|
||||
num_bins += 1
|
||||
|
||||
filter.bins = filter.bins[bin_indices]
|
||||
filter.num_bins = len(filter_bins[i])
|
||||
find_filter.bins = find_filter.bins[bin_indices]
|
||||
find_filter.num_bins = num_bins
|
||||
|
||||
# Correct each Filter's stride
|
||||
stride = new_tally.num_nuclides * new_tally.num_scores
|
||||
for filter in reversed(new_tally.filters):
|
||||
filter.stride = stride
|
||||
stride *= filter.num_bins
|
||||
# Update the new tally's filter strides
|
||||
new_tally._update_filter_strides()
|
||||
|
||||
# If original tally was sparse, sparsify the sliced tally
|
||||
new_tally.sparse = self.sparse
|
||||
|
|
@ -2737,7 +2777,7 @@ class Tally(object):
|
|||
A list of the filter bins corresponding to the filter_type parameter
|
||||
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
|
||||
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. Each bin is an
|
||||
(x,y,z) 3-tuple for 'mesh' filters corresponding to the mesh cell of
|
||||
|
|
@ -2755,65 +2795,109 @@ class Tally(object):
|
|||
A new tally which encapsulates the sum of data requested.
|
||||
"""
|
||||
|
||||
# If user did not specify any scores, do not sum across scores
|
||||
if len(scores) == 0:
|
||||
scores = [[]]
|
||||
# Sum across any scores specified by the user
|
||||
else:
|
||||
scores = [[score] for score in scores]
|
||||
# Create new derived Tally for summation
|
||||
tally_sum = Tally()
|
||||
tally_sum._derived = True
|
||||
tally_sum._estimator = self.estimator
|
||||
tally_sum._num_realizations = self.num_realizations
|
||||
tally_sum.with_batch_statistics = self.with_batch_statistics
|
||||
tally_sum._with_summary = self.with_summary
|
||||
tally_sum._sp_filename = self._sp_filename
|
||||
tally_sum._results_read = self._results_read
|
||||
|
||||
# If user did not specify any nuclides, do not sum across nuclides
|
||||
if len(nuclides) == 0:
|
||||
nuclides = [[]]
|
||||
# Sum across any nuclides specified by the user
|
||||
else:
|
||||
nuclides = [[nuclide] for nuclide in nuclides]
|
||||
# Get tally data arrays reshaped with one dimension per filter
|
||||
mean = self.get_reshaped_data(value='mean')
|
||||
std_dev = self.get_reshaped_data(value='std_dev')
|
||||
|
||||
# Sum across any filter bins specified by the user
|
||||
if filter_type in _FILTER_TYPES:
|
||||
find_filter = self.find_filter(filter_type)
|
||||
|
||||
# If user did not specify filter bins, sum across all bins
|
||||
if len(filter_bins) == 0:
|
||||
filter = self.find_filter(filter_type)
|
||||
filter_bins = [[(filter.get_bin(i),)] for i in range(filter.num_bins)]
|
||||
bin_indices = np.arange(find_filter.num_bins)
|
||||
|
||||
if filter_type == 'distribcell':
|
||||
filter_bins = np.arange(find_filter.num_bins)
|
||||
else:
|
||||
num_bins = find_filter.num_bins
|
||||
filter_bins = \
|
||||
[(find_filter.get_bin(i)) for i in range(num_bins)]
|
||||
|
||||
# Only sum across bins specified by the user
|
||||
else:
|
||||
filter_bins = [[(filter_bin,)] for filter_bin in filter_bins]
|
||||
bin_indices = \
|
||||
[find_filter.get_bin_index(bin) for bin in filter_bins]
|
||||
|
||||
filters = [[filter_type]]
|
||||
# If user did not specify a filter type, do not sum across filter bins
|
||||
# Sum across the bins in the user-specified filter
|
||||
for i, self_filter in enumerate(self.filters):
|
||||
if self_filter.type == filter_type:
|
||||
mean = np.take(mean, indices=bin_indices, axis=i)
|
||||
std_dev = np.take(std_dev, indices=bin_indices, axis=i)
|
||||
mean = np.sum(mean, axis=i, keepdims=True)
|
||||
std_dev = np.sum(std_dev**2, axis=i, keepdims=True)
|
||||
std_dev = np.sqrt(std_dev)
|
||||
|
||||
# Add AggregateFilter to the tally sum
|
||||
if not remove_filter:
|
||||
filter_sum = \
|
||||
AggregateFilter(self_filter, filter_bins, 'sum')
|
||||
tally_sum.add_filter(filter_sum)
|
||||
|
||||
# Add a copy of each filter not summed across to the tally sum
|
||||
else:
|
||||
tally_sum.add_filter(copy.deepcopy(self_filter))
|
||||
|
||||
# Add a copy of this tally's filters to the tally sum
|
||||
else:
|
||||
filter_bins = [[]]
|
||||
filters = [[]]
|
||||
tally_sum._filters = copy.deepcopy(self.filters)
|
||||
|
||||
# Initialize Tally sum
|
||||
tally_sum = 0
|
||||
# Sum across any nuclides specified by the user
|
||||
if len(nuclides) != 0:
|
||||
nuclide_bins = [self.get_nuclide_index(nuclide) for nuclide in nuclides]
|
||||
axis_index = self.num_filters
|
||||
mean = np.take(mean, indices=nuclide_bins, axis=axis_index)
|
||||
std_dev = np.take(std_dev, indices=nuclide_bins, axis=axis_index)
|
||||
mean = np.sum(mean, axis=axis_index, keepdims=True)
|
||||
std_dev = np.sum(std_dev**2, axis=axis_index, keepdims=True)
|
||||
std_dev = np.sqrt(std_dev)
|
||||
|
||||
# Iterate over all Tally slice operands in summation
|
||||
prod = [scores, filters, filter_bins, nuclides]
|
||||
summed_filters = defaultdict(list)
|
||||
for scores, filters, filter_bins, nuclides in itertools.product(*prod):
|
||||
tally_slice = self.get_slice(scores, filters, filter_bins, nuclides)
|
||||
# Add AggregateNuclide to the tally sum
|
||||
nuclide_sum = AggregateNuclide(nuclides, 'sum')
|
||||
tally_sum.add_nuclide(nuclide_sum)
|
||||
|
||||
# Remove filters summed across to avoid bulky CrossFilters
|
||||
if filter_type:
|
||||
filter = tally_slice.find_filter(filter_type)
|
||||
tally_slice.remove_filter(filter)
|
||||
summed_filters[filter_type].append(filter)
|
||||
|
||||
# Accumulate this Tally slice into the Tally sum
|
||||
tally_sum += tally_slice
|
||||
|
||||
# Add back the filter(s) which were summed across to derived tally,
|
||||
# if filter bins were input; otherwise, leave out summed filter(s)
|
||||
if remove_filter and filter_type is not None:
|
||||
# Rename tally sum indicating a summation over a particular filter
|
||||
tally_sum.name = 'sum({0}, {1})'.format(self.name, filter_type)
|
||||
# Add a copy of this tally's nuclides to the tally sum
|
||||
else:
|
||||
for summed_filter_type in summed_filters:
|
||||
filters = summed_filters[summed_filter_type]
|
||||
for i in range(1, len(filters)):
|
||||
filters[i] = CrossFilter(filters[i-1], filters[i], '+')
|
||||
tally_sum.add_filter(filters[-1])
|
||||
tally_sum._nuclides = copy.deepcopy(self.nuclides)
|
||||
|
||||
# Sum across any scores specified by the user
|
||||
if len(scores) != 0:
|
||||
score_bins = [self.get_score_index(score) for score in scores]
|
||||
axis_index = self.num_filters + 1
|
||||
mean = np.take(mean, indices=score_bins, axis=axis_index)
|
||||
std_dev = np.take(std_dev, indices=score_bins, axis=axis_index)
|
||||
mean = np.sum(mean, axis=axis_index, keepdims=True)
|
||||
std_dev = np.sum(std_dev**2, axis=axis_index, keepdims=True)
|
||||
std_dev = np.sqrt(std_dev)
|
||||
|
||||
# Add AggregateScore to the tally sum
|
||||
score_sum = AggregateScore(scores, 'sum')
|
||||
tally_sum.add_score(score_sum)
|
||||
|
||||
# Add a copy of this tally's scores to the tally sum
|
||||
else:
|
||||
tally_sum._scores = copy.deepcopy(self.scores)
|
||||
|
||||
# Update the tally sum's filter strides
|
||||
tally_sum._update_filter_strides()
|
||||
|
||||
# Reshape condensed data arrays with one dimension for all filters
|
||||
mean = np.reshape(mean, tally_sum.shape)
|
||||
std_dev = np.reshape(std_dev, tally_sum.shape)
|
||||
|
||||
# Assign tally sum's data with the new arrays
|
||||
tally_sum._mean = mean
|
||||
tally_sum._std_dev = std_dev
|
||||
|
||||
# If original tally was sparse, sparsify the tally summation
|
||||
tally_sum.sparse = self.sparse
|
||||
|
|
@ -2880,11 +2964,8 @@ class Tally(object):
|
|||
new_tally._std_dev = np.zeros(new_tally.shape, dtype=np.float64)
|
||||
new_tally._std_dev[diag_indices, :, :] = self.std_dev
|
||||
|
||||
# Correct each Filter's stride
|
||||
stride = new_tally.num_nuclides * new_tally.num_scores
|
||||
for filter in reversed(new_tally.filters):
|
||||
filter.stride = stride
|
||||
stride *= filter.num_bins
|
||||
# Update the new tally's filter strides
|
||||
new_tally._update_filter_strides()
|
||||
|
||||
# If original tally was sparse, sparsify the diagonalized tally
|
||||
new_tally.sparse = self.sparse
|
||||
|
|
|
|||
1
tests/test_tally_aggregation/inputs_true.dat
Normal file
1
tests/test_tally_aggregation/inputs_true.dat
Normal file
|
|
@ -0,0 +1 @@
|
|||
530a5e969901e153531f74aed46246b1e8783a0e2f347e472f7554c9970152f45d85499f17d7df9c35c74fed6f78d449aa70bf0c1f8947cd34d3a829483a0055
|
||||
1
tests/test_tally_aggregation/results_true.dat
Normal file
1
tests/test_tally_aggregation/results_true.dat
Normal file
|
|
@ -0,0 +1 @@
|
|||
ba8bfe764fcc0484a4fdab8fdc4ff8ad0e4a98b1ff33e8687899c8cc6bf80cb28b3a59aeaec84bd74681b8b5f19f714292ccaa9c9d4ba852b2cc29872f612e10
|
||||
99
tests/test_tally_aggregation/test_tally_aggregation.py
Normal file
99
tests/test_tally_aggregation/test_tally_aggregation.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import hashlib
|
||||
sys.path.insert(0, os.pardir)
|
||||
from testing_harness import PyAPITestHarness
|
||||
import openmc
|
||||
|
||||
|
||||
class TallyAggregationTestHarness(PyAPITestHarness):
|
||||
def _build_inputs(self):
|
||||
|
||||
# The summary.h5 file needs to be created to read in the tallies
|
||||
self._input_set.settings.output = {'summary': True}
|
||||
|
||||
# Initialize the tallies file
|
||||
tallies_file = openmc.TalliesFile()
|
||||
|
||||
# Initialize the nuclides
|
||||
u235 = openmc.Nuclide('U-235')
|
||||
u238 = openmc.Nuclide('U-238')
|
||||
pu239 = openmc.Nuclide('Pu-239')
|
||||
|
||||
# Initialize the filters
|
||||
energy_filter = openmc.Filter(type='energy', bins=[0.0, 0.253e-6,
|
||||
1.0e-3, 1.0, 20.0])
|
||||
distrib_filter = openmc.Filter(type='distribcell', bins=[60])
|
||||
|
||||
# Initialized the tallies
|
||||
tally = openmc.Tally(name='distribcell tally')
|
||||
tally.add_filter(energy_filter)
|
||||
tally.add_filter(distrib_filter)
|
||||
tally.add_score('nu-fission')
|
||||
tally.add_score('total')
|
||||
tally.add_nuclide(u235)
|
||||
tally.add_nuclide(u238)
|
||||
tally.add_nuclide(pu239)
|
||||
tallies_file.add_tally(tally)
|
||||
|
||||
# Export tallies to file
|
||||
self._input_set.tallies = tallies_file
|
||||
super(TallyAggregationTestHarness, self)._build_inputs()
|
||||
|
||||
def _get_results(self, hash_output=True):
|
||||
"""Digest info in the statepoint and return as a string."""
|
||||
|
||||
# Read the statepoint file.
|
||||
statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0]
|
||||
sp = openmc.StatePoint(statepoint)
|
||||
|
||||
# Read the summary file.
|
||||
summary = glob.glob(os.path.join(os.getcwd(), 'summary.h5'))[0]
|
||||
su = openmc.Summary(summary)
|
||||
sp.link_with_summary(su)
|
||||
|
||||
# Extract the tally of interest
|
||||
tally = sp.get_tally(name='distribcell tally')
|
||||
|
||||
# Perform tally aggregations across filter bins, nuclides and scores
|
||||
outstr = ''
|
||||
|
||||
# Sum across all energy filter bins
|
||||
tally_sum = tally.summation(filter_type='energy')
|
||||
outstr += ', '.join(map(str, tally_sum.mean))
|
||||
outstr += ', '.join(map(str, tally_sum.std_dev))
|
||||
|
||||
# Sum across all distribcell filter bins
|
||||
tally_sum = tally.summation(filter_type='distribcell')
|
||||
outstr += ', '.join(map(str, tally_sum.mean))
|
||||
outstr += ', '.join(map(str, tally_sum.std_dev))
|
||||
|
||||
# Sum across all nuclides
|
||||
tally_sum = tally.summation(nuclides=['U-235', 'U-238', 'Pu-239'])
|
||||
outstr += ', '.join(map(str, tally_sum.mean))
|
||||
outstr += ', '.join(map(str, tally_sum.std_dev))
|
||||
|
||||
# Sum across all scores
|
||||
tally_sum = tally.summation(scores=['nu-fission', 'total'])
|
||||
outstr += ', '.join(map(str, tally_sum.mean))
|
||||
outstr += ', '.join(map(str, tally_sum.std_dev))
|
||||
|
||||
# Hash the results if necessary
|
||||
if hash_output:
|
||||
sha512 = hashlib.sha512()
|
||||
sha512.update(outstr.encode('utf-8'))
|
||||
outstr = sha512.hexdigest()
|
||||
|
||||
return outstr
|
||||
|
||||
def _cleanup(self):
|
||||
super(TallyAggregationTestHarness, self)._cleanup()
|
||||
f = os.path.join(os.getcwd(), 'tallies.xml')
|
||||
if os.path.exists(f): os.remove(f)
|
||||
|
||||
if __name__ == '__main__':
|
||||
harness = TallyAggregationTestHarness('statepoint.10.h5', True)
|
||||
harness.main()
|
||||
|
|
@ -100,8 +100,6 @@ class TallyArithmeticTestHarness(PyAPITestHarness):
|
|||
'entrywise')
|
||||
outstr += str(tally_3.mean)
|
||||
|
||||
print(outstr)
|
||||
|
||||
# Hash the results if necessary
|
||||
if hash_output:
|
||||
sha512 = hashlib.sha512()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue