Force all filters to have a .bins attribute that makes sense

This commit is contained in:
Paul Romano 2018-03-28 15:56:03 -05:00
parent 39bbb2de46
commit d41ac48467
10 changed files with 350 additions and 736 deletions

View file

@ -196,7 +196,7 @@ def check_less_than(name, value, maximum, equality=False):
maximum : object
Maximum value to check against
equality : bool, optional
Whether equality is allowed. Defaluts to False.
Whether equality is allowed. Defaults to False.
"""
@ -223,7 +223,7 @@ def check_greater_than(name, value, minimum, equality=False):
minimum : object
Minimum value to check against
equality : bool, optional
Whether equality is allowed. Defaluts to False.
Whether equality is allowed. Defaults to False.
"""

View file

@ -3,6 +3,7 @@ from collections import Iterable, OrderedDict
import copy
from functools import reduce
import hashlib
from itertools import product
from numbers import Real, Integral
import operator
from xml.etree import ElementTree as ET
@ -192,9 +193,6 @@ class Filter(IDManagerMixin, metaclass=FilterMeta):
@bins.setter
def bins(self, bins):
# Format the bins as a 1D numpy array.
bins = np.atleast_1d(bins)
# Check the bin values.
self.check_bins(bins)
@ -221,8 +219,6 @@ class Filter(IDManagerMixin, metaclass=FilterMeta):
XML element containing filter data
"""
element = ET.Element('filter')
element.set('id', str(self.id))
element.set('type', self.short_name.lower())
@ -332,7 +328,10 @@ class Filter(IDManagerMixin, metaclass=FilterMeta):
'is not one of the bins'.format(filter_bin)
raise ValueError(msg)
return np.where(self.bins == filter_bin)[0][0]
if isinstance(self.bins, np.ndarray):
return np.where(self.bins == filter_bin)[0][0]
else:
return self.bins.index(filter_bin)
def get_bin(self, bin_index):
"""Returns the filter bin for some filter bin index.
@ -423,25 +422,24 @@ class Filter(IDManagerMixin, metaclass=FilterMeta):
class WithIDFilter(Filter):
"""Abstract parent for filters of types with ids (Cell, Material, etc.)."""
@Filter.bins.setter
def bins(self, bins):
# Format the bins as a 1D numpy array.
"""Abstract parent for filters of types with IDs (Cell, Material, etc.)."""
def __init__(self, bins, filter_id=None):
bins = np.atleast_1d(bins)
# Check the bin values.
# Make sure bins are either integers or appropriate objects
cv.check_iterable_type('filter bins', bins,
(Integral, self.expected_type))
# Extract ID values
bins = np.array([b if isinstance(b, Integral) else b.id
for b in bins])
self.bins = bins
self.id = filter_id
def check_bins(self, bins):
# Check the bin values.
for edge in bins:
if isinstance(edge, Integral):
cv.check_greater_than('filter bin', edge, 0, equality=True)
# Extract id values.
bins = np.atleast_1d([b if isinstance(b, Integral) else b.id
for b in bins])
self._bins = bins
cv.check_greater_than('filter bin', edge, 0, equality=True)
class UniverseFilter(WithIDFilter):
@ -601,12 +599,13 @@ class MeshFilter(Filter):
Attributes
----------
bins : Integral
The Mesh ID
mesh : openmc.Mesh
The Mesh object that events will be tallied onto
id : int
Unique identifier for the filter
bins : list of tuple
A list of mesh indices for each filter bin, e.g. [(1, 1, 1), (2, 1, 1),
...]
num_bins : Integral
The number of filter bins
@ -614,7 +613,7 @@ class MeshFilter(Filter):
def __init__(self, mesh, filter_id=None):
self.mesh = mesh
super().__init__(mesh.id, filter_id)
self.id = filter_id
@classmethod
def from_hdf5(cls, group, **kwargs):
@ -643,68 +642,14 @@ class MeshFilter(Filter):
def mesh(self, mesh):
cv.check_type('filter mesh', mesh, openmc.Mesh)
self._mesh = mesh
self.bins = mesh.id
@property
def num_bins(self):
return reduce(operator.mul, self.mesh.dimension)
def check_bins(self, bins):
if not len(bins) == 1:
msg = 'Unable to add bins "{0}" to a MeshFilter since ' \
'only a single mesh can be used per tally'.format(bins)
raise ValueError(msg)
elif not isinstance(bins[0], Integral):
msg = 'Unable to add bin "{0}" to MeshFilter since it ' \
'is a non-integer'.format(bins[0])
raise ValueError(msg)
elif bins[0] < 0:
msg = 'Unable to add bin "{0}" to MeshFilter since it ' \
'is a negative integer'.format(bins[0])
raise ValueError(msg)
self.bins = list(mesh.indices)
def can_merge(self, other):
# Mesh filters cannot have more than one bin
return False
def get_bin_index(self, filter_bin):
# Filter bins for a mesh are an (x,y,z) tuple. Convert (x,y,z) to a
# single bin -- this is similar to subroutine mesh_indices_to_bin in
# openmc/src/mesh.F90.
n_dim = len(self.mesh.dimension)
if n_dim == 3:
i, j, k = filter_bin
nx, ny, nz = self.mesh.dimension
return (i - 1) + (j - 1)*nx + (k - 1)*nx*ny
elif n_dim == 2:
i, j, *_ = filter_bin
nx, ny = self.mesh.dimension
return (i - 1) + (j - 1)*nx
else:
return filter_bin[0] - 1
def get_bin(self, bin_index):
cv.check_type('bin_index', bin_index, Integral)
cv.check_greater_than('bin_index', bin_index, 0, equality=True)
cv.check_less_than('bin_index', bin_index, self.num_bins)
n_dim = len(self.mesh.dimension)
if n_dim == 3:
# Construct 3-tuple of x,y,z cell indices for a 3D mesh
nx, ny, nz = self.mesh.dimension
x = (bin_index % nx) + 1
y = (bin_index % (nx * ny)) // nx + 1
z = bin_index // (nx * ny) + 1
return (x, y, z)
elif n_dim == 2:
# Construct 2-tuple of x,y cell indices for a 2D mesh
nx, ny = self.mesh.dimension
x = (bin_index % nx) + 1
y = bin_index // nx + 1
return (x, y)
else:
return (bin_index + 1,)
return self.bins[bin_index]
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
@ -783,6 +728,19 @@ class MeshFilter(Filter):
return df
def to_xml_element(self):
"""Return XML Element representing the Filter.
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing filter data
"""
element = super().to_xml_element()
element[0].text = str(self.mesh.id)
return element
class MeshSurfaceFilter(MeshFilter):
"""Filter events by surface crossings on a regular, rectangular mesh.
@ -802,36 +760,25 @@ class MeshSurfaceFilter(MeshFilter):
The Mesh object that events will be tallied onto
id : int
Unique identifier for the filter
bins : list of tuple
A list of mesh indices / surfaces for each filter bin, e.g. [(1, 1,
'x-min out'), (1, 1, 'x-min in'), ...]
num_bins : Integral
The number of filter bins
"""
@property
def num_bins(self):
n_dim = len(self.mesh.dimension)
return 4*n_dim*reduce(operator.mul, self.mesh.dimension)
@MeshFilter.mesh.setter
def mesh(self, mesh):
cv.check_type('filter mesh', mesh, openmc.Mesh)
self._mesh = mesh
def get_bin_index(self, filter_bin):
# Split bin into mesh/surface parts
*mesh_tuple, surf = filter_bin
# Get index for mesh alone
mesh_index = super().get_bin_index(mesh_tuple)
# Combine surface and mesh index
n_dim = len(self.mesh.dimension)
for surf_index, name in enumerate(_CURRENT_NAMES):
if surf == name:
return 4*n_dim*mesh_index + surf_index
else:
raise ValueError("'{}' is not a valid mesh surface.".format(surf))
def get_bin(self, bin_index):
n_dim = len(self.mesh.dimension)
mesh_index, surf_index = divmod(bin_index, 4*n_dim)
mesh_bin = super().get_bin(mesh_index)
return mesh_bin + (_CURRENT_NAMES[surf_index],)
# Take the product of mesh indices and current names
n_dim = len(mesh.dimension)
self.bins = [mesh_tuple + (surf,) for mesh_tuple, surf in
product(mesh.indices, _CURRENT_NAMES[:4*n_dim])]
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
@ -920,42 +867,68 @@ class RealFilter(Filter):
Parameters
----------
bins : Iterable of Real
A grid of bin values.
values : iterable of float
A list of values for which each successive pair constitutes a range of
values for a single bin
filter_id : int
Unique identifier for the filter
Attributes
----------
bins : Iterable of Real
A grid of bin values.
values : numpy.ndarray
An array of values for which each successive pair constitutes a range of
values for a single bin
id : int
Unique identifier for the filter
bins : numpy.ndarray
An array of shape (N, 2) where each row is a pair of values indicating a
filter bin range
num_bins : int
The number of filter bins
"""
def __init__(self, values, filter_id=None):
self.values = np.asarray(values)
self.bins = np.vstack((self.values[:-1], self.values[1:])).T
self.id = filter_id
def __gt__(self, other):
if type(self) is type(other):
# Compare largest/smallest bin edges in filters
# This logic is used when merging tallies with real filters
return self.bins[0] >= other.bins[-1]
return self.values[0] >= other.values[-1]
else:
return super().__gt__(other)
@property
def num_bins(self):
return len(self.bins) - 1
@Filter.bins.setter
def bins(self, bins):
Filter.bins.__set__(self, np.asarray(bins))
def check_bins(self, bins):
for v0, v1 in bins:
# Values should be real
cv.check_type('filter value', v0, Real)
cv.check_type('filter value', v1, Real)
# Make sure that each tuple has values that are increasing
if v1 < v0:
raise ValueError('Values {} and {} appear to be out of order'
.format(v0, v1))
for pair0, pair1 in zip(bins[:-1], bins[1:]):
# Successive pairs should be ordered
if pair1[1] < pair0[1]:
raise ValueError('Values {} and {} appear to be out of order'
.format(pair1[1], pair0[1]))
def can_merge(self, other):
if type(self) is not type(other):
return False
if self.bins[0] == other.bins[-1]:
if self.bins[0, 0] == other.bins[-1][1]:
# This low edge coincides with other's high edge
return True
elif self.bins[-1] == other.bins[0]:
elif self.bins[-1][1] == other.bins[0, 0]:
# This high edge coincides with other's low edge
return True
else:
@ -968,11 +941,11 @@ class RealFilter(Filter):
raise ValueError(msg)
# Merge unique filter bins
merged_bins = np.concatenate((self.bins, other.bins))
merged_bins = np.unique(merged_bins)
merged_values = np.concatenate((self.values, other.values))
merged_values = np.unique(merged_values)
# Create a new filter with these bins and a new auto-generated ID
return type(self)(sorted(merged_bins))
return type(self)(sorted(merged_values))
def is_subset(self, other):
"""Determine if another filter is a subset of this filter.
@ -994,80 +967,19 @@ class RealFilter(Filter):
if type(self) is not type(other):
return False
elif len(self.bins) != len(other.bins):
elif self.num_bins != other.num_bins:
return False
else:
return np.allclose(self.bins, other.bins)
return np.allclose(self.values, other.values)
def get_bin_index(self, filter_bin):
i = np.where(self.bins == filter_bin[1])[0]
i = np.where(self.bins[:, 1] == filter_bin[1])[0]
if len(i) == 0:
msg = 'Unable to get the bin index for Filter since "{0}" ' \
'is not one of the bins'.format(filter_bin)
raise ValueError(msg)
else:
return i[0] - 1
def get_bin(self, bin_index):
cv.check_type('bin_index', bin_index, Integral)
cv.check_greater_than('bin_index', bin_index, 0, equality=True)
cv.check_less_than('bin_index', bin_index, self.num_bins)
# Construct 2-tuple of lower, upper bins for real-valued filters
return (self.bins[bin_index], self.bins[bin_index + 1])
class EnergyFilter(RealFilter):
"""Bins tally events based on incident particle energy.
Parameters
----------
bins : Iterable of Real
A grid of energy values in eV.
filter_id : int
Unique identifier for the filter
Attributes
----------
bins : Iterable of Real
A grid of energy values in eV.
id : int
Unique identifier for the filter
num_bins : int
The number of filter bins
"""
def get_bin_index(self, filter_bin):
# Use lower energy bound to find index for RealFilters
deltas = np.abs(self.bins - filter_bin[1]) / filter_bin[1]
min_delta = np.min(deltas)
if min_delta < 1E-3:
return deltas.argmin() - 1
else:
msg = 'Unable to get the bin index for Filter since "{0}" ' \
'is not one of the bins'.format(filter_bin)
raise ValueError(msg)
def check_bins(self, bins):
for edge in bins:
if not isinstance(edge, Real):
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is a non-integer or floating point ' \
'value'.format(edge, type(self))
raise ValueError(msg)
elif edge < 0.:
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is a negative value'.format(edge, type(self))
raise ValueError(msg)
# Check that bin edges are monotonically increasing
for index in range(1, len(bins)):
if bins[index] < bins[index-1]:
msg = 'Unable to add bin edges "{0}" to a "{1}" Filter ' \
'since they are not monotonically ' \
'increasing'.format(bins, type(self))
raise ValueError(msg)
return i[0]
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
@ -1102,35 +1014,103 @@ class EnergyFilter(RealFilter):
# Extract the lower and upper energy bounds, then repeat and tile
# them as necessary to account for other filters.
lo_bins = np.repeat(self.bins[:-1], stride)
hi_bins = np.repeat(self.bins[1:], stride)
lo_bins = np.repeat(self.bins[:, 0], stride)
hi_bins = np.repeat(self.bins[:, 1], stride)
tile_factor = data_size // len(lo_bins)
lo_bins = np.tile(lo_bins, tile_factor)
hi_bins = np.tile(hi_bins, tile_factor)
# Add the new energy columns to the DataFrame.
df.loc[:, self.short_name.lower() + ' low [eV]'] = lo_bins
df.loc[:, self.short_name.lower() + ' high [eV]'] = hi_bins
if hasattr(self, 'units'):
units = ' [{}]'.format(self.units)
else:
units = ''
df.loc[:, self.short_name.lower() + ' low' + units] = lo_bins
df.loc[:, self.short_name.lower() + ' high' + units] = hi_bins
return df
def to_xml_element(self):
"""Return XML Element representing the Filter.
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing filter data
"""
element = super().to_xml_element()
element[0].text = ' '.join(str(x) for x in self.values)
return element
class EnergyFilter(RealFilter):
"""Bins tally events based on incident particle energy.
Parameters
----------
values : Iterable of Real
A list of values for which each successive pair constitutes a range of
energies in [eV] for a single bin
filter_id : int
Unique identifier for the filter
Attributes
----------
values : numpy.ndarray
An array of values for which each successive pair constitutes a range of
energies in [eV] for a single bin
id : int
Unique identifier for the filter
bins : numpy.ndarray
An array of shape (N, 2) where each row is a pair of energies in [eV]
for a single filter bin
num_bins : int
The number of filter bins
"""
units = 'eV'
def get_bin_index(self, filter_bin):
# Use lower energy bound to find index for RealFilters
deltas = np.abs(self.bins[:, 1] - filter_bin[1]) / filter_bin[1]
min_delta = np.min(deltas)
if min_delta < 1E-3:
return deltas.argmin()
else:
msg = 'Unable to get the bin index for Filter since "{0}" ' \
'is not one of the bins'.format(filter_bin)
raise ValueError(msg)
def check_bins(self, bins):
super().check_bins(bins)
for v0, v1 in bins:
cv.check_greater_than('filter value', v0, 0., equality=True)
cv.check_greater_than('filter value', v1, 0., equality=True)
class EnergyoutFilter(EnergyFilter):
"""Bins tally events based on outgoing particle energy.
Parameters
----------
bins : Iterable of Real
A grid of energy values in eV.
values : Iterable of Real
A list of values for which each successive pair constitutes a range of
energies in [eV] for a single bin
filter_id : int
Unique identifier for the filter
Attributes
----------
bins : Iterable of Real
A grid of energy values in eV.
values : numpy.ndarray
An array of values for which each successive pair constitutes a range of
energies in [eV] for a single bin
id : int
Unique identifier for the filter
bins : numpy.ndarray
An array of shape (N, 2) where each row is a pair of energies in [eV]
for a single filter bin
num_bins : int
The number of filter bins
@ -1412,98 +1392,41 @@ class MuFilter(RealFilter):
Parameters
----------
bins : Iterable of Real or Integral
A grid of scattering angles which events will binned into. Values
represent the cosine of the scattering angle. If an Iterable is given,
the values will be used explicitly as grid points. If a single Integral
is given, the range [-1, 1] will be divided up equally into that number
of bins.
values : int or Iterable of Real
A grid of scattering angles which events will binned into. Values
represent the cosine of the scattering angle. If an iterable is given,
the values will be used explicitly as grid points. If a single int is
given, the range [-1, 1] will be divided up equally into that number of
bins.
filter_id : int
Unique identifier for the filter
Attributes
----------
bins : Integral
A grid of scattering angles which events will binned into. Values
represent the cosine of the scattering angle. If an Iterable is given,
the values will be used explicitly as grid points. If a single Integral
is given, the range [-1, 1] will be divided up equally into that number
of bins.
values : numpy.ndarray
An array of values for which each successive pair constitutes a range of
scattering angle cosines for a single bin
id : int
Unique identifier for the filter
bins : numpy.ndarray
An array of shape (N, 2) where each row is a pair of scattering angle
cosines for a single filter bin
num_bins : Integral
The number of filter bins
"""
def __init__(self, values, filter_id=None):
if isinstance(values, Integral):
values = np.linspace(-1., 1., values + 1)
super().__init__(values, filter_id)
def check_bins(self, bins):
for edge in bins:
if not isinstance(edge, Real):
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is a non-integer or floating point ' \
'value'.format(edge, type(self))
raise ValueError(msg)
elif edge < -1.:
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is less than -1'.format(edge, type(self))
raise ValueError(msg)
elif edge > 1.:
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is greater than 1'.format(edge, type(self))
raise ValueError(msg)
# Check that bin edges are monotonically increasing
for index in range(1, len(bins)):
if bins[index] < bins[index-1]:
msg = 'Unable to add bin edges "{0}" to a "{1}" Filter ' \
'since they are not monotonically ' \
'increasing'.format(bins, type(self))
raise ValueError(msg)
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
This method constructs a Pandas DataFrame object for the filter with
columns annotated by filter bin information. This is a helper method
for :meth:`Tally.get_pandas_dataframe`.
Parameters
----------
data_size : int
The total number of bins in the tally corresponding to this filter
stride : int
Stride in memory for the filter
Returns
-------
pandas.DataFrame
A Pandas DataFrame with one column of the lower energy bound and one
column of upper energy bound for each filter bin. The number of
rows in the DataFrame is the same as the total number of bins in the
corresponding tally, with the filter bin appropriately tiled to map
to the corresponding tally bins.
See also
--------
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
"""
# Initialize Pandas DataFrame
df = pd.DataFrame()
# Extract the lower and upper energy bounds, then repeat and tile
# them as necessary to account for other filters.
lo_bins = np.repeat(self.bins[:-1], stride)
hi_bins = np.repeat(self.bins[1:], stride)
tile_factor = data_size // len(lo_bins)
lo_bins = np.tile(lo_bins, tile_factor)
hi_bins = np.tile(hi_bins, tile_factor)
# Add the new energy columns to the DataFrame.
df.loc[:, self.short_name.lower() + ' low'] = lo_bins
df.loc[:, self.short_name.lower() + ' high'] = hi_bins
return df
super().check_bins(bins)
for x in np.ravel(bins):
if not np.isclose(x, -1.):
cv.check_greater_than('filter value', x, -1., equality=True)
if not np.isclose(x, 1.):
cv.check_less_than('filter value', x, 1., equality=True)
class PolarFilter(RealFilter):
@ -1511,98 +1434,44 @@ class PolarFilter(RealFilter):
Parameters
----------
bins : Iterable of Real or Integral
A grid of polar angles which events will binned into. Values represent
an angle in radians relative to the z-axis. If an Iterable is given,
the values will be used explicitly as grid points. If a single Integral
is given, the range [0, pi] will be divided up equally into that number
of bins.
values : int or Iterable of Real
A grid of polar angles which events will binned into. Values represent
an angle in radians relative to the z-axis. If an iterable is given, the
values will be used explicitly as grid points. If a single int is given,
the range [0, pi] will be divided up equally into that number of bins.
filter_id : int
Unique identifier for the filter
Attributes
----------
bins : Iterable of Real or Integral
A grid of polar angles which events will binned into. Values represent
an angle in radians relative to the z-axis. If an Iterable is given,
the values will be used explicitly as grid points. If a single Integral
is given, the range [0, pi] will be divided up equally into that number
of bins.
values : numpy.ndarray
An array of values for which each successive pair constitutes a range of
polar angles in [rad] for a single bin
id : int
Unique identifier for the filter
bins : numpy.ndarray
An array of shape (N, 2) where each row is a pair of polar angles for a
single filter bin
id : int
Unique identifier for the filter
num_bins : Integral
The number of filter bins
"""
units = 'rad'
def __init__(self, values, filter_id=None):
if isinstance(values, Integral):
values = np.linspace(0., np.pi, values + 1)
super().__init__(values, filter_id)
def check_bins(self, bins):
for edge in bins:
if not isinstance(edge, Real):
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is a non-integer or floating point ' \
'value'.format(edge, type(self))
raise ValueError(msg)
elif edge < 0.:
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is less than 0'.format(edge, type(self))
raise ValueError(msg)
elif not np.isclose(edge, np.pi) and edge > np.pi:
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is greater than pi'.format(edge, type(self))
raise ValueError(msg)
# Check that bin edges are monotonically increasing
for index in range(1, len(bins)):
if bins[index] < bins[index-1]:
msg = 'Unable to add bin edges "{0}" to a "{1}" Filter ' \
'since they are not monotonically ' \
'increasing'.format(bins, type(self))
raise ValueError(msg)
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
This method constructs a Pandas DataFrame object for the filter with
columns annotated by filter bin information. This is a helper method for
:meth:`Tally.get_pandas_dataframe`.
Parameters
----------
data_size : int
The total number of bins in the tally corresponding to this filter
stride : int
Stride in memory for the filter
Returns
-------
pandas.DataFrame
A Pandas DataFrame with a column corresponding to the lower polar
angle bound for each of the filter'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 bin appropriately tiled to map
to the corresponding tally bins.
See also
--------
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
"""
# Initialize Pandas DataFrame
df = pd.DataFrame()
# Extract the lower and upper angle bounds, then repeat and tile
# them as necessary to account for other filters.
lo_bins = np.repeat(self.bins[:-1], stride)
hi_bins = np.repeat(self.bins[1:], stride)
tile_factor = data_size // len(lo_bins)
lo_bins = np.tile(lo_bins, tile_factor)
hi_bins = np.tile(hi_bins, tile_factor)
# Add the new angle columns to the DataFrame.
df.loc[:, 'polar low'] = lo_bins
df.loc[:, 'polar high'] = hi_bins
return df
super().check_bins(bins)
for x in np.ravel(bins):
if not np.isclose(x, 0.):
cv.check_greater_than('filter value', x, 0., equality=True)
if not np.isclose(x, np.pi):
cv.check_less_than('filter value', x, np.pi, equality=True)
class AzimuthalFilter(RealFilter):
@ -1610,98 +1479,43 @@ class AzimuthalFilter(RealFilter):
Parameters
----------
bins : Iterable of Real or Integral
A grid of azimuthal angles which events will binned into. Values
values : int or Iterable of Real
A grid of azimuthal angles which events will binned into. Values
represent an angle in radians relative to the x-axis and perpendicular
to the z-axis. If an Iterable is given, the values will be used
explicitly as grid points. If a single Integral is given, the range
to the z-axis. If an iterable is given, the values will be used
explicitly as grid points. If a single int is given, the range
[-pi, pi) will be divided up equally into that number of bins.
filter_id : int
Unique identifier for the filter
Attributes
----------
bins : Iterable of Real or Integral
A grid of azimuthal angles which events will binned into. Values
represent an angle in radians relative to the x-axis and perpendicular
to the z-axis. If an Iterable is given, the values will be used
explicitly as grid points. If a single Integral is given, the range
[-pi, pi) will be divided up equally into that number of bins.
values : numpy.ndarray
An array of values for which each successive pair constitutes a range of
azimuthal angles in [rad] for a single bin
id : int
Unique identifier for the filter
bins : numpy.ndarray
An array of shape (N, 2) where each row is a pair of azimuthal angles
for a single filter bin
num_bins : Integral
The number of filter bins
"""
units = 'rad'
def __init__(self, values, filter_id=None):
if isinstance(values, Integral):
values = np.linspace(-np.pi, np.pi, values + 1)
super().__init__(values, filter_id)
def check_bins(self, bins):
for edge in bins:
if not isinstance(edge, Real):
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is a non-integer or floating point ' \
'value'.format(edge, type(self))
raise ValueError(msg)
elif not np.isclose(edge, -np.pi) and edge < -np.pi:
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is less than -pi'.format(edge, type(self))
raise ValueError(msg)
elif not np.isclose(edge, np.pi) and edge > np.pi:
msg = 'Unable to add bin edge "{0}" to a "{1}" ' \
'since it is greater than pi'.format(edge, type(self))
raise ValueError(msg)
# Check that bin edges are monotonically increasing
for index in range(1, len(bins)):
if bins[index] < bins[index-1]:
msg = 'Unable to add bin edges "{0}" to a "{1}" Filter ' \
'since they are not monotonically ' \
'increasing'.format(bins, type(self))
raise ValueError(msg)
def get_pandas_dataframe(self, data_size, stride, paths=True):
"""Builds a Pandas DataFrame for the Filter's bins.
This method constructs a Pandas DataFrame object for the filter with
columns annotated by filter bin information. This is a helper method for
:meth:`Tally.get_pandas_dataframe`.
Parameters
----------
data_size : int
The total number of bins in the tally corresponding to this filter
stride : int
Stride in memory for the filter
Returns
-------
pandas.DataFrame
A Pandas DataFrame with a column corresponding to the lower
azimuthal angle bound for each of the filter'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 bin appropriately tiled to map
to the corresponding tally bins.
See also
--------
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
"""
# Initialize Pandas DataFrame
df = pd.DataFrame()
# Extract the lower and upper angle bounds, then repeat and tile
# them as necessary to account for other filters.
lo_bins = np.repeat(self.bins[:-1], stride)
hi_bins = np.repeat(self.bins[1:], stride)
tile_factor = data_size // len(lo_bins)
lo_bins = np.tile(lo_bins, tile_factor)
hi_bins = np.tile(hi_bins, tile_factor)
# Add the new angle columns to the DataFrame.
df.loc[:, 'azimuthal low'] = lo_bins
df.loc[:, 'azimuthal high'] = hi_bins
return df
super().check_bins(bins)
for x in np.ravel(bins):
if not np.isclose(x, -np.pi):
cv.check_greater_than('filter value', x, -np.pi, equality=True)
if not np.isclose(x, np.pi):
cv.check_less_than('filter value', x, np.pi, equality=True)
class DelayedGroupFilter(Filter):
@ -1709,7 +1523,7 @@ class DelayedGroupFilter(Filter):
Parameters
----------
bins : Integral or Iterable of Integral
bins : iterable of int
The delayed neutron precursor groups. For example, ENDF/B-VII.1 uses
6 precursor groups so a tally with all groups will have bins =
[1, 2, 3, 4, 5, 6].
@ -1718,7 +1532,7 @@ class DelayedGroupFilter(Filter):
Attributes
----------
bins : Integral or Iterable of Integral
bins : iterable of int
The delayed neutron precursor groups. For example, ENDF/B-VII.1 uses
6 precursor groups so a tally with all groups will have bins =
[1, 2, 3, 4, 5, 6].
@ -1728,17 +1542,10 @@ class DelayedGroupFilter(Filter):
The number of filter bins
"""
@Filter.bins.setter
def bins(self, bins):
# Format the bins as a 1D numpy array.
bins = np.atleast_1d(bins)
def check_bins(self, bins):
# Check the bin values.
cv.check_iterable_type('filter bins', bins, Integral)
for edge in bins:
cv.check_greater_than('filter bin', edge, 0, equality=True)
self._bins = bins
for g in bins:
cv.check_greater_than('delayed group', g, 0)
class EnergyFunctionFilter(Filter):
@ -1751,18 +1558,18 @@ class EnergyFunctionFilter(Filter):
Parameters
----------
energy : Iterable of Real
A grid of energy values in eV.
A grid of energy values in [eV]
y : iterable of Real
A grid of interpolant values in eV.
A grid of interpolant values in [eV]
filter_id : int
Unique identifier for the filter
Attributes
----------
energy : Iterable of Real
A grid of energy values in eV.
A grid of energy values in [eV]
y : iterable of Real
A grid of interpolant values in eV.
A grid of interpolant values in [eV]
id : int
Unique identifier for the filter
num_bins : Integral
@ -1903,6 +1710,14 @@ class EnergyFunctionFilter(Filter):
raise RuntimeError('EnergyFunctionFilters have no bins.')
def to_xml_element(self):
"""Return XML Element representing the Filter.
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing filter data
"""
element = ET.Element('filter')
element.set('id', str(self.id))
element.set('type', self.short_name.lower())

View file

@ -34,6 +34,7 @@ class LegendreFilter(Filter):
def __init__(self, order, filter_id=None):
self.order = order
self.bins = ['P{}'.format(i) for i in range(order + 1)]
self.id = filter_id
def __hash__(self):
@ -57,10 +58,6 @@ class LegendreFilter(Filter):
cv.check_greater_than('Legendre order', order, 0, equality=True)
self._order = order
@property
def num_bins(self):
return self._order + 1
@classmethod
def from_hdf5(cls, group, **kwargs):
if group['type'].value.decode() != cls.short_name.lower():
@ -74,44 +71,6 @@ class LegendreFilter(Filter):
return out
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
This method constructs a Pandas DataFrame object for the filter with
columns annotated by filter bin information. This is a helper method for
:meth:`Tally.get_pandas_dataframe`.
Parameters
----------
data_size : Integral
The total number of bins in the tally corresponding to this filter
stride : int
Stride in memory for the filter
Returns
-------
pandas.DataFrame
A Pandas DataFrame with a column that is filled with strings
indicating Legendre orders. The number of rows in the DataFrame is
the same as the total number of bins in the corresponding tally.
See also
--------
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
"""
# Initialize Pandas DataFrame
df = pd.DataFrame()
bins = np.array(['P{}'.format(i) for i in range(self.order + 1)])
filter_bins = np.repeat(bins, stride)
tile_factor = data_size // len(filter_bins)
filter_bins = np.tile(filter_bins, tile_factor)
df = pd.concat([df, pd.DataFrame(
{self.short_name.lower(): filter_bins})])
return df
def to_xml_element(self):
"""Return XML Element representing the filter.

View file

@ -44,6 +44,7 @@ class SpatialLegendreFilter(Filter):
self.axis = axis
self.minimum = minimum
self.maximum = maximum
self.bins = ['P{}'.format(i) for i in range(order + 1)]
self.id = filter_id
def __hash__(self):
@ -118,42 +119,6 @@ class SpatialLegendreFilter(Filter):
return cls(order, axis, min_, max_, filter_id)
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
This method constructs a Pandas DataFrame object for the filter with
columns annotated by filter bin information. This is a helper method for
:meth:`Tally.get_pandas_dataframe`.
Parameters
----------
data_size : Integral
The total number of bins in the tally corresponding to this filter
Returns
-------
pandas.DataFrame
A Pandas DataFrame with a column that is filled with strings
indicating Legendre orders. The number of rows in the DataFrame is
the same as the total number of bins in the corresponding tally.
See also
--------
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
"""
# Initialize Pandas DataFrame
df = pd.DataFrame()
bins = np.array(['P{}'.format(i) for i in range(self.order + 1)])
filter_bins = np.repeat(bins, stride)
tile_factor = data_size // len(filter_bins)
filter_bins = np.tile(filter_bins, tile_factor)
df = pd.concat([df, pd.DataFrame(
{self.short_name.lower(): filter_bins})])
return df
def to_xml_element(self):
"""Return XML Element representing the filter.

View file

@ -34,6 +34,9 @@ class SphericalHarmonicsFilter(Filter):
def __init__(self, order, filter_id=None):
self.order = order
self.id = filter_id
self.bins = ['Y{},{}'.format(n, m)
for n in range(order + 1)
for m in range(-n, n + 1)]
self._cosine = 'particle'
def __hash__(self):
@ -87,49 +90,6 @@ class SphericalHarmonicsFilter(Filter):
return out
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
This method constructs a Pandas DataFrame object for the filter with
columns annotated by filter bin information. This is a helper method for
:meth:`Tally.get_pandas_dataframe`.
Parameters
----------
data_size : Integral
The total number of bins in the tally corresponding to this filter
stride : int
Stride in memory for the filter
Returns
-------
pandas.DataFrame
A Pandas DataFrame with a column that is filled with strings
indicating spherical harmonics orders. The number of rows in the
DataFrame is the same as the total number of bins in the
corresponding tally.
See also
--------
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
"""
# Initialize Pandas DataFrame
df = pd.DataFrame()
bins = []
for n in range(self.order + 1):
bins.extend('Y{},{}'.format(n, m) for m in range(-n, n + 1))
bins = np.array(bins)
filter_bins = np.repeat(bins, stride)
tile_factor = data_size // len(filter_bins)
filter_bins = np.tile(filter_bins, tile_factor)
df = pd.concat([df, pd.DataFrame(
{self.short_name.lower(): filter_bins})])
return df
def to_xml_element(self):
"""Return XML Element representing the filter.

View file

@ -48,6 +48,9 @@ class ZernikeFilter(Filter):
self.x = x
self.y = y
self.r = r
self.bins = ['Z{},{}'.format(n, m)
for n in range(order + 1)
for m in range(-n, n + 1, 2)]
self.id = filter_id
def __hash__(self):
@ -116,48 +119,6 @@ class ZernikeFilter(Filter):
return cls(order, x, y, r, filter_id)
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
This method constructs a Pandas DataFrame object for the filter with
columns annotated by filter bin information. This is a helper method for
:meth:`Tally.get_pandas_dataframe`.
Parameters
----------
data_size : Integral
The total number of bins in the tally corresponding to this filter
Returns
-------
pandas.DataFrame
A Pandas DataFrame with a column that is filled with strings
indicating Zernike orders. The number of rows in the DataFrame is
the same as the total number of bins in the corresponding tally.
See also
--------
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
"""
# Initialize Pandas DataFrame
df = pd.DataFrame()
# Create list of strings for each order
orders = []
for n in range(self.order + 1):
for m in range(-n, n + 1, 2):
orders.append('Z{},{}'.format(n, m))
bins = np.array(orders)
filter_bins = np.repeat(bins, stride)
tile_factor = data_size // len(filter_bins)
filter_bins = np.tile(filter_bins, tile_factor)
df = pd.concat([df, pd.DataFrame(
{self.short_name.lower(): filter_bins})])
return df
def to_xml_element(self):
"""Return XML Element representing the filter.

View file

@ -38,6 +38,9 @@ class Mesh(IDManagerMixin):
are given, it is assumed that the mesh is an x-y mesh.
width : Iterable of float
The width of mesh cells in each direction.
indices : list of tuple
A list of mesh indices for each mesh element, e.g. [(1, 1, 1), (2, 1,
1), ...]
"""

View file

@ -1164,12 +1164,14 @@ class MGXS(metaclass=ABCMeta):
if not isinstance(tally_filter, (openmc.EnergyFilter,
openmc.EnergyoutFilter)):
continue
elif len(tally_filter.bins) != len(fine_edges):
elif len(tally_filter.bins) != len(fine_edges) - 1:
continue
elif not np.allclose(tally_filter.bins, fine_edges):
elif not np.allclose(tally_filter.bins[:, 0], fine_edges[:-1]):
continue
else:
tally_filter.bins = coarse_groups.group_edges
cedge = coarse_groups.group_edges
tally_filter.values = cedge
tally_filter.bins = np.vstack((cedge[:-1], cedge[1:])).T
mean = np.add.reduceat(mean, energy_indices, axis=i)
std_dev = np.add.reduceat(std_dev**2, energy_indices,
axis=i)
@ -2738,7 +2740,7 @@ class TransportXS(MGXS):
if self._rxn_rate_tally is None:
# Switch EnergyoutFilter to EnergyFilter.
old_filt = self.tallies['scatter-1'].filters[-1]
new_filt = openmc.EnergyFilter(old_filt.bins)
new_filt = openmc.EnergyFilter(old_filt.values)
self.tallies['scatter-1'].filters[-1] = new_filt
self._rxn_rate_tally = \
@ -2757,7 +2759,7 @@ class TransportXS(MGXS):
# Switch EnergyoutFilter to EnergyFilter.
old_filt = self.tallies['scatter-1'].filters[-1]
new_filt = openmc.EnergyFilter(old_filt.bins)
new_filt = openmc.EnergyFilter(old_filt.values)
self.tallies['scatter-1'].filters[-1] = new_filt
# Compute total cross section

