Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
32bd64c
Add local adjoint source
j-fletcher Nov 24, 2025
b1c39a6
local adjoint init
j-fletcher Jan 7, 2026
30cd002
Added forward-weighted adjoint sources from @jtramm
j-fletcher Jan 9, 2026
884dec7
Merge branch 'develop' into local_adjoint
j-fletcher Jan 12, 2026
767fa79
Use single simulation for fwd/adjoint transport
j-fletcher Jan 15, 2026
f3e49f2
Clean up updated adjoint source setting
j-fletcher Jan 15, 2026
97f8133
add cadis as option on WWG
j-fletcher Jan 16, 2026
001f176
CADIS WWG UI with model.tallies linking
j-fletcher Jan 27, 2026
5e27354
fix possible typo in WWG.from_xml_element()
j-fletcher Jan 27, 2026
eea6be3
cpp UI
j-fletcher Jan 30, 2026
291fb21
regression tests and import bugfixes
j-fletcher Jan 30, 2026
abc518e
Merge branch 'develop' into local_adjoint and keep changes to random_…
j-fletcher Jan 30, 2026
dcbcf75
update missing tests and formatting
j-fletcher Feb 2, 2026
37e1650
fix rr test input file
j-fletcher Feb 2, 2026
3d438fe
Check correct type provided for WWG targets
j-fletcher Feb 5, 2026
a04aa26
Regression tests and change CADIS to FW-CADIS
j-fletcher Feb 16, 2026
23dd631
Documentation; adjoint source type checking; multiple adj source
j-fletcher Feb 20, 2026
dc2c53f
docs/formatting, FW adjoint sources for FW-CADIS in eigenvalue mode
j-fletcher Feb 23, 2026
0076fb1
Fixing regression tests
j-fletcher Feb 27, 2026
6e4c366
Merge branch develop into local_adjoint
j-fletcher Feb 27, 2026
aad939b
fix formatting
j-fletcher Feb 28, 2026
5e8833c
Revert random_ray_simulation.cpp to before merge
j-fletcher Mar 3, 2026
dde172d
reattempt merge for random_ray_simulation
j-fletcher Mar 3, 2026
464886f
Move openmc_run_random_ray to C API functions
j-fletcher Mar 3, 2026
5e1c134
Add ray_source to new temperature test inputs
j-fletcher Mar 3, 2026
a511530
Fix FW-CADIS local regression test
j-fletcher Mar 14, 2026
a427927
Modify input files for RR local adjoint test and rerun local FW-CADIS…
j-fletcher Mar 15, 2026
da80ab7
Rerunning local FW-CADIS test with strict FP
j-fletcher Mar 16, 2026
0927541
Rerunning local FW-CADIS test with OpenMP off
j-fletcher Mar 16, 2026
18f6781
Add SR mesh to fix reg test. set_fw_adjoint_sources() now searches fo…
j-fletcher Mar 19, 2026
b96e89a
Merge branch 'develop' into local_adjoint
j-fletcher Mar 19, 2026
8f08c1f
fix random_ray_adjoint_local test and merge branch develop into local…
j-fletcher Mar 19, 2026
a195b23
Fix SR mesh not being applied during user-defined adjoint sim
j-fletcher Mar 25, 2026
af15541
Update random_ray_adjoint_local test results
j-fletcher Mar 25, 2026
11ca70b
stop printing source term for every adjoint source region
j-fletcher Mar 25, 2026
9e5432a
Merge branch 'develop' into local_adjoint
j-fletcher Mar 25, 2026
21160f7
FSR evaluates whether to apply fixed source from adjoint or external …
j-fletcher Mar 26, 2026
ab2a8c8
remove redundant filter validity check, always warn when point source…
j-fletcher Apr 14, 2026
48c21e2
Refactor openmc_run_random_ray to mirror updated version
j-fletcher Apr 15, 2026
9b31b16
fix declaration of prepare_adjoint_simulation
j-fletcher Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 26 additions & 22 deletions docs/source/methods/random_ray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1081,28 +1081,32 @@ lifetimes.

