Skip to content

Commit 7fda3eb

Browse files
paulromanoshimwellpshriwise
authored
Implement a new MeshMaterialFilter (#3406)
Co-authored-by: Jonathan Shimwell <drshimwell@gmail.com> Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
1 parent 67aa0de commit 7fda3eb

13 files changed

Lines changed: 614 additions & 16 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ list(APPEND libopenmc_SOURCES
414414
src/tallies/filter_materialfrom.cpp
415415
src/tallies/filter_mesh.cpp
416416
src/tallies/filter_meshborn.cpp
417+
src/tallies/filter_meshmaterial.cpp
417418
src/tallies/filter_meshsurface.cpp
418419
src/tallies/filter_mu.cpp
419420
src/tallies/filter_musurface.cpp

docs/source/pythonapi/base.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ Constructing Tallies
129129
openmc.SurfaceFilter
130130
openmc.MeshFilter
131131
openmc.MeshBornFilter
132+
openmc.MeshMaterialFilter
132133
openmc.MeshSurfaceFilter
133134
openmc.EnergyFilter
134135
openmc.EnergyoutFilter

include/openmc/tallies/filter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ enum class FilterType {
3333
MATERIALFROM,
3434
MESH,
3535
MESHBORN,
36+
MESH_MATERIAL,
3637
MESH_SURFACE,
3738
MU,
3839
MUSURFACE,

include/openmc/tallies/filter_mesh.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
namespace openmc {
1010

1111
//==============================================================================
12-
//! Indexes the location of particle events to a regular mesh. For tracklength
13-
//! tallies, it will produce multiple valid bins and the bin weight will
14-
//! correspond to the fraction of the track length that lies in that bin.
12+
//! Indexes the location of particle events to a mesh. For tracklength tallies,
13+
//! it will produce multiple valid bins and the bin weight will correspond to
14+
//! the fraction of the track length that lies in that bin.
1515
//==============================================================================
1616

1717
class MeshFilter : public Filter {
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#ifndef OPENMC_TALLIES_FILTER_MESHMATERIAL_H
2+
#define OPENMC_TALLIES_FILTER_MESHMATERIAL_H
3+
4+
#include <cstdint>
5+
#include <string>
6+
#include <unordered_map>
7+
#include <unordered_set>
8+
9+
#include "openmc/position.h"
10+
#include "openmc/random_ray/source_region.h"
11+
#include "openmc/span.h"
12+
#include "openmc/tallies/filter.h"
13+
#include "openmc/vector.h"
14+
15+
namespace openmc {
16+
17+
//==============================================================================
18+
//! Helper structs that define a combination of a mesh element index and a
19+
//! material index and a functor for hashing to place in an unordered_map
20+
//==============================================================================
21+
22+
struct ElementMat {
23+
//! Check for equality
24+
bool operator==(const ElementMat& other) const
25+
{
26+
return index_element == other.index_element && index_mat == other.index_mat;
27+
}
28+
29+
int32_t index_element;
30+
int32_t index_mat;
31+
};
32+
33+
struct ElementMatHash {
34+
std::size_t operator()(const ElementMat& k) const
35+
{
36+
size_t seed = 0;
37+
hash_combine(seed, k.index_element);
38+
hash_combine(seed, k.index_mat);
39+
return seed;
40+
}
41+
};
42+
43+
//==============================================================================
44+
//! Indexes the location of particle events to combinations of mesh element
45+
//! index and material. For tracklength tallies, it will produce multiple valid
46+
//! bins and the bin weight will correspond to the fraction of the track length
47+
//! that lies in that bin.
48+
//==============================================================================
49+
50+
class MeshMaterialFilter : public Filter {
51+
public:
52+
//----------------------------------------------------------------------------
53+
// Constructors, destructors
54+
55+
~MeshMaterialFilter() = default;
56+
57+
//----------------------------------------------------------------------------
58+
// Methods
59+
60+
std::string type_str() const override { return "meshmaterial"; }
61+
FilterType type() const override { return FilterType::MESH_MATERIAL; }
62+
63+
void from_xml(pugi::xml_node node) override;
64+
65+
void get_all_bins(const Particle& p, TallyEstimator estimator,
66+
FilterMatch& match) const override;
67+
68+
void to_statepoint(hid_t filter_group) const override;
69+
70+
std::string text_label(int bin) const override;
71+
72+
//----------------------------------------------------------------------------
73+
// Accessors
74+
75+
int32_t mesh() const { return mesh_; }
76+
77+
void set_mesh(int32_t mesh);
78+
79+
//! Set the bins based on a flat vector of alternating element index and
80+
//! material IDs
81+
void set_bins(span<int32_t> bins);
82+
83+
//! Set the bins based on a vector of (element, material index) pairs
84+
void set_bins(vector<ElementMat>&& bins);
85+
86+
virtual void set_translation(const Position& translation);
87+
88+
virtual void set_translation(const double translation[3]);
89+
90+
virtual const Position& translation() const { return translation_; }
91+
92+
virtual bool translated() const { return translated_; }
93+
94+
private:
95+
//----------------------------------------------------------------------------
96+
// Data members
97+
98+
int32_t mesh_; //!< Index of the mesh
99+
bool translated_ {false}; //!< Whether or not the filter is translated
100+
Position translation_ {0.0, 0.0, 0.0}; //!< Filter translation
101+
102+
//! The indices of the mesh element-material combinations binned by this
103+
//! filter.
104+
vector<ElementMat> bins_;
105+
106+
//! The set of materials used in this filter
107+
std::unordered_set<int32_t> materials_;
108+
109+
//! A map from mesh element-material indices to filter bin indices.
110+
std::unordered_map<ElementMat, int32_t, ElementMatHash> map_;
111+
};
112+
113+
} // namespace openmc
114+
#endif // OPENMC_TALLIES_FILTER_MESHMATERIAL_H

openmc/deplete/microxs.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ def get_microxs_and_flux(
5656
----------
5757
model : openmc.Model
5858
OpenMC model object. Must contain geometry, materials, and settings.
59-
domains : list of openmc.Material or openmc.Cell or openmc.Universe, or openmc.MeshBase
60-
Domains in which to tally reaction rates.
59+
domains : list of openmc.Material or openmc.Cell or openmc.Universe, or openmc.MeshBase, or openmc.Filter
60+
Domains in which to tally reaction rates, or a spatial tally filter.
6161
nuclides : list of str
6262
Nuclides to get cross sections for. If not specified, all burnable
6363
nuclides from the depletion chain file are used.
@@ -103,7 +103,10 @@ def get_microxs_and_flux(
103103
energy_filter = openmc.EnergyFilter.from_group_structure(energies)
104104
else:
105105
energy_filter = openmc.EnergyFilter(energies)
106-
if isinstance(domains, openmc.MeshBase):
106+
107+
if isinstance(domains, openmc.Filter):
108+
domain_filter = domains
109+
elif isinstance(domains, openmc.MeshBase):
107110
domain_filter = openmc.MeshFilter(domains)
108111
elif isinstance(domains[0], openmc.Material):
109112
domain_filter = openmc.MaterialFilter(domains)

openmc/filter.py

Lines changed: 185 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
'energyout', 'mu', 'musurface', 'polar', 'azimuthal', 'distribcell', 'delayedgroup',
2626
'energyfunction', 'cellfrom', 'materialfrom', 'legendre', 'spatiallegendre',
2727
'sphericalharmonics', 'zernike', 'zernikeradial', 'particle', 'cellinstance',
28-
'collision', 'time', 'parentnuclide', 'weight'
28+
'collision', 'time', 'parentnuclide', 'weight', 'meshborn', 'meshsurface',
29+
'meshmaterial',
2930
)
3031

3132
_CURRENT_NAMES = (
@@ -900,8 +901,6 @@ def mesh(self, mesh):
900901

901902
@property
902903
def shape(self):
903-
if isinstance(self, MeshSurfaceFilter):
904-
return (self.num_bins,)
905904
return self.mesh.dimension
906905

907906
@property
@@ -992,8 +991,11 @@ def to_xml_element(self):
992991
XML element containing filter data
993992
994993
"""
995-
element = super().to_xml_element()
996-
element[0].text = str(self.mesh.id)
994+
element = ET.Element('filter')
995+
element.set('id', str(self.id))
996+
element.set('type', self.short_name.lower())
997+
subelement = ET.SubElement(element, 'bins')
998+
subelement.text = str(self.mesh.id)
997999
if self.translation is not None:
9981000
element.set('translation', ' '.join(map(str, self.translation)))
9991001
return element
@@ -1039,6 +1041,181 @@ class MeshBornFilter(MeshFilter):
10391041
"""
10401042

10411043

1044+
class MeshMaterialFilter(MeshFilter):
1045+
"""Filter events by combinations of mesh elements and materials.
1046+
1047+
.. versionadded:: 0.15.3
1048+
1049+
Parameters
1050+
----------
1051+
mesh : openmc.MeshBase
1052+
The mesh object that events will be tallied onto
1053+
bins : iterable of 2-tuples or numpy.ndarray
1054+
Combinations of (mesh element, material) to tally, given as 2-tuples.
1055+
The first value in the tuple represents the index of the mesh element,
1056+
and the second value indicates the material (either a
1057+
:class:`openmc.Material` instance of the ID).
1058+
filter_id : int
1059+
Unique identifier for the filter
1060+
1061+
"""
1062+
def __init__(self, mesh: openmc.MeshBase, bins, filter_id=None):
1063+
self.mesh = mesh
1064+
self.bins = bins
1065+
self.id = filter_id
1066+
self._translation = None
1067+
1068+
@classmethod
1069+
def from_volumes(cls, mesh: openmc.MeshBase, volumes: openmc.MeshMaterialVolumes):
1070+
"""Construct a MeshMaterialFilter from a MeshMaterialVolumes object.
1071+
1072+
Parameters
1073+
----------
1074+
mesh : openmc.MeshBase
1075+
The mesh object that events will be tallied onto
1076+
volumes : openmc.MeshMaterialVolumes
1077+
The mesh material volumes to use for the filter
1078+
1079+
Returns
1080+
-------
1081+
MeshMaterialFilter
1082+
A new MeshMaterialFilter instance
1083+
1084+
"""
1085+
bins = list(zip(*np.where(volumes._materials > -1)))
1086+
return cls(mesh, bins)
1087+
1088+
def __hash__(self):
1089+
data = (type(self).__name__, self.mesh.id, tuple(self.bins.ravel()))
1090+
return hash(data)
1091+
1092+
def __repr__(self):
1093+
string = type(self).__name__ + '\n'
1094+
string += '{: <16}=\t{}\n'.format('\tID', self.id)
1095+
string += '{: <16}=\t{}\n'.format('\tMesh ID', self.mesh.id)
1096+
string += '{: <16}=\n{}\n'.format('\tBins', self.bins)
1097+
string += '{: <16}=\t{}\n'.format('\tTranslation', self.translation)
1098+
return string
1099+
1100+
@property
1101+
def shape(self):
1102+
return (self.num_bins,)
1103+
1104+
@property
1105+
def mesh(self):
1106+
return self._mesh
1107+
1108+
@mesh.setter
1109+
def mesh(self, mesh):
1110+
cv.check_type('filter mesh', mesh, openmc.MeshBase)
1111+
self._mesh = mesh
1112+
1113+
@Filter.bins.setter
1114+
def bins(self, bins):
1115+
pairs = np.empty((len(bins), 2), dtype=int)
1116+
for i, (elem, mat) in enumerate(bins):
1117+
cv.check_type('element', elem, Integral)
1118+
cv.check_type('material', mat, (Integral, openmc.Material))
1119+
pairs[i, 0] = elem
1120+
pairs[i, 1] = mat if isinstance(mat, Integral) else mat.id
1121+
self._bins = pairs
1122+
1123+
def to_xml_element(self):
1124+
"""Return XML element representing the filter.
1125+
1126+
Returns
1127+
-------
1128+
element : lxml.etree._Element
1129+
XML element containing filter data
1130+
1131+
"""
1132+
element = ET.Element('filter')
1133+
element.set('id', str(self.id))
1134+
element.set('type', self.short_name.lower())
1135+
element.set('mesh', str(self.mesh.id))
1136+
1137+
if self.translation is not None:
1138+
element.set('translation', ' '.join(map(str, self.translation)))
1139+
1140+
subelement = ET.SubElement(element, 'bins')
1141+
subelement.text = ' '.join(str(i) for i in self.bins.ravel())
1142+
1143+
return element
1144+
1145+
@classmethod
1146+
def from_xml_element(cls, elem: ET.Element, **kwargs) -> MeshMaterialFilter:
1147+
filter_id = int(elem.get('id'))
1148+
mesh_id = int(elem.get('mesh'))
1149+
mesh_obj = kwargs['meshes'][mesh_id]
1150+
bins = [int(x) for x in get_text(elem, 'bins').split()]
1151+
bins = list(zip(bins[::2], bins[1::2]))
1152+
out = cls(mesh_obj, bins, filter_id=filter_id)
1153+
1154+
translation = elem.get('translation')
1155+
if translation:
1156+
out.translation = [float(x) for x in translation.split()]
1157+
return out
1158+
1159+
@classmethod
1160+
def from_hdf5(cls, group, **kwargs):
1161+
if group['type'][()].decode() != cls.short_name.lower():
1162+
raise ValueError("Expected HDF5 data for filter type '"
1163+
+ cls.short_name.lower() + "' but got '"
1164+
+ group['type'][()].decode() + " instead")
1165+
1166+
if 'meshes' not in kwargs:
1167+
raise ValueError(cls.__name__ + " requires a 'meshes' keyword "
1168+
"argument.")
1169+
1170+
mesh_id = group['mesh'][()]
1171+
mesh_obj = kwargs['meshes'][mesh_id]
1172+
bins = group['bins'][()]
1173+
filter_id = int(group.name.split('/')[-1].lstrip('filter '))
1174+
out = cls(mesh_obj, bins, filter_id=filter_id)
1175+
1176+
translation = group.get('translation')
1177+
if translation:
1178+
out.translation = translation[()]
1179+
1180+
return out
1181+
1182+
def get_pandas_dataframe(self, data_size, stride, **kwargs):
1183+
"""Builds a Pandas DataFrame for the Filter's bins.
1184+
1185+
This method constructs a Pandas DataFrame object for the filter with
1186+
columns annotated by filter bin information. This is a helper method for
1187+
:meth:`Tally.get_pandas_dataframe`.
1188+
1189+
Parameters
1190+
----------
1191+
data_size : int
1192+
The total number of bins in the tally corresponding to this filter
1193+
stride : int
1194+
Stride in memory for the filter
1195+
1196+
Returns
1197+
-------
1198+
pandas.DataFrame
1199+
A Pandas DataFrame with a multi-index column for the cell instance.
1200+
The number of rows in the DataFrame is the same as the total number
1201+
of bins in the corresponding tally, with the filter bin appropriately
1202+
tiled to map to the corresponding tally bins.
1203+
1204+
See also
1205+
--------
1206+
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
1207+
1208+
"""
1209+
# Repeat and tile bins as necessary to account for other filters.
1210+
bins = np.repeat(self.bins, stride, axis=0)
1211+
tile_factor = data_size // len(bins)
1212+
bins = np.tile(bins, (tile_factor, 1))
1213+
1214+
columns = pd.MultiIndex.from_product([[self.short_name.lower()],
1215+
['element', 'material']])
1216+
return pd.DataFrame(bins, columns=columns)
1217+
1218+
10421219
class MeshSurfaceFilter(MeshFilter):
10431220
"""Filter events by surface crossings on a mesh.
10441221
@@ -1065,6 +1242,9 @@ class MeshSurfaceFilter(MeshFilter):
10651242
The number of filter bins
10661243
10671244
"""
1245+
@property
1246+
def shape(self):
1247+
return (self.num_bins,)
10681248

10691249
@MeshFilter.mesh.setter
10701250
def mesh(self, mesh):

0 commit comments

Comments
 (0)