mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-28 14:15:42 -04:00
Implement a new MeshMaterialFilter (#3406)
Co-authored-by: Jonathan Shimwell <drshimwell@gmail.com> Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
This commit is contained in:
parent
67aa0def9d
commit
7fda3eb846
13 changed files with 614 additions and 16 deletions
|
|
@ -414,6 +414,7 @@ list(APPEND libopenmc_SOURCES
|
|||
src/tallies/filter_materialfrom.cpp
|
||||
src/tallies/filter_mesh.cpp
|
||||
src/tallies/filter_meshborn.cpp
|
||||
src/tallies/filter_meshmaterial.cpp
|
||||
src/tallies/filter_meshsurface.cpp
|
||||
src/tallies/filter_mu.cpp
|
||||
src/tallies/filter_musurface.cpp
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ Constructing Tallies
|
|||
openmc.SurfaceFilter
|
||||
openmc.MeshFilter
|
||||
openmc.MeshBornFilter
|
||||
openmc.MeshMaterialFilter
|
||||
openmc.MeshSurfaceFilter
|
||||
openmc.EnergyFilter
|
||||
openmc.EnergyoutFilter
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ enum class FilterType {
|
|||
MATERIALFROM,
|
||||
MESH,
|
||||
MESHBORN,
|
||||
MESH_MATERIAL,
|
||||
MESH_SURFACE,
|
||||
MU,
|
||||
MUSURFACE,
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@
|
|||
namespace openmc {
|
||||
|
||||
//==============================================================================
|
||||
//! Indexes the location of particle events to a regular mesh. For tracklength
|
||||
//! tallies, it will produce multiple valid bins and the bin weight will
|
||||
//! correspond to the fraction of the track length that lies in that bin.
|
||||
//! Indexes the location of particle events to a mesh. For tracklength tallies,
|
||||
//! it will produce multiple valid bins and the bin weight will correspond to
|
||||
//! the fraction of the track length that lies in that bin.
|
||||
//==============================================================================
|
||||
|
||||
class MeshFilter : public Filter {
|
||||
|
|
|
|||
114
include/openmc/tallies/filter_meshmaterial.h
Normal file
114
include/openmc/tallies/filter_meshmaterial.h
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
#ifndef OPENMC_TALLIES_FILTER_MESHMATERIAL_H
|
||||
#define OPENMC_TALLIES_FILTER_MESHMATERIAL_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "openmc/position.h"
|
||||
#include "openmc/random_ray/source_region.h"
|
||||
#include "openmc/span.h"
|
||||
#include "openmc/tallies/filter.h"
|
||||
#include "openmc/vector.h"
|
||||
|
||||
namespace openmc {
|
||||
|
||||
//==============================================================================
|
||||
//! Helper structs that define a combination of a mesh element index and a
|
||||
//! material index and a functor for hashing to place in an unordered_map
|
||||
//==============================================================================
|
||||
|
||||
struct ElementMat {
|
||||
//! Check for equality
|
||||
bool operator==(const ElementMat& other) const
|
||||
{
|
||||
return index_element == other.index_element && index_mat == other.index_mat;
|
||||
}
|
||||
|
||||
int32_t index_element;
|
||||
int32_t index_mat;
|
||||
};
|
||||
|
||||
struct ElementMatHash {
|
||||
std::size_t operator()(const ElementMat& k) const
|
||||
{
|
||||
size_t seed = 0;
|
||||
hash_combine(seed, k.index_element);
|
||||
hash_combine(seed, k.index_mat);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
//! Indexes the location of particle events to combinations of mesh element
|
||||
//! index and material. For tracklength tallies, it will produce multiple valid
|
||||
//! bins and the bin weight will correspond to the fraction of the track length
|
||||
//! that lies in that bin.
|
||||
//==============================================================================
|
||||
|
||||
class MeshMaterialFilter : public Filter {
|
||||
public:
|
||||
//----------------------------------------------------------------------------
|
||||
// Constructors, destructors
|
||||
|
||||
~MeshMaterialFilter() = default;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Methods
|
||||
|
||||
std::string type_str() const override { return "meshmaterial"; }
|
||||
FilterType type() const override { return FilterType::MESH_MATERIAL; }
|
||||
|
||||
void from_xml(pugi::xml_node node) override;
|
||||
|
||||
void get_all_bins(const Particle& p, TallyEstimator estimator,
|
||||
FilterMatch& match) const override;
|
||||
|
||||
void to_statepoint(hid_t filter_group) const override;
|
||||
|
||||
std::string text_label(int bin) const override;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Accessors
|
||||
|
||||
int32_t mesh() const { return mesh_; }
|
||||
|
||||
void set_mesh(int32_t mesh);
|
||||
|
||||
//! Set the bins based on a flat vector of alternating element index and
|
||||
//! material IDs
|
||||
void set_bins(span<int32_t> bins);
|
||||
|
||||
//! Set the bins based on a vector of (element, material index) pairs
|
||||
void set_bins(vector<ElementMat>&& bins);
|
||||
|
||||
virtual void set_translation(const Position& translation);
|
||||
|
||||
virtual void set_translation(const double translation[3]);
|
||||
|
||||
virtual const Position& translation() const { return translation_; }
|
||||
|
||||
virtual bool translated() const { return translated_; }
|
||||
|
||||
private:
|
||||
//----------------------------------------------------------------------------
|
||||
// Data members
|
||||
|
||||
int32_t mesh_; //!< Index of the mesh
|
||||
bool translated_ {false}; //!< Whether or not the filter is translated
|
||||
Position translation_ {0.0, 0.0, 0.0}; //!< Filter translation
|
||||
|
||||
//! The indices of the mesh element-material combinations binned by this
|
||||
//! filter.
|
||||
vector<ElementMat> bins_;
|
||||
|
||||
//! The set of materials used in this filter
|
||||
std::unordered_set<int32_t> materials_;
|
||||
|
||||
//! A map from mesh element-material indices to filter bin indices.
|
||||
std::unordered_map<ElementMat, int32_t, ElementMatHash> map_;
|
||||
};
|
||||
|
||||
} // namespace openmc
|
||||
#endif // OPENMC_TALLIES_FILTER_MESHMATERIAL_H
|
||||
|
|
@ -56,8 +56,8 @@ def get_microxs_and_flux(
|
|||
----------
|
||||
model : openmc.Model
|
||||
OpenMC model object. Must contain geometry, materials, and settings.
|
||||
domains : list of openmc.Material or openmc.Cell or openmc.Universe, or openmc.MeshBase
|
||||
Domains in which to tally reaction rates.
|
||||
domains : list of openmc.Material or openmc.Cell or openmc.Universe, or openmc.MeshBase, or openmc.Filter
|
||||
Domains in which to tally reaction rates, or a spatial tally filter.
|
||||
nuclides : list of str
|
||||
Nuclides to get cross sections for. If not specified, all burnable
|
||||
nuclides from the depletion chain file are used.
|
||||
|
|
@ -103,7 +103,10 @@ def get_microxs_and_flux(
|
|||
energy_filter = openmc.EnergyFilter.from_group_structure(energies)
|
||||
else:
|
||||
energy_filter = openmc.EnergyFilter(energies)
|
||||
if isinstance(domains, openmc.MeshBase):
|
||||
|
||||
if isinstance(domains, openmc.Filter):
|
||||
domain_filter = domains
|
||||
elif isinstance(domains, openmc.MeshBase):
|
||||
domain_filter = openmc.MeshFilter(domains)
|
||||
elif isinstance(domains[0], openmc.Material):
|
||||
domain_filter = openmc.MaterialFilter(domains)
|
||||
|
|
|
|||
190
openmc/filter.py
190
openmc/filter.py
|
|
@ -25,7 +25,8 @@ _FILTER_TYPES = (
|
|||
'energyout', 'mu', 'musurface', 'polar', 'azimuthal', 'distribcell', 'delayedgroup',
|
||||
'energyfunction', 'cellfrom', 'materialfrom', 'legendre', 'spatiallegendre',
|
||||
'sphericalharmonics', 'zernike', 'zernikeradial', 'particle', 'cellinstance',
|
||||
'collision', 'time', 'parentnuclide', 'weight'
|
||||
'collision', 'time', 'parentnuclide', 'weight', 'meshborn', 'meshsurface',
|
||||
'meshmaterial',
|
||||
)
|
||||
|
||||
_CURRENT_NAMES = (
|
||||
|
|
@ -900,8 +901,6 @@ class MeshFilter(Filter):
|
|||
|
||||
@property
|
||||
def shape(self):
|
||||
if isinstance(self, MeshSurfaceFilter):
|
||||
return (self.num_bins,)
|
||||
return self.mesh.dimension
|
||||
|
||||
@property
|
||||
|
|
@ -992,8 +991,11 @@ class MeshFilter(Filter):
|
|||
XML element containing filter data
|
||||
|
||||
"""
|
||||
element = super().to_xml_element()
|
||||
element[0].text = str(self.mesh.id)
|
||||
element = ET.Element('filter')
|
||||
element.set('id', str(self.id))
|
||||
element.set('type', self.short_name.lower())
|
||||
subelement = ET.SubElement(element, 'bins')
|
||||
subelement.text = str(self.mesh.id)
|
||||
if self.translation is not None:
|
||||
element.set('translation', ' '.join(map(str, self.translation)))
|
||||
return element
|
||||
|
|
@ -1039,6 +1041,181 @@ class MeshBornFilter(MeshFilter):
|
|||
"""
|
||||
|
||||
|
||||
class MeshMaterialFilter(MeshFilter):
|
||||
"""Filter events by combinations of mesh elements and materials.
|
||||
|
||||
.. versionadded:: 0.15.3
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh : openmc.MeshBase
|
||||
The mesh object that events will be tallied onto
|
||||
bins : iterable of 2-tuples or numpy.ndarray
|
||||
Combinations of (mesh element, material) to tally, given as 2-tuples.
|
||||
The first value in the tuple represents the index of the mesh element,
|
||||
and the second value indicates the material (either a
|
||||
:class:`openmc.Material` instance of the ID).
|
||||
filter_id : int
|
||||
Unique identifier for the filter
|
||||
|
||||
"""
|
||||
def __init__(self, mesh: openmc.MeshBase, bins, filter_id=None):
|
||||
self.mesh = mesh
|
||||
self.bins = bins
|
||||
self.id = filter_id
|
||||
self._translation = None
|
||||
|
||||
@classmethod
|
||||
def from_volumes(cls, mesh: openmc.MeshBase, volumes: openmc.MeshMaterialVolumes):
|
||||
"""Construct a MeshMaterialFilter from a MeshMaterialVolumes object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh : openmc.MeshBase
|
||||
The mesh object that events will be tallied onto
|
||||
volumes : openmc.MeshMaterialVolumes
|
||||
The mesh material volumes to use for the filter
|
||||
|
||||
Returns
|
||||
-------
|
||||
MeshMaterialFilter
|
||||
A new MeshMaterialFilter instance
|
||||
|
||||
"""
|
||||
bins = list(zip(*np.where(volumes._materials > -1)))
|
||||
return cls(mesh, bins)
|
||||
|
||||
def __hash__(self):
|
||||
data = (type(self).__name__, self.mesh.id, tuple(self.bins.ravel()))
|
||||
return hash(data)
|
||||
|
||||
def __repr__(self):
|
||||
string = type(self).__name__ + '\n'
|
||||
string += '{: <16}=\t{}\n'.format('\tID', self.id)
|
||||
string += '{: <16}=\t{}\n'.format('\tMesh ID', self.mesh.id)
|
||||
string += '{: <16}=\n{}\n'.format('\tBins', self.bins)
|
||||
string += '{: <16}=\t{}\n'.format('\tTranslation', self.translation)
|
||||
return string
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return (self.num_bins,)
|
||||
|
||||
@property
|
||||
def mesh(self):
|
||||
return self._mesh
|
||||
|
||||
@mesh.setter
|
||||
def mesh(self, mesh):
|
||||
cv.check_type('filter mesh', mesh, openmc.MeshBase)
|
||||
self._mesh = mesh
|
||||
|
||||
@Filter.bins.setter
|
||||
def bins(self, bins):
|
||||
pairs = np.empty((len(bins), 2), dtype=int)
|
||||
for i, (elem, mat) in enumerate(bins):
|
||||
cv.check_type('element', elem, Integral)
|
||||
cv.check_type('material', mat, (Integral, openmc.Material))
|
||||
pairs[i, 0] = elem
|
||||
pairs[i, 1] = mat if isinstance(mat, Integral) else mat.id
|
||||
self._bins = pairs
|
||||
|
||||
def to_xml_element(self):
|
||||
"""Return XML element representing the filter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
element : lxml.etree._Element
|
||||
XML element containing filter data
|
||||
|
||||
"""
|
||||
element = ET.Element('filter')
|
||||
element.set('id', str(self.id))
|
||||
element.set('type', self.short_name.lower())
|
||||
element.set('mesh', str(self.mesh.id))
|
||||
|
||||
if self.translation is not None:
|
||||
element.set('translation', ' '.join(map(str, self.translation)))
|
||||
|
||||
subelement = ET.SubElement(element, 'bins')
|
||||
subelement.text = ' '.join(str(i) for i in self.bins.ravel())
|
||||
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem: ET.Element, **kwargs) -> MeshMaterialFilter:
|
||||
filter_id = int(elem.get('id'))
|
||||
mesh_id = int(elem.get('mesh'))
|
||||
mesh_obj = kwargs['meshes'][mesh_id]
|
||||
bins = [int(x) for x in get_text(elem, 'bins').split()]
|
||||
bins = list(zip(bins[::2], bins[1::2]))
|
||||
out = cls(mesh_obj, bins, filter_id=filter_id)
|
||||
|
||||
translation = elem.get('translation')
|
||||
if translation:
|
||||
out.translation = [float(x) for x in translation.split()]
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
def from_hdf5(cls, group, **kwargs):
|
||||
if group['type'][()].decode() != cls.short_name.lower():
|
||||
raise ValueError("Expected HDF5 data for filter type '"
|
||||
+ cls.short_name.lower() + "' but got '"
|
||||
+ group['type'][()].decode() + " instead")
|
||||
|
||||
if 'meshes' not in kwargs:
|
||||
raise ValueError(cls.__name__ + " requires a 'meshes' keyword "
|
||||
"argument.")
|
||||
|
||||
mesh_id = group['mesh'][()]
|
||||
mesh_obj = kwargs['meshes'][mesh_id]
|
||||
bins = group['bins'][()]
|
||||
filter_id = int(group.name.split('/')[-1].lstrip('filter '))
|
||||
out = cls(mesh_obj, bins, filter_id=filter_id)
|
||||
|
||||
translation = group.get('translation')
|
||||
if translation:
|
||||
out.translation = translation[()]
|
||||
|
||||
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 : 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 multi-index column for the cell instance.
|
||||
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()
|
||||
|
||||
"""
|
||||
# Repeat and tile bins as necessary to account for other filters.
|
||||
bins = np.repeat(self.bins, stride, axis=0)
|
||||
tile_factor = data_size // len(bins)
|
||||
bins = np.tile(bins, (tile_factor, 1))
|
||||
|
||||
columns = pd.MultiIndex.from_product([[self.short_name.lower()],
|
||||
['element', 'material']])
|
||||
return pd.DataFrame(bins, columns=columns)
|
||||
|
||||
|
||||
class MeshSurfaceFilter(MeshFilter):
|
||||
"""Filter events by surface crossings on a mesh.
|
||||
|
||||
|
|
@ -1065,6 +1242,9 @@ class MeshSurfaceFilter(MeshFilter):
|
|||
The number of filter bins
|
||||
|
||||
"""
|
||||
@property
|
||||
def shape(self):
|
||||
return (self.num_bins,)
|
||||
|
||||
@MeshFilter.mesh.setter
|
||||
def mesh(self, mesh):
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ __all__ = [
|
|||
'CellInstanceFilter', 'CollisionFilter', 'DistribcellFilter', 'DelayedGroupFilter',
|
||||
'EnergyFilter', 'EnergyoutFilter', 'EnergyFunctionFilter', 'LegendreFilter',
|
||||
'MaterialFilter', 'MaterialFromFilter', 'MeshFilter', 'MeshBornFilter',
|
||||
'MeshSurfaceFilter', 'MuFilter', 'MuSurfaceFilter', 'ParentNuclideFilter',
|
||||
'ParticleFilter', 'PolarFilter', 'SphericalHarmonicsFilter',
|
||||
'SpatialLegendreFilter', 'SurfaceFilter', 'UniverseFilter', 'ZernikeFilter',
|
||||
'ZernikeRadialFilter', 'filters'
|
||||
'MeshMaterialFilter', 'MeshSurfaceFilter', 'MuFilter', 'MuSurfaceFilter',
|
||||
'ParentNuclideFilter', 'ParticleFilter', 'PolarFilter', 'SphericalHarmonicsFilter',
|
||||
'SpatialLegendreFilter', 'SurfaceFilter', 'TimeFilter', 'UniverseFilter',
|
||||
'WeightFilter', 'ZernikeFilter', 'ZernikeRadialFilter', 'filters'
|
||||
]
|
||||
|
||||
# Tally functions
|
||||
|
|
@ -480,6 +480,10 @@ class MeshBornFilter(Filter):
|
|||
_dll.openmc_meshborn_filter_set_translation(self._index, (c_double*3)(*translation))
|
||||
|
||||
|
||||
class MeshMaterialFilter(Filter):
|
||||
filter_type = 'meshmaterial'
|
||||
|
||||
|
||||
class MeshSurfaceFilter(Filter):
|
||||
"""MeshSurface filter stored internally.
|
||||
|
||||
|
|
@ -606,6 +610,10 @@ class SurfaceFilter(Filter):
|
|||
filter_type = 'surface'
|
||||
|
||||
|
||||
class TimeFilter(Filter):
|
||||
filter_type = 'time'
|
||||
|
||||
|
||||
class UniverseFilter(Filter):
|
||||
filter_type = 'universe'
|
||||
|
||||
|
|
@ -643,6 +651,7 @@ _FILTER_TYPE_MAP = {
|
|||
'cellborn': CellbornFilter,
|
||||
'cellfrom': CellfromFilter,
|
||||
'cellinstance': CellInstanceFilter,
|
||||
'collision': CollisionFilter,
|
||||
'delayedgroup': DelayedGroupFilter,
|
||||
'distribcell': DistribcellFilter,
|
||||
'energy': EnergyFilter,
|
||||
|
|
@ -653,6 +662,7 @@ _FILTER_TYPE_MAP = {
|
|||
'materialfrom': MaterialFromFilter,
|
||||
'mesh': MeshFilter,
|
||||
'meshborn': MeshBornFilter,
|
||||
'meshmaterial': MeshMaterialFilter,
|
||||
'meshsurface': MeshSurfaceFilter,
|
||||
'mu': MuFilter,
|
||||
'musurface': MuSurfaceFilter,
|
||||
|
|
@ -662,7 +672,9 @@ _FILTER_TYPE_MAP = {
|
|||
'sphericalharmonics': SphericalHarmonicsFilter,
|
||||
'spatiallegendre': SpatialLegendreFilter,
|
||||
'surface': SurfaceFilter,
|
||||
'time': TimeFilter,
|
||||
'universe': UniverseFilter,
|
||||
'weight': WeightFilter,
|
||||
'zernike': ZernikeFilter,
|
||||
'zernikeradial': ZernikeRadialFilter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1575,7 +1575,7 @@ class Tally(IDManagerMixin):
|
|||
for i, f in enumerate(self.filters):
|
||||
if expand_dims:
|
||||
# Mesh filter indices are backwards so we need to flip them
|
||||
if isinstance(f, openmc.MeshFilter):
|
||||
if type(f) in {openmc.MeshFilter, openmc.MeshBornFilter}:
|
||||
fshape = f.shape[::-1]
|
||||
new_shape += fshape
|
||||
idx0, idx1 = i, i + len(fshape) - 1
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "openmc/tallies/filter_materialfrom.h"
|
||||
#include "openmc/tallies/filter_mesh.h"
|
||||
#include "openmc/tallies/filter_meshborn.h"
|
||||
#include "openmc/tallies/filter_meshmaterial.h"
|
||||
#include "openmc/tallies/filter_meshsurface.h"
|
||||
#include "openmc/tallies/filter_mu.h"
|
||||
#include "openmc/tallies/filter_musurface.h"
|
||||
|
|
@ -133,6 +134,8 @@ Filter* Filter::create(const std::string& type, int32_t id)
|
|||
return Filter::create<MeshFilter>(id);
|
||||
} else if (type == "meshborn") {
|
||||
return Filter::create<MeshBornFilter>(id);
|
||||
} else if (type == "meshmaterial") {
|
||||
return Filter::create<MeshMaterialFilter>(id);
|
||||
} else if (type == "meshsurface") {
|
||||
return Filter::create<MeshSurfaceFilter>(id);
|
||||
} else if (type == "mu") {
|
||||
|
|
|
|||
185
src/tallies/filter_meshmaterial.cpp
Normal file
185
src/tallies/filter_meshmaterial.cpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include "openmc/tallies/filter_meshmaterial.h"
|
||||
|
||||
#include <utility> // for move
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "openmc/capi.h"
|
||||
#include "openmc/constants.h"
|
||||
#include "openmc/container_util.h"
|
||||
#include "openmc/error.h"
|
||||
#include "openmc/material.h"
|
||||
#include "openmc/mesh.h"
|
||||
#include "openmc/xml_interface.h"
|
||||
|
||||
namespace openmc {
|
||||
|
||||
void MeshMaterialFilter::from_xml(pugi::xml_node node)
|
||||
{
|
||||
// Get mesh ID
|
||||
auto mesh = get_node_array<int32_t>(node, "mesh");
|
||||
if (mesh.size() != 1) {
|
||||
fatal_error(
|
||||
"Only one mesh can be specified per " + type_str() + " mesh filter.");
|
||||
}
|
||||
|
||||
auto id = mesh[0];
|
||||
auto search = model::mesh_map.find(id);
|
||||
if (search == model::mesh_map.end()) {
|
||||
fatal_error(
|
||||
fmt::format("Could not find mesh {} specified on tally filter.", id));
|
||||
}
|
||||
set_mesh(search->second);
|
||||
|
||||
// Get pairs of (element index, material) and set the bins
|
||||
auto bins = get_node_array<int32_t>(node, "bins");
|
||||
this->set_bins(bins);
|
||||
|
||||
if (check_for_node(node, "translation")) {
|
||||
set_translation(get_node_array<double>(node, "translation"));
|
||||
}
|
||||
}
|
||||
|
||||
void MeshMaterialFilter::set_bins(span<int32_t> bins)
|
||||
{
|
||||
if (bins.size() % 2 != 0) {
|
||||
fatal_error(
|
||||
fmt::format("Size of mesh material bins is not even: {}", bins.size()));
|
||||
}
|
||||
|
||||
// Create a vector of ElementMat pairs from the flat vector of bins
|
||||
vector<ElementMat> element_mats;
|
||||
for (int64_t i = 0; i < bins.size() / 2; ++i) {
|
||||
int32_t element = bins[2 * i];
|
||||
int32_t mat_id = bins[2 * i + 1];
|
||||
auto search = model::material_map.find(mat_id);
|
||||
if (search == model::material_map.end()) {
|
||||
fatal_error(fmt::format(
|
||||
"Could not find material {} specified on tally filter.", mat_id));
|
||||
}
|
||||
int32_t mat_index = search->second;
|
||||
element_mats.push_back({element, mat_index});
|
||||
}
|
||||
|
||||
this->set_bins(std::move(element_mats));
|
||||
}
|
||||
|
||||
void MeshMaterialFilter::set_bins(vector<ElementMat>&& bins)
|
||||
{
|
||||
// Swap internal bins_ with the provided vector to avoid copying
|
||||
bins_.swap(bins);
|
||||
|
||||
// Clear and update the mapping and vector of materials
|
||||
materials_.clear();
|
||||
map_.clear();
|
||||
for (std::size_t i = 0; i < bins_.size(); ++i) {
|
||||
const auto& x = bins_[i];
|
||||
assert(x.index_mat >= 0);
|
||||
assert(x.index_mat < model::materials.size());
|
||||
materials_.insert(x.index_mat);
|
||||
map_[x] = i;
|
||||
}
|
||||
|
||||
n_bins_ = bins_.size();
|
||||
}
|
||||
|
||||
void MeshMaterialFilter::set_mesh(int32_t mesh)
|
||||
{
|
||||
// perform any additional perparation for mesh tallies here
|
||||
mesh_ = mesh;
|
||||
model::meshes[mesh_]->prepare_for_point_location();
|
||||
}
|
||||
|
||||
void MeshMaterialFilter::set_translation(const Position& translation)
|
||||
{
|
||||
translated_ = true;
|
||||
translation_ = translation;
|
||||
}
|
||||
|
||||
void MeshMaterialFilter::set_translation(const double translation[3])
|
||||
{
|
||||
this->set_translation({translation[0], translation[1], translation[2]});
|
||||
}
|
||||
|
||||
void MeshMaterialFilter::get_all_bins(
|
||||
const Particle& p, TallyEstimator estimator, FilterMatch& match) const
|
||||
{
|
||||
// If current material is not in any bins, don't bother checking
|
||||
if (!contains(materials_, p.material())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Position last_r = p.r_last();
|
||||
Position r = p.r();
|
||||
Position u = p.u();
|
||||
|
||||
// apply translation if present
|
||||
if (translated_) {
|
||||
last_r -= translation();
|
||||
r -= translation();
|
||||
}
|
||||
|
||||
if (estimator != TallyEstimator::TRACKLENGTH) {
|
||||
int32_t index_element = model::meshes[mesh_]->get_bin(r);
|
||||
if (index_element >= 0) {
|
||||
auto search = map_.find({index_element, p.material()});
|
||||
if (search != map_.end()) {
|
||||
match.bins_.push_back(search->second);
|
||||
match.weights_.push_back(1.0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// First determine which elements the particle crosses (may or may not
|
||||
// actually match bins so we have to adjust bins_/weight_ after)
|
||||
int32_t n_start = match.bins_.size();
|
||||
model::meshes[mesh_]->bins_crossed(
|
||||
last_r, r, u, match.bins_, match.weights_);
|
||||
int32_t n_end = match.bins_.size();
|
||||
|
||||
// Go through bins and weights and check which ones are actually a match
|
||||
// based on the (element, material) pair. For matches, overwrite the bin.
|
||||
int i = 0;
|
||||
for (int j = n_start; j < n_end; ++j) {
|
||||
int32_t index_element = match.bins_[j];
|
||||
double weight = match.weights_[j];
|
||||
auto search = map_.find({index_element, p.material()});
|
||||
if (search != map_.end()) {
|
||||
match.bins_[n_start + i] = search->second;
|
||||
match.weights_[n_start + i] = weight;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize the vectors to remove the unmatched bins
|
||||
match.bins_.resize(n_start + i);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshMaterialFilter::to_statepoint(hid_t filter_group) const
|
||||
{
|
||||
Filter::to_statepoint(filter_group);
|
||||
write_dataset(filter_group, "mesh", model::meshes[mesh_]->id_);
|
||||
|
||||
size_t n = bins_.size();
|
||||
xt::xtensor<size_t, 2> data({n, 2});
|
||||
for (int64_t i = 0; i < n; ++i) {
|
||||
const auto& x = bins_[i];
|
||||
data(i, 0) = x.index_element;
|
||||
data(i, 1) = model::materials[x.index_mat]->id_;
|
||||
}
|
||||
write_dataset(filter_group, "bins", data);
|
||||
|
||||
if (translated_) {
|
||||
write_dataset(filter_group, "translation", translation_);
|
||||
}
|
||||
}
|
||||
|
||||
std::string MeshMaterialFilter::text_label(int bin) const
|
||||
{
|
||||
auto& x = bins_[bin];
|
||||
auto& mesh = *model::meshes.at(mesh_);
|
||||
return fmt::format("Mesh {}, {}, Material {}", mesh.id(),
|
||||
mesh.bin_label(x.index_element), model::materials[x.index_mat]->id_);
|
||||
}
|
||||
|
||||
} // namespace openmc
|
||||
65
tests/unit_tests/test_filter_meshmaterial.py
Normal file
65
tests/unit_tests/test_filter_meshmaterial.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import numpy as np
|
||||
import openmc
|
||||
from pytest import approx
|
||||
|
||||
|
||||
def test_filter_mesh_material(run_in_tmpdir):
|
||||
# Create four identical materials
|
||||
openmc.reset_auto_ids()
|
||||
materials = []
|
||||
for i in range(4):
|
||||
mat = openmc.Material()
|
||||
mat.add_nuclide('Fe56', 1.0)
|
||||
materials.append(mat)
|
||||
|
||||
# Create a slab model with four cells
|
||||
z_values = [-10., -5., 0., 5., 10.]
|
||||
planes = [openmc.ZPlane(z) for z in z_values]
|
||||
planes[0].boundary_type = 'vacuum'
|
||||
planes[-1].boundary_type = 'vacuum'
|
||||
regions = [+left & -right for left, right in zip(planes[:-1], planes[1:])]
|
||||
cells = [openmc.Cell(fill=m, region=r) for r, m in zip(regions, materials)]
|
||||
model = openmc.Model()
|
||||
model.geometry = openmc.Geometry(cells)
|
||||
model.settings.particles = 1_000
|
||||
model.settings.batches = 5
|
||||
model.settings.run_mode = 'fixed source'
|
||||
|
||||
# Create a mesh that does not align with all planar surfaces
|
||||
mesh = openmc.RegularMesh()
|
||||
mesh.lower_left = (-1., -1., -10.)
|
||||
mesh.upper_right = (1., 1., 10.)
|
||||
mesh.dimension = (1, 1, 5)
|
||||
|
||||
# Determine material volumes in each mesh element and use result to create a
|
||||
# MeshMaterialFilter with corresponding bins
|
||||
vols = mesh.material_volumes(model)
|
||||
mmf = openmc.MeshMaterialFilter.from_volumes(mesh, vols)
|
||||
expected_bins = [(0, 1), (1, 1), (1, 2), (2, 2), (2, 3), (3, 3), (3, 4), (4, 4)]
|
||||
np.testing.assert_equal(mmf.bins, expected_bins)
|
||||
|
||||
# Create two tallies, one with a mesh filter and one with mesh-material
|
||||
mesh_tally = openmc.Tally()
|
||||
mesh_tally.filters = [openmc.MeshFilter(mesh)]
|
||||
mesh_tally.scores = ['flux']
|
||||
mesh_material_tally = openmc.Tally()
|
||||
mesh_material_tally.filters = [mmf]
|
||||
mesh_material_tally.scores = ['flux']
|
||||
model.tallies = [mesh_tally, mesh_material_tally]
|
||||
|
||||
# Run model to get results on the two tallies
|
||||
model.run(apply_tally_results=True)
|
||||
|
||||
# The sum of the flux in each mesh-material combination within a single mesh
|
||||
# element should be equal to the flux in that mesh element
|
||||
mesh_mean = mesh_tally.mean.ravel()
|
||||
meshmat_mean = mesh_material_tally.mean.ravel()
|
||||
assert mesh_mean[0] == approx(meshmat_mean[0])
|
||||
assert mesh_mean[1] == approx(meshmat_mean[1] + meshmat_mean[2])
|
||||
assert mesh_mean[2] == approx(meshmat_mean[3] + meshmat_mean[4])
|
||||
assert mesh_mean[3] == approx(meshmat_mean[5] + meshmat_mean[6])
|
||||
assert mesh_mean[4] == approx(meshmat_mean[7])
|
||||
assert mesh_tally.mean.sum() == approx(mesh_material_tally.mean.sum())
|
||||
|
||||
# Make sure get_pandas_dataframe method works
|
||||
mesh_material_tally.get_pandas_dataframe()
|
||||
|
|
@ -333,3 +333,36 @@ def test_weight():
|
|||
new_f = openmc.Filter.from_xml_element(elem)
|
||||
assert new_f.id == f.id
|
||||
assert np.allclose(new_f.bins, f.bins)
|
||||
|
||||
|
||||
def test_mesh_material():
|
||||
mat1 = openmc.Material()
|
||||
mat2 = openmc.Material()
|
||||
|
||||
mesh = openmc.RegularMesh()
|
||||
mesh.lower_left = (-1., -1., -1.)
|
||||
mesh.upper_right = (1., 1., 1.)
|
||||
mesh.dimension = (2, 4, 1)
|
||||
bins = [(0, mat1), (0, mat2), (6, mat1), (7, mat2)]
|
||||
f = openmc.MeshMaterialFilter(mesh, bins)
|
||||
|
||||
expected_bins = [(0, mat1.id), (0, mat2.id), (6, mat1.id), (7, mat2.id)]
|
||||
assert np.allclose(f.bins, expected_bins)
|
||||
assert f.mesh == mesh
|
||||
assert f.shape == (4,)
|
||||
|
||||
# to_xml_element()
|
||||
elem = f.to_xml_element()
|
||||
assert elem.tag == 'filter'
|
||||
assert elem.attrib['type'] == 'meshmaterial'
|
||||
|
||||
# from_xml_element()
|
||||
new_f = openmc.Filter.from_xml_element(elem, meshes={mesh.id: mesh})
|
||||
assert isinstance(new_f, openmc.MeshMaterialFilter)
|
||||
assert new_f.id == f.id
|
||||
assert new_f.mesh == f.mesh
|
||||
assert np.allclose(new_f.bins, expected_bins)
|
||||
|
||||
# Test hash and str
|
||||
hash(f)
|
||||
str(f)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue