Skip to content

Commit 9b5678b

Browse files
jtrammpaulromano
andauthored
Random Ray Source Region Mesh Subdivision (Cell-Under-Voxel Geometry) (#3333)
Co-authored-by: Paul Romano <[email protected]>
1 parent e8c9134 commit 9b5678b

35 files changed

+16637
-459
lines changed
107 KB
Loading

docs/source/io_formats/settings.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,24 @@ found in the :ref:`random ray user guide <random_ray>`.
470470

471471
*Default*: prng
472472

473+
:source_region_meshes:
474+
Relates meshes to spatial domains for subdividing source regions with each domain.
475+
476+
:mesh:
477+
Contains an ``id`` attribute and one or more ``<domain>`` sub-elements.
478+
479+
:id:
480+
The unique identifier for the mesh.
481+
482+
:domain:
483+
Each domain element has an ``id`` attribute and a ``type`` attribute.
484+
485+
:id:
486+
The unique identifier for the domain.
487+
488+
:type:
489+
The type of the domain. Can be ``material``, ``cell``, or ``universe``.
490+
473491
----------------------------------
474492
``<resonance_scattering>`` Element
475493
----------------------------------

docs/source/usersguide/random_ray.rst

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -319,20 +319,24 @@ Default behavior using OpenMC's native PRNG can be manually specified as::
319319

320320
.. _subdivision_fsr:
321321

322-
----------------------------------
323-
Subdivision of Flat Source Regions
324-
----------------------------------
325-
326-
While the scattering and fission sources in Monte Carlo
327-
are treated continuously, they are assumed to be invariant (flat) within a
328-
MOC or random ray flat source region (FSR). This introduces bias into the
329-
simulation, which can be remedied by reducing the physical size of the FSR
330-
to dimensions below that of typical mean free paths of particles.
322+
-----------------------------
323+
Subdivision of Source Regions
324+
-----------------------------
331325

332-
In OpenMC, this subdivision currently must be done manually. The level of
326+
While the scattering and fission sources in Monte Carlo are treated
327+
continuously, they are assumed to have a shape (flat or linear) within a MOC or
328+
random ray source region (SR). This introduces bias into the simulation that can
329+
be remedied by reducing the physical size of the SR to be smaller than the
330+
typical mean free paths of particles. While use of linear sources in OpenMC
331+
greatly reduces the error stemming from this approximation, subdivision is still
332+
typically required.
333+
334+
In OpenMC, this subdivision can be done either manually by the user (by defining
335+
additional surfaces and cells in the geometry) or automatically by assigning a
336+
mesh to one or more cells, universes, or material types. The level of
333337
subdivision needed will be dependent on the fidelity the user requires. For
334-
typical light water reactor analysis, consider the following example subdivision
335-
of a two-dimensional 2x2 reflective pincell lattice:
338+
typical light water reactor analysis, consider the following example of manual
339+
subdivision of a two-dimensional 2x2 reflective pincell lattice:
336340

337341
.. figure:: ../_images/2x2_materials.jpeg
338342
:class: with-border
@@ -344,9 +348,79 @@ of a two-dimensional 2x2 reflective pincell lattice:
344348
:class: with-border
345349
:width: 400
346350

347-
FSR decomposition for an asymmetrical 2x2 lattice (1.26 cm pitch)
351+
Manual decomposition for an asymmetrical 2x2 lattice (1.26 cm pitch)
352+
353+
Geometry cells can also be subdivided into small source regions by assigning a
354+
mesh to a list of domains, with each domain being of type
355+
:class:`openmc.Material`, :class:`openmc.Cell`, or :class:`openmc.Universe`. The
356+
idea of defining a source region as a combination of a base geometry cell and a
357+
mesh element is known as "cell-under-voxel" style geometry, although in OpenMC
358+
the mesh can be any kind and is not restricted to 3D regular voxels. An example
359+
of overlaying a simple 2D mesh over a geometry is given as::
360+
361+
sr_mesh = openmc.RegularMesh()
362+
sr_mesh.dimension = (n, n)
363+
sr_mesh.lower_left = (0.0, 0.0)
364+
sr_mesh.upper_right = (x, y)
365+
domain = geometry.root_universe
366+
settings.random_ray['source_region_meshes'] = [(sr_mesh, [domain])]
367+
368+
In the above example, we apply a single :math:`n \times n` uniform mesh over the
369+
entire domain by assigning it to the root universe of the geometry.
370+
Alternatively, we might want to apply a finer or coarser mesh to different
371+
regions of a 3D problem, for instance, as::
372+
373+
fuel = openmc.Material(name='UO2 fuel')
374+
...
375+
water = openmc.Material(name='hot borated water')
376+
...
377+
clad = openmc.Material(name='Zr cladding')
378+
...
379+
380+
coarse_mesh = openmc.RegularMesh()
381+
coarse_mesh.dimension = (n, n, n)
382+
coarse_mesh.lower_left = (0.0, 0.0, 0.0)
383+
coarse_mesh.upper_right = (x, y, z)
384+
385+
fine_mesh = openmc.RegularMesh()
386+
fine_mesh.dimension = (2*n, 2*n, 2*n)
387+
fine_mesh.lower_left = (0.0, 0.0, 0.0)
388+
fine_mesh.upper_right = (x, y, z)
389+
390+
settings.random_ray['source_region_meshes'] = [(fine_mesh, [fuel, clad]), (coarse_mesh, [water])]
391+
392+
Note that we don't need to adjust the outer bounds of the mesh to tightly wrap
393+
the domain we assign the mesh to. Rather, OpenMC will dynamically generate
394+
source regions based on the mesh bins rays actually visit, such that no
395+
additional memory is wasted even if a domain only intersects a few mesh bins.
396+
Going back to our 2x2 lattice example, if using a mesh-based subdivision, this
397+
might look as below:
398+
399+
.. figure:: ../_images/2x2_sr_mesh.png
400+
:class: with-border
401+
:width: 400
348402

349-
In the future, automated subdivision of FSRs via mesh overlay may be supported.
403+
20x20 overlaid "cell-under-voxel" mesh decomposition for an asymmetrical 2x2 lattice (1.26 cm pitch)
404+
405+
Note that mesh-bashed subdivision is much easier for a user to implement but
406+
does have a few downsides compared to manual subdivision. Manual subdivision can
407+
be done with the specifics of the geometry in mind. As in the pincell example,
408+
it is more efficient to subdivide the fuel region into azimuthal sectors and
409+
radial rings as opposed to a Cartesian mesh. This is more efficient because the
410+
regions are a more uniform size and follow the material boundaries closer,
411+
resulting in the need for fewer source regions. Fewer source regions tends to
412+
equate to a faster computational speed and/or the need for fewer rays per batch
413+
to achieve good statistics. Additionally, applying a mesh often tends to create
414+
a few very small source regions, as shown in the above picture where corners of
415+
the mesh happen to intersect close to the actual fuel-moderator interface. These
416+
small regions are rarely visited by rays, which can result in inaccurate
417+
estimates of the source within those small regions and, thereby, numerical
418+
instability. However, OpenMC utilizes several techniques to detect these small
419+
source regions and mitigate instabilities that are associated with them. In
420+
conclusion, mesh overlay is a great way to subdivide any geometry into smaller
421+
source regions. It can be used while retaining stability, though typically at
422+
the cost of generating more source regions relative to an optimal manual
423+
subdivision.
350424

351425
.. _usersguide_flux_norm:
352426

docs/source/usersguide/variance_reduction.rst

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,15 @@ random ray mode can be found in the :ref:`Random Ray User Guide <random_ray>`.
130130

131131
.. warning::
132132
If using FW-CADIS weight window generation, ensure that the selected weight
133-
window mesh does not subdivide any cells in the problem. In the future, this
134-
restriction is intended to be relaxed, but for now subdivision of cells by a
135-
mesh tally will result in undefined behavior.
133+
window mesh does not subdivide any source regions in the problem. This can
134+
be ensured by assigning the weight window tally mesh to the root universe so
135+
as to create source region boundaries that conform to the mesh, as in the
136+
example below.
137+
138+
::
139+
140+
root = model.geometry.root_universe
141+
settings.random_ray['source_region_meshes'] = [(ww_mesh, [root])]
136142

137143
6. When running your multigroup random ray input deck, OpenMC will automatically
138144
run a forward solve followed by an adjoint solve, with a

include/openmc/constants.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ constexpr double RADIAL_MESH_TOL {1e-10};
5959
// Maximum number of random samples per history
6060
constexpr int MAX_SAMPLE {100000};
6161

62+
// Avg. number of hits per batch to be defined as a "small"
63+
// source region in the random ray solver
64+
constexpr double MIN_HITS_PER_BATCH {1.5};
65+
6266
// ============================================================================
6367
// MATH AND PHYSICAL CONSTANTS
6468

include/openmc/random_ray/flat_source_domain.h

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
#include "openmc/constants.h"
55
#include "openmc/openmp_interface.h"
66
#include "openmc/position.h"
7+
#include "openmc/random_ray/parallel_map.h"
78
#include "openmc/random_ray/source_region.h"
89
#include "openmc/source.h"
10+
#include <unordered_map>
911
#include <unordered_set>
1012

1113
namespace openmc {
@@ -37,7 +39,6 @@ class FlatSourceDomain {
3739
void random_ray_tally();
3840
virtual void accumulate_iteration_flux();
3941
void output_to_vtk() const;
40-
void all_reduce_replicated_source_regions();
4142
void convert_external_sources();
4243
void count_external_source_regions();
4344
void set_adjoint_sources(const vector<double>& forward_flux);
@@ -47,12 +48,34 @@ class FlatSourceDomain {
4748
void flatten_xs();
4849
void transpose_scattering_matrix();
4950
void serialize_final_fluxes(vector<double>& flux);
51+
void apply_meshes();
52+
void apply_mesh_to_cell_instances(int32_t i_cell, int32_t mesh_idx,
53+
int target_material_id, const vector<int32_t>& instances,
54+
bool is_target_void);
55+
void apply_mesh_to_cell_and_children(int32_t i_cell, int32_t mesh_idx,
56+
int32_t target_material_id, bool is_target_void);
57+
void prepare_base_source_regions();
58+
SourceRegionHandle get_subdivided_source_region_handle(
59+
int64_t sr, int mesh_bin, Position r, double dist, Direction u);
60+
void finalize_discovered_source_regions();
61+
int64_t n_source_regions() const
62+
{
63+
return source_regions_.n_source_regions();
64+
}
65+
int64_t n_source_elements() const
66+
{
67+
return source_regions_.n_source_regions() * negroups_;
68+
}
5069

5170
//----------------------------------------------------------------------------
5271
// Static Data members
5372
static bool volume_normalized_flux_tallies_;
5473
static bool adjoint_; // If the user wants outputs based on the adjoint flux
5574

75+
// Static variables to store source region meshes and domains
76+
static std::unordered_map<int, vector<std::pair<Source::DomainType, int>>>
77+
mesh_domain_map_;
78+
5679
//----------------------------------------------------------------------------
5780
// Static data members
5881
static RandomRayVolumeEstimator volume_estimator_;
@@ -61,7 +84,6 @@ class FlatSourceDomain {
6184
// Public Data members
6285
bool mapped_all_tallies_ {false}; // If all source regions have been visited
6386

64-
int64_t n_source_regions_ {0}; // Total number of source regions in the model
6587
int64_t n_external_source_regions_ {0}; // Total number of source regions with
6688
// non-zero external source terms
6789

@@ -84,6 +106,27 @@ class FlatSourceDomain {
84106
// The abstract container holding all source region-specific data
85107
SourceRegionContainer source_regions_;
86108

109+
// Base source region container. When source region subdivision via mesh
110+
// is in use, this container holds the original (non-subdivided) material
111+
// filled cell instance source regions. These are useful as they can be
112+
// initialized with external source and mesh domain information ahead of time.
113+
// Then, dynamically discovered source regions can be initialized by cloning
114+
// their base region.
115+
SourceRegionContainer base_source_regions_;
116+
117+
// Parallel hash map holding all source regions discovered during
118+
// a single iteration. This is a threadsafe data structure that is cleaned
119+
// out after each iteration and stored in the "source_regions_" container.
120+
// It is keyed with a SourceRegionKey, which combines the base source
121+
// region index and the mesh bin.
122+
ParallelMap<SourceRegionKey, SourceRegion, SourceRegionKey::HashFunctor>
123+
discovered_source_regions_;
124+
125+
// Map that relates a SourceRegionKey to the index at which the source
126+
// region can be found in the "source_regions_" container.
127+
std::unordered_map<SourceRegionKey, int64_t, SourceRegionKey::HashFunctor>
128+
source_region_map_;
129+
87130
protected:
88131
//----------------------------------------------------------------------------
89132
// Methods
@@ -100,9 +143,7 @@ class FlatSourceDomain {
100143

101144
//----------------------------------------------------------------------------
102145
// Private data members
103-
int negroups_; // Number of energy groups in simulation
104-
int64_t n_source_elements_ {0}; // Total number of source regions in the model
105-
// times the number of energy groups
146+
int negroups_; // Number of energy groups in simulation
106147

107148
double
108149
simulation_volume_; // Total physical volume of the simulation domain, as

0 commit comments

Comments
 (0)