Skip to content

Commit f018524

Browse files
jtrammclaude
andauthored
Random Ray Forward Flux Save in Adjoint Mode (#3962)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d06fdee commit f018524

12 files changed

Lines changed: 97 additions & 72 deletions

File tree

docs/source/usersguide/random_ray.rst

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,10 +1105,15 @@ external source is present in the problem. Simulation settings (e.g., number of
11051105
rays, batches, etc.) will be identical for both calculations. At the
11061106
conclusion of the run, all results (e.g., tallies, plots, etc.) will be
11071107
derived from the adjoint flux rather than the forward flux but are not labeled
1108-
any differently. The initial forward flux solution will not be stored or
1109-
available in the final statepoint file. Those wishing to do analysis requiring
1110-
both the forward and adjoint solutions will need to run two separate
1111-
simulations and load both statepoint files.
1108+
any differently. When an initial forward solve is performed (i.e., when no
1109+
user-specified adjoint source is present), its output files are also written to
1110+
disk with a ``forward`` infix, so they are not overwritten by the subsequent
1111+
adjoint solve. This applies to the statepoint, ``tallies.out``, and any voxel
1112+
plots, e.g., ``statepoint.forward.N.h5`` and ``tallies.forward.out``; the
1113+
adjoint solve keeps the usual file names. This allows analyses requiring both
1114+
the forward and adjoint solutions to be performed from a single run. When
1115+
generating FW-CADIS weight windows, no weight window file is written for the
1116+
forward solve, as only the final adjoint-derived weight windows are meaningful.
11121117

11131118
.. note::
11141119
Use of the automated

include/openmc/constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ enum class SolverType { MONTE_CARLO, RANDOM_RAY };
368368
enum class RandomRayVolumeEstimator { NAIVE, SIMULATION_AVERAGED, HYBRID };
369369
enum class RandomRaySourceShape { FLAT, LINEAR, LINEAR_XY };
370370
enum class RandomRaySampleMethod { PRNG, HALTON, S2 };
371+
enum class RandomRaySolve { FORWARD, FORWARD_FOR_ADJOINT, ADJOINT };
371372

372373
//==============================================================================
373374
// Geometry Constants

include/openmc/random_ray/flat_source_domain.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ class FlatSourceDomain {
7676
//----------------------------------------------------------------------------
7777
// Static Data members
7878
static bool volume_normalized_flux_tallies_;
79-
static bool adjoint_; // If the user wants outputs based on the adjoint flux
79+
// If the user wants outputs based on the adjoint flux
80+
static bool adjoint_requested_;
81+
// The solve currently being executed
82+
static RandomRaySolve solve_;
8083
static bool fw_cadis_local_;
8184
static double
8285
diagonal_stabilization_rho_; // Adjusts strength of diagonal stabilization

include/openmc/random_ray/random_ray_simulation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class RandomRaySimulation {
2222
void apply_fixed_sources_and_mesh_domains();
2323
void prepare_fw_fixed_sources_adjoint();
2424
void prepare_local_fixed_sources_adjoint();
25-
void prepare_adjoint_simulation(bool fw_adjoint);
25+
void prepare_adjoint_simulation(bool from_forward);
2626
void simulate();
2727
void output_simulation_results() const;
2828
void instability_check(

src/output.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,8 +621,15 @@ void write_tallies()
621621
if (model::tallies.empty())
622622
return;
623623

624+
// Tag tallies.out written during the forward solve of an adjoint run
625+
const char* forward =
626+
(FlatSourceDomain::solve_ == RandomRaySolve::FORWARD_FOR_ADJOINT)
627+
? "forward."
628+
: "";
629+
624630
// Set filename for tallies_out
625-
std::string filename = fmt::format("{}tallies.out", settings::path_output);
631+
std::string filename =
632+
fmt::format("{}tallies.{}out", settings::path_output, forward);
626633

627634
// Open the tallies.out file.
628635
std::ofstream tallies_out;

src/random_ray/flat_source_domain.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ namespace openmc {
3030
RandomRayVolumeEstimator FlatSourceDomain::volume_estimator_ {
3131
RandomRayVolumeEstimator::HYBRID};
3232
bool FlatSourceDomain::volume_normalized_flux_tallies_ {false};
33-
bool FlatSourceDomain::adjoint_ {false};
33+
bool FlatSourceDomain::adjoint_requested_ {false};
34+
RandomRaySolve FlatSourceDomain::solve_ {RandomRaySolve::FORWARD};
3435
bool FlatSourceDomain::fw_cadis_local_ {false};
3536
double FlatSourceDomain::diagonal_stabilization_rho_ {1.0};
3637
std::unordered_map<int, vector<std::pair<Source::DomainType, int>>>
@@ -556,7 +557,7 @@ double FlatSourceDomain::compute_fixed_source_normalization_factor() const
556557
// If we are in adjoint mode of a fixed source problem, the external
557558
// source is already normalized, such that all resulting fluxes are
558559
// also normalized.
559-
if (adjoint_) {
560+
if (solve_ == RandomRaySolve::ADJOINT) {
560561
return 1.0;
561562
}
562563

@@ -795,6 +796,12 @@ void FlatSourceDomain::output_to_vtk() const
795796
double z_delta = width.z / Nz;
796797
std::string filename = openmc_plot->path_plot();
797798

799+
// Tag plots written during the forward solve of an adjoint run
800+
if (solve_ == RandomRaySolve::FORWARD_FOR_ADJOINT) {
801+
auto dot = filename.find_last_of('.');
802+
filename = filename.substr(0, dot) + ".forward" + filename.substr(dot);
803+
}
804+
798805
// Perform sanity checks on file size
799806
uint64_t bytes = Nx * Ny * Nz * (negroups_ + 1 + 1 + 1) * sizeof(float);
800807
write_message(5, "Processing plot {}: {}... (Estimated size is {} MB)",
@@ -1002,9 +1009,10 @@ void FlatSourceDomain::output_to_vtk() const
10021009
void FlatSourceDomain::apply_external_source_to_source_region(
10031010
int src_idx, SourceRegionHandle& srh)
10041011
{
1005-
auto s = (adjoint_ && !model::adjoint_sources.empty())
1006-
? model::adjoint_sources[src_idx].get()
1007-
: model::external_sources[src_idx].get();
1012+
auto s =
1013+
(solve_ == RandomRaySolve::ADJOINT && !model::adjoint_sources.empty())
1014+
? model::adjoint_sources[src_idx].get()
1015+
: model::external_sources[src_idx].get();
10081016
auto is = dynamic_cast<IndependentSource*>(s);
10091017
auto discrete = dynamic_cast<Discrete*>(is->energy());
10101018
double strength_factor = is->strength();

src/random_ray/random_ray_simulation.cpp

Lines changed: 40 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ void validate_random_ray_inputs()
189189

190190
// Validate adjoint sources
191191
///////////////////////////////////////////////////////////////////
192-
if (FlatSourceDomain::adjoint_ && !model::adjoint_sources.empty()) {
192+
if (FlatSourceDomain::adjoint_requested_ && !model::adjoint_sources.empty()) {
193193
for (int i = 0; i < model::adjoint_sources.size(); i++) {
194194
Source* s = model::adjoint_sources[i].get();
195195

@@ -289,7 +289,8 @@ void openmc_finalize_random_ray()
289289
{
290290
FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::HYBRID;
291291
FlatSourceDomain::volume_normalized_flux_tallies_ = false;
292-
FlatSourceDomain::adjoint_ = false;
292+
FlatSourceDomain::adjoint_requested_ = false;
293+
FlatSourceDomain::solve_ = RandomRaySolve::FORWARD;
293294
FlatSourceDomain::fw_cadis_local_ = false;
294295
FlatSourceDomain::fw_cadis_local_targets_.clear();
295296
FlatSourceDomain::mesh_domain_map_.clear();
@@ -356,19 +357,17 @@ void RandomRaySimulation::prepare_local_fixed_sources_adjoint()
356357
}
357358
}
358359

359-
void RandomRaySimulation::prepare_adjoint_simulation(bool fw_adjoint)
360+
void RandomRaySimulation::prepare_adjoint_simulation(bool from_forward)
360361
{
361362
reset_timers();
362363

363364
if (mpi::master)
364365
header("ADJOINT FLUX SOLVE", 3);
365366

366-
if (fw_adjoint) {
367-
// Forward simulation has already been run;
368-
// Configure the domain for adjoint simulation and
369-
// re-initialize OpenMC general data structures
370-
FlatSourceDomain::adjoint_ = true;
371-
367+
if (from_forward) {
368+
// The forward solve has already run. Re-initialize OpenMC's general data
369+
// structures for the adjoint solve and derive the adjoint source from the
370+
// forward flux.
372371
openmc_simulation_init();
373372

374373
prepare_fw_fixed_sources_adjoint();
@@ -603,7 +602,8 @@ void RandomRaySimulation::print_results_random_ray(
603602
}
604603
fmt::print(" Volume Estimator Type = {}\n", estimator);
605604

606-
std::string adjoint_true = (FlatSourceDomain::adjoint_) ? "ON" : "OFF";
605+
std::string adjoint_true =
606+
(FlatSourceDomain::solve_ == RandomRaySolve::ADJOINT) ? "ON" : "OFF";
607607
fmt::print(" Adjoint Flux Mode = {}\n", adjoint_true);
608608

609609
std::string shape;
@@ -675,60 +675,49 @@ void RandomRaySimulation::print_results_random_ray(
675675

676676
void openmc_run_random_ray()
677677
{
678-
//////////////////////////////////////////////////////////
679-
// Run forward simulation
680-
//////////////////////////////////////////////////////////
681-
682-
// Check if adjoint calculation is needed, and if local adjoint source(s)
683-
// are present. If an adjoint calculation is needed and no sources are
684-
// specified, we will run a forward calculation first to calculate adjoint
685-
// sources for global variance reduction, then perform an adjoint
686-
// calculation later.
687-
bool adjoint_needed = openmc::FlatSourceDomain::adjoint_;
688-
bool fw_adjoint = openmc::model::adjoint_sources.empty() && adjoint_needed;
689-
690-
// If we're going to do an adjoint simulation with forward-weighted adjoint
691-
// sources afterwards, report that this is the initial forward flux solve.
692-
if (!adjoint_needed || fw_adjoint) {
693-
// Configure the domain for forward simulation
694-
openmc::FlatSourceDomain::adjoint_ = false;
695-
696-
if (adjoint_needed && openmc::mpi::master)
697-
openmc::header("FORWARD FLUX SOLVE", 3);
678+
using namespace openmc;
679+
680+
// Determine which solves to run. If adjoint results are requested and no
681+
// user-defined adjoint source is present, an initial forward solve is needed
682+
// to construct the adjoint source from the forward flux (FW-CADIS). If the
683+
// user has defined an adjoint source, the forward solve is skipped and only
684+
// the adjoint solve is run.
685+
const bool run_adjoint = FlatSourceDomain::adjoint_requested_;
686+
const bool have_adjoint_source = !model::adjoint_sources.empty();
687+
const bool run_forward = !(run_adjoint && have_adjoint_source);
688+
689+
// Set the initial solve type
690+
if (!run_forward) {
691+
FlatSourceDomain::solve_ = RandomRaySolve::ADJOINT;
692+
} else if (run_adjoint) {
693+
FlatSourceDomain::solve_ = RandomRaySolve::FORWARD_FOR_ADJOINT;
698694
} else {
699-
// Configure domain for adjoint simulation (later)
700-
openmc::FlatSourceDomain::adjoint_ = true;
695+
FlatSourceDomain::solve_ = RandomRaySolve::FORWARD;
701696
}
702697

703698
// Initialize OpenMC general data structures
704699
openmc_simulation_init();
705700

706701
// Validate that inputs meet requirements for random ray mode
707-
if (openmc::mpi::master)
708-
openmc::validate_random_ray_inputs();
702+
if (mpi::master)
703+
validate_random_ray_inputs();
709704

710705
// Initialize Random Ray Simulation Object
711-
openmc::RandomRaySimulation sim;
706+
RandomRaySimulation sim;
712707

713-
if (!adjoint_needed || fw_adjoint) {
714-
// Initialize fixed sources, if present
708+
// Run the forward solve
709+
if (run_forward) {
710+
// When an adjoint solve follows, report this as the initial forward solve
711+
if (run_adjoint && mpi::master)
712+
header("FORWARD FLUX SOLVE", 3);
715713
sim.apply_fixed_sources_and_mesh_domains();
716-
717-
// Execute random ray simulation
718714
sim.simulate();
719715
}
720716

721-
//////////////////////////////////////////////////////////
722-
// Run adjoint simulation (if enabled)
723-
//////////////////////////////////////////////////////////
724-
725-
if (!adjoint_needed) {
726-
return;
717+
// Run the adjoint solve
718+
if (run_adjoint) {
719+
FlatSourceDomain::solve_ = RandomRaySolve::ADJOINT;
720+
sim.prepare_adjoint_simulation(run_forward);
721+
sim.simulate();
727722
}
728-
729-
// Setup for adjoint simulation
730-
sim.prepare_adjoint_simulation(fw_adjoint);
731-
732-
// Execute random ray simulation
733-
sim.simulate();
734723
}

src/settings.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ void get_run_parameters(pugi::xml_node node_base)
322322
get_node_value_bool(random_ray_node, "volume_normalized_flux_tallies");
323323
}
324324
if (check_for_node(random_ray_node, "adjoint")) {
325-
FlatSourceDomain::adjoint_ =
325+
FlatSourceDomain::adjoint_requested_ =
326326
get_node_value_bool(random_ray_node, "adjoint");
327327
}
328328
if (check_for_node(random_ray_node, "sample_method")) {

src/simulation.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "openmc/particle.h"
1717
#include "openmc/photon.h"
1818
#include "openmc/random_lcg.h"
19+
#include "openmc/random_ray/flat_source_domain.h"
1920
#include "openmc/settings.h"
2021
#include "openmc/source.h"
2122
#include "openmc/state_point.h"
@@ -200,9 +201,12 @@ int openmc_simulation_finalize()
200201
if (settings::output_tallies && mpi::master)
201202
write_tallies();
202203

203-
// If weight window generators are present in this simulation,
204-
// write a weight windows file
205-
if (variance_reduction::weight_windows_generators.size() > 0) {
204+
// If weight window generators are present in this simulation, write a
205+
// weight windows file. This is skipped during the forward solve of an
206+
// adjoint (FW-CADIS) run, where only the adjoint-derived weight windows
207+
// are meaningful.
208+
if (variance_reduction::weight_windows_generators.size() > 0 &&
209+
FlatSourceDomain::solve_ != RandomRaySolve::FORWARD_FOR_ADJOINT) {
206210
openmc_weight_windows_export();
207211
}
208212

src/state_point.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "openmc/nuclide.h"
2323
#include "openmc/output.h"
2424
#include "openmc/particle_type.h"
25+
#include "openmc/random_ray/flat_source_domain.h"
2526
#include "openmc/settings.h"
2627
#include "openmc/simulation.h"
2728
#include "openmc/tallies/derivative.h"
@@ -46,9 +47,15 @@ extern "C" int openmc_statepoint_write(const char* filename, bool* write_source)
4647
// Determine width for zero padding
4748
int w = std::to_string(settings::n_max_batches).size();
4849

50+
// Tag statepoints written during the forward solve of an adjoint run
51+
const char* forward =
52+
(FlatSourceDomain::solve_ == RandomRaySolve::FORWARD_FOR_ADJOINT)
53+
? "forward."
54+
: "";
55+
4956
// Set filename for state point
50-
filename_ = fmt::format("{0}statepoint.{1:0{2}}.h5", settings::path_output,
51-
simulation::current_batch, w);
57+
filename_ = fmt::format("{0}statepoint.{3}{1:0{2}}.h5",
58+
settings::path_output, simulation::current_batch, w, forward);
5259
}
5360

5461
// If a file name was specified, ensure it has .h5 file extension

0 commit comments

Comments
 (0)