In OpenMC, the random ray adjoint solver is implemented simply by transposing
the scattering matrix, swapping :math:`\nu\Sigma_f` and :math:`\chi`, and then
running a normal transport solve. When no external fixed source is present, no
additional changes are needed in the transport process. However, if an external
fixed forward source is present in the simulation problem, then an additional
step is taken to compute the accompanying fixed adjoint source. In OpenMC, the
adjoint flux does *not* represent a response function for a particular detector
region. Rather, the adjoint flux is the global response, making it appropriate
for use with weight window generation schemes for global variance reduction.
Thus, if using a fixed source, the external source for the adjoint mode is
simply computed as being :math:`1 / \phi`, where :math:`\phi` is the forward
scalar flux that results from a normal forward solve (which OpenMC will run
first automatically when in adjoint mode). The adjoint external source will be
computed for each source region in the simulation mesh, independent of any
tallies. The adjoint external source is always flat, even when a linear
scattering and fission source shape is used. When in adjoint mode, all reported
results (e.g., tallies, eigenvalues, etc.) are derived from the adjoint flux,
even when the physical meaning is not necessarily obvious. These values are
still reported, though we emphasize that the primary use case for adjoint mode
is for producing adjoint flux tallies to support subsequent perturbation studies
and weight window generation.

Note that the adjoint :math:`k_{eff}` is statistically the same as the forward
:math:`k_{eff}`, despite the flux distributions taking different shapes.
running a normal transport solve. When no external fixed forward source is
present, or if an adjoint fixed source is specifically provided, no additional
changes are needed in the transport process. This adjoint source can
correspond, for example, to a detector response function in a particular
region. However, if an external fixed forward source is present in the
simulation problem without an adjoint fixed source, an additional step is taken
to compute the accompanying forward-weighted adjoint source. In this case, the
adjoint flux does *not* represent the importance of locations in phase space to
detector response; rather, the "response" in question is a uniform distribution
of Monte Carlo particle density, making the importance provided by the adjoint
flux appropriate for use with weight window generation schemes for global
variance reduction. Thus, if using a fixed source, the forward-weighted
external source for adjoint mode is simply computed as being :math:`1 / \phi`,
where :math:`\phi` is the forward scalar flux that results from a normal
forward solve (which OpenMC will run first automatically when in adjoint mode).
The adjoint external source will be computed for each source region in the
simulation mesh, independent of any tallies. The adjoint external source is
always flat, even when a linear scattering and fission source shape is used.

When in adjoint mode, all reported results (e.g., tallies, eigenvalues, etc.)
are derived from the adjoint flux, even when the physical meaning is not
necessarily obvious. These values are still reported, though we emphasize that
the primary use case for adjoint mode is for producing adjoint flux tallies to
support subsequent perturbation studies and weight window generation. Note
however that the adjoint :math:`k_{eff}` is statistically the same as the
forward :math:`k_{eff}`, despite the flux distributions taking different shapes.

---------------------------
Fundamental Sources of Bias
Expand Down
16 changes: 14 additions & 2 deletions docs/source/methods/variance_reduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ where it was born from.

The Forward-Weighted Consistent Adjoint Driven Importance Sampling method, or
`FW-CADIS method <https://doi.org/10.13182/NSE12-33>`_, produces weight windows
for global variance reduction given adjoint flux information throughout the
entire domain. The weight window lower bound is defined in Equation
for global or local variance reduction given adjoint flux information throughout
the entire domain. The weight window lower bound is defined in Equation
:eq:`fw_cadis`, and also involves a normalization step not shown here.

.. math::
Expand Down Expand Up @@ -135,6 +135,18 @@ aware of this.

\text{FOM} = \frac{1}{\text{Time} \times \sigma^2}

Finally, one unique capability of the FW-CADIS weight window generator is to
produce weight windows for local variance reduction, given a list of the
responses of interest. This is controlled by optionally specifying target
tallies from the :class:`openmc.model.Model` to the
:class:`openmc.WeightWindowGenerator`, as illustrated in the
:ref:`user guide<variance_reduction>`. If target tallies for local variance
reduction are supplied, then the adjoint sources are only populated after the
initial forward simulation in the source regions associated with those tallies.
In other regions, the adjoint source term is instead set to zero. The Random
Ray solver then determines the adjoint flux map used to generate FW-CADIS
weight windows following the usual technique.

.. _methods_source_biasing:

--------------
Expand Down
55 changes: 41 additions & 14 deletions docs/source/usersguide/random_ray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,8 @@ as::
which will greatly improve the quality of the linear source term in 2D
simulations.

.. _usersguide_random_ray_run_modes:

---------------------------------
Fixed Source and Eigenvalue Modes
---------------------------------
Expand Down Expand Up @@ -1073,22 +1075,47 @@ The adjoint flux random ray solver mode can be enabled as::

settings.random_ray['adjoint'] = True

