From cc848effe77c90decdcb453679f0f5ee18e872f7 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville <54550047+JoffreyDorville@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:32:21 -0500 Subject: [PATCH] Meshborn filter (#2925) Co-authored-by: Paul Romano --- CMakeLists.txt | 1 + docs/source/capi/index.rst | 102 ++++++++++++ docs/source/pythonapi/base.rst | 1 + docs/source/pythonapi/capi.rst | 1 + include/openmc/particle_data.h | 5 + include/openmc/tallies/filter.h | 1 + include/openmc/tallies/filter_meshborn.h | 26 +++ openmc/filter.py | 28 ++++ openmc/lib/filter.py | 151 ++++++++++++++++-- src/particle.cpp | 1 + src/tallies/filter.cpp | 3 + src/tallies/filter_mesh.cpp | 2 + src/tallies/filter_meshborn.cpp | 61 +++++++ src/tallies/filter_meshsurface.cpp | 12 ++ src/tallies/tally.cpp | 1 + .../filter_meshborn/__init__.py | 0 .../filter_meshborn/inputs_true.dat | 51 ++++++ .../filter_meshborn/results_true.dat | 54 +++++++ .../regression_tests/filter_meshborn/test.py | 107 +++++++++++++ tests/unit_tests/test_filter_meshborn.py | 116 ++++++++++++++ tests/unit_tests/test_tallies.py | 7 +- 21 files changed, 720 insertions(+), 11 deletions(-) create mode 100644 include/openmc/tallies/filter_meshborn.h create mode 100644 src/tallies/filter_meshborn.cpp create mode 100644 tests/regression_tests/filter_meshborn/__init__.py create mode 100644 tests/regression_tests/filter_meshborn/inputs_true.dat create mode 100644 tests/regression_tests/filter_meshborn/results_true.dat create mode 100644 tests/regression_tests/filter_meshborn/test.py create mode 100644 tests/unit_tests/test_filter_meshborn.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 1280daf2b..6922e6b89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -411,6 +411,7 @@ list(APPEND libopenmc_SOURCES src/tallies/filter_material.cpp src/tallies/filter_materialfrom.cpp src/tallies/filter_mesh.cpp + src/tallies/filter_meshborn.cpp src/tallies/filter_meshsurface.cpp src/tallies/filter_mu.cpp src/tallies/filter_particle.cpp diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index 52c0b2406..d9ac0d1e0 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -420,6 +420,16 @@ Functions :return: Return status (negative if an error occurred) :rtype: int +.. c:function:: int openmc_mesh_filter_get_mesh(int32_t index, int32_t* index_mesh) + + Get the mesh for a mesh filter + + :param int32_t index: Index in the filters array + :param index_mesh: Index in the meshes array + :type index_mesh: int32_t* + :return: Return status (negative if an error occurred) + :rtype: int + .. c:function:: int openmc_mesh_filter_set_mesh(int32_t index, int32_t index_mesh) Set the mesh for a mesh filter @@ -429,6 +439,98 @@ Functions :return: Return status (negative if an error occurred) :rtype: int +.. c:function:: int openmc_mesh_filter_get_translation(int32_t index, double translation[3]) + + Get the 3-D translation coordinates for a mesh filter + + :param int32_t index: Index in the filters array + :param double[3] translation: 3-D translation coordinates + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_mesh_filter_set_translation(int32_t index, double translation[3]) + + Set the 3-D translation coordinates for a mesh filter + + :param int32_t index: Index in the filters array + :param double[3] translation: 3-D translation coordinates + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_meshborn_filter_get_mesh(int32_t index, int32_t* index_mesh) + + Get the mesh for a meshborn filter + + :param int32_t index: Index in the filters array + :param index_mesh: Index in the meshes array + :type index_mesh: int32_t* + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_meshborn_filter_set_mesh(int32_t index, int32_t index_mesh) + + Set the mesh for a meshborn filter + + :param int32_t index: Index in the filters array + :param int32_t index_mesh: Index in the meshes array + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_meshborn_filter_get_translation(int32_t index, double translation[3]) + + Get the 3-D translation coordinates for a meshborn filter + + :param int32_t index: Index in the filters array + :param double[3] translation: 3-D translation coordinates + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_meshborn_filter_set_translation(int32_t index, double translation[3]) + + Set the 3-D translation coordinates for a meshborn filter + + :param int32_t index: Index in the filters array + :param double[3] translation: 3-D translation coordinates + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_meshsurface_filter_get_mesh(int32_t index, int32_t* index_mesh) + + Get the mesh for a mesh surface filter + + :param int32_t index: Index in the filters array + :param index_mesh: Index in the meshes array + :type index_mesh: int32_t* + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_meshsurface_filter_set_mesh(int32_t index, int32_t index_mesh) + + Set the mesh for a mesh surface filter + + :param int32_t index: Index in the filters array + :param int32_t index_mesh: Index in the meshes array + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_meshsurface_filter_get_translation(int32_t index, double translation[3]) + + Get the 3-D translation coordinates for a mesh surface filter + + :param int32_t index: Index in the filters array + :param double[3] translation: 3-D translation coordinates + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_meshsurface_filter_set_translation(int32_t index, double translation[3]) + + Set the 3-D translation coordinates for a mesh surface filter + + :param int32_t index: Index in the filters array + :param double[3] translation: 3-D translation coordinates + :return: Return status (negative if an error occurred) + :rtype: int + .. c:function:: int openmc_next_batch() Simulate next batch of particles. Must be called after openmc_simulation_init(). diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index 7b924e374..5ae9f20ed 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -128,6 +128,7 @@ Constructing Tallies openmc.CollisionFilter openmc.SurfaceFilter openmc.MeshFilter + openmc.MeshBornFilter openmc.MeshSurfaceFilter openmc.EnergyFilter openmc.EnergyoutFilter diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index ef713801c..16bb68ca4 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -59,6 +59,7 @@ Classes MaterialFilter Material MeshFilter + MeshBornFilter MeshSurfaceFilter Nuclide RectilinearMesh diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index cb68fc457..cceab1d11 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -262,6 +262,10 @@ public: int& cell_last(int i) { return cell_last_[i]; } const int& cell_last(int i) const { return cell_last_[i]; } + // Coordinates at birth + Position& r_born() { return r_born_; } + const Position& r_born() const { return r_born_; } + // Coordinates of last collision or reflective/periodic surface // crossing for current tallies Position& r_last_current() { return r_last_current_; } @@ -323,6 +327,7 @@ private: int n_coord_last_ {1}; //!< number of current coordinates vector cell_last_; //!< coordinates for all levels + Position r_born_; //!< coordinates at birth Position r_last_current_; //!< coordinates of the last collision or //!< reflective/periodic surface crossing for //!< current tallies diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index dc5872ce6..1166c0eee 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -33,6 +33,7 @@ enum class FilterType { MATERIAL, MATERIALFROM, MESH, + MESHBORN, MESH_SURFACE, MU, PARTICLE, diff --git a/include/openmc/tallies/filter_meshborn.h b/include/openmc/tallies/filter_meshborn.h new file mode 100644 index 000000000..8ab7a8c76 --- /dev/null +++ b/include/openmc/tallies/filter_meshborn.h @@ -0,0 +1,26 @@ +#ifndef OPENMC_TALLIES_FILTER_MESHBORN_H +#define OPENMC_TALLIES_FILTER_MESHBORN_H + +#include + +#include "openmc/position.h" +#include "openmc/tallies/filter_mesh.h" + +namespace openmc { + +class MeshBornFilter : public MeshFilter { +public: + //---------------------------------------------------------------------------- + // Methods + + std::string type_str() const override { return "meshborn"; } + FilterType type() const override { return FilterType::MESHBORN; } + + void get_all_bins(const Particle& p, TallyEstimator estimator, + FilterMatch& match) const override; + + std::string text_label(int bin) const override; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_MESHBORN_H diff --git a/openmc/filter.py b/openmc/filter.py index f2bbf2f50..04b30261d 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -982,6 +982,34 @@ class MeshFilter(Filter): if translation: out.translation = [float(x) for x in translation.split()] return out + + +class MeshBornFilter(MeshFilter): + """Filter events by the mesh cell a particle originated from. + + Parameters + ---------- + mesh : openmc.MeshBase + The mesh object that events will be tallied onto + filter_id : int + Unique identifier for the filter + + Attributes + ---------- + mesh : openmc.MeshBase + The mesh object that events will be tallied onto + id : int + Unique identifier for the filter + translation : Iterable of float + This array specifies a vector that is used to translate (shift) + the mesh for this 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 + + """ class MeshSurfaceFilter(MeshFilter): diff --git a/openmc/lib/filter.py b/openmc/lib/filter.py index 7c2e7e5c4..340c2fa34 100644 --- a/openmc/lib/filter.py +++ b/openmc/lib/filter.py @@ -20,7 +20,8 @@ __all__ = [ 'Filter', 'AzimuthalFilter', 'CellFilter', 'CellbornFilter', 'CellfromFilter', 'CellInstanceFilter', 'CollisionFilter', 'DistribcellFilter', 'DelayedGroupFilter', 'EnergyFilter', 'EnergyoutFilter', 'EnergyFunctionFilter', 'LegendreFilter', - 'MaterialFilter', 'MaterialFromFilter', 'MeshFilter', 'MeshSurfaceFilter', 'MuFilter', 'ParticleFilter', + 'MaterialFilter', 'MaterialFromFilter', 'MeshFilter', 'MeshBornFilter', + 'MeshSurfaceFilter', 'MuFilter', 'ParticleFilter', 'PolarFilter', 'SphericalHarmonicsFilter', 'SpatialLegendreFilter', 'SurfaceFilter', 'UniverseFilter', 'ZernikeFilter', 'ZernikeRadialFilter', 'filters' ] @@ -89,18 +90,36 @@ _dll.openmc_mesh_filter_get_mesh.errcheck = _error_handler _dll.openmc_mesh_filter_set_mesh.argtypes = [c_int32, c_int32] _dll.openmc_mesh_filter_set_mesh.restype = c_int _dll.openmc_mesh_filter_set_mesh.errcheck = _error_handler -_dll.openmc_meshsurface_filter_get_mesh.argtypes = [c_int32, POINTER(c_int32)] -_dll.openmc_meshsurface_filter_get_mesh.restype = c_int -_dll.openmc_meshsurface_filter_get_mesh.errcheck = _error_handler -_dll.openmc_meshsurface_filter_set_mesh.argtypes = [c_int32, c_int32] -_dll.openmc_meshsurface_filter_set_mesh.restype = c_int -_dll.openmc_meshsurface_filter_set_mesh.errcheck = _error_handler _dll.openmc_mesh_filter_get_translation.argtypes = [c_int32, POINTER(c_double*3)] _dll.openmc_mesh_filter_get_translation.restype = c_int _dll.openmc_mesh_filter_get_translation.errcheck = _error_handler _dll.openmc_mesh_filter_set_translation.argtypes = [c_int32, POINTER(c_double*3)] _dll.openmc_mesh_filter_set_translation.restype = c_int _dll.openmc_mesh_filter_set_translation.errcheck = _error_handler +_dll.openmc_meshborn_filter_get_mesh.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_meshborn_filter_get_mesh.restype = c_int +_dll.openmc_meshborn_filter_get_mesh.errcheck = _error_handler +_dll.openmc_meshborn_filter_set_mesh.argtypes = [c_int32, c_int32] +_dll.openmc_meshborn_filter_set_mesh.restype = c_int +_dll.openmc_meshborn_filter_set_mesh.errcheck = _error_handler +_dll.openmc_meshborn_filter_get_translation.argtypes = [c_int32, POINTER(c_double*3)] +_dll.openmc_meshborn_filter_get_translation.restype = c_int +_dll.openmc_meshborn_filter_get_translation.errcheck = _error_handler +_dll.openmc_meshborn_filter_set_translation.argtypes = [c_int32, POINTER(c_double*3)] +_dll.openmc_meshborn_filter_set_translation.restype = c_int +_dll.openmc_meshborn_filter_set_translation.errcheck = _error_handler +_dll.openmc_meshsurface_filter_get_mesh.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_meshsurface_filter_get_mesh.restype = c_int +_dll.openmc_meshsurface_filter_get_mesh.errcheck = _error_handler +_dll.openmc_meshsurface_filter_set_mesh.argtypes = [c_int32, c_int32] +_dll.openmc_meshsurface_filter_set_mesh.restype = c_int +_dll.openmc_meshsurface_filter_set_mesh.errcheck = _error_handler +_dll.openmc_meshsurface_filter_get_translation.argtypes = [c_int32, POINTER(c_double*3)] +_dll.openmc_meshsurface_filter_get_translation.restype = c_int +_dll.openmc_meshsurface_filter_get_translation.errcheck = _error_handler +_dll.openmc_meshsurface_filter_set_translation.argtypes = [c_int32, POINTER(c_double*3)] +_dll.openmc_meshsurface_filter_set_translation.restype = c_int +_dll.openmc_meshsurface_filter_set_translation.errcheck = _error_handler _dll.openmc_new_filter.argtypes = [c_char_p, POINTER(c_int32)] _dll.openmc_new_filter.restype = c_int _dll.openmc_new_filter.errcheck = _error_handler @@ -347,6 +366,34 @@ class MaterialFromFilter(Filter): class MeshFilter(Filter): + """Mesh filter stored internally. + + This class exposes a Mesh filter that is stored internally in the OpenMC + library. To obtain a view of a Mesh filter with a given ID, use the + :data:`openmc.lib.filters` mapping. + + Parameters + ---------- + mesh : openmc.lib.Mesh + Mesh to use for the filter + uid : int or None + Unique ID of the Mesh filter + new : bool + When `index` is None, this argument controls whether a new object is + created or a view of an existing object is returned. + index : int + Index in the `filters` array. + + Attributes + ---------- + filter_type : str + Type of filter + mesh : openmc.lib.Mesh + Mesh used for the filter + translation : Iterable of float + 3-D coordinates of the translation vector + + """ filter_type = 'mesh' def __init__(self, mesh=None, uid=None, new=True, index=None): @@ -375,7 +422,92 @@ class MeshFilter(Filter): _dll.openmc_mesh_filter_set_translation(self._index, (c_double*3)(*translation)) +class MeshBornFilter(Filter): + """MeshBorn filter stored internally. + + This class exposes a MeshBorn filter that is stored internally in the OpenMC + library. To obtain a view of a MeshBorn filter with a given ID, use the + :data:`openmc.lib.filters` mapping. + + Parameters + ---------- + mesh : openmc.lib.Mesh + Mesh to use for the filter + uid : int or None + Unique ID of the MeshBorn filter + new : bool + When `index` is None, this argument controls whether a new object is + created or a view of an existing object is returned. + index : int + Index in the `filters` array. + + Attributes + ---------- + filter_type : str + Type of filter + mesh : openmc.lib.Mesh + Mesh used for the filter + translation : Iterable of float + 3-D coordinates of the translation vector + + """ + filter_type = 'meshborn' + + def __init__(self, mesh=None, uid=None, new=True, index=None): + super().__init__(uid, new, index) + if mesh is not None: + self.mesh = mesh + + @property + def mesh(self): + index_mesh = c_int32() + _dll.openmc_meshborn_filter_get_mesh(self._index, index_mesh) + return _get_mesh(index_mesh.value) + + @mesh.setter + def mesh(self, mesh): + _dll.openmc_meshborn_filter_set_mesh(self._index, mesh._index) + + @property + def translation(self): + translation = (c_double*3)() + _dll.openmc_meshborn_filter_get_translation(self._index, translation) + return tuple(translation) + + @translation.setter + def translation(self, translation): + _dll.openmc_meshborn_filter_set_translation(self._index, (c_double*3)(*translation)) + + class MeshSurfaceFilter(Filter): + """MeshSurface filter stored internally. + + This class exposes a MeshSurface filter that is stored internally in the + OpenMC library. To obtain a view of a MeshSurface filter with a given ID, + use the :data:`openmc.lib.filters` mapping. + + Parameters + ---------- + mesh : openmc.lib.Mesh + Mesh to use for the filter + uid : int or None + Unique ID of the MeshSurface filter + new : bool + When `index` is None, this argument controls whether a new object is + created or a view of an existing object is returned. + index : int + Index in the `filters` array. + + Attributes + ---------- + filter_type : str + Type of filter + mesh : openmc.lib.Mesh + Mesh used for the filter + translation : Iterable of float + 3-D coordinates of the translation vector + + """ filter_type = 'meshsurface' def __init__(self, mesh=None, uid=None, new=True, index=None): @@ -396,12 +528,12 @@ class MeshSurfaceFilter(Filter): @property def translation(self): translation = (c_double*3)() - _dll.openmc_mesh_filter_get_translation(self._index, translation) + _dll.openmc_meshsurface_filter_get_translation(self._index, translation) return tuple(translation) @translation.setter def translation(self, translation): - _dll.openmc_mesh_filter_set_translation(self._index, (c_double*3)(*translation)) + _dll.openmc_meshsurface_filter_set_translation(self._index, (c_double*3)(*translation)) class MuFilter(Filter): @@ -507,6 +639,7 @@ _FILTER_TYPE_MAP = { 'material': MaterialFilter, 'materialfrom': MaterialFromFilter, 'mesh': MeshFilter, + 'meshborn': MeshBornFilter, 'meshsurface': MeshSurfaceFilter, 'mu': MuFilter, 'particle': ParticleFilter, diff --git a/src/particle.cpp b/src/particle.cpp index 549de5cff..c2e340201 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -121,6 +121,7 @@ void Particle::from_source(const SourceSite* src) wgt_last() = src->wgt; r() = src->r; u() = src->u; + r_born() = src->r; r_last_current() = src->r; r_last() = src->r; u_last() = src->u; diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index 5fae4cf60..ff7a3416b 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -23,6 +23,7 @@ #include "openmc/tallies/filter_material.h" #include "openmc/tallies/filter_materialfrom.h" #include "openmc/tallies/filter_mesh.h" +#include "openmc/tallies/filter_meshborn.h" #include "openmc/tallies/filter_meshsurface.h" #include "openmc/tallies/filter_mu.h" #include "openmc/tallies/filter_particle.h" @@ -126,6 +127,8 @@ Filter* Filter::create(const std::string& type, int32_t id) return Filter::create(id); } else if (type == "mesh") { return Filter::create(id); + } else if (type == "meshborn") { + return Filter::create(id); } else if (type == "meshsurface") { return Filter::create(id); } else if (type == "mu") { diff --git a/src/tallies/filter_mesh.cpp b/src/tallies/filter_mesh.cpp index ed9c71667..5b01da1f6 100644 --- a/src/tallies/filter_mesh.cpp +++ b/src/tallies/filter_mesh.cpp @@ -161,6 +161,7 @@ extern "C" int openmc_mesh_filter_get_translation( // Check the filter type const auto& filter = model::tally_filters[index]; if (filter->type() != FilterType::MESH && + filter->type() != FilterType::MESHBORN && filter->type() != FilterType::MESH_SURFACE) { set_errmsg("Tried to get a translation from a non-mesh-based filter."); return OPENMC_E_INVALID_TYPE; @@ -186,6 +187,7 @@ extern "C" int openmc_mesh_filter_set_translation( const auto& filter = model::tally_filters[index]; // Check the filter type if (filter->type() != FilterType::MESH && + filter->type() != FilterType::MESHBORN && filter->type() != FilterType::MESH_SURFACE) { set_errmsg("Tried to set mesh on a non-mesh-based filter."); return OPENMC_E_INVALID_TYPE; diff --git a/src/tallies/filter_meshborn.cpp b/src/tallies/filter_meshborn.cpp new file mode 100644 index 000000000..c95dc3dc7 --- /dev/null +++ b/src/tallies/filter_meshborn.cpp @@ -0,0 +1,61 @@ +#include "openmc/tallies/filter_meshborn.h" + +#include "openmc/capi.h" +#include "openmc/constants.h" +#include "openmc/error.h" +#include "openmc/mesh.h" + +namespace openmc { + +void MeshBornFilter::get_all_bins( + const Particle& p, TallyEstimator estimator, FilterMatch& match) const +{ + Position r_born = p.r_born(); + + // apply translation if present + if (translated_) { + r_born -= translation(); + } + + auto bin = model::meshes[mesh_]->get_bin(r_born); + if (bin >= 0) { + match.bins_.push_back(bin); + match.weights_.push_back(1.0); + } +} + +std::string MeshBornFilter::text_label(int bin) const +{ + auto& mesh = *model::meshes.at(mesh_); + return mesh.bin_label(bin) + " (born)"; +} + +//============================================================================== +// C-API functions +//============================================================================== + +extern "C" int openmc_meshborn_filter_get_mesh( + int32_t index, int32_t* index_mesh) +{ + return openmc_mesh_filter_get_mesh(index, index_mesh); +} + +extern "C" int openmc_meshborn_filter_set_mesh( + int32_t index, int32_t index_mesh) +{ + return openmc_mesh_filter_set_mesh(index, index_mesh); +} + +extern "C" int openmc_meshborn_filter_get_translation( + int32_t index, double translation[3]) +{ + return openmc_mesh_filter_get_translation(index, translation); +} + +extern "C" int openmc_meshborn_filter_set_translation( + int32_t index, double translation[3]) +{ + return openmc_mesh_filter_set_translation(index, translation); +} + +} // namespace openmc diff --git a/src/tallies/filter_meshsurface.cpp b/src/tallies/filter_meshsurface.cpp index b22085ebb..b26cd198b 100644 --- a/src/tallies/filter_meshsurface.cpp +++ b/src/tallies/filter_meshsurface.cpp @@ -100,4 +100,16 @@ extern "C" int openmc_meshsurface_filter_set_mesh( return openmc_mesh_filter_set_mesh(index, index_mesh); } +extern "C" int openmc_meshsurface_filter_get_translation( + int32_t index, double translation[3]) +{ + return openmc_mesh_filter_get_translation(index, translation); +} + +extern "C" int openmc_meshsurface_filter_set_translation( + int32_t index, double translation[3]) +{ + return openmc_mesh_filter_set_translation(index, translation); +} + } // namespace openmc diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 7fd444a58..df1ec5ea7 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -26,6 +26,7 @@ #include "openmc/tallies/filter_energy.h" #include "openmc/tallies/filter_legendre.h" #include "openmc/tallies/filter_mesh.h" +#include "openmc/tallies/filter_meshborn.h" #include "openmc/tallies/filter_meshsurface.h" #include "openmc/tallies/filter_particle.h" #include "openmc/tallies/filter_sph_harm.h" diff --git a/tests/regression_tests/filter_meshborn/__init__.py b/tests/regression_tests/filter_meshborn/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/filter_meshborn/inputs_true.dat b/tests/regression_tests/filter_meshborn/inputs_true.dat new file mode 100644 index 000000000..3ba38d56e --- /dev/null +++ b/tests/regression_tests/filter_meshborn/inputs_true.dat @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + fixed source + 2000 + 8 + + + 0.0 -10.0 -10.0 10.0 10.0 10.0 + + + + + + 2 2 1 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + 1 + + + 1 + + + 1 2 + scatter + + + 1 + scatter + + + 2 + scatter + + + scatter + + + diff --git a/tests/regression_tests/filter_meshborn/results_true.dat b/tests/regression_tests/filter_meshborn/results_true.dat new file mode 100644 index 000000000..62f2707ff --- /dev/null +++ b/tests/regression_tests/filter_meshborn/results_true.dat @@ -0,0 +1,54 @@ +tally 1: +0.000000E+00 +0.000000E+00 +2.631246E+01 +8.845079E+01 +0.000000E+00 +0.000000E+00 +2.450265E+00 +9.462266E-01 +0.000000E+00 +0.000000E+00 +3.878380E+02 +1.881752E+04 +0.000000E+00 +0.000000E+00 +2.932956E+01 +1.091674E+02 +0.000000E+00 +0.000000E+00 +1.837753E+00 +5.195343E-01 +0.000000E+00 +0.000000E+00 +2.944919E+01 +1.095819E+02 +0.000000E+00 +0.000000E+00 +2.921731E+01 +1.097387E+02 +0.000000E+00 +0.000000E+00 +4.019442E+02 +2.021184E+04 +tally 2: +2.876273E+01 +1.060244E+02 +4.171676E+02 +2.176683E+04 +3.128695E+01 +1.238772E+02 +4.311615E+02 +2.325871E+04 +tally 3: +0.000000E+00 +0.000000E+00 +4.452055E+02 +2.478148E+04 +0.000000E+00 +0.000000E+00 +4.631732E+02 +2.683862E+04 +tally 4: +9.083787E+02 +1.031695E+05 diff --git a/tests/regression_tests/filter_meshborn/test.py b/tests/regression_tests/filter_meshborn/test.py new file mode 100644 index 000000000..ff4adbc9f --- /dev/null +++ b/tests/regression_tests/filter_meshborn/test.py @@ -0,0 +1,107 @@ +"""Test the meshborn filter using a fixed source calculation on a H1 sphere. + +""" + +from numpy.testing import assert_allclose +import numpy as np +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +RTOL = 1.0e-7 +ATOL = 0.0 + + +@pytest.fixture +def model(): + """Sphere of H1 with one hemisphere containing the source (x>0) and one + hemisphere with no source (x<0). + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # Materials + h1 = openmc.Material() + h1.add_nuclide("H1", 1.0) + h1.set_density("g/cm3", 1.0) + model.materials = openmc.Materials([h1]) + + # Core geometry + r = 10.0 + sphere = openmc.Sphere(r=r, boundary_type="reflective") + core = openmc.Cell(fill=h1, region=-sphere) + model.geometry = openmc.Geometry([core]) + + # Settings + model.settings.run_mode = 'fixed source' + model.settings.particles = 2000 + model.settings.batches = 8 + distribution = openmc.stats.Box((0., -r, -r), (r, r, r)) + model.settings.source = openmc.IndependentSource(space=distribution) + + # Tallies + mesh = openmc.RegularMesh() + mesh.dimension = (2, 2, 1) + mesh.lower_left = (-r, -r, -r) + mesh.upper_right = (r, r, r) + + f_1 = openmc.MeshFilter(mesh) + f_2 = openmc.MeshBornFilter(mesh) + + t_1 = openmc.Tally(name="scatter") + t_1.filters = [f_1, f_2] + t_1.scores = ["scatter"] + + t_2 = openmc.Tally(name="scatter-mesh") + t_2.filters = [f_1] + t_2.scores = ["scatter"] + + t_3 = openmc.Tally(name="scatter-meshborn") + t_3.filters = [f_2] + t_3.scores = ["scatter"] + + t_4 = openmc.Tally(name="scatter-total") + t_4.scores = ["scatter"] + + model.tallies = [t_1, t_2, t_3, t_4] + + return model + + +class MeshBornFilterTest(PyAPITestHarness): + + def _compare_results(self): + """Additional unit tests on the tally results to check consistency.""" + with openmc.StatePoint(self.statepoint_name) as sp: + + t1 = sp.get_tally(name="scatter").mean.reshape(4, 4) + t2 = sp.get_tally(name="scatter-mesh").mean.reshape(4) + t3 = sp.get_tally(name="scatter-meshborn").mean.reshape(4) + t4 = sp.get_tally(name="scatter-total").mean.reshape(1) + + # Consistency between mesh+meshborn matrix tally and meshborn tally + for i in range(4): + assert_allclose(t1[:, i].sum(), t3[i], rtol=RTOL, atol=ATOL) + + # Consistency between mesh+meshborn matrix tally and mesh tally + for i in range(4): + assert_allclose(t1[i, :].sum(), t2[i], rtol=RTOL, atol=ATOL) + + # Mesh cells in x<0 do not contribute to meshborn + assert_allclose(t1[:, 0].sum(), np.zeros(4), rtol=RTOL, atol=ATOL) + assert_allclose(t1[:, 2].sum(), np.zeros(4), rtol=RTOL, atol=ATOL) + + # Consistency with total scattering + assert_allclose(t1.sum(), t4, rtol=RTOL, atol=ATOL) + assert_allclose(t2.sum(), t4, rtol=RTOL, atol=ATOL) + assert_allclose(t3.sum(), t4, rtol=RTOL, atol=ATOL) + + super()._compare_results() + + +def test_filter_meshborn(model): + harness = MeshBornFilterTest("statepoint.8.h5", model) + harness.main() diff --git a/tests/unit_tests/test_filter_meshborn.py b/tests/unit_tests/test_filter_meshborn.py new file mode 100644 index 000000000..62fa1174e --- /dev/null +++ b/tests/unit_tests/test_filter_meshborn.py @@ -0,0 +1,116 @@ +"""Test the meshborn filter using a fixed source calculation on a H1 sphere. + +""" + +import numpy as np +from uncertainties import unumpy +import openmc +import pytest + + +@pytest.fixture +def model(): + """Sphere of H1 with one hemisphere containing the source (x>0) and one + hemisphere with no source (x<0). + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # Materials + h1 = openmc.Material() + h1.add_nuclide("H1", 1.0) + h1.set_density("g/cm3", 1.0) + model.materials = openmc.Materials([h1]) + + # Core geometry + r = 10.0 + sphere = openmc.Sphere(r=r, boundary_type="reflective") + core = openmc.Cell(fill=h1, region=-sphere) + model.geometry = openmc.Geometry([core]) + + # Settings + model.settings.run_mode = 'fixed source' + model.settings.particles = 2000 + model.settings.batches = 8 + + distribution = openmc.stats.Box((0., -r, -r), (r, r, r)) + model.settings.source = openmc.IndependentSource(space=distribution) + + # ============================================================================= + # Tallies + # ============================================================================= + + mesh = openmc.RegularMesh() + mesh.dimension = (2, 2, 1) + mesh.lower_left = (-r, -r, -r) + mesh.upper_right = (r, r, r) + + f = openmc.MeshBornFilter(mesh) + t_1 = openmc.Tally(name="scatter-collision") + t_1.filters = [f] + t_1.scores = ["scatter"] + t_1.estimator = "collision" + + t_2 = openmc.Tally(name="scatter-tracklength") + t_2.filters = [f] + t_2.scores = ["scatter"] + t_2.estimator = "tracklength" + + model.tallies = [t_1, t_2] + + return model + + +def test_estimator_consistency(model, run_in_tmpdir): + """Test that resuts obtained from a tracklength estimator are + consistent with results obtained from a collision estimator. + + """ + # Run OpenMC + sp_filename = model.run() + + # Get radial flux distribution + with openmc.StatePoint(sp_filename) as sp: + scatter_collision = sp.get_tally(name="scatter-collision").mean.ravel() + scatter_collision_std_dev = sp.get_tally(name="scatter-collision").std_dev.ravel() + scatter_tracklength = sp.get_tally(name="scatter-tracklength").mean.ravel() + scatter_tracklength_std_dev = sp.get_tally(name="scatter-tracklength").std_dev.ravel() + + collision = unumpy.uarray(scatter_collision, scatter_collision_std_dev) + tracklength = unumpy.uarray(scatter_tracklength, scatter_tracklength_std_dev) + delta = abs(collision - tracklength) + + diff = unumpy.nominal_values(delta) + std_dev = unumpy.std_devs(delta) + assert np.all(diff <= 3 * std_dev) + + +def test_xml_serialization(): + """Test xml serialization of the meshborn filter.""" + openmc.reset_auto_ids() + + mesh = openmc.RegularMesh() + mesh.dimension = (1, 1, 1) + mesh.lower_left = (0.0, 0.0, 0.0) + mesh.upper_right = (1.0, 1.0, 1.0) + + filter = openmc.MeshBornFilter(mesh) + filter.translation = (2.0, 2.0, 2.0) + assert filter.mesh.id == 1 + assert filter.mesh.dimension == (1, 1, 1) + assert filter.mesh.lower_left == (0.0, 0.0, 0.0) + assert filter.mesh.upper_right == (1.0, 1.0, 1.0) + + repr(filter) + + elem = filter.to_xml_element() + assert elem.tag == 'filter' + assert elem.attrib['type'] == 'meshborn' + assert elem[0].text == "1" + assert elem.get("translation") == "2.0 2.0 2.0" + + meshes = {1: mesh} + new_filter = openmc.Filter.from_xml_element(elem, meshes=meshes) + assert new_filter.bins == filter.bins + np.testing.assert_equal(new_filter.translation, [2.0, 2.0, 2.0]) diff --git a/tests/unit_tests/test_tallies.py b/tests/unit_tests/test_tallies.py index 14319a0a2..544443312 100644 --- a/tests/unit_tests/test_tallies.py +++ b/tests/unit_tests/test_tallies.py @@ -10,8 +10,9 @@ def test_xml_roundtrip(run_in_tmpdir): mesh.upper_right = (10., 10., 10.,) mesh.dimension = (5, 5, 5) mesh_filter = openmc.MeshFilter(mesh) + meshborn_filter = openmc.MeshBornFilter(mesh) tally = openmc.Tally() - tally.filters = [mesh_filter] + tally.filters = [mesh_filter, meshborn_filter] tally.nuclides = ['U235', 'I135', 'Li6'] tally.scores = ['total', 'fission', 'heating'] tally.derivative = openmc.TallyDerivative( @@ -27,9 +28,11 @@ def test_xml_roundtrip(run_in_tmpdir): assert len(new_tallies) == 1 new_tally = new_tallies[0] assert new_tally.id == tally.id - assert len(new_tally.filters) == 1 + assert len(new_tally.filters) == 2 assert isinstance(new_tally.filters[0], openmc.MeshFilter) assert np.allclose(new_tally.filters[0].mesh.lower_left, mesh.lower_left) + assert isinstance(new_tally.filters[1], openmc.MeshBornFilter) + assert np.allclose(new_tally.filters[1].mesh.lower_left, mesh.lower_left) assert new_tally.nuclides == tally.nuclides assert new_tally.scores == tally.scores assert new_tally.derivative.variable == tally.derivative.variable