View file

@ -218,10 +218,9 @@ class Tally(IDManagerMixin):
f = h5py.File(self._sp_filename, 'r')
# Extract Tally data from the file
data = f['tallies/tally {0}/results'.format(
self.id)].value
sum = data[:,:,0]
sum_sq = data[:,:,1]
data = f['tallies/tally {0}/results'.format(self.id)].value
sum = data[:, :, 0]
sum_sq = data[:, :, 1]
# Reshape the results arrays
sum = np.reshape(sum, self.shape)
@ -273,8 +272,8 @@ class Tally(IDManagerMixin):
# Convert NumPy array to SciPy sparse LIL matrix
if self.sparse:
self._mean = \
sps.lil_matrix(self._mean.flatten(), self._mean.shape)
self._mean = sps.lil_matrix(self._mean.flatten(),
self._mean.shape)
if self.sparse:
return np.reshape(self._mean.toarray(), self.shape)
@ -295,8 +294,8 @@ class Tally(IDManagerMixin):
# Convert NumPy array to SciPy sparse LIL matrix
if self.sparse:
self._std_dev = \
sps.lil_matrix(self._std_dev.flatten(), self._std_dev.shape)
self._std_dev = sps.lil_matrix(self._std_dev.flatten(),
self._std_dev.shape)
self.with_batch_statistics = True
@ -436,17 +435,16 @@ class Tally(IDManagerMixin):
# Convert NumPy arrays to SciPy sparse LIL matrices
if sparse and not self.sparse:
if self._sum is not None:
self._sum = \
sps.lil_matrix(self._sum.flatten(), self._sum.shape)
self._sum = sps.lil_matrix(self._sum.flatten(), self._sum.shape)
if self._sum_sq is not None:
self._sum_sq = \
sps.lil_matrix(self._sum_sq.flatten(), self._sum_sq.shape)
self._sum_sq = sps.lil_matrix(self._sum_sq.flatten(),
self._sum_sq.shape)
if self._mean is not None:
self._mean = \
sps.lil_matrix(self._mean.flatten(), self._mean.shape)
self._mean = sps.lil_matrix(self._mean.flatten(),
self._mean.shape)
if self._std_dev is not None:
self._std_dev = \
sps.lil_matrix(self._std_dev.flatten(), self._std_dev.shape)
self._std_dev = sps.lil_matrix(self._std_dev.flatten(),
self._std_dev.shape)
self._sparse = True
@ -776,11 +774,11 @@ class Tally(IDManagerMixin):
other_sum = other_copy.get_reshaped_data(value='sum')
if join_right:
merged_sum = \
np.concatenate((self_sum, other_sum), axis=merge_axis)
merged_sum = np.concatenate((self_sum, other_sum),
axis=merge_axis)
else:
merged_sum = \
np.concatenate((other_sum, self_sum), axis=merge_axis)
merged_sum = np.concatenate((other_sum, self_sum),
axis=merge_axis)
merged_tally._sum = np.reshape(merged_sum, merged_tally.shape)
@ -790,11 +788,11 @@ class Tally(IDManagerMixin):
other_sum_sq = other_copy.get_reshaped_data(value='sum_sq')
if join_right:
merged_sum_sq = \
np.concatenate((self_sum_sq, other_sum_sq), axis=merge_axis)
merged_sum_sq = np.concatenate((self_sum_sq, other_sum_sq),
axis=merge_axis)
else:
merged_sum_sq = \
np.concatenate((other_sum_sq, self_sum_sq), axis=merge_axis)
merged_sum_sq = np.concatenate((other_sum_sq, self_sum_sq),
axis=merge_axis)
merged_tally._sum_sq = np.reshape(merged_sum_sq, merged_tally.shape)
@ -804,11 +802,11 @@ class Tally(IDManagerMixin):
other_mean = other_copy.get_reshaped_data(value='mean')
if join_right:
merged_mean = \
np.concatenate((self_mean, other_mean), axis=merge_axis)
merged_mean = np.concatenate((self_mean, other_mean),
axis=merge_axis)
else:
merged_mean = \
np.concatenate((other_mean, self_mean), axis=merge_axis)
merged_mean = np.concatenate((other_mean, self_mean),
axis=merge_axis)
merged_tally._mean = np.reshape(merged_mean, merged_tally.shape)
@ -818,11 +816,11 @@ class Tally(IDManagerMixin):
other_std_dev = other_copy.get_reshaped_data(value='std_dev')
if join_right:
merged_std_dev = \
np.concatenate((self_std_dev, other_std_dev), axis=merge_axis)
merged_std_dev = np.concatenate((self_std_dev, other_std_dev),
axis=merge_axis)
else:
merged_std_dev = \
np.concatenate((other_std_dev, self_std_dev), axis=merge_axis)
merged_std_dev = np.concatenate((other_std_dev, self_std_dev),
axis=merge_axis)
merged_tally._std_dev = np.reshape(merged_std_dev, merged_tally.shape)
@ -1003,35 +1001,6 @@ class Tally(IDManagerMixin):
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
Parameters
----------
filter_type : openmc.FilterMeta
Type of the filter, e.g. MeshFilter
filter_bin : int or tuple
The bin is an 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
-------
The index in the Tally data array for this filter bin
"""
# Find the equivalent Filter in this Tally's list of Filters
filter_found = self.find_filter(filter_type)
# Get the index for the requested bin from the Filter and return it
filter_index = filter_found.get_bin_index(filter_bin)
return filter_index
def get_nuclide_index(self, nuclide):
"""Returns the index in the Tally's results array for a Nuclide bin
@ -1151,48 +1120,28 @@ class Tally(IDManagerMixin):
# Loop over all of the Tally's 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 type(self_filter) is test_filter:
bins = filter_bins[j]
user_filter = True
break
else:
# If not a user-requested Filter, get all bins
if isinstance(self_filter, openmc.DistribcellFilter):
# Create list of cell instance IDs for distribcell Filters
bins = list(range(self_filter.num_bins))
# 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 isinstance(self_filter, openmc.MeshFilter):
bins = list(self_filter.mesh.indices)
# Create list of 2-tuples for energy boundary bins
elif isinstance(self_filter, (openmc.EnergyFilter,
openmc.EnergyoutFilter, openmc.MuFilter,
openmc.PolarFilter, openmc.AzimuthalFilter)):
bins = []
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 isinstance(self_filter, openmc.DistribcellFilter):
bins = [b for b in range(self_filter.num_bins)]
# EnergyFunctionFilters don't have bins so just add a None
elif isinstance(self_filter, openmc.EnergyFunctionFilter):
# EnergyFunctionFilters don't have bins so just add a None
bins = [None]
# Create list of IDs for bins for all other filter types
else:
# Create list of IDs for bins for all other filter types
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(type(self_filter), bin)
filter_indices[i][j] = filter_index
indices = np.array([self_filter.get_bin_index(b) for b in bins])
filter_indices.append(indices)
# Account for stride in each of the previous filters
for indices in filter_indices[:i]:
@ -1956,14 +1905,14 @@ class Tally(IDManagerMixin):
elif isinstance(filter1, openmc.EnergyFunctionFilter):
filter1_bins = [None]
else:
filter1_bins = [filter1.get_bin(i) for i in range(filter1.num_bins)]
filter1_bins = filter1.bins
if isinstance(filter2, openmc.DistribcellFilter):
filter2_bins = [b for b in range(filter2.num_bins)]
elif isinstance(filter2, openmc.EnergyFunctionFilter):
filter2_bins = [None]
else:
filter2_bins = [filter2.get_bin(i) for i in range(filter2.num_bins)]
filter2_bins = filter2.bins
# Create variables to store views of data in the misaligned structure
mean = {}
@ -2604,7 +2553,8 @@ class Tally(IDManagerMixin):
new_tally = self * -1
return new_tally
def get_slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]):
def get_slice(self, scores=[], filters=[], filter_bins=[], nuclides=[],
squeeze=False):
"""Build a sliced tally for the specified filters, scores and nuclides.
This method constructs a new tally to encapsulate a subset of the data
@ -2615,26 +2565,26 @@ class Tally(IDManagerMixin):
Parameters
----------
scores : list of str
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']
filters : Iterable of openmc.FilterMeta
An iterable of filter types
(e.g., [MeshFilter, EnergyFilter]; default is [])
An iterable of filter types (e.g., [MeshFilter, EnergyFilter])
filter_bins : list of Iterables
A list of tuples of filter bins corresponding to the filter_types
parameter (e.g., [(1,), ((0., 0.625e-6),)]; default is []). Each
tuple contains bins to slice for the corresponding filter type in
the filters parameter. Each bins is the integer ID for 'material',
A list of iterables of filter bins corresponding to the specified
filter types (e.g., [(1,), ((0., 0.625e-6),)]). Each iterable
contains bins to slice for the corresponding filter type in the
filters parameter. Each bin 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 an (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 to the filter_types parameter.
correspond to the `filters` argument.
nuclides : list of str
A list of nuclide name strings
(e.g., ['U235', 'U238']; default is [])
A list of nuclide name strings (e.g., ['U235', 'U238'])
squeeze : bool
Whether to remove filters with only a single bin in the sliced tally
Returns
-------
@ -2714,32 +2664,29 @@ class Tally(IDManagerMixin):
# Determine the filter indices from any of the requested filters
for i, filter_type in enumerate(filters):
find_filter = new_tally.find_filter(filter_type)
f = new_tally.find_filter(filter_type)
# Remove filters with only a single bin if requested
if squeeze:
if len(filter_bins[i]) == 1:
new_tally.filters.remove(f)
continue
else:
raise RuntimeError('Cannot remove sliced filter with '
'more than one bin.')
# Remove and/or reorder filter bins to user specifications
bin_indices = []
bin_indices = [f.get_bin_index(b)
for b in filter_bins[i]]
bin_indices = np.unique(bin_indices)
for filter_bin in filter_bins[i]:
bin_index = find_filter.get_bin_index(filter_bin)
if issubclass(filter_type, openmc.RealFilter):
bin_indices.extend([bin_index, bin_index+1])
else:
bin_indices.append(bin_index)
# Set bins for mesh/distribcell filters apart from others
if filter_type is openmc.MeshFilter:
bins = find_filter.mesh
elif filter_type is openmc.DistribcellFilter:
bins = find_filter.bins
else:
bins = np.unique(find_filter.bins[bin_indices])
# Create new filter
new_filter = filter_type(bins)
# Set bins for sliced filter
new_filter = copy.copy(f)
new_filter.bins = [f.bins[i] for i in bin_indices]
# Set number of bins manually for mesh/distribcell filters
if filter_type is openmc.DistribcellFilter:
new_filter._num_bins = find_filter._num_bins
new_filter._num_bins = f._num_bins
# Replace existing filter with new one
for j, test_filter in enumerate(new_tally.filters):

View file

@ -87,12 +87,14 @@ class TallySliceMergeTestHarness(PyAPITestHarness):
# Slice the tallies by cell filter bins
cell_filter_prod = itertools.product(tallies, self.cell_filters)
tallies = map(lambda tf: tf[0].get_slice(filters=[type(tf[1])],
filter_bins=[tf[1].get_bin(0)]), cell_filter_prod)
filter_bins=[tf[1].get_bin(0)]),
cell_filter_prod)
# Slice the tallies by energy filter bins
energy_filter_prod = itertools.product(tallies, self.energy_filters)
tallies = map(lambda tf: tf[0].get_slice(filters=[type(tf[1])],
filter_bins=[(tf[1].get_bin(0),)]), energy_filter_prod)
filter_bins=[tf[1].get_bin(0)]),
energy_filter_prod)
# Slice the tallies by nuclide
nuclide_prod = itertools.product(tallies, self.nuclides)