2018-10-18 05:54:36 -05:00
|
|
|
from collections.abc import Iterable
|
2020-04-06 15:16:09 -05:00
|
|
|
import copy
|
2016-01-27 16:44:52 -05:00
|
|
|
|
|
|
|
|
import numpy as np
|
2017-06-02 06:41:20 -05:00
|
|
|
import pandas as pd
|
2015-09-12 21:02:13 -04:00
|
|
|
|
2016-09-30 12:19:22 -04:00
|
|
|
import openmc
|
2015-09-12 21:02:13 -04:00
|
|
|
import openmc.checkvalue as cv
|
2020-04-06 15:16:09 -05:00
|
|
|
from .filter import _FILTER_TYPES
|
2015-09-12 21:02:13 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# Acceptable tally arithmetic binary operations
|
2024-06-19 15:48:34 +01:00
|
|
|
_TALLY_ARITHMETIC_OPS = {'+', '-', '*', '/', '^'}
|
2015-07-26 23:17:59 -07:00
|
|
|
|
2016-01-27 16:44:52 -05:00
|
|
|
# Acceptable tally aggregation operations
|
2024-06-19 15:48:34 +01:00
|
|
|
_TALLY_AGGREGATE_OPS = {'sum', 'avg'}
|
2016-01-27 16:44:52 -05:00
|
|
|
|
2015-07-26 23:17:59 -07:00
|
|
|
|
2020-03-19 15:21:15 -05:00
|
|
|
class CrossScore:
|
2015-07-26 23:17:59 -07:00
|
|
|
"""A special-purpose tally score used to encapsulate all combinations of two
|
2015-10-03 00:04:01 -04:00
|
|
|
tally's scores as an outer product for tally arithmetic.
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2015-08-03 00:00:40 -07:00
|
|
|
left_score : str or CrossScore
|
2015-07-29 23:18:59 -07:00
|
|
|
The left score in the outer product
|
2015-08-03 00:00:40 -07:00
|
|
|
right_score : str or CrossScore
|
2015-07-29 23:18:59 -07:00
|
|
|
The right score in the outer product
|
2015-07-26 23:17:59 -07:00
|
|
|
binary_op : str
|
|
|
|
|
The tally arithmetic binary operator (e.g., '+', '-', etc.) used to
|
2015-08-03 00:00:40 -07:00
|
|
|
combine two tally's scores with this CrossNuclide
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
Attributes
|
|
|
|
|
----------
|
2015-08-03 00:00:40 -07:00
|
|
|
left_score : str or CrossScore
|
2015-07-29 23:18:59 -07:00
|
|
|
The left score in the outer product
|
2015-08-03 00:00:40 -07:00
|
|
|
right_score : str or CrossScore
|
2015-07-29 23:18:59 -07:00
|
|
|
The right score in the outer product
|
2015-07-26 23:17:59 -07:00
|
|
|
binary_op : str
|
|
|
|
|
The tally arithmetic binary operator (e.g., '+', '-', etc.) used to
|
2016-01-14 15:08:28 -05:00
|
|
|
combine two tally's scores with this CrossScore
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2020-03-25 06:53:52 -05:00
|
|
|
def __init__(self, left_score, right_score, binary_op):
|
|
|
|
|
self.left_score = left_score
|
|
|
|
|
self.right_score = right_score
|
|
|
|
|
self.binary_op = binary_op
|
2015-07-26 23:17:59 -07:00
|
|
|
|
2015-09-12 21:02:13 -04:00
|
|
|
def __hash__(self):
|
2015-12-01 10:13:37 -05:00
|
|
|
return hash(repr(self))
|
2015-09-12 21:02:13 -04:00
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
|
return str(other) == str(self)
|
|
|
|
|
|
2015-10-08 14:23:19 -04:00
|
|
|
def __repr__(self):
|
2024-04-29 22:45:37 +01:00
|
|
|
return f'({self.left_score} {self.binary_op} {self.right_score})'
|
2015-10-08 14:23:19 -04:00
|
|
|
|
2015-07-26 23:17:59 -07:00
|
|
|
@property
|
|
|
|
|
def left_score(self):
|
|
|
|
|
return self._left_score
|
|
|
|
|
|
|
|
|
|
@left_score.setter
|
|
|
|
|
def left_score(self, left_score):
|
2016-01-27 16:44:52 -05:00
|
|
|
cv.check_type('left_score', left_score,
|
2017-12-24 16:06:05 +07:00
|
|
|
(str, CrossScore, AggregateScore))
|
2015-07-26 23:17:59 -07:00
|
|
|
self._left_score = left_score
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def right_score(self):
|
|
|
|
|
return self._right_score
|
|
|
|
|
|
2015-07-26 23:17:59 -07:00
|
|
|
@right_score.setter
|
|
|
|
|
def right_score(self, right_score):
|
2016-01-27 16:44:52 -05:00
|
|
|
cv.check_type('right_score', right_score,
|
2017-12-24 16:06:05 +07:00
|
|
|
(str, CrossScore, AggregateScore))
|
2015-07-26 23:17:59 -07:00
|
|
|
self._right_score = right_score
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def binary_op(self):
|
|
|
|
|
return self._binary_op
|
|
|
|
|
|
2015-07-26 23:17:59 -07:00
|
|
|
@binary_op.setter
|
|
|
|
|
def binary_op(self, binary_op):
|
2017-12-24 16:06:05 +07:00
|
|
|
cv.check_type('binary_op', binary_op, str)
|
2015-10-08 14:23:19 -04:00
|
|
|
cv.check_value('binary_op', binary_op, _TALLY_ARITHMETIC_OPS)
|
2015-07-26 23:17:59 -07:00
|
|
|
self._binary_op = binary_op
|
|
|
|
|
|
|
|
|
|
|
2020-03-19 15:21:15 -05:00
|
|
|
class CrossNuclide:
|
2015-07-26 23:17:59 -07:00
|
|
|
"""A special-purpose nuclide used to encapsulate all combinations of two
|
2015-09-12 21:02:13 -04:00
|
|
|
tally's nuclides as an outer product for tally arithmetic.
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2025-01-24 16:49:58 -06:00
|
|
|
left_nuclide : str or CrossNuclide
|
2015-07-29 23:18:59 -07:00
|
|
|
The left nuclide in the outer product
|
2025-01-24 16:49:58 -06:00
|
|
|
right_nuclide : str or CrossNuclide
|
2015-07-29 23:18:59 -07:00
|
|
|
The right nuclide in the outer product
|
2015-07-26 23:17:59 -07:00
|
|
|
binary_op : str
|
|
|
|
|
The tally arithmetic binary operator (e.g., '+', '-', etc.) used to
|
2015-08-03 00:00:40 -07:00
|
|
|
combine two tally's nuclides with this CrossNuclide
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
Attributes
|
|
|
|
|
----------
|
2025-01-24 16:49:58 -06:00
|
|
|
left_nuclide : str or CrossNuclide
|
2015-07-29 23:18:59 -07:00
|
|
|
The left nuclide in the outer product
|
2025-01-24 16:49:58 -06:00
|
|
|
right_nuclide : str or CrossNuclide
|
2015-07-29 23:18:59 -07:00
|
|
|
The right nuclide in the outer product
|
2015-07-26 23:17:59 -07:00
|
|
|
binary_op : str
|
|
|
|
|
The tally arithmetic binary operator (e.g., '+', '-', etc.) used to
|
2015-08-03 00:00:40 -07:00
|
|
|
combine two tally's nuclides with this CrossNuclide
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2020-03-25 06:53:52 -05:00
|
|
|
def __init__(self, left_nuclide, right_nuclide, binary_op):
|
|
|
|
|
self.left_nuclide = left_nuclide
|
|
|
|
|
self.right_nuclide = right_nuclide
|
|
|
|
|
self.binary_op = binary_op
|
2015-07-26 23:17:59 -07:00
|
|
|
|
2015-09-12 21:02:13 -04:00
|
|
|
def __hash__(self):
|
2015-12-01 10:13:37 -05:00
|
|
|
return hash(repr(self))
|
2015-09-12 21:02:13 -04:00
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
|
return str(other) == str(self)
|
|
|
|
|
|
2015-10-08 14:23:19 -04:00
|
|
|
def __repr__(self):
|
2016-02-15 22:42:57 -05:00
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def left_nuclide(self):
|
|
|
|
|
return self._left_nuclide
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@left_nuclide.setter
|
|
|
|
|
def left_nuclide(self, left_nuclide):
|
|
|
|
|
cv.check_type('left_nuclide', left_nuclide,
|
2025-01-24 16:49:58 -06:00
|
|
|
(str, CrossNuclide, AggregateNuclide))
|
2023-06-17 04:34:02 +01:00
|
|
|
self._left_nuclide = left_nuclide
|
|
|
|
|
|
2016-02-15 22:42:57 -05:00
|
|
|
@property
|
|
|
|
|
def right_nuclide(self):
|
|
|
|
|
return self._right_nuclide
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@right_nuclide.setter
|
|
|
|
|
def right_nuclide(self, right_nuclide):
|
|
|
|
|
cv.check_type('right_nuclide', right_nuclide,
|
2025-01-24 16:49:58 -06:00
|
|
|
(str, CrossNuclide, AggregateNuclide))
|
2023-06-17 04:34:02 +01:00
|
|
|
self._right_nuclide = right_nuclide
|
|
|
|
|
|
2016-02-15 22:42:57 -05:00
|
|
|
@property
|
|
|
|
|
def binary_op(self):
|
|
|
|
|
return self._binary_op
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@binary_op.setter
|
|
|
|
|
def binary_op(self, binary_op):
|
|
|
|
|
cv.check_type('binary_op', binary_op, str)
|
|
|
|
|
cv.check_value('binary_op', binary_op, _TALLY_ARITHMETIC_OPS)
|
|
|
|
|
self._binary_op = binary_op
|
|
|
|
|
|
2016-02-15 22:42:57 -05:00
|
|
|
@property
|
|
|
|
|
def name(self):
|
2025-01-24 16:49:58 -06:00
|
|
|
return f'({self.left_nuclide} {self.binary_op} {self.right_nuclide})'
|
2015-10-08 14:23:19 -04:00
|
|
|
|
2015-07-26 23:17:59 -07:00
|
|
|
|
2020-03-19 15:21:15 -05:00
|
|
|
class CrossFilter:
|
2015-07-26 23:17:59 -07:00
|
|
|
"""A special-purpose filter used to encapsulate all combinations of two
|
2015-09-12 21:02:13 -04:00
|
|
|
tally's filter bins as an outer product for tally arithmetic.
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2024-04-07 17:50:44 +01:00
|
|
|
left_filter : openmc.Filter or CrossFilter
|
2015-07-29 23:18:59 -07:00
|
|
|
The left filter in the outer product
|
2024-04-07 17:50:44 +01:00
|
|
|
right_filter : openmc.Filter or CrossFilter
|
2015-07-29 23:18:59 -07:00
|
|
|
The right filter in the outer product
|
2015-07-26 23:17:59 -07:00
|
|
|
binary_op : str
|
|
|
|
|
The tally arithmetic binary operator (e.g., '+', '-', etc.) used to
|
2015-08-03 00:00:40 -07:00
|
|
|
combine two tally's filter bins with this CrossFilter
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
Attributes
|
|
|
|
|
----------
|
2016-01-15 09:52:56 -05:00
|
|
|
type : str
|
|
|
|
|
The type of the crossfilter (e.g., 'energy / energy')
|
2024-04-07 17:50:44 +01:00
|
|
|
left_filter : openmc.Filter or CrossFilter
|
2015-07-29 23:18:59 -07:00
|
|
|
The left filter in the outer product
|
2024-04-07 17:50:44 +01:00
|
|
|
right_filter : openmc.Filter or CrossFilter
|
2015-07-29 23:18:59 -07:00
|
|
|
The right filter in the outer product
|
2015-07-26 23:17:59 -07:00
|
|
|
binary_op : str
|
|
|
|
|
The tally arithmetic binary operator (e.g., '+', '-', etc.) used to
|
2015-08-03 00:00:40 -07:00
|
|
|
combine two tally's filter bins with this CrossFilter
|
2016-01-15 09:52:56 -05:00
|
|
|
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)
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2020-03-25 06:53:52 -05:00
|
|
|
def __init__(self, left_filter, right_filter, binary_op):
|
|
|
|
|
self.left_filter = left_filter
|
|
|
|
|
self.right_filter = right_filter
|
|
|
|
|
self.binary_op = binary_op
|
2015-07-26 23:17:59 -07:00
|
|
|
|
2015-08-03 22:02:44 -07:00
|
|
|
def __hash__(self):
|
2015-09-11 00:26:11 -04:00
|
|
|
return hash((self.left_filter, self.right_filter))
|
2015-08-03 22:02:44 -07:00
|
|
|
|
2015-09-12 21:02:13 -04:00
|
|
|
def __eq__(self, other):
|
|
|
|
|
return str(other) == str(self)
|
|
|
|
|
|
2015-10-08 14:23:19 -04:00
|
|
|
def __repr__(self):
|
2020-03-19 15:40:25 -05:00
|
|
|
filter_bins = '({} {} {})'.format(self.left_filter.bins,
|
|
|
|
|
self.binary_op,
|
|
|
|
|
self.right_filter.bins)
|
|
|
|
|
parts = [
|
|
|
|
|
'CrossFilter',
|
2020-03-25 06:53:52 -05:00
|
|
|
'{: <16}=\t{}'.format('\tType', self.type),
|
2020-03-19 15:40:25 -05:00
|
|
|
'{: <16}=\t{}'.format('\tBins', filter_bins)
|
|
|
|
|
]
|
|
|
|
|
return '\n'.join(parts)
|
2015-10-27 17:37:22 -04:00
|
|
|
|
2015-07-26 23:17:59 -07:00
|
|
|
@property
|
|
|
|
|
def left_filter(self):
|
|
|
|
|
return self._left_filter
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@left_filter.setter
|
|
|
|
|
def left_filter(self, left_filter):
|
|
|
|
|
cv.check_type('left_filter', left_filter,
|
|
|
|
|
(openmc.Filter, CrossFilter, AggregateFilter))
|
|
|
|
|
self._left_filter = left_filter
|
|
|
|
|
|
2015-07-26 23:17:59 -07:00
|
|
|
@property
|
|
|
|
|
def right_filter(self):
|
|
|
|
|
return self._right_filter
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@right_filter.setter
|
|
|
|
|
def right_filter(self, right_filter):
|
|
|
|
|
cv.check_type('right_filter', right_filter,
|
|
|
|
|
(openmc.Filter, CrossFilter, AggregateFilter))
|
|
|
|
|
self._right_filter = right_filter
|
|
|
|
|
|
2015-07-26 23:17:59 -07:00
|
|
|
@property
|
|
|
|
|
def binary_op(self):
|
|
|
|
|
return self._binary_op
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@binary_op.setter
|
|
|
|
|
def binary_op(self, binary_op):
|
|
|
|
|
cv.check_type('binary_op', binary_op, str)
|
|
|
|
|
cv.check_value('binary_op', binary_op, _TALLY_ARITHMETIC_OPS)
|
|
|
|
|
self._binary_op = binary_op
|
|
|
|
|
|
2015-07-26 23:17:59 -07:00
|
|
|
@property
|
|
|
|
|
def type(self):
|
2020-03-25 06:53:52 -05:00
|
|
|
left_type = self.left_filter.type
|
|
|
|
|
right_type = self.right_filter.type
|
2024-04-29 22:45:37 +01:00
|
|
|
return f'({left_type} {self.binary_op} {right_type})'
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def bins(self):
|
2020-03-19 15:40:25 -05:00
|
|
|
return self._left_filter.bins, self._right_filter.bins
|
2015-07-26 23:17:59 -07:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def num_bins(self):
|
2015-10-03 00:04:01 -04:00
|
|
|
if self.left_filter is not None and self.right_filter is not None:
|
|
|
|
|
return self.left_filter.num_bins * self.right_filter.num_bins
|
|
|
|
|
else:
|
|
|
|
|
return 0
|
2015-07-26 23:17:59 -07:00
|
|
|
|
2015-08-03 22:02:44 -07:00
|
|
|
def get_bin_index(self, filter_bin):
|
|
|
|
|
"""Returns the index in the CrossFilter for some bin.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
filter_bin : 2-tuple
|
|
|
|
|
A 2-tuple where each value corresponds to the bin of interest
|
|
|
|
|
in the left and right filter, respectively. A bin is the integer
|
|
|
|
|
ID for 'material', 'surface', 'cell', 'cellborn', and 'universe'
|
|
|
|
|
Filters. The bin is an integer for the cell instance ID for
|
|
|
|
|
'distribcell' Filters. The bin is a 2-tuple of floats for 'energy'
|
|
|
|
|
and 'energyout' filters corresponding to the energy boundaries of
|
|
|
|
|
the bin of interest. The bin is a (x,y,z) 3-tuple for 'mesh'
|
|
|
|
|
filters corresponding to the mesh cell of interest.
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
2015-09-12 21:02:13 -04:00
|
|
|
filter_index : Integral
|
2015-08-03 22:02:44 -07:00
|
|
|
The index in the Tally data array for this filter bin.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
left_index = self.left_filter.get_bin_index(filter_bin[0])
|
|
|
|
|
right_index = self.right_filter.get_bin_index(filter_bin[0])
|
|
|
|
|
filter_index = left_index * self.right_filter.num_bins + right_index
|
|
|
|
|
return filter_index
|
|
|
|
|
|
2016-02-08 00:36:49 -05:00
|
|
|
def get_pandas_dataframe(self, data_size, summary=None):
|
2015-09-07 15:45:20 -04:00
|
|
|
"""Builds a Pandas DataFrame for the CrossFilter's bins.
|
|
|
|
|
|
|
|
|
|
This method constructs a Pandas DataFrame object for the CrossFilter
|
|
|
|
|
with columns annotated by filter bin information. This is a helper
|
2015-10-03 02:29:07 -04:00
|
|
|
method for the Tally.get_pandas_dataframe(...) method. This method
|
2015-09-12 21:02:13 -04:00
|
|
|
recursively builds and concatenates Pandas DataFrames for the left
|
2015-09-07 15:45:20 -04:00
|
|
|
and right filters and crossfilters.
|
|
|
|
|
|
|
|
|
|
This capability has been tested for Pandas >=0.13.1. However, it is
|
|
|
|
|
recommended to use v0.16 or newer versions of Pandas since this method
|
2015-09-12 21:02:13 -04:00
|
|
|
uses Pandas' Multi-index functionality.
|
2015-09-07 15:45:20 -04:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2016-02-08 00:36:49 -05:00
|
|
|
data_size : Integral
|
2015-09-07 15:45:20 -04:00
|
|
|
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). The geometric
|
|
|
|
|
information in the Summary object is embedded into a Multi-index
|
2015-09-12 21:02:13 -04:00
|
|
|
column with a geometric "path" to each distribcell instance.
|
2015-09-07 15:45:20 -04:00
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
pandas.DataFrame
|
|
|
|
|
A Pandas DataFrame with columns of strings that characterize the
|
2015-09-12 21:02:13 -04:00
|
|
|
crossfilter's bins. Each entry in the DataFrame will include one
|
2015-09-07 15:45:20 -04:00
|
|
|
or more binary operations used to construct the crossfilter's bins.
|
|
|
|
|
The number of rows in the DataFrame is the same as the total number
|
2015-09-12 21:02:13 -04:00
|
|
|
of bins in the corresponding tally, with the filter bins
|
2015-09-07 15:45:20 -04:00
|
|
|
appropriately tiled to map to the corresponding tally bins.
|
|
|
|
|
|
|
|
|
|
See also
|
|
|
|
|
--------
|
|
|
|
|
Tally.get_pandas_dataframe(), Filter.get_pandas_dataframe()
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# If left and right filters are identical, do not combine bins
|
|
|
|
|
if self.left_filter == self.right_filter:
|
2016-02-08 00:36:49 -05:00
|
|
|
df = self.left_filter.get_pandas_dataframe(data_size, summary)
|
2015-09-12 21:02:13 -04:00
|
|
|
|
2015-09-07 15:45:20 -04:00
|
|
|
# If left and right filters are different, combine their bins
|
|
|
|
|
else:
|
2016-02-08 00:36:49 -05:00
|
|
|
left_df = self.left_filter.get_pandas_dataframe(data_size, summary)
|
|
|
|
|
right_df = self.right_filter.get_pandas_dataframe(data_size, summary)
|
2015-09-08 00:13:23 -04:00
|
|
|
left_df = left_df.astype(str)
|
|
|
|
|
right_df = right_df.astype(str)
|
2021-07-29 18:25:37 +01:00
|
|
|
df = f'({left_df} {self.binary_op} {right_df})'
|
2015-09-07 15:45:20 -04:00
|
|
|
|
2015-10-27 17:37:22 -04:00
|
|
|
return df
|
2016-01-27 16:44:52 -05:00
|
|
|
|
2020-03-19 15:21:15 -05:00
|
|
|
|
|
|
|
|
class AggregateScore:
|
2016-01-27 16:44:52 -05:00
|
|
|
"""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
|
2016-02-03 12:54:23 -05:00
|
|
|
The tally aggregation operator (e.g., 'sum', 'avg', etc.) used
|
2016-01-27 16:44:52 -05:00
|
|
|
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
|
2016-02-03 12:54:23 -05:00
|
|
|
The tally aggregation operator (e.g., 'sum', 'avg', etc.) used
|
2016-01-27 16:44:52 -05:00
|
|
|
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 __repr__(self):
|
|
|
|
|
string = ', '.join(map(str, self.scores))
|
2021-07-29 18:25:37 +01:00
|
|
|
string = f'{self.aggregate_op}({string})'
|
2016-01-27 16:44:52 -05:00
|
|
|
return string
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def scores(self):
|
|
|
|
|
return self._scores
|
|
|
|
|
|
|
|
|
|
@scores.setter
|
|
|
|
|
def scores(self, scores):
|
2017-12-24 16:06:05 +07:00
|
|
|
cv.check_iterable_type('scores', scores, str)
|
2016-01-27 16:44:52 -05:00
|
|
|
self._scores = scores
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def aggregate_op(self):
|
|
|
|
|
return self._aggregate_op
|
|
|
|
|
|
2016-01-27 16:44:52 -05:00
|
|
|
@aggregate_op.setter
|
|
|
|
|
def aggregate_op(self, aggregate_op):
|
2017-12-24 16:06:05 +07:00
|
|
|
cv.check_type('aggregate_op', aggregate_op, (str, CrossScore))
|
2016-01-27 16:44:52 -05:00
|
|
|
cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS)
|
|
|
|
|
self._aggregate_op = aggregate_op
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def name(self):
|
|
|
|
|
|
|
|
|
|
# Append each score in the aggregate to the string
|
|
|
|
|
string = '(' + ', '.join(self.scores) + ')'
|
|
|
|
|
return string
|
|
|
|
|
|
2016-01-27 16:44:52 -05:00
|
|
|
|
2020-03-19 15:21:15 -05:00
|
|
|
class AggregateNuclide:
|
2016-01-27 16:44:52 -05:00
|
|
|
"""A special-purpose tally nuclide used to encapsulate an aggregate of a
|
|
|
|
|
subset or all of tally's nuclides for tally aggregation.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2025-01-24 16:49:58 -06:00
|
|
|
nuclides : Iterable of str or CrossNuclide
|
2016-01-27 16:44:52 -05:00
|
|
|
The nuclides included in the aggregation
|
|
|
|
|
aggregate_op : str
|
2016-02-03 12:54:23 -05:00
|
|
|
The tally aggregation operator (e.g., 'sum', 'avg', etc.) used
|
2016-01-27 16:44:52 -05:00
|
|
|
to aggregate across a tally's nuclides with this AggregateNuclide
|
|
|
|
|
|
|
|
|
|
Attributes
|
|
|
|
|
----------
|
2025-01-24 16:49:58 -06:00
|
|
|
nuclides : Iterable of str or CrossNuclide
|
2016-01-27 16:44:52 -05:00
|
|
|
The nuclides included in the aggregation
|
|
|
|
|
aggregate_op : str
|
2016-02-03 12:54:23 -05:00
|
|
|
The tally aggregation operator (e.g., 'sum', 'avg', etc.) used
|
2016-01-27 16:44:52 -05:00
|
|
|
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 __repr__(self):
|
2025-01-24 16:49:58 -06:00
|
|
|
return f'{self.aggregate_op}{self.name}'
|
2016-01-27 16:44:52 -05:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def nuclides(self):
|
|
|
|
|
return self._nuclides
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@nuclides.setter
|
|
|
|
|
def nuclides(self, nuclides):
|
|
|
|
|
cv.check_iterable_type('nuclides', nuclides, (str, CrossNuclide))
|
|
|
|
|
self._nuclides = nuclides
|
|
|
|
|
|
2016-01-27 16:44:52 -05:00
|
|
|
@property
|
|
|
|
|
def aggregate_op(self):
|
|
|
|
|
return self._aggregate_op
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@aggregate_op.setter
|
|
|
|
|
def aggregate_op(self, aggregate_op):
|
|
|
|
|
cv.check_type('aggregate_op', aggregate_op, str)
|
|
|
|
|
cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS)
|
|
|
|
|
self._aggregate_op = aggregate_op
|
|
|
|
|
|
2016-02-26 20:38:42 -05:00
|
|
|
@property
|
|
|
|
|
def name(self):
|
|
|
|
|
# Append each nuclide in the aggregate to the string
|
2025-01-24 16:49:58 -06:00
|
|
|
names = [str(nuclide) for nuclide in self.nuclides]
|
|
|
|
|
return '(' + ', '.join(map(str, names)) + ')'
|
2016-02-26 20:38:42 -05:00
|
|
|
|
2016-01-27 16:44:52 -05:00
|
|
|
|
2020-03-19 15:21:15 -05:00
|
|
|
class AggregateFilter:
|
2016-01-27 16:44:52 -05:00
|
|
|
"""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
|
|
|
|
|
----------
|
2024-04-07 17:50:44 +01:00
|
|
|
aggregate_filter : openmc.Filter or CrossFilter
|
2016-01-27 16:44:52 -05:00
|
|
|
The filter included in the aggregation
|
|
|
|
|
bins : Iterable of tuple
|
|
|
|
|
The filter bins included in the aggregation
|
|
|
|
|
aggregate_op : str
|
2016-02-03 12:54:23 -05:00
|
|
|
The tally aggregation operator (e.g., 'sum', 'avg', etc.) used
|
2016-01-27 16:44:52 -05:00
|
|
|
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)')
|
2024-04-07 17:50:44 +01:00
|
|
|
aggregate_filter : openmc.Filter
|
2016-01-27 16:44:52 -05:00
|
|
|
The filter included in the aggregation
|
|
|
|
|
aggregate_op : str
|
2016-02-03 12:54:23 -05:00
|
|
|
The tally aggregation operator (e.g., 'sum', 'avg', etc.) used
|
2016-01-27 16:44:52 -05:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2020-03-19 15:40:25 -05:00
|
|
|
def __init__(self, aggregate_filter, bins=None, aggregate_op=None):
|
2016-01-27 16:44:52 -05:00
|
|
|
|
2021-07-29 18:25:37 +01:00
|
|
|
self._type = f'{aggregate_op}({aggregate_filter.short_name.lower()})'
|
2016-01-27 16:44:52 -05:00
|
|
|
self._bins = None
|
|
|
|
|
|
|
|
|
|
self._aggregate_filter = None
|
|
|
|
|
self._aggregate_op = None
|
|
|
|
|
|
2020-03-19 15:40:25 -05:00
|
|
|
self.aggregate_filter = aggregate_filter
|
2016-01-27 16:44:52 -05:00
|
|
|
if bins is not None:
|
|
|
|
|
self.bins = bins
|
|
|
|
|
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)
|
|
|
|
|
|
2016-02-08 00:36:49 -05:00
|
|
|
def __gt__(self, other):
|
2016-02-11 12:07:23 -05:00
|
|
|
if self.type != other.type:
|
|
|
|
|
if self.aggregate_filter.type in _FILTER_TYPES and \
|
|
|
|
|
other.aggregate_filter.type in _FILTER_TYPES:
|
|
|
|
|
delta = _FILTER_TYPES.index(self.aggregate_filter.type) - \
|
|
|
|
|
_FILTER_TYPES.index(other.aggregate_filter.type)
|
2016-03-02 11:35:26 -05:00
|
|
|
return delta > 0
|
2016-02-11 12:07:23 -05:00
|
|
|
else:
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
return False
|
2016-02-08 00:36:49 -05:00
|
|
|
|
|
|
|
|
def __lt__(self, other):
|
|
|
|
|
return not self > other
|
|
|
|
|
|
2016-01-27 16:44:52 -05:00
|
|
|
def __repr__(self):
|
2020-03-19 15:40:25 -05:00
|
|
|
parts = [
|
|
|
|
|
'AggregateFilter',
|
|
|
|
|
'{: <16}=\t{}'.format('\tType', self.type),
|
|
|
|
|
'{: <16}=\t{}'.format('\tBins', self.bins)
|
|
|
|
|
]
|
|
|
|
|
return '\n'.join(parts)
|
2016-01-27 16:44:52 -05:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def aggregate_filter(self):
|
|
|
|
|
return self._aggregate_filter
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@aggregate_filter.setter
|
|
|
|
|
def aggregate_filter(self, aggregate_filter):
|
|
|
|
|
cv.check_type('aggregate_filter', aggregate_filter,
|
|
|
|
|
(openmc.Filter, CrossFilter))
|
|
|
|
|
self._aggregate_filter = aggregate_filter
|
|
|
|
|
|
2016-01-27 16:44:52 -05:00
|
|
|
@property
|
|
|
|
|
def aggregate_op(self):
|
|
|
|
|
return self._aggregate_op
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@aggregate_op.setter
|
|
|
|
|
def aggregate_op(self, aggregate_op):
|
|
|
|
|
cv.check_type('aggregate_op', aggregate_op, str)
|
|
|
|
|
cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS)
|
|
|
|
|
self._aggregate_op = aggregate_op
|
|
|
|
|
|
2016-01-27 16:44:52 -05:00
|
|
|
@property
|
|
|
|
|
def type(self):
|
|
|
|
|
return self._type
|
|
|
|
|
|
|
|
|
|
@type.setter
|
|
|
|
|
def type(self, filter_type):
|
2016-07-01 22:57:14 +07:00
|
|
|
if filter_type not in _FILTER_TYPES:
|
2021-07-29 18:25:37 +01:00
|
|
|
msg = f'Unable to set AggregateFilter type to "{filter_type}" ' \
|
|
|
|
|
'since it is not one of the supported types'
|
2016-01-27 16:44:52 -05:00
|
|
|
raise ValueError(msg)
|
|
|
|
|
|
|
|
|
|
self._type = filter_type
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def bins(self):
|
|
|
|
|
return self._bins
|
2016-01-27 16:44:52 -05:00
|
|
|
|
|
|
|
|
@bins.setter
|
|
|
|
|
def bins(self, bins):
|
2016-02-08 00:36:49 -05:00
|
|
|
cv.check_iterable_type('bins', bins, Iterable)
|
2016-02-11 12:59:14 -05:00
|
|
|
self._bins = list(map(tuple, bins))
|
2016-01-27 16:44:52 -05:00
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def num_bins(self):
|
|
|
|
|
return len(self.bins) if self.aggregate_filter else 0
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def shape(self):
|
|
|
|
|
return (self.num_bins,)
|
2016-01-27 16:44:52 -05:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2016-02-03 12:54:23 -05:00
|
|
|
if filter_bin not in self.bins:
|
2021-07-31 22:33:45 +01:00
|
|
|
msg = ('Unable to get the bin index for AggregateFilter since '
|
|
|
|
|
f'"{filter_bin}" is not one of the bins')
|
2016-01-27 16:44:52 -05:00
|
|
|
raise ValueError(msg)
|
|
|
|
|
else:
|
2016-02-08 00:36:49 -05:00
|
|
|
return self.bins.index(filter_bin)
|
2016-01-27 16:44:52 -05:00
|
|
|
|
2017-12-19 15:58:14 +07:00
|
|
|
def get_pandas_dataframe(self, data_size, stride, summary=None, **kwargs):
|
2016-01-27 16:44:52 -05:00
|
|
|
"""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
|
|
|
|
|
----------
|
2017-12-19 15:58:14 +07:00
|
|
|
data_size : int
|
2016-01-27 16:44:52 -05:00
|
|
|
The total number of bins in the tally corresponding to this filter
|
2017-12-19 15:58:14 +07:00
|
|
|
stride : int
|
|
|
|
|
Stride in memory for the filter
|
2016-01-27 16:44:52 -05:00
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
"""
|
2016-02-08 00:36:49 -05:00
|
|
|
# Create NumPy array of the bin tuples for repeating / tiling
|
|
|
|
|
filter_bins = np.empty(self.num_bins, dtype=tuple)
|
|
|
|
|
for i, bin in enumerate(self.bins):
|
|
|
|
|
filter_bins[i] = bin
|
2016-01-27 16:44:52 -05:00
|
|
|
|
2016-02-08 00:36:49 -05:00
|
|
|
# Repeat and tile bins as needed for DataFrame
|
2017-12-19 15:58:14 +07:00
|
|
|
filter_bins = np.repeat(filter_bins, stride)
|
2016-02-08 00:36:49 -05:00
|
|
|
tile_factor = data_size / len(filter_bins)
|
|
|
|
|
filter_bins = np.tile(filter_bins, tile_factor)
|
2016-01-27 16:44:52 -05:00
|
|
|
|
2016-02-08 00:36:49 -05:00
|
|
|
# Create DataFrame with aggregated bins
|
|
|
|
|
df = pd.DataFrame({self.type: filter_bins})
|
2016-01-27 16:44:52 -05:00
|
|
|
return df
|
2016-02-08 00:36:49 -05:00
|
|
|
|
|
|
|
|
def can_merge(self, other):
|
|
|
|
|
"""Determine if AggregateFilter can be merged with another.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
other : AggregateFilter
|
|
|
|
|
Filter to compare with
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
bool
|
|
|
|
|
Whether the filter can be merged
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if not isinstance(other, AggregateFilter):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Filters must be of the same type
|
|
|
|
|
elif self.type != other.type:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# None of the bins in this filter should match in the other filter
|
2020-03-19 15:40:25 -05:00
|
|
|
return not any(b in other.bins for b in self.bins)
|
2016-02-08 00:36:49 -05:00
|
|
|
|
|
|
|
|
def merge(self, other):
|
|
|
|
|
"""Merge this aggregatefilter with another.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
other : AggregateFilter
|
|
|
|
|
Filter to merge with
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
merged_filter : AggregateFilter
|
|
|
|
|
Filter resulting from the merge
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if not self.can_merge(other):
|
2021-07-29 18:25:37 +01:00
|
|
|
msg = f'Unable to merge "{self.type}" with "{other.type}" filters'
|
2016-02-08 00:36:49 -05:00
|
|
|
raise ValueError(msg)
|
|
|
|
|
|
|
|
|
|
# Create deep copy of filter to return as merged filter
|
|
|
|
|
merged_filter = copy.deepcopy(self)
|
|
|
|
|
|
|
|
|
|
# Merge unique filter bins
|
|
|
|
|
merged_bins = self.bins + other.bins
|
|
|
|
|
|
|
|
|
|
# Sort energy bin edges
|
|
|
|
|
if 'energy' in self.type:
|
|
|
|
|
merged_bins = sorted(merged_bins)
|
|
|
|
|
|
|
|
|
|
# Assign merged bins to merged filter
|
|
|
|
|
merged_filter.bins = list(merged_bins)
|
2016-02-11 12:07:23 -05:00
|
|
|
return merged_filter
|