When enabled, OpenMC will first run a forward transport simulation followed by
an adjoint transport simulation. The purpose of the forward solve is to compute
the adjoint external source when an external source is present in the
simulation. Simulation settings (e.g., number of rays, batches, etc.) will be
identical for both simulations. At the conclusion of the run, all results (e.g.,
tallies, plots, etc.) will be derived from the adjoint flux rather than the
forward flux but are not labeled any differently. The initial forward flux
solution will not be stored or available in the final statepoint file. Those
wishing to do analysis requiring both the forward and adjoint solutions will
need to run two separate simulations and load both statepoint files.
When enabled, OpenMC will first run a forward transport simulation if there are
no user-specified adjoint sources present, followed by an adjoint transport
simulation. Fixed adjoint sources can be specified on the
:attr:`openmc.Settings.random_ray` dictionary as follows::

# Geometry definition
...
detector_cell = openmc.Cell(fill=detector_mat, name='cell where detector will be')
...
# Define fixed adjoint neutron source
strengths = [1.0]
midpoints = [1.0e-4]
energy_distribution = openmc.stats.Discrete(x=midpoints, p=strengths)

adj_source = openmc.IndependentSource(
energy=energy_distribution,
constraints={'domains': [detector_cell]}
)

# Add to random_ray dict
settings.random_ray['adjoint_source'] = adj_source

The same constraints apply to the user-defined adjoint source as to the forward
source, described in the :ref:`Fixed Source and Eigenvalue section
<usersguide_random_ray_run_modes>`. If this source is not provided, a forward
solve must take place to compute the adjoint external source when a forward
external source is present in the problem. Simulation settings (e.g., number of
rays, batches, etc.) will be identical for both calculations. At the
conclusion of the run, all results (e.g., tallies, plots, etc.) will be
derived from the adjoint flux rather than the forward flux but are not labeled
any differently. The initial forward flux solution will not be stored or
available in the final statepoint file. Those wishing to do analysis requiring
both the forward and adjoint solutions will need to run two separate
simulations and load both statepoint files.

.. note::
When adjoint mode is selected, OpenMC will always perform a full forward
solve and then run a full adjoint solve immediately afterwards. Statepoint
and tally results will be derived from the adjoint flux, but will not be
labeled any differently.
Use of the automated
:ref:`FW-CADIS weight window generator<usersguide_fw_cadis>` is not
currently compatible with user-defined adjoint sources. Instead, the
initial forward calculation is used to assign "forward-weighted" adjoint
sources to the tally regions of interest.

---------------------------------------
Putting it All Together: Example Inputs
Expand Down
94 changes: 73 additions & 21 deletions docs/source/usersguide/variance_reduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,27 @@
Variance Reduction
==================

Global variance reduction in OpenMC is accomplished by weight windowing
or source biasing techniques, the latter of which additionally provides a
local variance reduction capability. OpenMC is capable of generating weight
windows using either the MAGIC or FW-CADIS methods. Both techniques will
produce a ``weight_windows.h5`` file that can be loaded and used later on. In
Global and local variance reduction are possible in OpenMC through both weight
windowing and source biasing techniques. OpenMC is capable of generating weight
windows using either the MAGIC or FW-CADIS methods, the latter with an optional
capability for local variance reduction. Both techniques will produce a
``weight_windows.h5`` file that can be loaded and used later on. In
this section, we first break down the steps required to generate and apply
weight windows, then describe how source biasing may be applied.

.. _ww_generator:

------------------------------------
Generating Weight Windows with MAGIC
------------------------------------
-------------------------------------------
Generating Global Weight Windows with MAGIC
-------------------------------------------

As discussed in the :ref:`methods section <methods_variance_reduction>`, MAGIC
is an iterative method that uses flux tally information from a Monte Carlo
simulation to produce weight windows for a user-defined mesh. While generating
the weight windows, OpenMC is capable of applying the weight windows generated
from a previous batch while processing the next batch, allowing for progressive
improvement in the weight window quality across iterations.
simulation to produce weight windows for a user-defined mesh with the objective
of global variance reduction. While generating the weight windows, OpenMC is
capable of applying the weight windows generated from a previous batch while
processing the next batch, allowing for progressive improvement in the weight
window quality across iterations.

The typical way of generating weight windows is to define a mesh and then add an
:class:`openmc.WeightWindowGenerator` object to an :attr:`openmc.Settings`
Expand Down Expand Up @@ -71,15 +72,20 @@ At the end of the simulation, a ``weight_windows.h5`` file will be saved to disk
for later use. Loading it in another subsequent simulation will be discussed in
the "Using Weight Windows" section below.

