diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst
index 7091757f23b..dfab96131df 100644
--- a/docs/source/io_formats/settings.rst
+++ b/docs/source/io_formats/settings.rst
@@ -626,7 +626,11 @@ found in the :ref:`random ray user guide `.
:volume_estimator:
Specifies choice of volume estimator for the random ray solver. Options
- are 'naive', 'simulation_averaged', or 'hybrid'. The default is 'hybrid'.
+ are 'naive', 'simulation_averaged', 'hybrid', 'adaptive',
+ 'strict_adaptive', or 'auto'. The default is 'auto', which selects
+ 'adaptive' for standard solves and 'strict_adaptive' for solves whose
+ results feed variance reduction (weight window generation and adjoint
+ workflows).
*Default*: None
@@ -1648,6 +1652,7 @@ and 10. The verbosity levels are defined as follows:
:5: all of the above + file I/O
:6: all of the above + timing statistics and initialization messages
:7: all of the above + :math:`k` by generation
+ :8: all of the above + random ray volume-estimator diagnostics
:9: all of the above + indicate when each particle starts
:10: all of the above + event information
diff --git a/docs/source/methods/random_ray.rst b/docs/source/methods/random_ray.rst
index 8bc2a0a1bf5..537930096d8 100644
--- a/docs/source/methods/random_ray.rst
+++ b/docs/source/methods/random_ray.rst
@@ -511,18 +511,53 @@ when using the naive estimator, though at the cost of a notable increase in
variance. Empirical testing reveals that on most eigenvalue problems, the
simulation averaged estimator does win out overall in numerical performance, as
a much coarser quadrature can be used resulting in faster runtimes overall.
-Thus, OpenMC uses the simulation averaged estimator as default in its random ray
-mode for eigenvalue solves.
+Thus, the simulation averaged estimator is generally preferred over the naive
+estimator for eigenvalue solves.
OpenMC also features a "hybrid" volume estimator that uses the naive estimator
for all regions containing an external (fixed) source term. For all other
source regions, the "simulation averaged" estimator is used. This typically achieves
-a best of both worlds result, with the benefits of the low bias simulation averaged
+a best of both worlds result, with the benefits of the unbiased simulation averaged
estimator in most regions, while preventing instability and/or large biases in regions
-with external source terms via use of the naive estimator. In general, it is
-recommended to use the "hybrid" estimator, which is the default method used
-in OpenMC. If instability is encountered despite high ray densities, then
-the naive estimator may be preferable.
+with external source terms via use of the naive estimator. If instability is
+encountered despite high ray densities, then the naive estimator may be
+preferable.
+
+OpenMC also features an "adaptive" volume estimator that generalizes the
+hybrid estimator. It uses the simulation averaged estimator by default and
+automatically demotes individual cells to the naive treatment (the naive
+volume and the previous-flux miss treatment) when they show signs of
+instability. The most important case this adds over the hybrid estimator is
+a cell fed almost entirely by in-scatter from other groups, as in the
+optically thin air regions common to shielding problems, where the reduced
+source dwarfs the flux even though no external source is present.
+
+A cell is demoted when it is hit-starved or when its reduced source is
+negative. During the inactive batches, a cell whose reduced source is much
+larger than its scalar flux is also demoted. Finally, beginning at the end
+of the inactive batches and re-evaluated throughout the active phase, a
+cell is demoted permanently if its flux accumulated over the simulation is
+negative or is dominated by sources that do not derive from its own flux.
+Because these last decisions are made from accumulated statistics and are
+never reversed, the estimator choice does not churn with iteration noise
+during the tallied batches. When a linear source shape is in use, demoted
+cells also revert to a flat source representation.
+
+A "strict adaptive" variant is provided for solves whose results feed
+variance reduction, where even a small number of slightly negative flux
+estimates can degrade the adjoint solve and the quality of generated weight
+windows. It runs the same machinery and additionally repairs any negative
+flux estimate each batch, first by recomputing it with the batch's own
+volume and then, if it is still negative, by falling back on the previous
+iterate. A cell that needs the repair repeatedly is demoted outright, which
+keeps the one-sided repair from biasing its flux upward. The repair
+introduces a small conservative bias overall (several hundred pcm on
+typical eigenvalue problems), so the strict variant is not used for
+standard solves.
+
+By default, OpenMC selects the volume estimator automatically ("auto").
+Weight window generation and adjoint solves receive the strict adaptive
+estimator, and all other solves receive the adaptive estimator.
A table that summarizes the pros and cons, as well as recommendations for
different use cases, is given in the :ref:`volume
diff --git a/docs/source/usersguide/random_ray.rst b/docs/source/usersguide/random_ray.rst
index 81db61ae062..88b5c8fab1c 100644
--- a/docs/source/usersguide/random_ray.rst
+++ b/docs/source/usersguide/random_ray.rst
@@ -1080,6 +1080,12 @@ following methods are currently available in OpenMC:
- Description
- Pros
- Cons
+ * - ``auto`` (default)
+ - Automatically selects an appropriate estimator for the type of
+ simulation being performed. Most users do not need to consider this
+ setting further.
+ - * No user input needed
+ - * N/A
* - ``simulation_averaged``
- Accumulates total active ray lengths in each FSR over all iterations,
improving the estimate of the volume in each cell each iteration.
@@ -1099,15 +1105,44 @@ following methods are currently available in OpenMC:
unstable
- * Biased estimator
* Requires more rays or longer active ray length to mitigate bias
- * - ``hybrid`` (default)
+ * - ``hybrid``
- Applies the naive estimator to all cells that contain an external (fixed)
source contribution. Applies the simulation averaged estimator to all
other cells.
- - * High accuracy/low bias of the simulation averaged estimator in most
+ - * Accuracy of the unbiased simulation averaged estimator in most
cells
* Stability of the naive estimator in cells with fixed sources
- * Can lead to slightly negative fluxes in cells where the simulation
averaged estimator is used
+ * - ``adaptive``
+ - Generalizes the hybrid estimator. Uses the simulation averaged
+ estimator by default, but automatically (and permanently) demotes
+ individual cells to the naive treatment when their accumulated
+ statistics indicate the simulation averaged estimator is unstable
+ there (e.g., cells dominated by external or in-scatter sources, and
+ hit-starved cells).
+ - * Accuracy of the simulation averaged estimator in most cells
+ * Stable in cases where the simulation averaged and hybrid estimators
+ are not
+ * No parameters to tune
+ - * Benefits from a longer inactive phase to inform the demotion
+ decisions
+ * - ``strict_adaptive``
+ - As ``adaptive``, but additionally applies a per-batch fixup to any
+ negative flux estimate (recomputing it with the batch's own volume,
+ then falling back on the previous iterate) and demotes chronically
+ affected cells to the naive treatment.
+ - * Suppresses the negative flux estimates other estimators can produce
+ in pathological cases
+ * Improves the quality of generated weight windows
+ - * The one-sided fixup introduces a small conservative bias, so it is
+ not recommended where unbiased results are the priority
+
+By default, the ``volume_estimator`` field is set to ``auto``, which selects
+``strict_adaptive`` for solves whose results feed variance reduction (weight
+window generation and adjoint workflows) and ``adaptive`` for all other
+solves. The end-of-simulation output reports which estimator was selected,
+and explicitly setting any other value overrides the automatic selection.
These estimators can be selected by setting the ``volume_estimator`` field in the
:attr:`openmc.Settings.random_ray` dictionary. For example, to use the naive
@@ -1117,6 +1152,22 @@ estimator, the following code would be used:
settings.random_ray['volume_estimator'] = 'naive'
+The ``auto`` setting is the default, as it gives reliable behavior out of
+the box across problem types. The adaptive estimator is especially valuable
+for fixed source and shielding problems, where optically thin, scattering-
+or streaming-dominated regions (for example, the air- or void-filled regions
+of a shielding model) can destabilize the ``hybrid`` and
+``simulation_averaged`` estimators. It detects and stabilizes the affected
+cells automatically while leaving the rest of the problem on the unbiased
+simulation averaged estimator. Because demotions are decided from each
+cell's accumulated statistics rather than from single-iteration values, the
+estimator choice does not churn with iteration noise and avoids the bias
+that per-iteration selection can introduce. Solves that feed variance
+reduction are routed to ``strict_adaptive`` instead, as even a small number
+of slightly negative flux estimates in the near-void regions of pathological
+problems can otherwise degrade the adjoint solve and the quality of
+generated weight windows.
+
-----------------
Adjoint Flux Mode
-----------------
diff --git a/include/openmc/constants.h b/include/openmc/constants.h
index a1d94e5819e..b1573446156 100644
--- a/include/openmc/constants.h
+++ b/include/openmc/constants.h
@@ -61,20 +61,6 @@ constexpr double RADIAL_MESH_TOL {1e-10};
// Maximum number of random samples per history
constexpr int MAX_SAMPLE {100000};
-// Avg. number of hits per batch to be defined as a "small"
-// source region in the random ray solver
-constexpr double MIN_HITS_PER_BATCH {1.5};
-
-// The minimum flux value to be considered non-zero when computing adjoint
-// sources. Positive values below this cutoff will be treated as zero, so as to
-// prevent extremely large adjoint source terms from being generated.
-constexpr double ZERO_FLUX_CUTOFF {1e-22};
-
-// The minimum macroscopic cross section value considered non-void for the
-// random ray solver. Materials with any group with a cross section below this
-// value will be converted to pure void.
-constexpr double MINIMUM_MACRO_XS {1e-6};
-
// Relative dead band applied to weight window comparisons: particles split
// only above upper * (1 + tol) and roulette only below lower * (1 - tol).
// Weight window arithmetic can land a particle's weight exactly back on a
@@ -378,7 +364,14 @@ enum class RunMode {
enum class SolverType { MONTE_CARLO, RANDOM_RAY };
-enum class RandomRayVolumeEstimator { NAIVE, SIMULATION_AVERAGED, HYBRID };
+enum class RandomRayVolumeEstimator {
+ NAIVE,
+ SIMULATION_AVERAGED,
+ HYBRID,
+ ADAPTIVE,
+ STRICT_ADAPTIVE,
+ AUTO
+};
enum class RandomRaySourceShape { FLAT, LINEAR, LINEAR_XY };
enum class RandomRaySampleMethod { PRNG, HALTON, S2 };
enum class RandomRaySolve { FORWARD, FORWARD_FOR_ADJOINT, ADJOINT };
diff --git a/include/openmc/random_ray/flat_source_domain.h b/include/openmc/random_ray/flat_source_domain.h
index 09414fd4465..c87ff48f301 100644
--- a/include/openmc/random_ray/flat_source_domain.h
+++ b/include/openmc/random_ray/flat_source_domain.h
@@ -12,6 +12,50 @@
namespace openmc {
+// Avg. number of hits per batch to be defined as a "small" source region.
+constexpr double MIN_HITS_PER_BATCH {1.5};
+
+// Strong-source ratio threshold for the adaptive volume estimator. A source
+// region is treated as having a "strong" inhomogeneous source in any group
+// where the reduced source q/Sigma_t exceeds this multiple of the region's
+// scalar flux, indicating a source sustained by an external or in-scatter
+// contribution rather than by the local flux. Such regions are given the
+// naive volume and previous-flux miss treatment. The value sits well inside
+// the range over which benign problems remain untriggered while pathological
+// cells are still caught.
+constexpr double ADAPTIVE_VOLUME_KAPPA {4.0};
+
+// Chronic-negativity demotion thresholds for the strict adaptive volume
+// estimator. A region whose flux has gone negative (before the fixup) in at
+// least max(MIN_COUNT, RATE * current_batch) batches is demoted to the naive
+// volume and previous-flux miss treatment. Without this channel the
+// non-negativity floor would mask the accumulated-flux sign signal that the
+// adaptive demotion relies on, leaving noisy regions to be clipped every
+// batch and biasing their fluxes upward. Demotion instead moves such regions
+// onto an estimator that does not need clipping.
+constexpr int NEGATIVE_FLUX_DEMOTION_MIN_COUNT {3};
+constexpr double NEGATIVE_FLUX_DEMOTION_RATE {0.005};
+
+// The minimum flux value to be considered non-zero when computing adjoint
+// sources. Positive values below this cutoff will be treated as zero, so as to
+// prevent extremely large adjoint source terms from being generated.
+constexpr double ZERO_FLUX_CUTOFF {1e-22};
+
+// The minimum macroscopic cross section value considered non-void for the
+// random ray solver. Materials with any group with a cross section below this
+// value will be converted to pure void.
+constexpr double MINIMUM_MACRO_XS {1e-6};
+
+// True for the members of the adaptive volume estimator family: the
+// adaptive estimator, and the strict adaptive estimator, which runs the
+// same machinery plus a per-batch non-negativity enforcement on the flux
+// iterates.
+inline bool is_adaptive_family(RandomRayVolumeEstimator e)
+{
+ return e == RandomRayVolumeEstimator::ADAPTIVE ||
+ e == RandomRayVolumeEstimator::STRICT_ADAPTIVE;
+}
+
/*
* The FlatSourceDomain class encompasses data and methods for storing
* scalar flux and source region for all flat source regions in a
@@ -39,6 +83,7 @@ class FlatSourceDomain {
void reset_tally_volumes();
void random_ray_tally();
virtual void accumulate_iteration_flux();
+ void demotion_step();
void output_to_vtk() const;
void convert_external_sources(bool use_adjoint_sources);
void count_external_source_regions();
@@ -59,7 +104,6 @@ class FlatSourceDomain {
SourceRegionHandle get_subdivided_source_region_handle(
SourceRegionKey sr_key, Position r, Direction u);
void finalize_discovered_source_regions();
- void apply_transport_stabilization();
int64_t n_source_regions() const
{
return source_regions_.n_source_regions();
@@ -93,7 +137,14 @@ class FlatSourceDomain {
//----------------------------------------------------------------------------
// Static data members
+ // The volume estimator as configured ("auto" by default). This is set when
+ // the settings are read and is never modified by the solver.
static RandomRayVolumeEstimator volume_estimator_;
+ // The concrete estimator the solver runs with, assigned at the start of
+ // every random ray solve. It holds the configured value, or for "auto" the
+ // estimator selected for the type of simulation being performed. All
+ // solver code reads this member rather than volume_estimator_.
+ static RandomRayVolumeEstimator resolved_volume_estimator_;
//----------------------------------------------------------------------------
// Public Data members
@@ -103,6 +154,27 @@ class FlatSourceDomain {
int64_t n_external_source_regions_ {0}; // Total number of source regions with
// non-zero external source terms
+ // Final-batch snapshot of the naive volume treatment, partitioned by
+ // mutually exclusive cause (the cause counts sum to n_final_naive_), for
+ // end-of-simulation reporting. The two demote-only decisions made from the
+ // running accumulated flux (a strong accumulated feed and a negative
+ // accumulated flux) are counted with first priority, so their counts equal
+ // the decisions settled by the final batch. The per-batch strong-source
+ // test and hit-starved causes count the remainder.
+ int64_t n_final_naive_ {0};
+ int64_t n_final_latch_ {0}; // strong source, from the accumulated feed
+ int64_t n_final_strong_ {0}; // strong source, from the per-batch test
+ int64_t n_final_sign_ {0}; // negative accumulated flux
+ int64_t n_final_small_ {0}; // hit-starved
+ int64_t n_final_chronic_ {0}; // chronic negativity (strict adaptive)
+ // Final-batch counts of the strict adaptive estimator's non-negativity
+ // enforcement: regions whose negative batch flux was recomputed with the
+ // batch volume (rescued), and regions floored at the previous iterate
+ // after the rescue was insufficient or unavailable.
+ int64_t n_final_rescued_ {0};
+ int64_t n_final_floored_ {0};
+ bool final_stats_valid_ {false};
+
// 1D array representing source region starting offset for each OpenMC Cell
// in model::cells
vector source_region_offsets_;
@@ -170,6 +242,20 @@ class FlatSourceDomain {
virtual void set_flux_to_flux_plus_source(int64_t sr, double volume, int g);
void set_flux_to_source(int64_t sr, int g);
virtual void set_flux_to_old_flux(int64_t sr, int g);
+ double flux_additive_term(int64_t sr, int g) const;
+ double stabilized_flux(int64_t sr, int g, double phi_new) const;
+
+ //! Adaptive-estimator "strong source" test. Returns true if, in any group,
+ //! the region's reduced source q/Sigma_t is negative (with a non-negative
+ //! previous-iteration flux) or exceeds ADAPTIVE_VOLUME_KAPPA times the
+ //! (non-negative) previous-iteration scalar flux. The ratio condition is
+ //! only checked when include_ratio is set, which callers do only during
+ //! the inactive batches. The test is shared by the flat volume switch
+ //! (add_source_to_scalar_flux) and the linear gradient fallback
+ //! (update_single_neutron_source), with the region's per-group
+ //! reduced-source and previous-flux arrays passed directly.
+ bool region_has_strong_source(const float* reduced_source,
+ const double* flux_old, bool include_ratio) const;
//----------------------------------------------------------------------------
// Private data members
diff --git a/include/openmc/random_ray/source_region.h b/include/openmc/random_ray/source_region.h
index 1d2bbe1e8dc..f488aa293aa 100644
--- a/include/openmc/random_ray/source_region.h
+++ b/include/openmc/random_ray/source_region.h
@@ -149,6 +149,8 @@ class SourceRegionHandle {
int* temperature_idx_;
double* density_mult_;
int* is_small_;
+ int* n_negative_batches_;
+ int* converged_negative_;
int* n_hits_;
int* birthday_;
OpenMPMutex* lock_;
@@ -204,7 +206,10 @@ class SourceRegionHandle {
const int temperature_idx() const { return *temperature_idx_; }
int& is_small() { return *is_small_; }
+ int& n_negative_batches() { return *n_negative_batches_; }
const int is_small() const { return *is_small_; }
+ int& converged_negative() { return *converged_negative_; }
+ const int converged_negative() const { return *converged_negative_; }
int& n_hits() { return *n_hits_; }
const int n_hits() const { return *n_hits_; }
@@ -337,8 +342,19 @@ class SourceRegion {
double volume_naive_ {0.0}; //!< Volume as integrated from this iteration only
int position_recorded_ {0}; //!< Has the position been recorded yet?
int external_source_present_ {
- 0}; //!< Is an external source present in this region?
- int is_small_ {0}; //!< Is it "small", receiving < 1.5 hits per iteration?
+ 0}; //!< Is an external source present in this region?
+ int is_small_ {0}; //!< Is it "small", receiving < 1.5 hits per iteration?
+ int n_negative_batches_ {
+ 0}; //!< Number of batches in which this region's flux went negative
+ //!< before the strict adaptive estimator's non-negativity
+ //!< enforcement (drives the chronic-negativity demotion)
+ int converged_negative_ {
+ 0}; //!< Demote-only flag (adaptive estimator only), evaluated from the
+ //!< running accumulated flux at the inactive->active transition and
+ //!< re-evaluated every active batch. 1 = accumulated flux negative
+ //!< in some group, 2 = strong accumulated feed (latch). Any value
+ //!< > 0 demotes the region to the naive volume estimator, and once
+ //!< set the flag is never released.
int n_hits_ {0}; //!< Number of total hits (ray crossings)
// Mesh that subdivides this source region
int mesh_ {C_NONE}; //!< Index in openmc::model::meshes array that subdivides
@@ -395,8 +411,10 @@ class SourceRegionContainer {
public:
//----------------------------------------------------------------------------
// Constructors
- SourceRegionContainer(int negroups, bool is_linear)
- : negroups_(negroups), is_linear_(is_linear)
+ SourceRegionContainer(
+ int negroups, bool is_linear, bool is_adaptive, bool is_strict_adaptive)
+ : negroups_(negroups), is_linear_(is_linear), is_adaptive_(is_adaptive),
+ is_strict_adaptive_(is_strict_adaptive)
{}
SourceRegionContainer() = default;
@@ -412,7 +430,13 @@ class SourceRegionContainer {
const double density_mult(int64_t sr) const { return density_mult_[sr]; }
int& is_small(int64_t sr) { return is_small_[sr]; }
+ int& n_negative_batches(int64_t sr) { return n_negative_batches_[sr]; }
const int is_small(int64_t sr) const { return is_small_[sr]; }
+ int& converged_negative(int64_t sr) { return converged_negative_[sr]; }
+ const int converged_negative(int64_t sr) const
+ {
+ return converged_negative_[sr];
+ }
int& n_hits(int64_t sr) { return n_hits_[sr]; }
const int n_hits(int64_t sr) const { return n_hits_[sr]; }
@@ -572,6 +596,17 @@ class SourceRegionContainer {
return scalar_flux_final_[se];
}
+ double& scalar_flux_t(int64_t sr, int g)
+ {
+ return scalar_flux_t_[index(sr, g)];
+ }
+ const double scalar_flux_t(int64_t sr, int g) const
+ {
+ return scalar_flux_t_[index(sr, g)];
+ }
+ double& scalar_flux_t(int64_t se) { return scalar_flux_t_[se]; }
+ const double scalar_flux_t(int64_t se) const { return scalar_flux_t_[se]; }
+
float& source(int64_t sr, int g) { return source_[index(sr, g)]; }
const float source(int64_t sr, int g) const { return source_[index(sr, g)]; }
float& source(int64_t se) { return source_[se]; }
@@ -639,12 +674,16 @@ class SourceRegionContainer {
int64_t n_source_regions_ {0};
int negroups_ {0};
bool is_linear_ {false};
+ bool is_adaptive_ {false};
+ bool is_strict_adaptive_ {false};
// SoA storage for scalar fields (one item per source region)
vector material_;
vector temperature_idx_;
vector density_mult_;
vector is_small_;
+ vector n_negative_batches_;
+ vector converged_negative_;
vector n_hits_;
vector mesh_;
vector parent_sr_;
@@ -671,6 +710,12 @@ class SourceRegionContainer {
vector scalar_flux_old_;
vector scalar_flux_new_;
vector scalar_flux_final_;
+ // Running sum of the scalar flux over every batch of the current solve,
+ // inactive and active. Unlike scalar_flux_final, which holds only the
+ // active-batch accumulation used for tallies, it is never reset within a
+ // solve. Allocated only for the adaptive volume estimator, which makes its
+ // demotion decisions from it.
+ vector scalar_flux_t_;
vector source_;
vector external_source_;
diff --git a/openmc/settings.py b/openmc/settings.py
index 3d75ee80dc2..78bed32b8ff 100644
--- a/openmc/settings.py
+++ b/openmc/settings.py
@@ -202,8 +202,11 @@ class Settings:
specified by a :class:`openmc.SourceBase` object.
:volume_estimator:
Choice of volume estimator for the random ray solver. Options are
- 'naive', 'simulation_averaged', or 'hybrid'.
- The default is 'hybrid'.
+ 'naive', 'simulation_averaged', 'hybrid', 'adaptive',
+ 'strict_adaptive', or 'auto'. The default is 'auto', which
+ selects 'adaptive' for standard solves and 'strict_adaptive' for
+ solves whose results feed variance reduction (weight window
+ generation and adjoint workflows).
:source_shape:
Assumed shape of the source distribution within each source region.
Options are 'flat' (default), 'linear', or 'linear_xy'.
@@ -1416,7 +1419,8 @@ def random_ray(self, random_ray: dict):
elif key == 'volume_estimator':
cv.check_value('volume estimator', value,
('naive', 'simulation_averaged',
- 'hybrid'))
+ 'hybrid', 'adaptive', 'strict_adaptive',
+ 'auto'))
elif key == 'source_shape':
cv.check_value('source shape', value,
('flat', 'linear', 'linear_xy'))
diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp
index a2434a901dc..f3567e6f9db 100644
--- a/src/random_ray/flat_source_domain.cpp
+++ b/src/random_ray/flat_source_domain.cpp
@@ -28,7 +28,9 @@ namespace openmc {
// Static Variable Declarations
RandomRayVolumeEstimator FlatSourceDomain::volume_estimator_ {
- RandomRayVolumeEstimator::HYBRID};
+ RandomRayVolumeEstimator::AUTO};
+RandomRayVolumeEstimator FlatSourceDomain::resolved_volume_estimator_ {
+ RandomRayVolumeEstimator::AUTO};
bool FlatSourceDomain::volume_normalized_flux_tallies_ {false};
bool FlatSourceDomain::adjoint_requested_ {false};
RandomRaySolve FlatSourceDomain::solve_ {RandomRaySolve::FORWARD};
@@ -56,7 +58,11 @@ FlatSourceDomain::FlatSourceDomain() : negroups_(data::mg.num_energy_groups_)
// Initialize source regions.
bool is_linear = RandomRay::source_shape_ != RandomRaySourceShape::FLAT;
- source_regions_ = SourceRegionContainer(negroups_, is_linear);
+ bool is_adaptive = is_adaptive_family(resolved_volume_estimator_);
+ bool is_strict_adaptive =
+ resolved_volume_estimator_ == RandomRayVolumeEstimator::STRICT_ADAPTIVE;
+ source_regions_ = SourceRegionContainer(
+ negroups_, is_linear, is_adaptive, is_strict_adaptive);
// Initialize tally volumes
if (volume_normalized_flux_tallies_) {
@@ -103,6 +109,148 @@ void FlatSourceDomain::accumulate_iteration_flux()
}
}
+// Demotion step for the adaptive volume estimator (no-op for the
+// others). Rather than reacting to per-iteration negatives, this estimator
+// runs the unmodified simulation-averaged update and accumulates every
+// batch's flux into a running sum (scalar_flux_t, kept separate from the
+// active-only tally accumulator), from which it makes two demotion
+// decisions:
+//
+// 1. Accumulated-negative (sign): any region whose accumulated flux is
+// negative in any group is demoted to the naive (iteration) volume
+// estimator, a positively weighted estimator that cannot go negative
+// with a non-negative source. During the active phase the test also
+// watches the active-only tally accumulation (scalar_flux_final), the
+// quantity tally means are actually computed from. Because the decision
+// is made on accumulated means rather than on individual fluctuations,
+// the lower tail of the noise distribution is not clipped, so regions
+// that are merely noisy (and average non-negative) are left unbiased.
+//
+// 2. Strong-feed latch: any region whose flux-independent feed (cross-group
+// in-scatter, fission, and external source), evaluated from the same
+// accumulated flux, exceeds ADAPTIVE_VOLUME_KAPPA times its own
+// accumulated flux in any group is likewise demoted. This is the same
+// physical condition the per-iteration strong-source test targets,
+// decided from accumulated data: the per-iteration test, evaluated on
+// noisy iterates, cannot fire in the joint excursion where a bad
+// iteration drags a region's source and flux negative together, so a
+// strong region would otherwise ride such excursions on unprotected
+// simulation-averaged updates and can accumulate a negative window
+// average. The latch removes the whole strong-feed class. A region with
+// no cross-group or external feed can never latch, so sign-locked (e.g.
+// one-group) noise cannot cause demotion through this path.
+//
+// Both conditions are first decided at the inactive->active transition and
+// then re-evaluated every active batch as the accumulation keeps growing.
+// Decisions are demote-only: a set flag is never released, so the estimator
+// choice cannot churn with active-batch noise (a release/re-demote cycle
+// conditioned on tallied iterations would bias the tallies), and a marginal
+// region whose accumulated ratio converges below the threshold is never
+// eroded into demotion by continued re-evaluation. The active-phase
+// re-evaluation exists for solves whose inactive phase is too short to
+// converge deep regions: there the transition-time decision alone misses
+// chronically unstable regions whose accumulated flux only turns negative
+// (or whose feed ratio only crosses the threshold) after active batches
+// begin, and an escapee left on unprotected simulation-averaged updates can
+// corrupt the solution far beyond its own boundary through scattering
+// feedback.
+//
+// The decisions are recorded in converged_negative (1 = sign, 2 = latch;
+// > 0 == demoted), consumed by the volume switch and miss treatment in
+// add_source_to_scalar_flux and by the linear-source gradient fallback.
+void FlatSourceDomain::demotion_step()
+{
+ if (!is_adaptive_family(resolved_volume_estimator_))
+ return;
+
+#pragma omp parallel for
+ for (int64_t se = 0; se < n_source_elements(); se++) {
+ source_regions_.scalar_flux_t(se) += source_regions_.scalar_flux_new(se);
+ }
+
+ // Decisions start on the last inactive batch and continue every active
+ // batch thereafter.
+ if (simulation::current_batch < settings::n_inactive)
+ return;
+
+#pragma omp parallel for
+ for (int64_t sr = 0; sr < n_source_regions(); sr++) {
+ // Demote-only: settled regions are never re-evaluated or released.
+ if (source_regions_.converged_negative(sr) > 0)
+ continue;
+ bool negative = false;
+ for (int g = 0; g < negroups_; g++) {
+ if (source_regions_.scalar_flux_t(sr, g) < 0.0) {
+ negative = true;
+ break;
+ }
+ }
+ // During the active phase, a negative accumulated tally flux
+ // (scalar_flux_final, the active-only sum that tally means are computed
+ // from) also demotes, as a region with a strong positive inactive
+ // accumulation can hold the running sum positive while the active-only
+ // sum that is actually reported goes negative. This branch fires only
+ // when the reported mean has already lost positivity, so it clips
+ // realized-negative outcomes rather than one tail of a healthy region's
+ // noise.
+ if (!negative && simulation::current_batch > settings::n_inactive) {
+ for (int g = 0; g < negroups_; g++) {
+ if (source_regions_.scalar_flux_final(sr, g) < 0.0) {
+ negative = true;
+ break;
+ }
+ }
+ }
+ bool latched = false;
+ int material = source_regions_.material(sr);
+ if (!negative && material != MATERIAL_VOID) {
+ int temp = source_regions_.temperature_idx(sr);
+ const int material_offset = (material * ntemperature_ + temp) * negroups_;
+ const int scatter_offset =
+ (material * ntemperature_ + temp) * negroups_ * negroups_;
+ double inverse_k_eff = 1.0 / k_eff_;
+ for (int g = 0; g < negroups_ && !latched; g++) {
+ double feed = 0.0;
+ double chi = chi_[material_offset + g];
+ for (int gp = 0; gp < negroups_; gp++) {
+ double phi = std::max(source_regions_.scalar_flux_t(sr, gp), 0.0);
+ if (gp != g) {
+ feed += sigma_s_[scatter_offset + g * negroups_ + gp] * phi;
+ }
+ if (settings::create_fission_neutrons) {
+ feed +=
+ chi * nu_sigma_f_[material_offset + gp] * phi * inverse_k_eff;
+ }
+ }
+ double sigma_t = sigma_t_[material_offset + g];
+ double q_indep = feed / sigma_t;
+ // The external source arrays are only allocated in fixed source
+ // mode, so the external term must not be read in an eigenvalue
+ // solve (where no external sources exist). The external source is a
+ // per-batch quantity while the flux is an accumulated one, so the
+ // term is scaled by the number of accumulated batches.
+ if (settings::run_mode == RunMode::FIXED_SOURCE) {
+ q_indep +=
+ simulation::current_batch * source_regions_.external_source(sr, g);
+ }
+ if (q_indep > ADAPTIVE_VOLUME_KAPPA *
+ std::max(source_regions_.scalar_flux_t(sr, g), 0.0)) {
+ latched = true;
+ }
+ }
+ }
+ if (negative) {
+ source_regions_.converged_negative(sr) = 1;
+ } else if (latched) {
+ source_regions_.converged_negative(sr) = 2;
+ }
+ }
+ // No separate decision-count bookkeeping is needed here: demote-only flags
+ // can only accumulate, so the final-batch by-cause snapshot in
+ // add_source_to_scalar_flux (which counts them with first priority)
+ // reports the settled decisions exactly.
+}
+
void FlatSourceDomain::update_single_neutron_source(SourceRegionHandle& srh)
{
// Reset all source regions to zero (important for void regions)
@@ -194,6 +342,24 @@ void FlatSourceDomain::normalize_scalar_flux_and_volumes(
}
}
+// The additive term of the flux update for a source region and group. A
+// material region adds its reduced source q/Sigma_t. A void region has no
+// such term and instead adds a bounded contribution from its external
+// source, which is nonzero only in fixed source mode. The same term is used
+// by the strict estimator's rescue, which rescales only the transport part
+// of an update, so the two cannot drift apart.
+double FlatSourceDomain::flux_additive_term(int64_t sr, int g) const
+{
+ if (source_regions_.material(sr) == MATERIAL_VOID) {
+ if (settings::run_mode == RunMode::FIXED_SOURCE) {
+ return 0.5f * source_regions_.external_source(sr, g) *
+ source_regions_.volume_sq(sr);
+ }
+ return 0.0;
+ }
+ return source_regions_.source(sr, g);
+}
+
void FlatSourceDomain::set_flux_to_flux_plus_source(
int64_t sr, double volume, int g)
{
@@ -201,18 +367,72 @@ void FlatSourceDomain::set_flux_to_flux_plus_source(
int temp = source_regions_.temperature_idx(sr);
if (material == MATERIAL_VOID) {
source_regions_.scalar_flux_new(sr, g) /= volume;
- if (settings::run_mode == RunMode::FIXED_SOURCE) {
- source_regions_.scalar_flux_new(sr, g) +=
- 0.5f * source_regions_.external_source(sr, g) *
- source_regions_.volume_sq(sr);
- }
} else {
double sigma_t =
sigma_t_[(material * ntemperature_ + temp) * negroups_ + g] *
source_regions_.density_mult(sr);
source_regions_.scalar_flux_new(sr, g) /= (sigma_t * volume);
- source_regions_.scalar_flux_new(sr, g) += source_regions_.source(sr, g);
}
+ source_regions_.scalar_flux_new(sr, g) += flux_additive_term(sr, g);
+}
+
+// Applies the "diagonal stabilization" technique developed by Gunow et al.
+// to one flux iterate:
+//
+// Geoffrey Gunow, Benoit Forget, Kord Smith, Stabilization of multi-group
+// neutron transport with transport-corrected cross-sections, Annals of Nuclear
+// Energy, Volume 126, 2019, Pages 211-219, ISSN 0306-4549,
+// https://doi.org/10.1016/j.anucene.2018.10.036.
+//
+// Returns the given iterate unchanged unless the region's within-group
+// scattering cross section for the group is negative, in which case the
+// stabilized iterate is returned. The stabilization is part of the flux
+// update rather than a separate pass, so that every candidate value the
+// update considers, including the strict estimator's rescued and floored
+// candidates, is assessed in stabilized form. With transport-corrected
+// cross sections a raw iterate can legitimately be negative and stabilize
+// to a positive value, and a positivity fixup applied to the raw value
+// would instead freeze the iteration, since the previous iterate is a
+// fixed point of the stabilization.
+double FlatSourceDomain::stabilized_flux(
+ int64_t sr, int g, double phi_new) const
+{
+ // Nothing to do if all in-group scattering cross sections are positive
+ if (!is_transport_stabilization_needed_) {
+ return phi_new;
+ }
+ int material = source_regions_.material(sr);
+ if (material == MATERIAL_VOID) {
+ return phi_new;
+ }
+ int temp = source_regions_.temperature_idx(sr);
+ double density_mult = source_regions_.density_mult(sr);
+
+ // Only apply stabilization if the diagonal (in-group) scattering XS is
+ // negative
+ double sigma_s =
+ sigma_s_[((material * ntemperature_ + temp) * negroups_ + g) * negroups_ +
+ g] *
+ density_mult;
+ if (sigma_s >= 0.0) {
+ return phi_new;
+ }
+ double sigma_t =
+ sigma_t_[(material * ntemperature_ + temp) * negroups_ + g] * density_mult;
+ double phi_old = source_regions_.scalar_flux_old(sr, g);
+
+ // Equation 18 in the above Gunow et al. 2019 paper. For a default
+ // rho of 1.0, this ensures there are no negative diagonal elements
+ // in the iteration matrix. A lesser rho could be used (or exposed
+ // as a user input parameter) to reduce the negative impact on
+ // convergence rate though would need to be experimentally tested to see
+ // if it doesn't become unstable. rho = 1.0 is good as it gives the
+ // highest assurance of stability, and the impacts on convergence rate
+ // are pretty mild.
+ double D = diagonal_stabilization_rho_ * sigma_s / sigma_t;
+
+ // Equation 16 in the above Gunow et al. 2019 paper
+ return (phi_new - D * phi_old) / (1.0 - D);
}
void FlatSourceDomain::set_flux_to_old_flux(int64_t sr, int g)
@@ -226,14 +446,67 @@ void FlatSourceDomain::set_flux_to_source(int64_t sr, int g)
source_regions_.scalar_flux_new(sr, g) = source_regions_.source(sr, g);
}
+bool FlatSourceDomain::region_has_strong_source(
+ const float* reduced_source, const double* flux_old, bool include_ratio) const
+{
+ for (int g = 0; g < negroups_; g++) {
+ double src = reduced_source[g];
+ // A negative reduced source counts as strong only when the region's own
+ // previous flux is non-negative. That is the transport-corrected (TCP0)
+ // signature, where negative within-group scattering drives the source
+ // negative independently of the flux. When the previous flux is itself
+ // negative, a negative source is just the sign-locked image of that
+ // fluctuation (exactly so in one-group problems, where q = c*phi +
+ // q_external), and reacting to it iteration-by-iteration would condition
+ // the estimator choice on the sign of the noise, which is the bias the
+ // converged-negative demotion exists to avoid. Chronically negative
+ // regions are handled by that demotion instead.
+ if (src < 0.0 && flux_old[g] >= 0.0) {
+ return true;
+ }
+ // The ratio condition is consulted only while the source is converging
+ // (include_ratio is false in the active batches): evaluated on noisy
+ // single-batch iterates, it demotes a churning population of regions
+ // whose converged ratios are below kappa, and that noise-conditioned,
+ // one-sided treatment biases the accumulated tallies. Once the
+ // transition decisions are made from the converged flux, the stable
+ // classifications (the strong-feed latch, the converged-negative sign
+ // demotion, and the hit-starved treatment) govern the tallied batches.
+ if (include_ratio &&
+ src > ADAPTIVE_VOLUME_KAPPA * std::max(flux_old[g], 0.0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
// Combine transport flux contributions and flat source contributions from the
// previous iteration to generate this iteration's estimate of scalar flux.
int64_t FlatSourceDomain::add_source_to_scalar_flux()
{
int64_t n_hits = 0;
double inverse_batch = 1.0 / simulation::current_batch;
-
-#pragma omp parallel for reduction(+ : n_hits)
+ int64_t n_naive = 0;
+ int64_t n_latch = 0;
+ int64_t n_strong = 0;
+ int64_t n_sign = 0;
+ int64_t n_small = 0;
+ bool final_iteration = (simulation::current_batch == settings::n_batches);
+ // The adaptive estimator uses the proactive strong-source (kappa) test, the
+ // demote-to-naive volume switch, and the previous-flux miss treatment, with
+ // demote-only decisions made from the running accumulated flux (recorded
+ // as a flag in converged_negative by demotion_step).
+ const bool is_adaptive = is_adaptive_family(resolved_volume_estimator_);
+ // The strict adaptive estimator additionally enforces non-negativity on
+ // the flux iterates each batch (see the enforcement step below).
+ const bool is_strict =
+ resolved_volume_estimator_ == RandomRayVolumeEstimator::STRICT_ADAPTIVE;
+ int64_t n_rescued = 0;
+ int64_t n_floored = 0;
+ int64_t n_chronic = 0;
+
+#pragma omp parallel for reduction(+ : n_hits, n_naive, n_latch, n_strong, \
+ n_sign, n_small, n_rescued, n_floored, n_chronic)
for (int64_t sr = 0; sr < n_source_regions(); sr++) {
double volume_simulation_avg = source_regions_.volume(sr);
@@ -252,54 +525,174 @@ int64_t FlatSourceDomain::add_source_to_scalar_flux()
source_regions_.is_small(sr) = 0;
}
- // The volume treatment depends on the volume estimator type
- // and whether or not an external source is present in the cell.
- double volume;
- switch (volume_estimator_) {
+ // Determine if the source region has a "strong" inhomogeneous source,
+ // defined as any group whose reduced source greatly exceeds the previous
+ // iteration's scalar flux. In that condition the cell sits far below its
+ // own infinite-medium flux (q/Sigma_t), which arises when an optically
+ // thin cell holds a source that does not derive from its own local flux
+ // (an external source, or in-scatter from other groups). The
+ // flux update in such cells is a near-cancellation of the transport term
+ // against q/Sigma_t, which is only exact when the volumes used by the two
+ // terms are consistent. These cells therefore require the naive
+ // (iteration) volume estimator and the previous-flux miss treatment to
+ // avoid error
+ // terms proportional to (q/Sigma_t) * (1 - V_iteration/V_average) that
+ // can greatly exceed the physical flux.
+ //
+ // A reduced source that is itself negative is also treated as strong. This
+ // arises under transport-corrected (e.g. TCP0) cross sections, whose
+ // within-group scattering term can be negative, driving q/Sigma_t below
+ // zero even for a non-negative flux. It can also arise transiently from a
+ // negative previous-iteration flux, which the estimator permits by design
+ // (individual iterations are never modified). The diagonal (Gunow)
+ // stabilization keeps the TCP0 iteration convergent but acts on the flux,
+ // not on the source sign, so such regions still need the consistent
+ // (naive) volume and previous-flux miss treatment to keep a negative
+ // source from depositing negative flux through the miss path. In a normal
+ // slowing-down spectrum the positive in-scatter from faster groups
+ // dominates the negative within-group term, so in practice this condition
+ // rarely fires.
+ //
+ // Only the adaptive estimator consults the strong-source flag, so the
+ // other estimators skip the test (and its end-of-run report) entirely.
+ // Void (and effectively-void, sub-MINIMUM_MACRO_XS) regions are also
+ // excluded. They carry no q/Sigma_t term, as their flux is the streaming
+ // tally plus a bounded external contribution, so the near-cancellation
+ // the test guards against cannot occur and demoting them to the naive
+ // volume would only add ratio bias. This matches the linear domain, which
+ // already gates its strong-source gradient fallback on MATERIAL_VOID.
+ bool strong_source =
+ is_adaptive && source_regions_.material(sr) != MATERIAL_VOID &&
+ region_has_strong_source(&source_regions_.source(sr, 0),
+ &source_regions_.scalar_flux_old(sr, 0),
+ simulation::current_batch <= settings::n_inactive);
+ // Per-region demotion reasons, all g-independent. The hit-starved
+ // (small) flag is re-evaluated every iteration. The strong-source flag
+ // is also re-evaluated every iteration, though in the active batches it
+ // reduces to the negative-source (TCP0) condition, as the stable
+ // accumulated-flux decisions govern there instead. converged_neg is the
+ // demote-only flag set from the running accumulated flux by
+ // demotion_step. The external-source flag drives only the hybrid policy
+ // and the default miss treatment, since the adaptive estimator catches a
+ // low-cross-section external region through the kappa strong-source test
+ // (during the inactive batches) and the strong-feed latch (thereafter),
+ // its external term being folded into q/Sigma_t.
+ bool external = source_regions_.external_source_present(sr);
+ bool small = source_regions_.is_small(sr);
+ int conv_flag = is_adaptive ? source_regions_.converged_negative(sr) : 0;
+ bool converged_neg = conv_flag > 0;
+
+ // Every estimator reduces to two g-independent per-region decisions:
+ // 1. which volume to use on a hit (the simulation-averaged volume,
+ // unless the region is demoted to the naive (iteration) volume)
+ // 2. what to substitute on a miss (the reduced source by default, or
+ // the previous iterate)
+ // The previous-flux miss treatment is needed wherever assigning the bare
+ // reduced source q/Sigma_t to a missed region would bias it, as a low
+ // cross section region would otherwise deposit its full infinite-medium
+ // flux every time it is missed. Hybrid keys this on the external-source
+ // flag. The adaptive estimator instead extends the previous-flux
+ // treatment to every region it demotes, which (through the kappa test)
+ // already covers any region whose q/Sigma_t greatly exceeds its flux,
+ // external or not. Both decisions are made once here so the per-group
+ // loop stays estimator-agnostic.
+ bool use_naive_volume = false;
+ bool use_old_flux_on_miss = external;
+ switch (resolved_volume_estimator_) {
case RandomRayVolumeEstimator::NAIVE:
- volume = volume_iteration;
+ use_naive_volume = true;
break;
case RandomRayVolumeEstimator::SIMULATION_AVERAGED:
- volume = volume_simulation_avg;
break;
case RandomRayVolumeEstimator::HYBRID:
- if (source_regions_.external_source_present(sr) ||
- source_regions_.is_small(sr)) {
- volume = volume_iteration;
- } else {
- volume = volume_simulation_avg;
- }
+ use_naive_volume = external || small;
+ break;
+ case RandomRayVolumeEstimator::ADAPTIVE:
+ case RandomRayVolumeEstimator::STRICT_ADAPTIVE:
+ use_naive_volume = small || strong_source || converged_neg;
+ use_old_flux_on_miss = use_naive_volume;
break;
default:
fatal_error("Invalid volume estimator type");
}
+ double volume = use_naive_volume ? volume_iteration : volume_simulation_avg;
+
+ // On the final iteration, classify the demoted (naive-volume) regions by
+ // cause for the end-of-simulation report. The causes are mutually
+ // exclusive and assigned in priority order, so they sum to the total.
+ // The accumulated-flux demotions are counted first, as their demote-only
+ // flags can only accumulate and so equal the decisions settled by the
+ // final batch. The per-batch causes count only the remainder.
+ if (final_iteration && is_adaptive && use_naive_volume) {
+ n_naive++;
+ if (conv_flag == 2) {
+ n_latch++;
+ } else if (conv_flag == 1) {
+ n_sign++;
+ } else if (conv_flag == 3) {
+ n_chronic++;
+ } else if (strong_source) {
+ n_strong++;
+ } else if (small) {
+ n_small++;
+ }
+ }
+ bool region_rescued = false;
+ bool region_floored = false;
for (int g = 0; g < negroups_; g++) {
- // There are three scenarios we need to consider:
if (volume_iteration > 0.0) {
- // 1. If the FSR was hit this iteration, then the new flux is equal to
- // the flat source from the previous iteration plus the contributions
- // from rays passing through the source region (computed during the
- // transport sweep)
+ // Hit this iteration: the flat source from the previous iteration plus
+ // this iteration's transport contribution, normalized by the chosen
+ // volume, then stabilized.
set_flux_to_flux_plus_source(sr, volume, g);
+ double raw = source_regions_.scalar_flux_new(sr, g);
+ double phi = stabilized_flux(sr, g, raw);
+ // The strict adaptive estimator applies a per-batch fixup to
+ // negative flux iterates, assessed on the stabilized value. First
+ // the flux is rescued by rescaling the transport term from the
+ // volume used to the batch's own volume, algebraically reproducing
+ // the naive-volume update, with the rescued candidate stabilized in
+ // turn. If it is still negative (or the region already used the
+ // batch volume), it is floored at the previous iterate, which the
+ // stabilization leaves unchanged. A value-level fixup is needed
+ // here because demotion alone cannot prevent a region from
+ // inheriting a negative excursion through in-scatter from
+ // not-yet-demoted neighbors. The price is a small conservative
+ // (one-sided clip) bias, which is why the strict estimator is not
+ // the standard-solve default. Linear-source flux moments are left
+ // untouched, as demoted and hit-starved regions already fall back to
+ // flat shapes.
+ if (is_strict && phi < 0.0) {
+ if (volume != volume_iteration) {
+ double additive = flux_additive_term(sr, g);
+ double rescued =
+ (raw - additive) * (volume / volume_iteration) + additive;
+ phi = stabilized_flux(sr, g, rescued);
+ region_rescued = true;
+ }
+ if (phi < 0.0) {
+ phi = source_regions_.scalar_flux_old(sr, g);
+ region_floored = true;
+ }
+ }
+ source_regions_.scalar_flux_new(sr, g) = phi;
} else if (volume_simulation_avg > 0.0) {
- // 2. If the FSR was not hit this iteration, but has been hit some
- // previous iteration, then we need to make a choice about what
- // to do. Naively we will usually want to set the flux to be equal
- // to the reduced source. However, in fixed source problems where
- // there is a strong external source present in the cell, and where
- // the cell has a very low cross section, this approximation will
- // cause a huge upward bias in the flux estimate of the cell (in these
- // conditions, the flux estimate can be orders of magnitude too large).
- // Thus, to avoid this bias, if any external source is present
- // in the cell we will use the previous iteration's flux estimate. This
- // injects a small degree of correlation into the simulation, but this
- // is going to be trivial when the miss rate is a few percent or less.
- if (source_regions_.external_source_present(sr)) {
+ // Missed this iteration but hit previously: substitute per the miss
+ // policy decided above (the previous iterate, or the reduced source),
+ // then stabilize.
+ if (use_old_flux_on_miss) {
set_flux_to_old_flux(sr, g);
} else {
set_flux_to_source(sr, g);
}
+ source_regions_.scalar_flux_new(sr, g) =
+ stabilized_flux(sr, g, source_regions_.scalar_flux_new(sr, g));
+ } else {
+ // Never hit: the iterate stays at its reset value, stabilized like
+ // every other element.
+ source_regions_.scalar_flux_new(sr, g) =
+ stabilized_flux(sr, g, source_regions_.scalar_flux_new(sr, g));
}
// Halt if NaN implosion is detected
if (!std::isfinite(source_regions_.scalar_flux_new(sr, g))) {
@@ -309,6 +702,42 @@ int64_t FlatSourceDomain::add_source_to_scalar_flux()
"the source region mesh.");
}
}
+ // Chronic-negativity demotion (strict adaptive only). The
+ // non-negativity floor prevents a chronically noisy region's
+ // accumulated flux from ever going negative, masking the very signal
+ // the accumulated sign demotion detects. Left alone, such a region
+ // would be clipped every batch, biasing its flux upward. Counting the
+ // batches that needed the fixup restores the escape, as after a few
+ // events the region is demoted to the naive volume and previous-flux
+ // miss treatment, where clipping
+ // is no longer needed.
+ if (is_strict && (region_rescued || region_floored)) {
+ int n = ++source_regions_.n_negative_batches(sr);
+ double threshold =
+ std::max(static_cast(NEGATIVE_FLUX_DEMOTION_MIN_COUNT),
+ NEGATIVE_FLUX_DEMOTION_RATE * simulation::current_batch);
+ if (n >= threshold && source_regions_.converged_negative(sr) == 0) {
+ source_regions_.converged_negative(sr) = 3;
+ }
+ }
+ if (final_iteration) {
+ n_rescued += region_rescued;
+ n_floored += region_floored;
+ }
+ }
+
+ // Store the final-iteration treatment snapshot for reporting (adaptive only;
+ // the other estimators do not produce a by-cause naive-treatment breakdown)
+ if (final_iteration && is_adaptive) {
+ n_final_naive_ = n_naive;
+ n_final_latch_ = n_latch;
+ n_final_strong_ = n_strong;
+ n_final_sign_ = n_sign;
+ n_final_small_ = n_small;
+ n_final_chronic_ = n_chronic;
+ n_final_rescued_ = n_rescued;
+ n_final_floored_ = n_floored;
+ final_stats_valid_ = true;
}
// Return the number of source regions that were hit this iteration
@@ -1751,62 +2180,6 @@ void FlatSourceDomain::finalize_discovered_source_regions()
discovered_source_regions_.clear();
}
-// This is the "diagonal stabilization" technique developed by Gunow et al. in:
-//
-// Geoffrey Gunow, Benoit Forget, Kord Smith, Stabilization of multi-group
-// neutron transport with transport-corrected cross-sections, Annals of Nuclear
-// Energy, Volume 126, 2019, Pages 211-219, ISSN 0306-4549,
-// https://doi.org/10.1016/j.anucene.2018.10.036.
-void FlatSourceDomain::apply_transport_stabilization()
-{
- // Don't do anything if all in-group scattering
- // cross sections are positive
- if (!is_transport_stabilization_needed_) {
- return;
- }
-
- // Apply the stabilization factor to all source elements
-#pragma omp parallel for
- for (int64_t sr = 0; sr < n_source_regions(); sr++) {
- int material = source_regions_.material(sr);
- int temp = source_regions_.temperature_idx(sr);
- double density_mult = source_regions_.density_mult(sr);
- if (material == MATERIAL_VOID) {
- continue;
- }
- for (int g = 0; g < negroups_; g++) {
- // Only apply stabilization if the diagonal (in-group) scattering XS is
- // negative
- double sigma_s =
- sigma_s_[((material * ntemperature_ + temp) * negroups_ + g) *
- negroups_ +
- g] *
- density_mult;
- if (sigma_s < 0.0) {
- double sigma_t =
- sigma_t_[(material * ntemperature_ + temp) * negroups_ + g] *
- density_mult;
- double phi_new = source_regions_.scalar_flux_new(sr, g);
- double phi_old = source_regions_.scalar_flux_old(sr, g);
-
- // Equation 18 in the above Gunow et al. 2019 paper. For a default
- // rho of 1.0, this ensures there are no negative diagonal elements
- // in the iteration matrix. A lesser rho could be used (or exposed
- // as a user input parameter) to reduce the negative impact on
- // convergence rate though would need to be experimentally tested to see
- // if it doesn't become unstable. rho = 1.0 is good as it gives the
- // highest assurance of stability, and the impacts on convergence rate
- // are pretty mild.
- double D = diagonal_stabilization_rho_ * sigma_s / sigma_t;
-
- // Equation 16 in the above Gunow et al. 2019 paper
- source_regions_.scalar_flux_new(sr, g) =
- (phi_new - D * phi_old) / (1.0 - D);
- }
- }
- }
-}
-
// Determines the base source region index (i.e., a material filled cell
// instance) that corresponds to a particular location in the geometry. Requires
// that the "gs" object passed in has already been initialized and has called
diff --git a/src/random_ray/linear_source_domain.cpp b/src/random_ray/linear_source_domain.cpp
index b4701ed1fa9..229752ca7f9 100644
--- a/src/random_ray/linear_source_domain.cpp
+++ b/src/random_ray/linear_source_domain.cpp
@@ -111,6 +111,28 @@ void LinearSourceDomain::update_single_neutron_source(SourceRegionHandle& srh)
srh.source(g) += srh.external_source(g);
}
}
+
+ // Under the adaptive volume estimator, demoted regions also fall back to a
+ // flat source representation, extending the flat-source fallback already
+ // applied to hit-starved (small) regions so that demotion is uniform in
+ // effect. For strong-source regions the reduced source greatly exceeds the
+ // scalar flux, so the flat-source cancellation must be exact, while the
+ // gradient terms attenuate segments against the local rather than the flat
+ // source, introducing per-iteration noise at the gradient scale that the
+ // volume choice cannot cancel. The same reasoning applies to regions
+ // demoted from the accumulated flux (converged_negative > 0). A negative
+ // accumulated flux means the fitted gradients carry no meaningful shape
+ // information, and a latched strong feed is the gradient-scale noise
+ // hazard the strong-source fallback above exists for.
+ if (is_adaptive_family(resolved_volume_estimator_) &&
+ material != MATERIAL_VOID &&
+ (srh.converged_negative() > 0 ||
+ region_has_strong_source(&srh.source(0), &srh.scalar_flux_old(0),
+ simulation::current_batch <= settings::n_inactive))) {
+ for (int g = 0; g < negroups_; g++) {
+ srh.source_gradients(g) = {0.0, 0.0, 0.0};
+ }
+ }
}
void LinearSourceDomain::normalize_scalar_flux_and_volumes(
diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp
index 0a1ed0381d9..29d98bf1dbd 100644
--- a/src/random_ray/random_ray_simulation.cpp
+++ b/src/random_ray/random_ray_simulation.cpp
@@ -287,7 +287,8 @@ void validate_random_ray_inputs()
void openmc_finalize_random_ray()
{
- FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::HYBRID;
+ FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::AUTO;
+ FlatSourceDomain::resolved_volume_estimator_ = RandomRayVolumeEstimator::AUTO;
FlatSourceDomain::volume_normalized_flux_tallies_ = false;
FlatSourceDomain::adjoint_requested_ = false;
FlatSourceDomain::solve_ = RandomRaySolve::FORWARD;
@@ -346,7 +347,19 @@ void RandomRaySimulation::prepare_fw_fixed_sources_adjoint()
// Prepare adjoint fixed sources using forward flux
domain_->source_regions_.adjoint_reset();
if (settings::run_mode == RunMode::FIXED_SOURCE) {
+ // Consumes the accumulated forward flux (and zeroes it as it goes), so
+ // the adjoint solve starts from a clean accumulator.
domain_->set_fw_adjoint_sources();
+ } else {
+ // In eigenvalue mode there are no fixed adjoint sources to derive from
+ // the forward flux, but the accumulated forward flux must still be
+ // cleared so that the adjoint solve's active accumulation starts from a
+ // clean array. Otherwise any consumer of the final flux would mix
+ // forward and adjoint modes.
+#pragma omp parallel for
+ for (int64_t se = 0; se < domain_->n_source_elements(); se++) {
+ domain_->source_regions_.scalar_flux_final(se) = 0.0;
+ }
}
}
@@ -448,12 +461,10 @@ void RandomRaySimulation::simulate()
domain_->normalize_scalar_flux_and_volumes(
settings::n_particles * RandomRay::distance_active_);
- // Add source to scalar flux, compute number of FSR hits
+ // Add source to scalar flux (applying any transport stabilization
+ // factors), compute number of FSR hits
int64_t n_hits = domain_->add_source_to_scalar_flux();
- // Apply transport stabilization factors
- domain_->apply_transport_stabilization();
-
if (settings::run_mode == RunMode::EIGENVALUE) {
// Compute random ray k-eff
domain_->compute_k_eff();
@@ -474,6 +485,11 @@ void RandomRaySimulation::simulate()
domain_->random_ray_tally();
}
+ // For the adaptive estimator, accumulate this batch's flux into the
+ // running sum and update (demote-only) which regions use the naive
+ // volume estimator (no-op for the other estimators).
+ domain_->demotion_step();
+
// Set phi_old = phi_new
domain_->flux_swap();
@@ -594,7 +610,7 @@ void RandomRaySimulation::print_results_random_ray(
total_integrations / settings::n_batches);
std::string estimator;
- switch (domain_->volume_estimator_) {
+ switch (FlatSourceDomain::resolved_volume_estimator_) {
case RandomRayVolumeEstimator::SIMULATION_AVERAGED:
estimator = "Simulation Averaged";
break;
@@ -604,10 +620,59 @@ void RandomRaySimulation::print_results_random_ray(
case RandomRayVolumeEstimator::HYBRID:
estimator = "Hybrid";
break;
+ case RandomRayVolumeEstimator::ADAPTIVE:
+ estimator = "Adaptive";
+ break;
+ case RandomRayVolumeEstimator::STRICT_ADAPTIVE:
+ estimator = "Strict Adaptive";
+ break;
default:
fatal_error("Invalid volume estimator type");
}
+ if (FlatSourceDomain::volume_estimator_ == RandomRayVolumeEstimator::AUTO) {
+ estimator += " (auto)";
+ }
fmt::print(" Volume Estimator Type = {}\n", estimator);
+ if (domain_->final_stats_valid_) {
+ double inv = 100.0 / domain_->n_source_regions();
+ // Single summary at default verbosity: every source region that
+ // received the naive volume treatment in the final batch, for any
+ // reason (the demote-only decisions made from the accumulated flux
+ // plus that batch's per-iteration demotions).
+ fmt::print(" Number of Naive Demotions = {} SRs ({:.4f}%)\n",
+ domain_->n_final_naive_, domain_->n_final_naive_ * inv);
+ // The per-cause diagnostic breakdown is developer-facing, so it is
+ // printed at verbosity 8, above the default (7) but below the
+ // per-particle output (9).
+ // The causes are mutually exclusive and sum to the total above:
+ // "accumulated" causes are the demote-only decisions made from the
+ // running accumulated flux (from the inactive->active transition
+ // onward), "per batch" causes are re-evaluated each batch and reported
+ // for the final batch.
+ if (settings::verbosity >= 8) {
+ fmt::print(" Strong source (accumulated) = {} SRs ({:.4f}%)\n",
+ domain_->n_final_latch_, domain_->n_final_latch_ * inv);
+ fmt::print(" Strong source (per batch) = {} SRs ({:.4f}%)\n",
+ domain_->n_final_strong_, domain_->n_final_strong_ * inv);
+ fmt::print(" Negative flux (accumulated) = {} SRs ({:.4f}%)\n",
+ domain_->n_final_sign_, domain_->n_final_sign_ * inv);
+ fmt::print(" Hit-starved (per batch) = {} SRs ({:.4f}%)\n",
+ domain_->n_final_small_, domain_->n_final_small_ * inv);
+ // The strict adaptive estimator's per-batch non-negativity
+ // enforcement, reported for the final batch. These overlap the
+ // partition above rather than extending it: a rescued or floored
+ // region may or may not also carry the naive treatment.
+ if (FlatSourceDomain::resolved_volume_estimator_ ==
+ RandomRayVolumeEstimator::STRICT_ADAPTIVE) {
+ fmt::print(" Chronic negative (per batch) = {} SRs ({:.4f}%)\n",
+ domain_->n_final_chronic_, domain_->n_final_chronic_ * inv);
+ fmt::print(" Rescued (batch volume) = {} SRs ({:.4f}%)\n",
+ domain_->n_final_rescued_, domain_->n_final_rescued_ * inv);
+ fmt::print(" Floored (previous flux) = {} SRs ({:.4f}%)\n",
+ domain_->n_final_floored_, domain_->n_final_floored_ * inv);
+ }
+ }
+ }
std::string adjoint_true =
(FlatSourceDomain::solve_ == RandomRaySolve::ADJOINT) ? "ON" : "OFF";
@@ -684,6 +749,26 @@ void openmc_run_random_ray()
{
using namespace openmc;
+ // Resolve the volume estimator for this solve, leaving the configured
+ // setting untouched. "Auto" (the default) maps to a concrete estimator
+ // based on the type of simulation being performed. Solves whose results
+ // feed variance reduction (weight window generation, and any adjoint
+ // workflow, including the forward solve an adjoint source is derived
+ // from) receive the strict adaptive estimator, whose per-batch fixup of
+ // negative flux iterates benefits those workflows. All other solves
+ // receive the unbiased adaptive estimator.
+ if (FlatSourceDomain::volume_estimator_ == RandomRayVolumeEstimator::AUTO) {
+ bool positivity_needed =
+ FlatSourceDomain::adjoint_requested_ ||
+ !variance_reduction::weight_windows_generators.empty();
+ FlatSourceDomain::resolved_volume_estimator_ =
+ positivity_needed ? RandomRayVolumeEstimator::STRICT_ADAPTIVE
+ : RandomRayVolumeEstimator::ADAPTIVE;
+ } else {
+ FlatSourceDomain::resolved_volume_estimator_ =
+ FlatSourceDomain::volume_estimator_;
+ }
+
// Determine which solves to run. If adjoint results are requested and no
// user-defined adjoint source is present, an initial forward solve is needed
// to construct the adjoint source from the forward flux (FW-CADIS). If the
diff --git a/src/random_ray/source_region.cpp b/src/random_ray/source_region.cpp
index 78543c5ab53..ab8126f1804 100644
--- a/src/random_ray/source_region.cpp
+++ b/src/random_ray/source_region.cpp
@@ -12,7 +12,8 @@ namespace openmc {
SourceRegionHandle::SourceRegionHandle(SourceRegion& sr)
: negroups_(sr.scalar_flux_old_.size()), material_(&sr.material_),
temperature_idx_(&sr.temperature_idx_), density_mult_(&sr.density_mult_),
- is_small_(&sr.is_small_), n_hits_(&sr.n_hits_),
+ is_small_(&sr.is_small_), n_negative_batches_(&sr.n_negative_batches_),
+ converged_negative_(&sr.converged_negative_), n_hits_(&sr.n_hits_),
is_linear_(sr.source_gradients_.size() > 0), lock_(&sr.lock_),
volume_(&sr.volume_), volume_t_(&sr.volume_t_), volume_sq_(&sr.volume_sq_),
volume_sq_t_(&sr.volume_sq_t_), volume_naive_(&sr.volume_naive_),
@@ -74,6 +75,12 @@ void SourceRegionContainer::push_back(const SourceRegion& sr)
temperature_idx_.push_back(sr.temperature_idx_);
density_mult_.push_back(sr.density_mult_);
is_small_.push_back(sr.is_small_);
+ if (is_strict_adaptive_) {
+ n_negative_batches_.push_back(sr.n_negative_batches_);
+ }
+ if (is_adaptive_) {
+ converged_negative_.push_back(sr.converged_negative_);
+ }
n_hits_.push_back(sr.n_hits_);
lock_.push_back(sr.lock_);
volume_.push_back(sr.volume_);
@@ -102,6 +109,10 @@ void SourceRegionContainer::push_back(const SourceRegion& sr)
scalar_flux_old_.push_back(sr.scalar_flux_old_[g]);
scalar_flux_new_.push_back(sr.scalar_flux_new_[g]);
scalar_flux_final_.push_back(sr.scalar_flux_final_[g]);
+ // A newly discovered region starts with nothing accumulated
+ if (is_adaptive_) {
+ scalar_flux_t_.push_back(0.0);
+ }
source_.push_back(sr.source_[g]);
if (settings::run_mode == RunMode::FIXED_SOURCE) {
external_source_.push_back(sr.external_source_[g]);
@@ -129,6 +140,8 @@ void SourceRegionContainer::assign(
temperature_idx_.clear();
density_mult_.clear();
is_small_.clear();
+ n_negative_batches_.clear();
+ converged_negative_.clear();
n_hits_.clear();
lock_.clear();
volume_.clear();
@@ -153,6 +166,7 @@ void SourceRegionContainer::assign(
scalar_flux_old_.clear();
scalar_flux_new_.clear();
scalar_flux_final_.clear();
+ scalar_flux_t_.clear();
source_.clear();
external_source_.clear();
@@ -188,6 +202,9 @@ SourceRegionHandle SourceRegionContainer::get_source_region_handle(int64_t sr)
handle.temperature_idx_ = &temperature_idx(sr);
handle.density_mult_ = &density_mult(sr);
handle.is_small_ = &is_small(sr);
+ handle.n_negative_batches_ =
+ is_strict_adaptive_ ? &n_negative_batches(sr) : nullptr;
+ handle.converged_negative_ = is_adaptive_ ? &converged_negative(sr) : nullptr;
handle.n_hits_ = &n_hits(sr);
handle.is_linear_ = is_linear();
handle.lock_ = &lock(sr);
@@ -231,6 +248,8 @@ SourceRegionHandle SourceRegionContainer::get_source_region_handle(int64_t sr)
void SourceRegionContainer::adjoint_reset()
{
std::fill(n_hits_.begin(), n_hits_.end(), 0);
+ std::fill(converged_negative_.begin(), converged_negative_.end(), 0);
+ std::fill(n_negative_batches_.begin(), n_negative_batches_.end(), 0);
std::fill(volume_.begin(), volume_.end(), 0.0);
std::fill(volume_t_.begin(), volume_t_.end(), 0.0);
std::fill(volume_sq_.begin(), volume_sq_.end(), 0.0);
@@ -253,6 +272,7 @@ void SourceRegionContainer::adjoint_reset()
std::fill(scalar_flux_old_.begin(), scalar_flux_old_.end(), 1.0);
}
std::fill(scalar_flux_new_.begin(), scalar_flux_new_.end(), 0.0);
+ std::fill(scalar_flux_t_.begin(), scalar_flux_t_.end(), 0.0);
std::fill(source_.begin(), source_.end(), 0.0f);
std::fill(external_source_.begin(), external_source_.end(), 0.0f);
std::fill(source_gradients_.begin(), source_gradients_.end(),
diff --git a/src/settings.cpp b/src/settings.cpp
index 3e20144f1bd..85d1f428b2c 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -309,6 +309,14 @@ void get_run_parameters(pugi::xml_node node_base)
FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::NAIVE;
} else if (temp_str == "hybrid") {
FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::HYBRID;
+ } else if (temp_str == "adaptive") {
+ FlatSourceDomain::volume_estimator_ =
+ RandomRayVolumeEstimator::ADAPTIVE;
+ } else if (temp_str == "strict_adaptive") {
+ FlatSourceDomain::volume_estimator_ =
+ RandomRayVolumeEstimator::STRICT_ADAPTIVE;
+ } else if (temp_str == "auto") {
+ FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::AUTO;
} else {
fatal_error("Unrecognized volume estimator: " + temp_str);
}
diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/adaptive_starved/inputs_true.dat b/tests/regression_tests/random_ray_adjoint_fixed_source/adaptive_starved/inputs_true.dat
new file mode 100644
index 00000000000..55f004b680b
--- /dev/null
+++ b/tests/regression_tests/random_ray_adjoint_fixed_source/adaptive_starved/inputs_true.dat
@@ -0,0 +1,248 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+ 2.5 2.5 2.5
+ 12 12 12
+ 0.0 0.0 0.0
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+
+
+
+
+
+
+
+
+ fixed source
+ 10
+ 40
+ 20
+
+
+ 100.0 1.0
+
+
+ universe
+ 1
+
+
+ multi-group
+
+ 500.0
+ 100.0
+
+
+
+ 0.0 0.0 0.0 30.0 30.0 30.0
+
+
+
+ true
+ true
+ adaptive
+
+
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 3
+ flux
+ tracklength
+
+
+ 2
+ flux
+ tracklength
+
+
+ 1
+ flux
+ tracklength
+
+
+
diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/adaptive_starved/results_true.dat b/tests/regression_tests/random_ray_adjoint_fixed_source/adaptive_starved/results_true.dat
new file mode 100644
index 00000000000..a8b8824e6b9
--- /dev/null
+++ b/tests/regression_tests/random_ray_adjoint_fixed_source/adaptive_starved/results_true.dat
@@ -0,0 +1,9 @@
+tally 1:
+3.923616E+06
+1.276954E+12
+tally 2:
+5.338607E+06
+1.444114E+12
+tally 3:
+1.722575E+07
+1.493106E+13
diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/test.py b/tests/regression_tests/random_ray_adjoint_fixed_source/test.py
index 6c2790fa093..489d3a70516 100644
--- a/tests/regression_tests/random_ray_adjoint_fixed_source/test.py
+++ b/tests/regression_tests/random_ray_adjoint_fixed_source/test.py
@@ -1,5 +1,7 @@
import os
+import openmc
+from openmc.utility_funcs import change_directory
from openmc.examples import random_ray_three_region_cube
from tests.testing_harness import TolerantPyAPITestHarness
@@ -20,3 +22,21 @@ def test_random_ray_adjoint_fixed_source():
model.settings.particles = 500
harness = MGXSTestHarness('statepoint.10.h5', model)
harness.main()
+
+
+def test_random_ray_adjoint_fixed_source_adaptive_starved():
+ # Ray-starved adaptive case (~20% forward / ~40% adjoint miss rate). The
+ # adjoint (second) solve makes real end-of-inactive demotion decisions on
+ # its own accumulated flux, guarding both the adaptive machinery in
+ # adjoint mode and the clearing of the forward solve's accumulated flux
+ # between the two solves (which would otherwise swamp the demotion test).
+ with change_directory('adaptive_starved'):
+ openmc.reset_auto_ids()
+ model = random_ray_three_region_cube()
+ model.settings.random_ray['adjoint'] = True
+ model.settings.random_ray['volume_estimator'] = 'adaptive'
+ model.settings.particles = 10
+ model.settings.inactive = 20
+ model.settings.batches = 40
+ harness = MGXSTestHarness('statepoint.40.h5', model)
+ harness.main()
diff --git a/tests/regression_tests/random_ray_cell_density/fs/inputs_true.dat b/tests/regression_tests/random_ray_cell_density/fs/inputs_true.dat
index f369bae89f3..9a578087213 100644
--- a/tests/regression_tests/random_ray_cell_density/fs/inputs_true.dat
+++ b/tests/regression_tests/random_ray_cell_density/fs/inputs_true.dat
@@ -215,6 +215,7 @@
true
+ hybrid
diff --git a/tests/regression_tests/random_ray_cell_density/test.py b/tests/regression_tests/random_ray_cell_density/test.py
index 48ebe0baaa2..9e77b4c8d35 100644
--- a/tests/regression_tests/random_ray_cell_density/test.py
+++ b/tests/regression_tests/random_ray_cell_density/test.py
@@ -39,5 +39,6 @@ def test_random_ray_basic(run_mode):
cell.density = 1e3
# Gold file was generated with manually scaled source cross sections.
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.10.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/adaptive/inputs_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/adaptive/inputs_true.dat
new file mode 100644
index 00000000000..8012b6c76b5
--- /dev/null
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/adaptive/inputs_true.dat
@@ -0,0 +1,68 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+ eigenvalue
+ 100
+ 20
+ 15
+
+
+ -0.63 -0.63 -1 0.63 0.63 1
+
+
+ true
+
+
+ multi-group
+
+
+
+
+ -0.63 -0.63 -1.0 0.63 0.63 1.0
+
+
+
+ 30.0
+ 150.0
+
+
+
+
+
+ linear
+ 0.5
+ adaptive
+
+
+ 2 2
+ -0.63 -0.63
+ 0.63 0.63
+
+
+
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/adaptive/results_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/adaptive/results_true.dat
new file mode 100644
index 00000000000..57f8c47bbc5
--- /dev/null
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/adaptive/results_true.dat
@@ -0,0 +1,2 @@
+k-combined:
+7.165325E-01 1.371333E-02
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_adaptive/inputs_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_adaptive/inputs_true.dat
new file mode 100644
index 00000000000..30c71486a91
--- /dev/null
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_adaptive/inputs_true.dat
@@ -0,0 +1,48 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+ eigenvalue
+ 100
+ 8
+ 3
+ multi-group
+
+ 30.0
+ 200.0
+
+
+
+ 0.0 0.0 0.0 10.0 10.0 10.0
+
+
+
+
+
+
+
+
+ adaptive
+
+
+ 4 4 4
+ 0.0 0.0 0.0
+ 10.0 10.0 10.0
+
+
+
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_adaptive/results_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_adaptive/results_true.dat
new file mode 100644
index 00000000000..530d8b49018
--- /dev/null
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_adaptive/results_true.dat
@@ -0,0 +1,2 @@
+k-combined:
+5.484375E-01 2.130966E-02
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_hybrid/inputs_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_hybrid/inputs_true.dat
new file mode 100644
index 00000000000..1cf9b55110a
--- /dev/null
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_hybrid/inputs_true.dat
@@ -0,0 +1,48 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+ eigenvalue
+ 100
+ 8
+ 3
+ multi-group
+
+ 30.0
+ 200.0
+
+
+
+ 0.0 0.0 0.0 10.0 10.0 10.0
+
+
+
+
+
+
+
+
+ hybrid
+
+
+ 4 4 4
+ 0.0 0.0 0.0
+ 10.0 10.0 10.0
+
+
+
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_hybrid/results_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_hybrid/results_true.dat
new file mode 100644
index 00000000000..530d8b49018
--- /dev/null
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_hybrid/results_true.dat
@@ -0,0 +1,2 @@
+k-combined:
+5.484375E-01 2.130966E-02
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_strict_adaptive/inputs_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_strict_adaptive/inputs_true.dat
new file mode 100644
index 00000000000..59d650d0526
--- /dev/null
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_strict_adaptive/inputs_true.dat
@@ -0,0 +1,48 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+ eigenvalue
+ 100
+ 8
+ 3
+ multi-group
+
+ 30.0
+ 200.0
+
+
+
+ 0.0 0.0 0.0 10.0 10.0 10.0
+
+
+
+
+
+
+
+
+ strict_adaptive
+
+
+ 4 4 4
+ 0.0 0.0 0.0
+ 10.0 10.0 10.0
+
+
+
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_strict_adaptive/results_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_strict_adaptive/results_true.dat
new file mode 100644
index 00000000000..530d8b49018
--- /dev/null
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/homogeneous_strict_adaptive/results_true.dat
@@ -0,0 +1,2 @@
+k-combined:
+5.484375E-01 2.130966E-02
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/hybrid/inputs_true.dat
similarity index 98%
rename from tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat
rename to tests/regression_tests/random_ray_diagonal_stabilization/hybrid/inputs_true.dat
index 0ea8c017760..4d48f151a51 100644
--- a/tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/hybrid/inputs_true.dat
@@ -57,6 +57,7 @@
linear
0.5
+ hybrid
2 2
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/hybrid/results_true.dat
similarity index 100%
rename from tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat
rename to tests/regression_tests/random_ray_diagonal_stabilization/hybrid/results_true.dat
diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/test.py b/tests/regression_tests/random_ray_diagonal_stabilization/test.py
index 4ea21ae1a72..22fabc56f27 100644
--- a/tests/regression_tests/random_ray_diagonal_stabilization/test.py
+++ b/tests/regression_tests/random_ray_diagonal_stabilization/test.py
@@ -1,8 +1,12 @@
import os
+import numpy as np
import openmc
+import openmc.mgxs
from openmc.examples import pwr_pin_cell
+from openmc.utility_funcs import change_directory
from openmc import RegularMesh
+import pytest
from tests.testing_harness import TolerantPyAPITestHarness
@@ -15,7 +19,7 @@ def _cleanup(self):
os.remove(f)
-def test_random_ray_diagonal_stabilization():
+def _build_model():
# Start with a normal continuous energy model
model = pwr_pin_cell()
@@ -59,5 +63,92 @@ def test_random_ray_diagonal_stabilization():
model.settings.inactive = 15
model.settings.batches = 20
- harness = MGXSTestHarness('statepoint.20.h5', model)
- harness.main()
+ return model
+
+
+def _build_homogeneous_model():
+ # A homogeneous two-group eigenvalue problem with a negative within-group
+ # scattering cross section in the fast group, as a transport correction
+ # produces. With chi = (1, 0) and no upscatter the infinite-medium
+ # eigenvalue is
+ # k = nu_sigma_f2 * sigma_s12 / ((sigma_t2 - sigma_s22) *
+ # (sigma_t1 - sigma_s11))
+ # = 0.375 * 1.0 / (0.5 * 1.5) = 0.5
+ # exactly, and the iteration approaches it geometrically from above
+ # (1.5, 1.0, 0.75, 0.625, ...). Only a few batches are run, enough for
+ # the stored reference to pin that direction.
+ groups = openmc.mgxs.EnergyGroups(group_edges=[1e-5, 1.0e3, 20.0e6])
+ xs = openmc.XSdata('mat', groups)
+ xs.order = 0
+ xs.set_total([1.0, 1.0])
+ xs.set_absorption([0.5, 0.5])
+ xs.set_scatter_matrix(np.array([[[-0.5], [1.0]],
+ [[0.0], [0.5]]]))
+ xs.set_fission([0.0, 0.15])
+ xs.set_nu_fission([0.0, 0.375])
+ xs.set_chi([1.0, 0.0])
+ lib = openmc.MGXSLibrary(groups)
+ lib.add_xsdatas([xs])
+ lib.export_to_hdf5('mgxs.h5')
+
+ mat = openmc.Material(name='mat')
+ mat.set_density('macro', 1.0)
+ mat.add_macroscopic(openmc.Macroscopic('mat'))
+ model = openmc.Model()
+ model.materials = openmc.Materials([mat])
+ model.materials.cross_sections = 'mgxs.h5'
+ box = openmc.model.RectangularParallelepiped(
+ 0.0, 10.0, 0.0, 10.0, 0.0, 10.0, boundary_type='reflective')
+ cell = openmc.Cell(fill=mat, region=-box)
+ model.geometry = openmc.Geometry([cell])
+
+ mesh = RegularMesh()
+ mesh.lower_left = (0.0, 0.0, 0.0)
+ mesh.upper_right = (10.0, 10.0, 10.0)
+ mesh.dimension = (4, 4, 4)
+
+ settings = model.settings
+ settings.energy_mode = 'multi-group'
+ settings.run_mode = 'eigenvalue'
+ settings.particles = 100
+ settings.inactive = 3
+ settings.batches = 8
+ settings.random_ray = {
+ 'distance_inactive': 30.0,
+ 'distance_active': 200.0,
+ 'ray_source': openmc.IndependentSource(
+ space=openmc.stats.Box((0.0, 0.0, 0.0), (10.0, 10.0, 10.0))),
+ 'source_region_meshes': [(mesh, [model.geometry.root_universe])],
+ }
+ return model
+
+
+# The transport-corrected (P0) library's negative within-group scattering
+# drives some reduced sources negative, which the adaptive estimator must
+# handle through its negative-source (strong) treatment and its
+# end-of-inactive demotion. The adaptive case pins that interplay.
+@pytest.mark.parametrize("estimator", ["hybrid", "adaptive"])
+def test_random_ray_diagonal_stabilization(estimator):
+ with change_directory(estimator):
+ openmc.reset_auto_ids()
+ model = _build_model()
+ model.settings.random_ray['volume_estimator'] = estimator
+ harness = MGXSTestHarness('statepoint.20.h5', model)
+ harness.main()
+
+
+# Every estimator must descend toward the analytic eigenvalue of the
+# homogeneous problem. The strict estimator's non-negativity fixup must
+# assess the stabilized flux iterate: the negative within-group scattering
+# drives the raw fast-group iterate negative early on, which the
+# stabilization maps to a positive value. Flooring the raw value first would
+# freeze the iteration at the previous iterate, and this problem then climbs
+# toward a spurious k of 2.0 instead.
+@pytest.mark.parametrize("estimator", ["hybrid", "adaptive", "strict_adaptive"])
+def test_random_ray_diagonal_stabilization_homogeneous(estimator):
+ with change_directory(f'homogeneous_{estimator}'):
+ openmc.reset_auto_ids()
+ model = _build_homogeneous_model()
+ model.settings.random_ray['volume_estimator'] = estimator
+ harness = MGXSTestHarness('statepoint.8.h5', model)
+ harness.main()
diff --git a/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat
index d650bbaf95c..d2fbd6d2247 100644
--- a/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat
@@ -215,6 +215,7 @@
true
+ hybrid
diff --git a/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat
index 98a51add1f3..bad8edb0ceb 100644
--- a/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat
@@ -215,6 +215,7 @@
true
+ hybrid
diff --git a/tests/regression_tests/random_ray_fixed_source_domain/test.py b/tests/regression_tests/random_ray_fixed_source_domain/test.py
index 5885a92009a..5b54871b780 100644
--- a/tests/regression_tests/random_ray_fixed_source_domain/test.py
+++ b/tests/regression_tests/random_ray_fixed_source_domain/test.py
@@ -47,5 +47,6 @@ def test_random_ray_fixed_source(domain_type):
constraints['domain_type'] = 'universe'
constraints['domain_ids'] = [universe.id]
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.10.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat
index 20deba664bd..4d1af46b121 100644
--- a/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat
@@ -215,6 +215,7 @@
true
+ hybrid
diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat
index 2268d82c391..dd11567f69d 100644
--- a/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat
@@ -216,6 +216,7 @@
true
linear
+ hybrid
diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat
index fe95baa7bb5..74a7a0b7f4d 100644
--- a/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat
@@ -216,6 +216,7 @@
true
linear_xy
+ hybrid
diff --git a/tests/regression_tests/random_ray_fixed_source_linear/test.py b/tests/regression_tests/random_ray_fixed_source_linear/test.py
index 99211024e6e..b0532a458bb 100644
--- a/tests/regression_tests/random_ray_fixed_source_linear/test.py
+++ b/tests/regression_tests/random_ray_fixed_source_linear/test.py
@@ -25,5 +25,6 @@ def test_random_ray_fixed_source_linear(shape):
model.settings.random_ray['source_shape'] = shape
model.settings.inactive = 20
model.settings.batches = 40
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.40.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat
index a5632ece960..6b0eaaab6c7 100644
--- a/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat
@@ -227,6 +227,7 @@
flat
+ hybrid
24 24 24
diff --git a/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat
index 9d22603c63c..d7a7d4f418b 100644
--- a/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat
@@ -227,6 +227,7 @@
linear
+ hybrid
24 24 24
diff --git a/tests/regression_tests/random_ray_fixed_source_mesh/test.py b/tests/regression_tests/random_ray_fixed_source_mesh/test.py
index 0b93b2a7a6a..9e09959e904 100644
--- a/tests/regression_tests/random_ray_fixed_source_mesh/test.py
+++ b/tests/regression_tests/random_ray_fixed_source_mesh/test.py
@@ -49,5 +49,6 @@ def test_random_ray_fixed_source_mesh(shape):
model.settings.inactive = 15
model.settings.batches = 30
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.30.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat
index de941f10fbb..ffe38772386 100644
--- a/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat
@@ -215,6 +215,7 @@
false
+ hybrid
diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat
index 20deba664bd..4d1af46b121 100644
--- a/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat
@@ -215,6 +215,7 @@
true
+ hybrid
diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/test.py b/tests/regression_tests/random_ray_fixed_source_normalization/test.py
index 3fa4ba2a63f..aa9823d0e27 100644
--- a/tests/regression_tests/random_ray_fixed_source_normalization/test.py
+++ b/tests/regression_tests/random_ray_fixed_source_normalization/test.py
@@ -23,5 +23,6 @@ def test_random_ray_fixed_source(normalize):
model = random_ray_three_region_cube()
model.settings.random_ray['volume_normalized_flux_tallies'] = normalize
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.10.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat
index 943468a1095..8a992399806 100644
--- a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat
@@ -119,6 +119,7 @@
false
flat
+ hybrid
diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat
index 650953c4b06..20e5095f8f2 100644
--- a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat
+++ b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat
@@ -119,6 +119,7 @@
false
linear_xy
+ hybrid
diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/test.py b/tests/regression_tests/random_ray_fixed_source_subcritical/test.py
index e2f3cf17582..b8f08e63696 100644
--- a/tests/regression_tests/random_ray_fixed_source_subcritical/test.py
+++ b/tests/regression_tests/random_ray_fixed_source_subcritical/test.py
@@ -129,5 +129,6 @@ def test_random_ray_fixed_source_subcritical(shape):
########################################
# Run test
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.125.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_k_eff_mesh/adaptive_starved/inputs_true.dat b/tests/regression_tests/random_ray_k_eff_mesh/adaptive_starved/inputs_true.dat
new file mode 100644
index 00000000000..0935ab4c9c0
--- /dev/null
+++ b/tests/regression_tests/random_ray_k_eff_mesh/adaptive_starved/inputs_true.dat
@@ -0,0 +1,122 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+ 0.126 0.126
+ 10 10
+ -0.63 -0.63
+
+3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3
+
+
+ 1.26 1.26
+ 2 2
+ -1.26 -1.26
+
+2 2
+2 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ eigenvalue
+ 25
+ 40
+ 20
+ multi-group
+
+ 100.0
+ 20.0
+
+
+
+ -1.26 -1.26 -1 1.26 1.26 1
+
+
+
+ true
+
+
+
+
+
+ adaptive
+
+
+ 40 40
+ -1.26 -1.26
+ 1.26 1.26
+
+
+
+
+ 2 2
+ -1.26 -1.26
+ 1.26 1.26
+
+
+ 1
+
+
+ 1e-05 0.0635 10.0 100.0 1000.0 500000.0 1000000.0 20000000.0
+
+
+ 1 2
+ flux fission nu-fission
+ analog
+
+
+
diff --git a/tests/regression_tests/random_ray_k_eff_mesh/adaptive_starved/results_true.dat b/tests/regression_tests/random_ray_k_eff_mesh/adaptive_starved/results_true.dat
new file mode 100644
index 00000000000..038d14115e4
--- /dev/null
+++ b/tests/regression_tests/random_ray_k_eff_mesh/adaptive_starved/results_true.dat
@@ -0,0 +1,171 @@
+k-combined:
+1.107191E+00 1.650659E-02
+tally 1:
+5.382339E+00
+1.458425E+00
+1.990201E+00
+1.996805E-01
+4.843752E+00
+1.182784E+00
+3.851449E+00
+7.437451E-01
+5.695747E-01
+1.627384E-02
+1.386231E+00
+9.639619E-02
+2.905354E+00
+4.222530E-01
+9.355436E-02
+4.378740E-04
+2.276926E-01
+2.593694E-03
+3.728133E+00
+6.951656E-01
+1.234731E-01
+7.626219E-04
+3.005089E-01
+4.517301E-03
+9.697786E+00
+4.702912E+00
+1.132007E-01
+6.408188E-04
+2.755113E-01
+3.795908E-03
+2.110307E+01
+2.227083E+01
+3.196285E-02
+5.109814E-05
+7.908983E-02
+3.128639E-04
+1.114013E+01
+6.210744E+00
+1.495081E-01
+1.120059E-03
+4.158491E-01
+8.665291E-03
+8.789910E+00
+3.891597E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+4.704870E+00
+1.109865E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+3.067040E+00
+4.705142E-01
+0.000000E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+4.067399E+00
+8.273897E-01
+0.000000E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+9.781891E+00
+4.784770E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+1.891493E+01
+1.789205E+01
+0.000000E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+9.664789E+00
+4.679805E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+0.000000E+00
+4.684700E+00
+1.105730E+00
+1.748804E+00
+1.543531E-01
+4.256239E+00
+9.142921E-01
+3.633542E+00
+6.620806E-01
+5.450623E-01
+1.490410E-02
+1.326573E+00
+8.828271E-02
+2.859797E+00
+4.091356E-01
+9.368855E-02
+4.391008E-04
+2.280192E-01
+2.600962E-03
+3.656296E+00
+6.686608E-01
+1.230360E-01
+7.572462E-04
+2.994450E-01
+4.485459E-03
+9.692731E+00
+4.698026E+00
+1.151164E-01
+6.626605E-04
+2.801737E-01
+3.925288E-03
+2.161344E+01
+2.335940E+01
+3.339064E-02
+5.576419E-05
+8.262280E-02
+3.414332E-04
+1.149109E+01
+6.606633E+00
+1.577263E-01
+1.246078E-03
+4.387076E-01
+9.640234E-03
+5.394231E+00
+1.465973E+00
+2.033778E+00
+2.084693E-01
+4.949809E+00
+1.234843E+00
+3.854239E+00
+7.449142E-01
+5.821861E-01
+1.699800E-02
+1.416925E+00
+1.006856E-01
+2.904495E+00
+4.219991E-01
+9.568897E-02
+4.580415E-04
+2.328878E-01
+2.713155E-03
+3.721089E+00
+6.925391E-01
+1.259809E-01
+7.939042E-04
+3.066124E-01
+4.702598E-03
+9.683004E+00
+4.688502E+00
+1.156990E-01
+6.693965E-04
+2.815917E-01
+3.965188E-03
+2.116801E+01
+2.240891E+01
+3.293516E-02
+5.425827E-05
+8.149575E-02
+3.322128E-04
+1.127270E+01
+6.359090E+00
+1.560609E-01
+1.220736E-03
+4.340755E-01
+9.444178E-03
diff --git a/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat b/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat
index f6e9c8e3e71..2dc2577e393 100644
--- a/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat
+++ b/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat
@@ -93,6 +93,7 @@
+ hybrid
40 40
diff --git a/tests/regression_tests/random_ray_k_eff_mesh/test.py b/tests/regression_tests/random_ray_k_eff_mesh/test.py
index cffdaf8bb4c..1f3b212d173 100644
--- a/tests/regression_tests/random_ray_k_eff_mesh/test.py
+++ b/tests/regression_tests/random_ray_k_eff_mesh/test.py
@@ -1,6 +1,7 @@
import os
import openmc
+from openmc.utility_funcs import change_directory
from openmc.examples import random_ray_lattice
from tests.testing_harness import TolerantPyAPITestHarness
@@ -27,10 +28,35 @@ def test_random_ray_k_eff_mesh():
mesh.dimension = (dim, dim)
mesh.lower_left = (-pitch, -pitch)
mesh.upper_right = (pitch, pitch)
-
+
root = model.geometry.root_universe
-
+
model.settings.random_ray['source_region_meshes'] = [(mesh, [root])]
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.10.h5', model)
harness.main()
+
+
+def test_random_ray_k_eff_mesh_adaptive_starved():
+ # Ray-starved adaptive eigenvalue case (~1.2% miss rate). The subdivision
+ # mesh plus a low ray count engages the adaptive demotion machinery
+ # (strong-source and hit-starved regions, plus the end-of-inactive
+ # demotion decision and the previous-flux miss treatment) in eigenvalue
+ # mode, which the nominal 0%-miss eigenvalue tests never exercise.
+ with change_directory('adaptive_starved'):
+ openmc.reset_auto_ids()
+ model = random_ray_lattice()
+ pitch = 1.26
+ mesh = openmc.RegularMesh()
+ mesh.dimension = (40, 40)
+ mesh.lower_left = (-pitch, -pitch)
+ mesh.upper_right = (pitch, pitch)
+ root = model.geometry.root_universe
+ model.settings.random_ray['source_region_meshes'] = [(mesh, [root])]
+ model.settings.random_ray['volume_estimator'] = 'adaptive'
+ model.settings.particles = 25
+ model.settings.inactive = 20
+ model.settings.batches = 40
+ harness = MGXSTestHarness('statepoint.40.h5', model)
+ harness.main()
diff --git a/tests/regression_tests/random_ray_low_density/inputs_true.dat b/tests/regression_tests/random_ray_low_density/inputs_true.dat
index 20deba664bd..4d1af46b121 100644
--- a/tests/regression_tests/random_ray_low_density/inputs_true.dat
+++ b/tests/regression_tests/random_ray_low_density/inputs_true.dat
@@ -215,6 +215,7 @@
true
+ hybrid
diff --git a/tests/regression_tests/random_ray_low_density/test.py b/tests/regression_tests/random_ray_low_density/test.py
index 1b4ffb78183..22f68cca291 100644
--- a/tests/regression_tests/random_ray_low_density/test.py
+++ b/tests/regression_tests/random_ray_low_density/test.py
@@ -56,5 +56,6 @@ def test_random_ray_low_density():
[source_mat_data, void_mat_data, absorber_mat_data])
mg_cross_sections_file.export_to_hdf5()
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.10.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat b/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat
index b4bd263f5ac..a66cd835c8b 100644
--- a/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat
+++ b/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat
@@ -219,6 +219,7 @@
+ hybrid
30 30 30
diff --git a/tests/regression_tests/random_ray_point_source_locator/test.py b/tests/regression_tests/random_ray_point_source_locator/test.py
index fd3d8a18fe9..c72e2b85699 100644
--- a/tests/regression_tests/random_ray_point_source_locator/test.py
+++ b/tests/regression_tests/random_ray_point_source_locator/test.py
@@ -40,5 +40,6 @@ def test_random_ray_point_source_locator():
model.settings.inactive = 15
model.settings.batches = 30
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.30.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_void/flat/inputs_true.dat b/tests/regression_tests/random_ray_void/flat/inputs_true.dat
index 66390c76661..39533ab0dd6 100644
--- a/tests/regression_tests/random_ray_void/flat/inputs_true.dat
+++ b/tests/regression_tests/random_ray_void/flat/inputs_true.dat
@@ -216,6 +216,7 @@
true
flat
+ hybrid
diff --git a/tests/regression_tests/random_ray_void/linear/inputs_true.dat b/tests/regression_tests/random_ray_void/linear/inputs_true.dat
index 45228a03955..83d960a441c 100644
--- a/tests/regression_tests/random_ray_void/linear/inputs_true.dat
+++ b/tests/regression_tests/random_ray_void/linear/inputs_true.dat
@@ -216,6 +216,7 @@
true
linear
+ hybrid
diff --git a/tests/regression_tests/random_ray_void/test.py b/tests/regression_tests/random_ray_void/test.py
index b48a7794d7e..cc45650175e 100644
--- a/tests/regression_tests/random_ray_void/test.py
+++ b/tests/regression_tests/random_ray_void/test.py
@@ -68,5 +68,6 @@ def test_random_ray_void(shape):
tallies = openmc.Tallies([source_tally, void_tally, absorber_tally])
model.tallies = tallies
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.40.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_volume_estimator/adaptive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/adaptive/inputs_true.dat
new file mode 100644
index 00000000000..33ab014d4c6
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator/adaptive/inputs_true.dat
@@ -0,0 +1,247 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+ 2.5 2.5 2.5
+ 12 12 12
+ 0.0 0.0 0.0
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+
+
+
+
+
+
+
+
+ fixed source
+ 10
+ 40
+ 20
+
+
+ 100.0 1.0
+
+
+ universe
+ 1
+
+
+ multi-group
+
+ 500.0
+ 100.0
+
+
+
+ 0.0 0.0 0.0 30.0 30.0 30.0
+
+
+
+ true
+ adaptive
+
+
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 3
+ flux
+ tracklength
+
+
+ 2
+ flux
+ tracklength
+
+
+ 1
+ flux
+ tracklength
+
+
+
diff --git a/tests/regression_tests/random_ray_volume_estimator/adaptive/results_true.dat b/tests/regression_tests/random_ray_volume_estimator/adaptive/results_true.dat
new file mode 100644
index 00000000000..ac9fcd45390
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator/adaptive/results_true.dat
@@ -0,0 +1,9 @@
+tally 1:
+2.259425E+00
+2.675738E-01
+tally 2:
+1.018431E-01
+6.713412E-04
+tally 3:
+7.288312E-03
+3.488945E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat
index 4d1af46b121..505d4c7dfed 100644
--- a/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat
@@ -191,9 +191,9 @@
fixed source
- 90
- 10
- 5
+ 10
+ 40
+ 20
100.0 1.0
diff --git a/tests/regression_tests/random_ray_volume_estimator/hybrid/results_true.dat b/tests/regression_tests/random_ray_volume_estimator/hybrid/results_true.dat
index 6da51a711bf..16adfa6d81a 100644
--- a/tests/regression_tests/random_ray_volume_estimator/hybrid/results_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator/hybrid/results_true.dat
@@ -1,9 +1,9 @@
tally 1:
-5.934460E-01
-7.058894E-02
+2.261079E+00
+2.678641E-01
tally 2:
-3.206214E-02
-2.063370E-04
+1.043325E-01
+7.552784E-04
tally 3:
-2.096411E-03
-8.804924E-07
+6.475295E-03
+2.740906E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat
index a268d55d04a..b408131f815 100644
--- a/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat
@@ -191,9 +191,9 @@
fixed source
- 90
- 10
- 5
+ 10
+ 40
+ 20
100.0 1.0
diff --git a/tests/regression_tests/random_ray_volume_estimator/naive/results_true.dat b/tests/regression_tests/random_ray_volume_estimator/naive/results_true.dat
index f8d6d10b001..6afcb204a05 100644
--- a/tests/regression_tests/random_ray_volume_estimator/naive/results_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator/naive/results_true.dat
@@ -1,9 +1,9 @@
tally 1:
-5.935538E-01
-7.061433E-02
+2.258260E+00
+2.673097E-01
tally 2:
-3.263210E-02
-2.134164E-04
+1.100975E-01
+7.432009E-04
tally 3:
-2.107977E-03
-8.905227E-07
+6.385176E-03
+2.616155E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat
index 777ccaea510..789d280f166 100644
--- a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat
@@ -191,9 +191,9 @@
fixed source
- 90
- 10
- 5
+ 10
+ 40
+ 20
100.0 1.0
diff --git a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/results_true.dat b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/results_true.dat
index 5f297586075..3466720b9f5 100644
--- a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/results_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/results_true.dat
@@ -1,9 +1,9 @@
tally 1:
--5.745886E+02
-9.758367E+04
+-5.372158E+03
+9.133802E+06
tally 2:
-2.971927E-02
-1.827222E-04
+8.960408E-02
+1.520820E-03
tally 3:
-1.978393E-03
-7.951531E-07
+4.634379E-03
+2.696798E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator/strict_adaptive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/strict_adaptive/inputs_true.dat
new file mode 100644
index 00000000000..f7aa1795cfe
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator/strict_adaptive/inputs_true.dat
@@ -0,0 +1,247 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+ 2.5 2.5 2.5
+ 12 12 12
+ 0.0 0.0 0.0
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+
+
+
+
+
+
+
+
+ fixed source
+ 10
+ 40
+ 20
+
+
+ 100.0 1.0
+
+
+ universe
+ 1
+
+
+ multi-group
+
+ 500.0
+ 100.0
+
+
+
+ 0.0 0.0 0.0 30.0 30.0 30.0
+
+
+
+ true
+ strict_adaptive
+
+
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 3
+ flux
+ tracklength
+
+
+ 2
+ flux
+ tracklength
+
+
+ 1
+ flux
+ tracklength
+
+
+
diff --git a/tests/regression_tests/random_ray_volume_estimator/strict_adaptive/results_true.dat b/tests/regression_tests/random_ray_volume_estimator/strict_adaptive/results_true.dat
new file mode 100644
index 00000000000..ea66b9cc72b
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator/strict_adaptive/results_true.dat
@@ -0,0 +1,9 @@
+tally 1:
+2.274462E+00
+2.703615E-01
+tally 2:
+1.222058E-01
+9.217159E-04
+tally 3:
+7.435355E-03
+3.594692E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator/test.py b/tests/regression_tests/random_ray_volume_estimator/test.py
index fba4bbbbe6d..db711adc90f 100644
--- a/tests/regression_tests/random_ray_volume_estimator/test.py
+++ b/tests/regression_tests/random_ray_volume_estimator/test.py
@@ -16,15 +16,27 @@ def _cleanup(self):
os.remove(f)
+# A deliberately ray-starved configuration (~20% source region miss rate).
+# The volume estimators only differ meaningfully when regions are missed or
+# sparsely hit, so a starved run exercises every estimator code path. At
+# this density the per-estimator volume choices, the miss treatments, and
+# for the adaptive estimator the strong-source (kappa) demotion, the
+# hit-starved demotion, the end-of-inactive converged-negative demotion,
+# and the previous-flux miss treatment all fire.
@pytest.mark.parametrize("estimator", ["hybrid",
"simulation_averaged",
- "naive"
+ "naive",
+ "adaptive",
+ "strict_adaptive"
])
def test_random_ray_volume_estimator(estimator):
with change_directory(estimator):
openmc.reset_auto_ids()
model = random_ray_three_region_cube()
model.settings.random_ray['volume_estimator'] = estimator
+ model.settings.particles = 10
+ model.settings.inactive = 20
+ model.settings.batches = 40
- harness = MGXSTestHarness('statepoint.10.h5', model)
+ harness = MGXSTestHarness('statepoint.40.h5', model)
harness.main()
diff --git a/tests/regression_tests/random_ray_volume_estimator_auto/__init__.py b/tests/regression_tests/random_ray_volume_estimator_auto/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/regression_tests/random_ray_volume_estimator_auto/adjoint/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_auto/adjoint/inputs_true.dat
new file mode 100644
index 00000000000..66d297ada61
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_auto/adjoint/inputs_true.dat
@@ -0,0 +1,247 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+ 2.5 2.5 2.5
+ 12 12 12
+ 0.0 0.0 0.0
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+
+
+
+
+
+
+
+
+ fixed source
+ 10
+ 40
+ 20
+
+
+ 100.0 1.0
+
+
+ universe
+ 1
+
+
+ multi-group
+
+ 500.0
+ 100.0
+
+
+
+ 0.0 0.0 0.0 30.0 30.0 30.0
+
+
+
+ true
+ true
+
+
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 3
+ flux
+ tracklength
+
+
+ 2
+ flux
+ tracklength
+
+
+ 1
+ flux
+ tracklength
+
+
+
diff --git a/tests/regression_tests/random_ray_volume_estimator_auto/adjoint/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_auto/adjoint/results_true.dat
new file mode 100644
index 00000000000..7b6c664d32f
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_auto/adjoint/results_true.dat
@@ -0,0 +1,9 @@
+tally 1:
+3.511731E+06
+7.366501E+11
+tally 2:
+4.076663E+06
+8.358445E+11
+tally 3:
+1.531116E+07
+1.175684E+13
diff --git a/tests/regression_tests/random_ray_volume_estimator_auto/forward/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_auto/forward/inputs_true.dat
new file mode 100644
index 00000000000..dca77cb4edb
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_auto/forward/inputs_true.dat
@@ -0,0 +1,246 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+ 2.5 2.5 2.5
+ 12 12 12
+ 0.0 0.0 0.0
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+
+
+
+
+
+
+
+
+ fixed source
+ 10
+ 40
+ 20
+
+
+ 100.0 1.0
+
+
+ universe
+ 1
+
+
+ multi-group
+
+ 500.0
+ 100.0
+
+
+
+ 0.0 0.0 0.0 30.0 30.0 30.0
+
+
+
+ true
+
+
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 3
+ flux
+ tracklength
+
+
+ 2
+ flux
+ tracklength
+
+
+ 1
+ flux
+ tracklength
+
+
+
diff --git a/tests/regression_tests/random_ray_volume_estimator_auto/forward/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_auto/forward/results_true.dat
new file mode 100644
index 00000000000..ac9fcd45390
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_auto/forward/results_true.dat
@@ -0,0 +1,9 @@
+tally 1:
+2.259425E+00
+2.675738E-01
+tally 2:
+1.018431E-01
+6.713412E-04
+tally 3:
+7.288312E-03
+3.488945E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator_auto/test.py b/tests/regression_tests/random_ray_volume_estimator_auto/test.py
new file mode 100644
index 00000000000..3a144e3055f
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_auto/test.py
@@ -0,0 +1,47 @@
+import os
+
+import openmc
+from openmc.utility_funcs import change_directory
+from openmc.examples import random_ray_three_region_cube
+import pytest
+
+from tests.testing_harness import TolerantPyAPITestHarness
+
+
+class MGXSTestHarness(TolerantPyAPITestHarness):
+ def _cleanup(self):
+ super()._cleanup()
+ for f in ('mgxs.h5', 'weight_windows.h5'):
+ if os.path.exists(f):
+ os.remove(f)
+
+
+# The default "auto" volume estimator resolves by solve type. Standard
+# solves receive the adaptive estimator, while solves whose results feed
+# variance reduction (any adjoint workflow, and weight window generation)
+# receive the strict adaptive estimator. No case sets an estimator
+# explicitly, so these golds pin the routing itself, and a regression in
+# the resolution policy shifts the affected case's results. The forward and
+# adjoint cases pin the adjoint-flag trigger, while the weight_windows case
+# pins the generator-presence trigger.
+@pytest.mark.parametrize("solve", ["forward", "adjoint", "weight_windows"])
+def test_random_ray_volume_estimator_auto(solve):
+ with change_directory(solve):
+ openmc.reset_auto_ids()
+ model = random_ray_three_region_cube()
+ if solve == "adjoint":
+ model.settings.random_ray['adjoint'] = True
+ elif solve == "weight_windows":
+ ww_mesh = openmc.RegularMesh()
+ ww_mesh.dimension = (6, 6, 6)
+ ww_mesh.lower_left = (0.0, 0.0, 0.0)
+ ww_mesh.upper_right = (30.0, 30.0, 30.0)
+ model.settings.weight_window_generators = \
+ openmc.WeightWindowGenerator(
+ method="fw_cadis", mesh=ww_mesh, max_realizations=40)
+ model.settings.particles = 10
+ model.settings.inactive = 20
+ model.settings.batches = 40
+
+ harness = MGXSTestHarness('statepoint.40.h5', model)
+ harness.main()
diff --git a/tests/regression_tests/random_ray_volume_estimator_auto/weight_windows/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_auto/weight_windows/inputs_true.dat
new file mode 100644
index 00000000000..edb3f4834ff
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_auto/weight_windows/inputs_true.dat
@@ -0,0 +1,261 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+ 2.5 2.5 2.5
+ 12 12 12
+ 0.0 0.0 0.0
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+
+
+
+
+
+
+
+
+ fixed source
+ 10
+ 40
+ 20
+
+
+ 100.0 1.0
+
+
+ universe
+ 1
+
+
+ multi-group
+
+
+ 1
+ neutron
+ 40
+ 1
+ true
+ fw_cadis
+
+
+
+ 6 6 6
+ 0.0 0.0 0.0
+ 30.0 30.0 30.0
+
+
+ 500.0
+ 100.0
+
+
+
+ 0.0 0.0 0.0 30.0 30.0 30.0
+
+
+
+ true
+
+
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 3
+ flux
+ tracklength
+
+
+ 2
+ flux
+ tracklength
+
+
+ 1
+ flux
+ tracklength
+
+
+
diff --git a/tests/regression_tests/random_ray_volume_estimator_auto/weight_windows/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_auto/weight_windows/results_true.dat
new file mode 100644
index 00000000000..f9eb7e003ae
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_auto/weight_windows/results_true.dat
@@ -0,0 +1,442 @@
+tally 1:
+3.511731E+06
+7.366501E+11
+tally 2:
+4.076663E+06
+8.358445E+11
+tally 3:
+1.531116E+07
+1.175684E+13
+tally 4:
+3.511731E+06
+7.366501E+11
+3.188749E+06
+5.374186E+11
+3.650230E+06
+7.551441E+11
+3.754951E+06
+7.778043E+11
+3.468497E+06
+7.956805E+11
+1.004161E+06
+5.103314E+10
+4.647621E+06
+1.334221E+12
+4.017974E+06
+8.770192E+11
+4.029574E+06
+8.966119E+11
+3.953177E+06
+8.652758E+11
+3.957070E+06
+8.892706E+11
+7.152463E+05
+2.836055E+10
+4.148038E+06
+9.672359E+11
+3.750889E+06
+7.648028E+11
+3.290958E+06
+6.160634E+11
+3.956685E+06
+8.356989E+11
+4.787057E+06
+1.189227E+12
+3.535766E+06
+6.427457E+11
+4.149550E+06
+1.006099E+12
+4.658888E+06
+1.149165E+12
+4.145351E+06
+9.728065E+11
+4.606747E+06
+1.205466E+12
+5.017471E+06
+1.401316E+12
+1.000641E+07
+5.175932E+12
+5.085297E+06
+1.542497E+12
+3.579261E+06
+7.951955E+11
+3.760606E+06
+8.413000E+11
+4.280654E+06
+9.893678E+11
+4.550993E+06
+1.153381E+12
+2.043152E+07
+2.134729E+13
+2.364753E+06
+2.880193E+11
+1.134082E+06
+6.713813E+10
+1.364444E+06
+9.760639E+10
+1.019639E+06
+5.340783E+10
+4.621795E+06
+1.181955E+12
+2.583832E+07
+3.849646E+13
+3.981869E+06
+1.086257E+12
+3.294522E+06
+6.029500E+11
+3.744218E+06
+7.967128E+11
+4.110475E+06
+8.967747E+11
+3.749926E+06
+8.162320E+11
+1.261849E+06
+8.335011E+10
+3.949725E+06
+9.014090E+11
+3.356913E+06
+5.874923E+11
+4.228265E+06
+1.026989E+12
+7.370925E+06
+3.037283E+12
+4.288574E+06
+1.000915E+12
+1.697752E+06
+1.488605E+11
+4.490552E+06
+1.208635E+12
+3.871653E+06
+8.072918E+11
+3.750247E+06
+7.870138E+11
+4.226575E+06
+9.572865E+11
+4.278271E+06
+9.839734E+11
+2.853690E+06
+4.295371E+11
+5.041098E+06
+1.477825E+12
+4.617993E+06
+1.225844E+12
+4.666636E+06
+1.204811E+12
+4.480502E+06
+1.062325E+12
+5.129911E+06
+1.423279E+12
+1.018553E+07
+5.448004E+12
+4.645548E+06
+1.225328E+12
+3.414316E+06
+7.183967E+11
+3.299665E+06
+6.152288E+11
+4.676476E+06
+1.226497E+12
+5.993866E+06
+1.916331E+12
+2.273457E+07
+2.634530E+13
+1.024619E+06
+5.418869E+10
+8.671716E+05
+4.105630E+10
+9.781613E+05
+4.922450E+10
+8.982715E+05
+4.632745E+10
+8.214506E+06
+3.502364E+12
+1.892745E+08
+1.817735E+15
+4.395286E+06
+1.140780E+12
+3.785742E+06
+8.767900E+11
+4.009253E+06
+9.466143E+11
+4.485226E+06
+1.135228E+12
+3.926414E+06
+8.341972E+11
+7.427310E+06
+2.962614E+12
+3.218770E+06
+5.613005E+11
+3.606096E+06
+6.835904E+11
+4.143995E+06
+8.943078E+11
+4.162951E+06
+9.119008E+11
+3.546854E+06
+6.595082E+11
+3.688159E+06
+7.104847E+11
+3.295761E+06
+6.129546E+11
+3.351777E+06
+6.027285E+11
+3.842937E+06
+8.073173E+11
+4.364532E+06
+1.012091E+12
+4.387931E+06
+1.025643E+12
+6.201362E+06
+1.994758E+12
+4.134093E+06
+9.194351E+11
+3.853368E+06
+9.429980E+11
+4.133933E+06
+1.051933E+12
+4.587503E+06
+1.204363E+12
+4.775310E+06
+1.220055E+12
+2.193940E+07
+2.484892E+13
+3.781856E+06
+8.523019E+11
+3.501634E+06
+7.260762E+11
+3.742681E+06
+7.892108E+11
+4.463231E+06
+1.270897E+12
+5.608885E+06
+1.688139E+12
+2.975687E+07
+4.750545E+13
+9.838028E+06
+5.129536E+12
+2.924712E+06
+4.804874E+11
+3.236877E+06
+5.427566E+11
+7.509008E+05
+3.294363E+10
+2.171245E+06
+2.478454E+11
+4.602123E+07
+1.190969E+14
+3.884936E+06
+8.360487E+11
+3.405745E+06
+6.871056E+11
+4.778259E+06
+1.368089E+12
+4.374198E+06
+1.117471E+12
+3.974206E+06
+8.501475E+11
+2.411812E+07
+2.928116E+13
+3.691588E+06
+7.339252E+11
+3.820362E+06
+8.680514E+11
+3.691492E+06
+7.170432E+11
+3.796136E+06
+7.551591E+11
+4.074632E+06
+8.975806E+11
+8.125921E+06
+3.353014E+12
+3.176230E+06
+5.742728E+11
+3.757790E+06
+7.535155E+11
+4.115031E+06
+8.998277E+11
+3.914108E+06
+7.994967E+11
+4.154937E+06
+9.090614E+11
+1.468863E+07
+1.108387E+13
+4.261661E+06
+1.221398E+12
+5.131497E+06
+1.479243E+12
+4.169697E+06
+9.408782E+11
+4.247139E+06
+9.734525E+11
+3.723081E+06
+8.021019E+11
+3.284082E+06
+6.716747E+11
+4.209247E+06
+1.033962E+12
+4.319313E+06
+1.026540E+12
+4.494468E+06
+1.068931E+12
+4.215369E+06
+9.851685E+11
+4.378681E+06
+1.054765E+12
+4.718820E+06
+1.455518E+12
+4.447193E+06
+1.021099E+12
+2.294114E+06
+2.729155E+11
+4.377327E+06
+9.788008E+11
+2.081229E+06
+2.307087E+11
+2.935037E+06
+5.750307E+11
+3.558339E+07
+6.512040E+13
+3.718008E+06
+7.878636E+11
+3.225813E+06
+6.619645E+11
+3.154046E+06
+5.675161E+11
+3.142328E+06
+5.655317E+11
+3.396229E+06
+6.808510E+11
+1.389886E+06
+1.101227E+11
+3.835349E+06
+7.871905E+11
+3.311217E+06
+5.752228E+11
+2.914361E+06
+4.489648E+11
+3.354950E+06
+6.075447E+11
+3.044035E+06
+5.047191E+11
+1.616458E+06
+1.372809E+11
+3.838041E+06
+7.978651E+11
+3.284366E+06
+6.000267E+11
+3.415545E+06
+6.273188E+11
+3.664195E+06
+7.879427E+11
+3.587293E+06
+9.920550E+11
+7.555655E+06
+2.958614E+12
+3.136367E+06
+5.698810E+11
+4.425221E+06
+1.090182E+12
+5.349680E+06
+1.601384E+12
+4.571635E+06
+1.116531E+12
+3.480027E+06
+6.946927E+11
+3.410988E+06
+6.135500E+11
+2.956921E+06
+5.456226E+11
+3.849969E+06
+8.590042E+11
+5.848881E+06
+1.842619E+12
+4.992325E+06
+1.491634E+12
+5.217210E+06
+1.450929E+12
+1.159970E+07
+7.155993E+12
+7.849803E+05
+3.676798E+10
+1.486403E+06
+1.171380E+11
+6.162755E+06
+1.936067E+12
+2.581138E+06
+3.504055E+11
+8.777419E+06
+5.124225E+12
+3.867422E+07
+8.940284E+13
+6.252498E+05
+2.797823E+10
+1.386801E+06
+9.832696E+10
+1.219518E+06
+7.524495E+10
+1.377031E+06
+9.892865E+10
+2.572278E+06
+3.346946E+11
+5.503366E+06
+1.544794E+12
+5.508395E+06
+1.568296E+12
+8.937474E+05
+4.416202E+10
+1.315355E+06
+1.010789E+11
+3.927717E+06
+8.017487E+11
+2.631941E+06
+3.604918E+11
+4.973776E+06
+1.320408E+12
+9.384108E+06
+4.487679E+12
+7.096218E+05
+2.913956E+10
+1.105527E+07
+6.197818E+12
+8.303532E+06
+3.557135E+12
+1.393782E+06
+1.059622E+11
+1.252992E+07
+8.452169E+12
+7.598952E+06
+2.951110E+12
+5.548974E+05
+1.868332E+10
+6.795833E+06
+2.499745E+12
+1.276300E+07
+8.359363E+12
+3.473792E+06
+6.366007E+11
+4.292349E+06
+1.051648E+12
+1.040833E+06
+6.564349E+10
+6.971116E+05
+3.020991E+10
+2.218284E+07
+2.522320E+13
+1.296505E+07
+8.891396E+12
+6.897286E+06
+2.768700E+12
+6.825276E+07
+2.402817E+14
+2.800386E+06
+4.295429E+11
+1.784838E+06
+1.678569E+11
+8.425260E+06
+3.647005E+12
+9.344687E+06
+4.541404E+12
+2.352474E+07
+5.039598E+13
+4.715877E+08
+1.133465E+16
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/adaptive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/adaptive/inputs_true.dat
new file mode 100644
index 00000000000..82587334ecb
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/adaptive/inputs_true.dat
@@ -0,0 +1,248 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+ 2.5 2.5 2.5
+ 12 12 12
+ 0.0 0.0 0.0
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+
+
+
+
+
+
+
+
+ fixed source
+ 10
+ 40
+ 20
+
+
+ 100.0 1.0
+
+
+ universe
+ 1
+
+
+ multi-group
+
+ 500.0
+ 100.0
+
+
+
+ 0.0 0.0 0.0 30.0 30.0 30.0
+
+
+
+ true
+ adaptive
+ linear
+
+
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 3
+ flux
+ tracklength
+
+
+ 2
+ flux
+ tracklength
+
+
+ 1
+ flux
+ tracklength
+
+
+
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/adaptive/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/adaptive/results_true.dat
new file mode 100644
index 00000000000..c80049b6427
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/adaptive/results_true.dat
@@ -0,0 +1,9 @@
+tally 1:
+2.285865E+00
+2.736206E-01
+tally 2:
+1.030651E-01
+8.460405E-04
+tally 3:
+7.895001E-03
+4.249732E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat
index dd11567f69d..53d7bbac171 100644
--- a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat
@@ -191,7 +191,7 @@
fixed source
- 90
+ 10
40
20
@@ -215,8 +215,8 @@
true
- linear
hybrid
+ linear
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat
index e90d6bfdcb8..e1937ae6510 100644
--- a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat
@@ -1,9 +1,9 @@
tally 1:
-2.339086E+00
-2.747305E-01
+2.334964E+00
+3.212860E-01
tally 2:
-1.089827E-01
-6.069324E-04
+8.558657E-02
+5.274716E-03
tally 3:
-7.300831E-03
-2.715940E-06
+6.549250E-03
+2.816575E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat
index 6933fba435e..31620e93ee8 100644
--- a/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat
@@ -191,7 +191,7 @@
fixed source
- 90
+ 10
40
20
@@ -215,8 +215,8 @@
true
- linear
naive
+ linear
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/naive/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/naive/results_true.dat
index 5258ffd9c84..991fc3e9abe 100644
--- a/tests/regression_tests/random_ray_volume_estimator_linear/naive/results_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/naive/results_true.dat
@@ -1,9 +1,9 @@
tally 1:
-2.339567E+00
-2.748423E-01
+2.313040E+00
+3.180880E-01
tally 2:
-1.085878E-01
-6.024509E-04
+1.040963E-01
+6.664367E-04
tally 3:
-7.299803E-03
-2.741867E-06
+6.583311E-03
+2.750085E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat
index 3ccab1d21b7..b681d2618a5 100644
--- a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat
@@ -191,7 +191,7 @@
fixed source
- 90
+ 10
40
20
@@ -215,8 +215,8 @@
true
- linear
simulation_averaged
+ linear
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/results_true.dat
index 1e8aa9fb75f..0ea3a88bd83 100644
--- a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/results_true.dat
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/results_true.dat
@@ -1,9 +1,9 @@
tally 1:
-2.670850E+02
-4.432939E+05
+-5.372146E+03
+9.133101E+06
tally 2:
-1.116994E-01
-6.491358E-04
+7.428803E-02
+3.870266E-03
tally 3:
-7.564527E-03
-2.947794E-06
+4.615173E-03
+2.683571E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/strict_adaptive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/strict_adaptive/inputs_true.dat
new file mode 100644
index 00000000000..4d0d5f1f7a6
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/strict_adaptive/inputs_true.dat
@@ -0,0 +1,248 @@
+
+
+
+ mgxs.h5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+ 2.5 2.5 2.5
+ 12 12 12
+ 0.0 0.0 0.0
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+1 1 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+2 2 2 2 2 2 2 2 2 2 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+3 3 3 3 3 3 3 3 3 3 3 3
+
+
+
+
+
+
+
+
+
+ fixed source
+ 10
+ 40
+ 20
+
+
+ 100.0 1.0
+
+
+ universe
+ 1
+
+
+ multi-group
+
+ 500.0
+ 100.0
+
+
+
+ 0.0 0.0 0.0 30.0 30.0 30.0
+
+
+
+ true
+ strict_adaptive
+ linear
+
+
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 3
+ flux
+ tracklength
+
+
+ 2
+ flux
+ tracklength
+
+
+ 1
+ flux
+ tracklength
+
+
+
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/strict_adaptive/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/strict_adaptive/results_true.dat
new file mode 100644
index 00000000000..4e0dbef63cd
--- /dev/null
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/strict_adaptive/results_true.dat
@@ -0,0 +1,9 @@
+tally 1:
+2.282556E+00
+2.728257E-01
+tally 2:
+1.337071E-01
+1.094397E-03
+tally 3:
+8.725872E-03
+5.015646E-06
diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/test.py b/tests/regression_tests/random_ray_volume_estimator_linear/test.py
index 94a14f3ad3b..5be5b79b04c 100644
--- a/tests/regression_tests/random_ray_volume_estimator_linear/test.py
+++ b/tests/regression_tests/random_ray_volume_estimator_linear/test.py
@@ -16,17 +16,28 @@ def _cleanup(self):
os.remove(f)
+# A deliberately ray-starved configuration (~20% source region miss rate).
+# The volume estimators only differ meaningfully when regions are missed or
+# sparsely hit, so a starved run exercises every estimator code path. At
+# this density the per-estimator volume choices, the miss treatments, and
+# for the adaptive estimator the strong-source (kappa) demotion, the
+# hit-starved demotion, the end-of-inactive converged-negative demotion,
+# and the previous-flux miss treatment all fire.
@pytest.mark.parametrize("estimator", ["hybrid",
"simulation_averaged",
- "naive"
+ "naive",
+ "adaptive",
+ "strict_adaptive"
])
def test_random_ray_volume_estimator_linear(estimator):
with change_directory(estimator):
openmc.reset_auto_ids()
model = random_ray_three_region_cube()
- model.settings.random_ray['source_shape'] = 'linear'
model.settings.random_ray['volume_estimator'] = estimator
+ model.settings.random_ray['source_shape'] = 'linear'
+ model.settings.particles = 10
model.settings.inactive = 20
model.settings.batches = 40
+
harness = MGXSTestHarness('statepoint.40.h5', model)
harness.main()
diff --git a/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat b/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat
index a0d84257a8d..ed14e0fbf6d 100644
--- a/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat
+++ b/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat
@@ -236,6 +236,7 @@
flat
+ hybrid
diff --git a/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat b/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat
index 62f8478586b..33c75bb271d 100644
--- a/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat
+++ b/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat
@@ -236,6 +236,7 @@
linear
+ hybrid
diff --git a/tests/regression_tests/weightwindows_fw_cadis_mesh/test.py b/tests/regression_tests/weightwindows_fw_cadis_mesh/test.py
index 680e9dc6df7..40caf1700f6 100644
--- a/tests/regression_tests/weightwindows_fw_cadis_mesh/test.py
+++ b/tests/regression_tests/weightwindows_fw_cadis_mesh/test.py
@@ -44,6 +44,7 @@ def test_weight_windows_fw_cadis_mesh(shape):
model.settings.inactive = 20
model.settings.random_ray['source_shape'] = shape
+ model.settings.random_ray['volume_estimator'] = 'hybrid'
harness = MGXSTestHarness('statepoint.30.h5', model)
harness.main()
diff --git a/tests/unit_tests/test_random_ray_default_persistence.py b/tests/unit_tests/test_random_ray_default_persistence.py
new file mode 100644
index 00000000000..d261917a6c9
--- /dev/null
+++ b/tests/unit_tests/test_random_ray_default_persistence.py
@@ -0,0 +1,46 @@
+"""The random ray volume-estimator setting must not leak across an
+in-process finalize/re-initialize cycle (openmc.lib workflows such as
+iterative weight window generation). The solver never modifies the
+configured setting, as "auto" is resolved into a separate run-scoped value.
+However, the configured setting is a static that XML parsing only assigns
+when the element is present, so openmc_finalize_random_ray() must restore
+the "auto" default between runs. Running an explicit estimator first and a
+default model second makes a missed restore visible, as the second run
+would report the first run's estimator instead of resolving "auto". The
+default second run is an adjoint solve, which also pins the auto routing
+under openmc.lib (it must resolve to the strict adaptive estimator, once
+per solve)."""
+
+import openmc
+import openmc.lib
+from openmc.examples import random_ray_three_region_cube
+
+
+def test_random_ray_default_estimator_persistence(run_in_tmpdir, capfd):
+ openmc.reset_auto_ids()
+ model = random_ray_three_region_cube()
+ model.settings.particles = 10
+ model.settings.inactive = 2
+ model.settings.batches = 4
+
+ reported = []
+ for explicit in (True, False):
+ if explicit:
+ model.settings.random_ray['volume_estimator'] = 'naive'
+ else:
+ del model.settings.random_ray['volume_estimator']
+ model.settings.random_ray['adjoint'] = True
+ model.export_to_model_xml()
+ openmc.lib.init()
+ openmc.lib.run_random_ray()
+ openmc.lib.finalize()
+ out = capfd.readouterr().out
+ for line in out.splitlines():
+ if 'Volume Estimator Type' in line:
+ reported.append(line.split('=')[-1].strip())
+
+ # The forward run reports once, while the adjoint run reports once per
+ # solve (forward-for-adjoint, adjoint).
+ assert reported == ['Naive', 'Strict Adaptive (auto)',
+ 'Strict Adaptive (auto)'], (
+ f"volume estimator leaked across in-process reruns: {reported}")