Adaptive Volume Estimators - #4110
Conversation
The adaptive estimator generalizes hybrid: it uses the simulation-averaged volume by default and demotes individual source regions to the naive (iteration) volume plus a previous-iteration miss treatment wherever the simulation-averaged estimator is at risk. Demotion is triggered by (1) a strong inhomogeneous source -- any group whose reduced source q/Sigma_t is negative or exceeds ADAPTIVE_VOLUME_KAPPA times the previous iteration's scalar flux, the condition under which the flux update is a near-cancellation that requires volume-consistent terms (this subsumes hybrid's external-source heuristic and also catches optically thin in-scatter-fed regions that hybrid misses); (2) hit starvation (the existing small-region criterion); and (3) a converged-negative flux: the unmodified simulation-averaged estimator runs through the inactive phase while each region's flux is accumulated, and any region whose accumulated flux is negative at the end of the inactive phase is demoted for all active batches. Deciding on the sign of the accumulated estimate rather than reacting to per-iteration fluctuations avoids clipping the lower tail of the noise distribution, so merely-noisy regions keep the unbiased estimator. Under linear sources, strong-source regions also fall back to a flat source representation, since their gradient terms carry per-iteration noise at the q/Sigma_t scale that the volume choice cannot cancel; the general gradient (tilt) limiter remains a separate follow-up. The adaptive estimator becomes the default (previously hybrid), both at static initialization and in openmc_finalize_random_ray(), which restores built-in defaults between in-process runs. The eigenvalue fw-adjoint double solve now clears the accumulated forward flux before the adjoint solve (in fixed source mode set_fw_adjoint_sources already consumes and zeroes it), so the adjoint solve's demotion decision operates on clean adjoint statistics. An end-of-run report breaks down the naive-volume treatment by cause. naive/simulation_averaged/hybrid behavior is unchanged (the estimator application is refactored into two per-region policy decisions that reproduce the legacy estimators exactly). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Methods-guide derivation and rationale for the strong-source (kappa) test and the end-of-inactive converged-negative demotion, a pros/cons and recommendations table in the user's guide, and the volume_estimator entry in the settings specification, all reflecting adaptive as the default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New reference cases: adaptive on the three-region cube (flat and linear source shapes); a deliberately ray-starved adaptive case (~20% miss rate) that exercises every mechanism at once (strong-source demotion, hit-starved demotion, end-of-inactive converged-negative demotion, and the previous-flux miss treatment); the transport-corrected (P0) pin cell under adaptive, whose negative within-group scattering exercises the negative-reduced-source treatment; a ray-starved fixed source adjoint case whose second (adjoint) solve makes real demotion decisions; and a ray-starved subdivided eigenvalue case engaging the demotion machinery in eigenvalue mode. A unit test runs the same model twice through openmc.lib in one process and asserts the reported estimator both times, guarding the default restored by openmc_finalize_random_ray(). The existing random ray regression tests that relied on the implicit default are pinned to hybrid so their reference results are byte-for-byte unchanged (only test.py and the recorded inputs change), also avoiding churn in the planned follow-up that changes the adaptive fallback estimator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… sources Previously only strong-source (kappa) demotions zeroed their source gradients under linear source shapes; converged-negative demotions kept live tilts fitted from the statistics of regions whose accumulated flux is negative -- shape information with no meaning that only injects noise into exactly the cells already prone to negativity. Demotion is now uniform in effect: any demoted region uses the naive volume, the previous-flux miss treatment, and a flat source. On a coarse-ray streaming problem this removes a third of the negative flux tally bins (9,664 -> 6,429 of 42,875) with the total flux unchanged well within statistics. The end-of-run report line becomes 'Demoted -> Flat (linear)' covering the full flattened set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documentation and code comments now describe present behavior only, with
change rationale left to commit messages: the methods guide states the
default once (with the adaptive estimator) instead of narrating what it
replaced, and wording that referenced mechanisms which do not exist in the
code base ('per-iteration rescue', 'positivity floor', 'no longer
enforced') is replaced with statements of what the solver actually does.
The converged-negative demotion flag is renamed from n_negative_fluxes
(a counter name on what is a 0/1 flag) to converged_negative, matching
the report and documentation terminology.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The volume-estimator parametrization axis enumerates estimators; 'adaptive_starved' is a scenario (the adaptive estimator at a deliberately starved ray density), so it moves to a dedicated test function. Reference results are unchanged (same configuration and working directory). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
At the previous ray density the source region miss rate was zero, so the miss-treatment code paths -- where the volume estimators meaningfully differ -- never executed in these tests. All four estimators now run deliberately ray-starved (~20% miss rate), which exercises the per-estimator volume choices, the miss treatments, and all of the adaptive demotion mechanisms (strong-source, hit-starved, and converged-negative) in every parametrization; the separate starved adaptive case is absorbed into the adaptive parametrization. Reference results are regenerated for the new configuration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ature The strong-source test demoted any region whose reduced source was negative in some group. In one-group problems the source sign is locked to the previous flux sign (q = c*phi + q_external), so that branch degenerated into a per-iteration negative-flux reactor: noisy streaming regions flip-flopped between the simulation-averaged and naive policies based on the sign of the previous iterate, conditioning the estimator choice on the noise -- exactly the fluctuation-reactive behavior the end-of-inactive converged-negative demotion is designed to avoid. On the cube stress problem of Cosgrove and Tramm (Negative fluxes and cell-miss errors in the random ray method, Prog. Nucl. Energy 192 (2026) 106153), built by openmc.examples.random_ray_three_region_cube, at a 20.5% miss rate this misclassified 29% of all regions as strong-source and biased the adaptive estimator's void-region integral flux by -19% relative to the Monte Carlo reference (hybrid: +0.1%). A negative reduced source now counts as strong only when the region's own previous flux is non-negative -- the genuine transport-corrected (TCP0) signature, where negative within-group scattering drives the source negative independently of the flux. With the fix the cube problem gives adaptive a void error of -0.04% (strong-source count 8, exactly the external source region), matching hybrid in all three regions, while sign-locked chronic negativity is handled by the converged-negative demotion (6 -> 24 regions). Adaptive reference results are regenerated; the other estimators are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The per-iteration strong-source test, evaluated on noisy single-iteration values, has one blind state: an unlucky iteration can drag a strongly fed region's source and flux negative together, and in that state neither the ratio condition nor the negative-source condition can fire, so the region rides the excursion on unprotected simulation-averaged updates. Because the per-iteration noise scale in such regions is set by q/Sigma_t rather than by the flux, their flux averaged over a whole phase of batches can straddle zero: on the irradiation-vault validation problem (in-scatter-fed thin hall, 5.9% miss), 82 of 42,875 slow-group tally bins finish negative, each within 1.5 sigma of zero, on a different set of bins for every seed. No sign-based demotion can remove this population -- the transition sign test already catches its fair share (92 regions), and the survivors simply re-roll in the active window. The latch keys on the stable property the whole class shares: at the inactive->active transition, alongside the existing converged-negative decision and from the same accumulated flux, any region whose flux-independent feed (cross-group in-scatter, fission, and external source) exceeds ADAPTIVE_VOLUME_KAPPA times its own accumulated flux is demoted for the active phase. A region with no cross-group or external feed can never latch, so the estimator choice still never reacts to sign-locked noise (the Cosgrove-cube constraint), and the external source term is only read in fixed source mode, where the arrays exist. Validation: the vault's negative bins go from 82/61 (two seeds) to 0/0 with the formerly negative bin set now scored to -2.7%/-1.8% (was -203%) and the global answer unchanged; the Cosgrove cube is identical to the digit; the latch is inert on TLD-class door-2 problems (no feed) and on C5G7 (0 regions latched, k/pin powers identical to the digit). Also reports the transition decisions on their own lines, since the existing by-cause block is a final-iteration snapshot whose priority attribution files transition-demoted regions under Strong Source. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ray-starved adaptive tests exist as sensitive detectors for changes to the adaptive estimator's demotion policy, and the latch fires in both at their starved densities (a noisy 20-batch accumulated flux trips the feed test in the adjoint fixed-source problem via the external term and in the starved eigenvalue lattice via the fission feed). The other thirteen tests in the adaptive-covered set -- including every volume-estimator parametrization and the pinned-hybrid suite -- pass against their existing golds byte-for-byte. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The user guide should describe what the mechanism does, not cite the PR's validation suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The end-of-run block had grown to nine diagnostic lines whose labels (strong-feed latch, transition demotions) mean little without developer context. At the default verbosity the report now prints one line -- 'Number of Naive Demotions', the count of source regions receiving the naive volume treatment in the final batch for any reason (the one-shot transition demotions plus that batch's per-iteration demotions) -- which is the barometer a user actually needs. The per-cause breakdown and the transition-decision counts move behind verbosity 8, a previously unused level that sits above the default (7) and below the per-particle output (9), and the verbosity table in the settings documentation gains the level-8 entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The verbosity-8 view had two blocks describing the same regions with colliding vocabulary: 'Converged Negative (demoted)' was a residual attribution bucket while 'Converged Negative (sign)' was a decision count, and 'Strong-Feed Latch' is the same physical condition as 'Strong Source', just decided once from converged data instead of per batch. Replace both blocks with a single partition of the demotion total into four mutually exclusive causes named on a consistent cause x when-decided axis: Strong source (end of inactive) Strong source (per batch) Negative flux (end of inactive) Hit-starved (per batch) The transition decisions are counted with first priority in the final-batch snapshot (their flags are fixed for the whole active phase, so those counts equal the decisions made at the transition), which removes the need for the separate transition-count bookkeeping and its report block entirely, and makes visible at a glance how few regions the per-batch test catches beyond the latched population. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Under a linear source shape every demoted region already runs with a flat representation -- hit-starved regions through the mainline small-region moment zeroing, the other causes through the adaptive gradient fallback -- so the true linear-to-flat count is simply the demotion total, and the printed line (total minus hit-starved) was both derivable from the other lines and not that count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comments describe the code that exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On a healthy overlay-mesh C5G7 eigenvalue (102x102 regions, 200 rays, 0.16% miss), the per-batch ratio test was demoting a churning ~1.4-2% of regions every active batch -- none of them strong in equilibrium (the strong-feed latch fires on zero regions) -- and that noise-conditioned, one-sided treatment biased tallied batches: k ran +18/+19/+18 pcm above simulation_averaged across three paired seeds with a flat source, and +106/+114/+83 pcm above hybrid with linear_xy (where demotion also flattens gradients, holding the solution at the flat-source answer). Two alternatives measured worse or partial: the tilt limiter recovers only ~40 pcm of the linear bias, and removing just the gradient fallback while keeping the volume demotion is worse than baseline. The ratio condition now applies only while the source is converging (the inactive batches), where it is the stability guard that keeps door-1 problems finite. In the active batches the estimator choice is governed by the stable classifications alone -- the strong-feed latch, the converged-negative sign demotion, hit-starved regions, and the per-batch negative-source (TCP0) condition -- so tallied batches never see noise-conditioned demotion. With the change the C5G7 overlay-mesh case gives k within -0/-1/-0 pcm of simulation_averaged (flat, paired seeds) and +11/+18/+8 pcm of hybrid (linear_xy), with pin-power AAPE matching to 0.006%; the irradiation vault (0 negative bins both seeds, totals to the digit), the Cosgrove cube (to the digit), and the TLD room (to the digit) are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ray-starved detector tests have active-phase per-batch ratio demotions on the previous code, so their results shift; the other thirteen adaptive-covered tests pass against their existing golds byte-for-byte. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…phase At scale, an inactive phase long enough to converge deep regions may be unaffordable, and the transition-time decisions alone then miss chronically unstable regions whose escapees corrupt the active phase (observed on a 105M-region shielding model as mass negative fluxes). The two accumulated- flux demotions now re-evaluate every active batch, demote-only, from a new running flux accumulator (kept separate from the active-only tally accumulator, which the sign test also watches so a positive inactive sum cannot mask a negative reported mean). Transition-batch decisions are unchanged; the five adaptive golds shift by a few pcm from the added active-phase demotions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Weight window generation and adjoint workflows require strictly non-negative fluxes: the adjoint source is built from the forward flux, so even a small population of noise-driven negative tally bins (which the adaptive estimator permits by design in near-zero-flux regions, and which grows through the adjoint solve) contaminates the generated windows. No demotion criterion can close this gap by itself -- a region can inherit negativity through in-scatter from neighbors that have not yet been demoted -- so a value-level treatment is required. The strict adaptive estimator runs the adaptive machinery unchanged and adds a per-batch non-negativity enforcement: a negative batch flux is first rescued (its transport term algebraically rescaled to the batch volume, reproducing the naive-volume update) and floored at the previous iterate if still negative, which an induction from the non-negative initial condition turns into a guarantee. Regions whose flux goes negative chronically are demoted outright: the floor masks the accumulated-flux sign signal the adaptive demotion relies on, and without the chronic channel such regions would be clipped every batch, a one-sided ratchet that biases them upward. On shielding benchmarks the strict estimator matches the adaptive estimator's accuracy with zero negative fluxes where adaptive leaves a small negative residue at short active batch counts, and it degrades far more gracefully than the naive estimator at coarse ray densities (region errors of a few percent where naive exceeds one hundred percent). The cost is a small conservative bias (several hundred pcm on eigenvalue problems). The volume estimator now defaults to "auto", which resolves by solve type at the start of the run: strict adaptive for solves whose results feed variance reduction (weight window generation, and any adjoint workflow, including the forward solve an adjoint source is derived from), and adaptive for all other solves. The resolution is reported in the simulation output, and explicit estimator selections override it. New regression tests pin the strict estimator in flat and linear source modes and the auto routing itself in both solve types. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The auto regression test's forward and adjoint cases pin the adjoint-flag trigger of the automatic volume estimator selection, but not its second trigger: a weight window generator present in settings routes even a purely forward run to the strict adaptive estimator. Add a weight_windows case that attaches an FW-CADIS generator without setting an estimator, so a regression in the generator-presence trigger shifts the gold. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The unit test guarding the finalize-time restoration of the default volume estimator still asserted the pre-auto report string, so it fails on the current branch (the report now reads "Adaptive (auto)"). Beyond the string, the auto default changes what the test must do to detect a leak: auto is resolved by overwriting the stored setting at the start of each run, and a forward rerun re-resolves to the same estimator whether or not finalize restored the default, so running the same forward model twice can no longer distinguish a reset from stale state. Run an adjoint solve first (which resolves the setting to the strict adaptive estimator) and a forward solve second: if finalize fails to restore "auto", the forward run inherits the strict estimator and the reported type exposes it. This is also the sequence real openmc.lib workflows use (an adjoint weight-window generation run followed by forward runs in the same process). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The auto default was resolved by overwriting the configured volume_estimator_ static with the concrete estimator at the start of the run, which made the solver a second writer of a user setting: if openmc_finalize_random_ray() failed to restore the default, a later in-process run inherited the previous run's resolved estimator (an adjoint weight-window generation run would leak the strict adaptive estimator into subsequent forward runs of the same process). Resolve into a separate resolved_volume_estimator_ instead, assigned unconditionally at the start of every random ray solve, and point all solver code at it. The configured setting is now never modified by the solver, so the resolved value cannot go stale by construction, and the "(auto)" report suffix follows directly from the configured value, replacing the volume_estimator_is_auto_ flag. The finalize-time restore of the configured setting remains, as for any settings static (XML parsing only assigns it when the element is present), and the persistence unit test now pins that surviving hazard: an explicit estimator run followed by a default adjoint run in one process, which also exercises the auto routing under openmc.lib. No behavior changes; all reference results are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compress the adaptive and strict adaptive documentation to match the register of the surrounding text: the user's guide estimator table rows are cut to glanceable descriptions in line with the existing rows, the default-selection paragraphs and the methods discussion are shortened to their load-bearing content, and the settings references state the options and the automatic selection without re-explaining the estimators. Also drop the claims that the strict adaptive estimator guarantees non-negative fluxes -- it enforces a per-batch fixup on the flat flux iterates, which is not a theoretical guarantee on all outputs (under a linear source shape the reconstructed in-region flux can still locally dip below zero) -- and confine the discussion of negative fluxes to the mechanism explanations, framing them as arising in pathological cases rather than as a routine occurrence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
The simulation averaged estimator is unbiased, not merely low-bias. Correct the description in the new default-selection paragraph, and the two pre-existing instances in the hybrid estimator's table row and methods discussion that made the same claim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
Restructure the added comments, docstrings, and documentation prose to read like the surrounding text, replacing dash asides, semicolon joins, and colon-led definitions with plain sentences. No wording changes to upstream text and no changes to code or reference results. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
Rewrite the methods discussion of the adaptive and strict adaptive volume estimators from scratch. The purpose of each estimator is now stated up front, the demotion conditions are given as short separate sentences instead of one long enumeration, and the mechanism detail that the section does not need is left to the code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
Place a short entry for the auto default first in the user's guide estimator comparison table, so readers see immediately that OpenMC selects an appropriate estimator on its own and that the rest of the table is only relevant when overriding it. The selection rule itself is described in the paragraphs that follow the table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
The weightwindows_fw_cadis_mesh test was the one FW-CADIS test that set no volume estimator, so it silently rode the default. Under the new auto default it resolves to the strict adaptive estimator and its reference results no longer match, which failed CI. Pin it to hybrid, the previous default, restoring its reference results byte for byte (only the inputs gain the explicit element). The auto behavior of weight window generation is covered by the volume estimator auto test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
paulromano
left a comment
There was a problem hiding this comment.
Thanks @jtramm! Doing a review in full right now but here is some immediate feedback from GPT-6-Astra:
Detailed findings
Blocking issues
[Major] Apply diagonal stabilization before flooring negative flux
In src/random_ray/flat_source_domain.cpp, the strict estimator replaces a negative raw flux with the previous iterate before apply_transport_stabilization() runs.
With transport-corrected cross sections, a negative diagonal (within-group) scattering term can produce a negative intermediate source-iteration value. This is expected: OpenMC's existing diagonal stabilization transforms that raw value into the appropriate stabilized value. Flooring the value first prevents stabilization from seeing the intermediate result and can freeze the iteration at an incorrect solution.
For example, if
the existing stabilization gives
Strict adaptive instead floors the raw value to the previous iterate, producing
so the iterate does not move.
I confirmed this with a homogeneous two-group eigenvalue problem using a negative diagonal scattering term:
- Expected k-effective:
$1/6$ -
adaptive:0.1666666642 -
strict_adaptive:2.5, unchanged through 200 batches
The relevant ordering is:
add_source_to_scalar_flux()performs the strict rescue/floor.apply_transport_stabilization()runs afterward.
The positivity fix should therefore be applied after diagonal stabilization. If the naive-volume rescue is retained, the rescued candidate should also receive the corresponding stabilization before its sign is assessed.
The transport-corrected regression test currently exercises only adaptive. Please add a strict_adaptive case that verifies convergence to the expected result.
Non-blocking suggestions
[Moderate] Use the void-specific additive term when rescuing void flux
The rescue calculation assumes the non-void update
and reconstructs the transport contribution by subtracting source(sr, g).
True void regions use a different update:
Consequently, subtracting and restoring source(sr, g) does not algebraically reproduce the naive-volume update for a sourced void region.
Using the implemented routines with the same transport contribution, I obtained:
- Naive-volume update:
4.5 - Strict rescue:
0
The rescue should preserve the original transport contribution directly or subtract the actual additive term used by the void update.
The strict adaptive estimator floored a negative raw flux iterate at the previous iterate before the diagonal stabilization ran. With transport corrected cross sections a raw iterate driven negative by the negative within-group scattering is expected, and the stabilization maps it to a positive value, but the previous iterate is a fixed point of the stabilization, so flooring first froze the iteration. On a homogeneous two-group problem with an analytic eigenvalue of 0.5, the strict estimator converged to a spurious 2.0. The stabilization is now applied inside the flux update, to every candidate value the update considers, so the fixup necessarily sees the stabilized value and the rescued candidate is stabilized before its own sign is assessed. The separate stabilization pass is removed, which makes the wrong ordering impossible rather than avoided. The arithmetic is unchanged, and the existing stabilization references for the hybrid and adaptive estimators pass unmodified. The rescue also reconstructed the transport contribution by subtracting the reduced source, which is not the additive term of a void region's update. One helper now supplies that term to both the update and the rescue, so the rescue reproduces the naive-volume update for void and material regions alike. A short strict variant of the transport-corrected regression test pins the corrected direction of the iteration toward the analytic answer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
The file had grown one test function per estimator, against the convention used by the other random ray regression tests, which parametrize over the estimator with one stored reference per case. The pin cell case now runs over the hybrid and adaptive estimators, with the hybrid reference moved unchanged into its own directory, and the homogeneous transport-corrected case runs over all three estimators. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
Resolves two conflicts. In constants.h the branch had moved the random ray constants into flat_source_domain.h while develop added WEIGHT_WINDOW_REL_TOL beside them, so only the new constant is kept. In the diagonal stabilization test the branch had restructured the file while develop renamed the convert_to_multigroup keyword to particles, and both are kept. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
|
Thanks @paulromano! I agree with both issues and was able to reproduce the diagonal stabilization issue on my end. I've added fixes for both. |
paulromano
left a comment
There was a problem hiding this comment.
Good to go now; I made one small update to avoid allocations when they weren't needed but everything else looked fine. Thanks for the improvement @jtramm!
Brings in the adaptive volume estimators (openmc-dev#4110). The source region container now takes both the adaptive flags and the limiter's extent flag, and the limiter block follows the adaptive estimator's demoted-region gradient zeroing in the linear source update. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9rxXRzNh13xQBGzePHEeK
Overview
In the random ray solver there are a variety of ways to numerically treat the volume term that shows up in the MOC equations. You can read more background info in the "Volume Dilemma" section of the theory docs.
The crux of the issue is that the most natural treatment (known as the "naive" volume estimator) generally guarantees positivity and stability and has a low variance, though at the expense of being a biased ratio estimator. As such, eigenvalue solves using this naive setting might experience hundreds or even thousands of pcm error in k-eff, which is unacceptably high. Early in the random ray development the "simulation averaged" estimator was developed that accumulates volume from rays run over the course of the entire simulation, rather than only the last batch as in the naive approach. The simulation averaged estimator is thus unbiased (as it is effectively no longer a ratio estimator) but comes at the cost of 1) higher variance, and 2) potential for instability and negative fluxes under certain conditions.
Subsequent work introduced the "hybrid" estimator (merged in #3060) that took a "best of both worlds" approach. Generally the stability/negativity problems with the simulation averaged estimator are worst when there is an external source present in a region with a very low Sigma_t. Thus, the hybrid estimator tries to use the unbiased simulation averaged estimator everywhere it can, but then demoted regions that had an external source present.
To date the hybrid estimator has been the default and works well in most cases. However, in working on the JET problem, it became apparent that some fixed source problems were still becoming unstable and producing significant numbers of source regions with negative (or extremely large) fluxes. As such, JET simulations needed to be performed with the
naiveestimator. Given that the naive estimator is biased, I wondered if we might be able to make more efficient weight windows if we were able to come up with a better volume estimator that allowed more cells to use the simulation averaged estimator while ensuring stability.This PR makes more progress to a "best of both worlds" approach between the naive and simulation averaged estimators by introducing more intelligent criteria for when to demote regions to naive that strike at the root issues. It adds two new volume estimators to the random ray solver (
adaptiveandstrict_adaptive), and also introduces anautosetting that allows for OpenMC to make an intelligent choice about which estimator to use given the type of problem being simulated.As a result of this PR, the user can now simply select "auto" for this setting (or leave it at default), and shouldn't need to think about it anymore:
The Adaptive Estimator
adaptiveusessimulation_averagedby default and "demotes" individual source regions to use thenaiveestimator wheresimulation_averagedis unsafe. Regions are selected in three ways:hybrid.simulation_averageddiverges.The division of labor matters. Per-batch tests act on noisy single-batch values, so they are confined to the inactive phase, where they serve only stability. In the active batches, the ones tallies accumulate, the treatment of every region changes only by demote-only decisions made from accumulated estimates, so the choice never churns with the sign or size of single-batch noise. Continuing the accumulated decisions through the active phase also matters at scale. On problems too large to afford an inactive phase that converges every deep region (verified on a 105M-region shielding model), the transition-time decisions alone miss regions whose instability only surfaces after tallies begin, and a single unprotected region can corrupt the solution far beyond its own boundary through scattering feedback.
The Strict Adaptive Estimator
adaptiveselects estimators but never modifies a computed flux value. That is what keeps it unbiased, yet in some cases very slightly negative fluxes can still occur. Adaptive is configured to ensure simulation averaged is used maximally while preserving stability and still ensuring minimal bias from use of the naive estimator. This ensures good results on almost all problems I tested. However, on weight window generation problems that computed an adjoint source based on the forward flux, I did notice some problems popping up still. On the 105M source region JET model, a 0.67% negative fraction in the forward flux grew to ~3% in the adjoint and produced visibly noisy weight windows. This is a major improvement over 'hybrid', but is worse in practice than windows generated from the crudenaiveestimator. As my goal for this project was to improve performance over naive, this was quite disappointing. Thus, I developed the "strict adaptive estimator"Fixing the remaining rare negative source regions via more demotion cannot close this gap. Negativity is contagious (a region on a safe estimator can still inherit it through in-scatter from neighbors that are not), so no selection rule alone controls the sign.
strict_adaptivetherefore runs theadaptivemachinery unchanged and adds a per-batch fixup applied to the flux values themselves:naiveupdate), whose term-by-term consistency removes the volume-mismatch noise responsible for most negative excursions.naivetreatment. This channel is essential for bias rather than coverage. The floor prevents the accumulated flux from ever showing the negative sign thatadaptive's own demotion watches for, so without an exit a chronically noisy region would be clipped upward every batch, biasing its flux. Demotion instead moves such regions onto an estimator that needs no clipping.The
autodefaultThe default
volume_estimatoris nowauto, resolved once at the start of the run. Solves whose results feed variance reduction (weight window generation, and any adjoint workflow, including the forward solve an adjoint source is derived from, since that is where the negativity originates) receivestrict_adaptive, and everything else receivesadaptive. The report prints the resolution (e.g.Volume Estimator Type = Strict Adaptive (auto)), and setting any concrete estimator disables the routing.Validation
Five problems, each comparing several estimators against a converged multigroup Monte Carlo or published reference. Every table reports the count of negative flux tally bins. The "miss rate" is the average fraction of source regions not crossed by any ray in a batch. OpenMC recommends staying below 1%, and several tests deliberately run far above it as a stress test. (A streaming-dominated problem, a one-group point source in a near-void room where
simulation_averaged's failure is driven by ray-count variance rather than by the source-to-flux ratio, was also checked.adaptivematcheshybridto within 0.25% on all region tallies there while leaving 2 negative tally bins tohybrid's 26, andsimulation_averagedfails catastrophically.)1. C5G7 eigenvalue benchmark: no change where the default was already good
Pin-resolved 2D C5G7 (143k source regions), scored against the published eigenvalue (1.18655) and pin powers (AAPE = average absolute pin power error, MAX = worst pin):
adaptivematches the accurate estimators from well-resolved conditions through heavy starvation, whilenaiveshows the bias that motivates not simply using it everywhere. Thestrict_adaptiverows show the cost of its repair machinery growing with starvation, which is part of why it is not used for standard solves.2. Shielding problem with scatter-fed regions: the failure the default has
The "irradiation vault" problem consists of a 12 cm cavity emitting 2 MeV neutrons at one side of a large near-void air hall (Σt ≈ 3×10⁻³ cm⁻¹ fast, 3×10⁻⁵ cm⁻¹ slow), several absorber blocks, and a thick concrete shell, discretized on a 1.3 cm source-region mesh (1.24M regions). The hall's slow group is fed almost entirely by down-scatter from the fast group, so its reduced source exceeds its flux by orders of magnitude with no external source anywhere, which is exactly the configuration
hybrid's trigger cannot see. Slow-group fluxes at 12,800 rays (2.8% miss) vs multigroup Monte Carlo:Geometry and converged adaptive-estimator flux (z = 0 slice, log scale). The fast group beams from the source cavity and is shadowed by the absorber blocks, while the slow group is the scatter-fed distribution across the hall.
3. Published stress test: the three-region cube
The cube problem from here, as built by
openmc.examples.random_ray_three_region_cubewith the source region at the severe end of the paper's cross-section sweep, discretized on a 0.6 cm source-region mesh (216k regions). Region-integrated fluxes vs multigroup Monte Carlo at two ray densities:4. Variations of the hall
This test isolates the case that motivated the strict adaptive estimator. It uses small variants of the irradiation vault from problem 2 (35^3 cells rather than 1.24M) run on a short schedule of 100 inactive and 100 active batches, which reproduces at small scale the conditions under which
adaptiveleaves a few negative bins on large problems. Two versions of the hall are used. The thin hall keeps the near-void air of the original problem, while the collisional variant raises the slow-group cross section of the hall gas by two orders of magnitude (keeping the scattering ratio near one), so the slow flux in the hall is built by many small collisions rather than by streaming. The collisional variant is also run at a quarter of the ray density (25% miss vs 4.3%) as a stress case. Slow-group region errors vs multigroup Monte Carlo, with negative bins counted over the 85,750 mesh tally bins:strict_adaptivematchesadaptive's accuracy where both are stable, produces zero negative fluxes whereadaptiveleaves dozens to hundreds, and degrades gracefully wherenaiveblows up. The cost of the one-sided fixup is a conservative bias. On the 1%-miss C5G7 benchmark above, k rises to +365 pcm (vs +30 pcm foradaptive) and pin-power AAPE to 0.79% (vs 0.37%), which is why it is not used as the standard default. For weight window generation, there is an extreme preference for guarantees on positivity5. JET
This was the original test case that motivated this project. The
hybridestimator resulted in instability. Thenaiveestimator worked but I had wondered if we were leaving WW efficiency on the table. I compared the two estimators on a full CADIS test of the JET model, where weight windows are generated with the given estimator and then an MC solve is run. The maximum relative standard deviation across the 20 TLD tallies is then reported in each run. The baseline naive run resulted in a maximum rel std dev of 4.54%. The new defaultautosetting (which routes to thestrict_adaptiveestimator when WW's are being generated) resulted in a max rel std dev of 3.24%, both cases using essentially the same exact runtime. This PR is therefore responsible for a nearly 2x improvement in overall MC FOM for the JET test caseCompatibility and testing
volume_estimatorchanges fromhybridtoauto;hybrid,naive, andsimulation_averagedare unchanged and remain selectable, and any explicit selection disables the automatic routing.hybrid, so their reference results are unchanged. Theadaptiveandstrict_adaptiveestimators are covered by the volume-estimator regression tests (flat and linear) and by two deliberately ray-starved test variants that are sensitive to the demotion decisions. A new three-case test pins theautorouting itself, with forward, adjoint, and weight-window-generator runs that set no estimator, so a regression in either routing trigger shifts a reference result.autodefault.Checklist