------------------------------------------------------
Generating Weight Windows with FW-CADIS and Random Ray
------------------------------------------------------
.. _usersguide_fw_cadis:

----------------------------------------------------------------------
Generating Global or Local Weight Windows with FW-CADIS and Random Ray
----------------------------------------------------------------------

Weight window generation with FW-CADIS and random ray in OpenMC uses the same
exact strategy as with MAGIC. An :class:`openmc.WeightWindowGenerator` object is
added to the :attr:`openmc.Settings` object, and a ``weight_windows.h5`` will be
generated at the end of the simulation. The only difference is that the code
must be run in random ray mode. A full description of how to enable and setup
exact strategy as with MAGIC. Using FW-CADIS, however, also enables
local variance reduction in fixed source problems through the :attr:`targets`
attribute, which is described later in this section. To enable FW-CADIS, an
:class:`openmc.WeightWindowGenerator` object is added to the
:attr:`openmc.Settings` object, and a ``weight_windows.h5`` will be generated
at the end of the simulation. The only procedural difference is that the code
must be run in random ray mode. A full description of how to enable and setup
random ray mode can be found in the :ref:`Random Ray User Guide <random_ray>`.

.. note::
Expand All @@ -90,7 +96,7 @@ random ray mode can be found in the :ref:`Random Ray User Guide <random_ray>`.
ray solver. A high level overview of the current workflow for generation of
weight windows with FW-CADIS using random ray is given below.

1. Begin by making a deepy copy of your continuous energy Python model and then
1. Begin by making a deep copy of your continuous energy Python model and then
convert the copy to be multigroup and use the random ray transport solver.
The conversion process can largely be automated as described in more detail
in the :ref:`random ray quick start guide <quick_start>`, summarized below::
Expand Down Expand Up @@ -148,7 +154,53 @@ random ray mode can be found in the :ref:`Random Ray User Guide <random_ray>`.
assigning to ``model.settings.random_ray['source_region_meshes']``) and for
weight window generation.

3. When running your multigroup random ray input deck, OpenMC will automatically
3. (Optional) If local variance reduction is desired in a fixed-source problem,
populate the :attr:`targets` attribute with an :class:`openmc.Tallies`
instance or an iterable of tally IDs indicating the tallies of interest for
variance reduction::

# Build a new example and WWG for local variance reduction
from openmc.examples import random_ray_three_region_cube_with_detectors
new_model = random_ray_three_region_cube_with_detectors()

ww_mesh = openmc.RegularMesh()
n = 7
width = 35.0
ww_mesh.dimension = (n, n, n)
ww_mesh.lower_left = (0.0, 0.0, 0.0)
ww_mesh.upper_right = (width, width, width)

wwg = openmc.WeightWindowGenerator(
method="fw_cadis",
mesh=ww_mesh,
max_realizations=new_model.settings.batches
)
new_model.settings.weight_window_generators = wwg
new_model.settings.random_ray['volume_estimator'] = 'naive'

# Get the tallies of interest
target_tallies = openmc.Tallies()

for tally in list(new_model.tallies):
if tally.name in {"Detector 1 Tally", "Detector 2 Tally"}:
target_tallies.append(tally)

# Add to WeightWindowGenerator
wwg.targets = target_tallies

.. warning::
The tallies designated as FW-CADIS targets to the
:class:`~openmc.WeightWindowGenerator` must be present under the
:class:`~openmc.model.Model.tallies` attribute of the
:class:`~openmc.model.Model` as well in order to be recognized as valid
local variance reduction targets. This check is performed when the
:func:`openmc.model.Model.export_to_model_xml` or
:func:`openmc.model.Model.export_to_xml` functions are called, meaning
that the standalone :func:`openmc.Settings.export_to_xml` and
:func:`openmc.Tallies.export_to_xml` methods should not be used with
FW-CADIS local variance reduction.
Comment on lines +191 to +201
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if we were able to enforce this restriction (if you're not already doing this?). Probably not feasible for the openmc.Tallies.export_to_xml call, but the openmc.Settings.export_to_xml call seems like it would have the needed info to determine if local variance reduction is active?

Copy link
Copy Markdown
Contributor Author

@j-fletcher j-fletcher Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way I'm currently enforcing it is to perform an equivalence check (during Model.export_to_xml/export_to_model_xml) between the tally objects or IDs provided on WeightWindowGenerator.targets, and the tallies currently present in the model. Each of the tallies (or IDs) specified as targets must be equivalent to a tally (or its ID) on Model.tallies. While the WeightWindowGenerator is stored under Model.settings, the tallies are under Model.tallies, hence I wasn't able to put the check under Settings itself.

The nearest comparison that comes to mind is the use of tallies with a CellFilter--when a cell filter is added to a tally, the tally and filter are written to the XML file from Python without verifying that these cells are present in Model.geometry, and instead the check is performed when the filter (with the rest of the tallies subelement) is read off the XML file in C++. However, because the settings (and hence any WeightWindowGenerators) are read from XML before any tallies, I felt it would be cumbersome to perform the check on target tallies with the same timing. In contrast, Model._link_geometry_to_filters() meanwhile is called during Model.export_to_xml/export_to_model_xml, and it ensures that the reference geometry for each DistribcellFilter present in the Model.tallies is the same geometry present under Model.geometry. I felt that Model._assign_fw_cadis_tally_IDs() performed a similar enough task, but let me know if you think it should still be changed!


4. When running your multigroup random ray input deck, OpenMC will automatically
run a forward solve followed by an adjoint solve, with a
``weight_windows.h5`` file generated at the end. The ``weight_windows.h5``
file will contain FW-CADIS generated weight windows. This file can be used in
Expand Down
8 changes: 6 additions & 2 deletions include/openmc/random_ray/flat_source_domain.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ class FlatSourceDomain {
void random_ray_tally();
virtual void accumulate_iteration_flux();
void output_to_vtk() const;
void convert_external_sources();
void convert_external_sources(bool use_adjoint_sources);
void count_external_source_regions();
void set_adjoint_sources();
void set_fw_adjoint_sources();
void set_local_adjoint_sources();
void flux_swap();
virtual double evaluate_flux_at_point(Position r, int64_t sr, int g) const;
double compute_fixed_source_normalization_factor() const;
Expand Down Expand Up @@ -76,6 +77,7 @@ class FlatSourceDomain {
// Static Data members
static bool volume_normalized_flux_tallies_;
static bool adjoint_; // If the user wants outputs based on the adjoint flux
static bool fw_cadis_local_;
static double
diagonal_stabilization_rho_; // Adjusts strength of diagonal stabilization
// for transport corrected MGXS data
Expand All @@ -84,6 +86,8 @@ class FlatSourceDomain {
static std::unordered_map<int, vector<std::pair<Source::DomainType, int>>>
mesh_domain_map_;

static std::vector<size_t> fw_cadis_local_targets_;

//----------------------------------------------------------------------------
// Static data members
static RandomRayVolumeEstimator volume_estimator_;
Expand Down
17 changes: 4 additions & 13 deletions include/openmc/random_ray/random_ray_simulation.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ class RandomRaySimulation {
//----------------------------------------------------------------------------
// Methods
void apply_fixed_sources_and_mesh_domains();
void prepare_fixed_sources_adjoint();
void prepare_adjoint_simulation();
void prepare_fw_fixed_sources_adjoint();
void prepare_local_fixed_sources_adjoint();
void prepare_adjoint_simulation(bool fw_adjoint);
void simulate();
void output_simulation_results() const;
void instability_check(
Expand All @@ -34,15 +35,9 @@ class RandomRaySimulation {
// Accessors
FlatSourceDomain* domain() const { return domain_.get(); }

//----------------------------------------------------------------------------
// Public data members

// Flag for adjoint simulation;
bool adjoint_needed_;

private:
//----------------------------------------------------------------------------
// Private data members
// Data members

// Contains all flat source region data
unique_ptr<FlatSourceDomain> domain_;
Expand All @@ -57,17 +52,13 @@ class RandomRaySimulation {
// Number of energy groups
int negroups_;

// Toggle for first simulation
bool is_first_simulation_;

}; // class RandomRaySimulation

//============================================================================
//! Non-member functions
//============================================================================

void validate_random_ray_inputs();
void print_adjoint_header();
void openmc_finalize_random_ray();

} // namespace openmc
Expand Down
1 change: 1 addition & 0 deletions include/openmc/source.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Source;
namespace model {

extern vector<unique_ptr<Source>> external_sources;
extern vector<unique_ptr<Source>> adjoint_sources;

// Probability distribution for selecting external sources
extern DiscreteIndex external_sources_probability;
Expand Down
3 changes: 3 additions & 0 deletions include/openmc/weight_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ class WeightWindowsGenerator {
double threshold_ {1.0}; //<! Relative error threshold for values used to
// update weight windows
double ratio_ {5.0}; //<! ratio of lower to upper weight window bounds

// Local FW-CADIS target tallies
std::vector<size_t> targets_;
};

//==============================================================================
Expand Down
Loading
Loading