Random Ray Adjoint Mode (#3191)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
John Tramm 2024-11-20 14:15:41 -06:00 committed by GitHub
parent fbb115921c
commit 172867b1df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 958 additions and 169 deletions

View file

@ -542,7 +542,7 @@ in that cell for the iteration from Equation :eq:`phi_naive` to:
.. math::
:label: phi_missed_one
\phi_{i,g,n}^{missed} = \frac{Q_{i,g,n} }{\Sigma_{t,i,g}}
\phi_{i,g,n}^{missed} = \frac{Q_{i,g,n} }{\Sigma_{t,i,g}}
as the streaming operator has gone to zero. While this is obviously innacurate
as it ignores transport, for most problems where the region is only occasionally
@ -1060,6 +1060,49 @@ random ray and Monte Carlo, however.
develop the scattering source by way of inactive batches before beginning
active batches.
------------------------
Adjoint Flux Solver Mode
------------------------
The random ray solver in OpenMC can also be used to solve for the adjoint flux,
:math:`\psi^{\dagger}`. In combination with the regular (forward) flux solution,
the adjoint flux is useful for perturbation methods as well as for computing
weight windows for subsequent Monte Carlo simulations. The adjoint flux can be
thought of as the "backwards" flux, representing the flux where a particle is
born at an absoprtion point (and typical absorption energy), and then undergoes
transport with a transposed scattering matrix. That is, instead of sampling a
particle and seeing where it might go as in a standard forward solve, we will
sample an absorption location and see where the particle that was absorbed there
might have come from. Notably, for typical neutron absorption at low energy
levels, this means that adjoint flux particles are typically sampled at a low
energy and then upscatter (via a transposed scattering matrix) over their
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.
---------------------------
Fundamental Sources of Bias
---------------------------

View file

@ -558,7 +558,7 @@ following methods are currently available in OpenMC:
- Cons
* - ``simulation_averaged``
- Accumulates total active ray lengths in each FSR over all iterations,
improving the estimate of the volume in each cell each iteration.
improving the estimate of the volume in each cell each iteration.
- * Virtually unbiased after several iterations
* Asymptotically approaches the true analytical volume
* Typically most efficient in terms of speed vs. accuracy
@ -593,6 +593,33 @@ estimator, the following code would be used:
settings.random_ray['volume_estimator'] = 'naive'
-----------------
Adjoint Flux Mode
-----------------
The adjoint flux random ray solver mode can be enabled as:
entire
::
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.
.. 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.
---------------------------------------
Putting it All Together: Example Inputs
---------------------------------------

View file

@ -86,8 +86,10 @@ public:
bool fissionable; // Is this fissionable
bool is_isotropic {
true}; // used to skip search for angle indices if isotropic
bool exists_in_model {true}; // Is this present in model
Mgxs() = default;
Mgxs(bool exists) : exists_in_model(exists) {}
//! \brief Constructor that loads the Mgxs object from the HDF5 file
//!

View file

@ -111,13 +111,17 @@ public:
virtual void all_reduce_replicated_source_regions();
void convert_external_sources();
void count_external_source_regions();
void set_adjoint_sources(const vector<double>& forward_flux);
virtual void flux_swap();
virtual double evaluate_flux_at_point(Position r, int64_t sr, int g) const;
double compute_fixed_source_normalization_factor() const;
void flatten_xs();
void transpose_scattering_matrix();
//----------------------------------------------------------------------------
// Static Data members
static bool volume_normalized_flux_tallies_;
static bool adjoint_; // If the user wants outputs based on the adjoint flux
//----------------------------------------------------------------------------
// Static data members
@ -150,6 +154,19 @@ public:
vector<float> source_;
vector<float> external_source_;
vector<bool> external_source_present_;
vector<double> scalar_flux_final_;
// 2D arrays stored in 1D representing values for all materials x energy
// groups
int n_materials_;
vector<double> sigma_t_;
vector<double> nu_sigma_f_;
vector<double> sigma_f_;
vector<double> chi_;
// 3D arrays stored in 1D representing values for all materials x energy
// groups x energy groups
vector<double> sigma_s_;
protected:
//----------------------------------------------------------------------------
@ -190,10 +207,6 @@ protected:
vector<int> material_;
vector<double> volume_naive_;
// 2D arrays stored in 1D representing values for all source regions x energy
// groups
vector<float> scalar_flux_final_;
// Volumes for each tally and bin/score combination. This intermediate data
// structure is used when tallying quantities that must be normalized by
// volume (i.e., flux). The vector is index by tally index, while the inner 2D

View file

@ -20,6 +20,8 @@ public:
//----------------------------------------------------------------------------
// Methods
void compute_segment_correction_factors();
void prepare_fixed_sources();
void prepare_fixed_sources_adjoint(vector<double>& forward_flux);
void simulate();
void reduce_simulation_statistics();
void output_simulation_results() const;
@ -30,8 +32,13 @@ public:
int64_t n_external_source_regions) const;
//----------------------------------------------------------------------------
// Data members
// Accessors
FlatSourceDomain* domain() const { return domain_.get(); }
private:
//----------------------------------------------------------------------------
// Data members
// Contains all flat source region data
unique_ptr<FlatSourceDomain> domain_;

View file

@ -208,7 +208,7 @@ class OpenMCOperator(TransportOperator):
if nuclide in self.nuclides_with_data or self._decay_nucs:
model_nuclides.add(nuclide)
else:
msg = (f"Nuclilde {nuclide} in material {mat.id} is not "
msg = (f"Nuclide {nuclide} in material {mat.id} is not "
"present in the depletion chain and has no cross "
"section data.")
warn(msg)

View file

@ -170,6 +170,9 @@ class Settings:
cm/cm^3. When disabled, flux tallies will be reported in units
of cm (i.e., total distance traveled by neutrons in the spatial
tally region).
:adjoint:
Whether to run the random ray solver in adjoint mode (bool). The
default is 'False'.
.. versionadded:: 0.15.0
resonance_scattering : dict
@ -1113,6 +1116,8 @@ class Settings:
('flat', 'linear', 'linear_xy'))
elif key == 'volume_normalized_flux_tallies':
cv.check_type('volume normalized flux tallies', random_ray[key], bool)
elif key == 'adjoint':
cv.check_type('adjoint', random_ray[key], bool)
else:
raise ValueError(f'Unable to set random ray to "{key}" which is '
'unsupported by OpenMC')
@ -1916,6 +1921,10 @@ class Settings:
self.random_ray['volume_normalized_flux_tallies'] = (
child.text in ('true', '1')
)
elif child.tag == 'adjoint':
self.random_ray['adjoint'] = (
child.text in ('true', '1')
)
def to_xml_element(self, mesh_memo=None):
"""Create a 'settings' element to be written to an XML file.

View file

@ -146,7 +146,7 @@ void MgxsInterface::create_macro_xs()
num_energy_groups_, num_delayed_groups_);
} else {
// Preserve the ordering of materials by including a blank entry
macro_xs_.emplace_back();
macro_xs_.emplace_back(false);
}
}
}

View file

@ -27,6 +27,7 @@ namespace openmc {
RandomRayVolumeEstimator FlatSourceDomain::volume_estimator_ {
RandomRayVolumeEstimator::HYBRID};
bool FlatSourceDomain::volume_normalized_flux_tallies_ {false};
bool FlatSourceDomain::adjoint_ {false};
FlatSourceDomain::FlatSourceDomain() : negroups_(data::mg.num_energy_groups_)
{
@ -134,31 +135,23 @@ void FlatSourceDomain::update_neutron_source(double k_eff)
double inverse_k_eff = 1.0 / k_eff;
// Temperature and angle indices, if using multiple temperature
// data sets and/or anisotropic data sets.
// TODO: Currently assumes we are only using single temp/single angle data.
const int t = 0;
const int a = 0;
// Add scattering source
#pragma omp parallel for
for (int sr = 0; sr < n_source_regions_; sr++) {
int material = material_[sr];
for (int e_out = 0; e_out < negroups_; e_out++) {
double sigma_t = data::mg.macro_xs_[material].get_xs(
MgxsType::TOTAL, e_out, nullptr, nullptr, nullptr, t, a);
double scatter_source = 0.0f;
for (int g_out = 0; g_out < negroups_; g_out++) {
double sigma_t = sigma_t_[material * negroups_ + g_out];
double scatter_source = 0.0;
for (int e_in = 0; e_in < negroups_; e_in++) {
double scalar_flux = scalar_flux_old_[sr * negroups_ + e_in];
double sigma_s = data::mg.macro_xs_[material].get_xs(
MgxsType::NU_SCATTER, e_in, &e_out, nullptr, nullptr, t, a);
for (int g_in = 0; g_in < negroups_; g_in++) {
double scalar_flux = scalar_flux_old_[sr * negroups_ + g_in];
double sigma_s =
sigma_s_[material * negroups_ * negroups_ + g_out * negroups_ + g_in];
scatter_source += sigma_s * scalar_flux;
}
source_[sr * negroups_ + e_out] = scatter_source / sigma_t;
source_[sr * negroups_ + g_out] = scatter_source / sigma_t;
}
}
@ -167,20 +160,17 @@ void FlatSourceDomain::update_neutron_source(double k_eff)
for (int sr = 0; sr < n_source_regions_; sr++) {
int material = material_[sr];
for (int e_out = 0; e_out < negroups_; e_out++) {
double sigma_t = data::mg.macro_xs_[material].get_xs(
MgxsType::TOTAL, e_out, nullptr, nullptr, nullptr, t, a);
double fission_source = 0.0f;
for (int g_out = 0; g_out < negroups_; g_out++) {
double sigma_t = sigma_t_[material * negroups_ + g_out];
double fission_source = 0.0;
for (int e_in = 0; e_in < negroups_; e_in++) {
double scalar_flux = scalar_flux_old_[sr * negroups_ + e_in];
double nu_sigma_f = data::mg.macro_xs_[material].get_xs(
MgxsType::NU_FISSION, e_in, nullptr, nullptr, nullptr, t, a);
double chi = data::mg.macro_xs_[material].get_xs(
MgxsType::CHI_PROMPT, e_in, &e_out, nullptr, nullptr, t, a);
for (int g_in = 0; g_in < negroups_; g_in++) {
double scalar_flux = scalar_flux_old_[sr * negroups_ + g_in];
double nu_sigma_f = nu_sigma_f_[material * negroups_ + g_in];
double chi = chi_[material * negroups_ + g_out];
fission_source += nu_sigma_f * scalar_flux * chi;
}
source_[sr * negroups_ + e_out] +=
source_[sr * negroups_ + g_out] +=
fission_source * inverse_k_eff / sigma_t;
}
}
@ -188,7 +178,7 @@ void FlatSourceDomain::update_neutron_source(double k_eff)
// Add external source if in fixed source mode
if (settings::run_mode == RunMode::FIXED_SOURCE) {
#pragma omp parallel for
for (int se = 0; se < n_source_elements_; se++) {
for (int64_t se = 0; se < n_source_elements_; se++) {
source_[se] += external_source_[se];
}
}
@ -206,8 +196,8 @@ void FlatSourceDomain::normalize_scalar_flux_and_volumes(
// Normalize scalar flux to total distance travelled by all rays this iteration
#pragma omp parallel for
for (int64_t e = 0; e < scalar_flux_new_.size(); e++) {
scalar_flux_new_[e] *= normalization_factor;
for (int64_t se = 0; se < scalar_flux_new_.size(); se++) {
scalar_flux_new_[se] *= normalization_factor;
}
// Accumulate cell-wise ray length tallies collected this iteration, then
@ -223,16 +213,7 @@ void FlatSourceDomain::normalize_scalar_flux_and_volumes(
void FlatSourceDomain::set_flux_to_flux_plus_source(
int64_t idx, double volume, int material, int g)
{
// Temperature and angle indices, if using multiple temperature
// data sets and/or anisotropic data sets.
// TODO: Currently assumes we are only using single temp/single
// angle data.
const int t = 0;
const int a = 0;
double sigma_t = data::mg.macro_xs_[material].get_xs(
MgxsType::TOTAL, g, nullptr, nullptr, nullptr, t, a);
double sigma_t = sigma_t_[material * negroups_ + g];
scalar_flux_new_[idx] /= (sigma_t * volume);
scalar_flux_new_[idx] += source_[idx];
}
@ -337,13 +318,6 @@ double FlatSourceDomain::compute_k_eff(double k_eff_old) const
double fission_rate_old = 0;
double fission_rate_new = 0;
// Temperature and angle indices, if using multiple temperature
// data sets and/or anisotropic data sets.
// TODO: Currently assumes we are only using single temp/single
// angle data.
const int t = 0;
const int a = 0;
// Vector for gathering fission source terms for Shannon entropy calculation
vector<float> p(n_source_regions_, 0.0f);
@ -363,8 +337,7 @@ double FlatSourceDomain::compute_k_eff(double k_eff_old) const
for (int g = 0; g < negroups_; g++) {
int64_t idx = (sr * negroups_) + g;
double nu_sigma_f = data::mg.macro_xs_[material].get_xs(
MgxsType::NU_FISSION, g, nullptr, nullptr, nullptr, t, a);
double nu_sigma_f = nu_sigma_f_[material * negroups_ + g];
sr_fission_source_old += nu_sigma_f * scalar_flux_old_[idx];
sr_fission_source_new += nu_sigma_f * scalar_flux_new_[idx];
}
@ -548,7 +521,7 @@ double FlatSourceDomain::compute_fixed_source_normalization_factor() const
{
// If we are not in fixed source mode, then there are no external sources
// so no normalization is needed.
if (settings::run_mode != RunMode::FIXED_SOURCE) {
if (settings::run_mode != RunMode::FIXED_SOURCE || adjoint_) {
return 1.0;
}
@ -559,17 +532,10 @@ double FlatSourceDomain::compute_fixed_source_normalization_factor() const
for (int sr = 0; sr < n_source_regions_; sr++) {
int material = material_[sr];
double volume = volume_[sr] * simulation_volume_;
for (int e = 0; e < negroups_; e++) {
// Temperature and angle indices, if using multiple temperature
// data sets and/or anisotropic data sets.
// TODO: Currently assumes we are only using single temp/single
// angle data.
const int t = 0;
const int a = 0;
double sigma_t = data::mg.macro_xs_[material].get_xs(
MgxsType::TOTAL, e, nullptr, nullptr, nullptr, t, a);
for (int g = 0; g < negroups_; g++) {
double sigma_t = sigma_t_[material * negroups_ + g];
simulation_external_source_strength +=
external_source_[sr * negroups_ + e] * sigma_t * volume;
external_source_[sr * negroups_ + g] * sigma_t * volume;
}
}
@ -603,13 +569,6 @@ void FlatSourceDomain::random_ray_tally()
// Reset our tally volumes to zero
reset_tally_volumes();
// Temperature and angle indices, if using multiple temperature
// data sets and/or anisotropic data sets.
// TODO: Currently assumes we are only using single temp/single
// angle data.
const int t = 0;
const int a = 0;
double source_normalization_factor =
compute_fixed_source_normalization_factor();
@ -644,21 +603,15 @@ void FlatSourceDomain::random_ray_tally()
break;
case SCORE_TOTAL:
score = flux * volume *
data::mg.macro_xs_[material].get_xs(
MgxsType::TOTAL, g, NULL, NULL, NULL, t, a);
score = flux * volume * sigma_t_[material * negroups_ + g];
break;
case SCORE_FISSION:
score = flux * volume *
data::mg.macro_xs_[material].get_xs(
MgxsType::FISSION, g, NULL, NULL, NULL, t, a);
score = flux * volume * sigma_f_[material * negroups_ + g];
break;
case SCORE_NU_FISSION:
score = flux * volume *
data::mg.macro_xs_[material].get_xs(
MgxsType::NU_FISSION, g, NULL, NULL, NULL, t, a);
score = flux * volume * nu_sigma_f_[material * negroups_ + g];
break;
case SCORE_EVENTS:
@ -957,9 +910,8 @@ void FlatSourceDomain::output_to_vtk() const
for (int g = 0; g < negroups_; g++) {
int64_t source_element = fsr * negroups_ + g;
float flux = evaluate_flux_at_point(voxel_positions[i], fsr, g);
float Sigma_f = data::mg.macro_xs_[mat].get_xs(
MgxsType::FISSION, g, nullptr, nullptr, nullptr, 0, 0);
total_fission += Sigma_f * flux;
double sigma_f = sigma_f_[mat * negroups_ + g];
total_fission += sigma_f * flux;
}
total_fission = convert_to_big_endian<float>(total_fission);
std::fwrite(&total_fission, sizeof(float), 1, plot);
@ -977,10 +929,10 @@ void FlatSourceDomain::apply_external_source_to_source_region(
const auto& discrete_energies = discrete->x();
const auto& discrete_probs = discrete->prob();
for (int e = 0; e < discrete_energies.size(); e++) {
int g = data::mg.get_group_index(discrete_energies[e]);
for (int i = 0; i < discrete_energies.size(); i++) {
int g = data::mg.get_group_index(discrete_energies[i]);
external_source_[source_region * negroups_ + g] +=
discrete_probs[e] * strength_factor;
discrete_probs[i] * strength_factor;
}
}
@ -1074,27 +1026,107 @@ void FlatSourceDomain::convert_external_sources()
}
} // End loop over external sources
// Divide the fixed source term by sigma t (to save time when applying each
// iteration)
#pragma omp parallel for
for (int sr = 0; sr < n_source_regions_; sr++) {
int material = material_[sr];
for (int g = 0; g < negroups_; g++) {
double sigma_t = sigma_t_[material * negroups_ + g];
external_source_[sr * negroups_ + g] /= sigma_t;
}
}
}
void FlatSourceDomain::flux_swap()
{
scalar_flux_old_.swap(scalar_flux_new_);
}
void FlatSourceDomain::flatten_xs()
{
// Temperature and angle indices, if using multiple temperature
// data sets and/or anisotropic data sets.
// TODO: Currently assumes we are only using single temp/single angle data.
const int t = 0;
const int a = 0;
// Divide the fixed source term by sigma t (to save time when applying each
// iteration)
#pragma omp parallel for
for (int sr = 0; sr < n_source_regions_; sr++) {
int material = material_[sr];
for (int e = 0; e < negroups_; e++) {
double sigma_t = data::mg.macro_xs_[material].get_xs(
MgxsType::TOTAL, e, nullptr, nullptr, nullptr, t, a);
external_source_[sr * negroups_ + e] /= sigma_t;
n_materials_ = data::mg.macro_xs_.size();
for (auto& m : data::mg.macro_xs_) {
for (int g_out = 0; g_out < negroups_; g_out++) {
if (m.exists_in_model) {
double sigma_t =
m.get_xs(MgxsType::TOTAL, g_out, NULL, NULL, NULL, t, a);
sigma_t_.push_back(sigma_t);
double nu_Sigma_f =
m.get_xs(MgxsType::NU_FISSION, g_out, NULL, NULL, NULL, t, a);
nu_sigma_f_.push_back(nu_Sigma_f);
double sigma_f =
m.get_xs(MgxsType::FISSION, g_out, NULL, NULL, NULL, t, a);
sigma_f_.push_back(sigma_f);
double chi =
m.get_xs(MgxsType::CHI_PROMPT, g_out, &g_out, NULL, NULL, t, a);
chi_.push_back(chi);
for (int g_in = 0; g_in < negroups_; g_in++) {
double sigma_s =
m.get_xs(MgxsType::NU_SCATTER, g_in, &g_out, NULL, NULL, t, a);
sigma_s_.push_back(sigma_s);
}
} else {
sigma_t_.push_back(0);
nu_sigma_f_.push_back(0);
sigma_f_.push_back(0);
chi_.push_back(0);
for (int g_in = 0; g_in < negroups_; g_in++) {
sigma_s_.push_back(0);
}
}
}
}
}
void FlatSourceDomain::flux_swap()
void FlatSourceDomain::set_adjoint_sources(const vector<double>& forward_flux)
{
scalar_flux_old_.swap(scalar_flux_new_);
// Set the external source to 1/forward_flux
// The forward flux is given in terms of total for the forward simulation
// so we must convert it to a "per batch" quantity
#pragma omp parallel for
for (int64_t se = 0; se < n_source_elements_; se++) {
external_source_[se] = 1.0 / forward_flux[se];
}
// Divide the fixed source term by sigma t (to save time when applying each
// iteration)
#pragma omp parallel for
for (int sr = 0; sr < n_source_regions_; sr++) {
int material = material_[sr];
for (int g = 0; g < negroups_; g++) {
double sigma_t = sigma_t_[material * negroups_ + g];
external_source_[sr * negroups_ + g] /= sigma_t;
}
}
}
void FlatSourceDomain::transpose_scattering_matrix()
{
// Transpose the inner two dimensions for each material
for (int m = 0; m < n_materials_; ++m) {
int material_offset = m * negroups_ * negroups_;
for (int i = 0; i < negroups_; ++i) {
for (int j = i + 1; j < negroups_; ++j) {
// Calculate indices of the elements to swap
int idx1 = material_offset + i * negroups_ + j;
int idx2 = material_offset + j * negroups_ + i;
// Swap the elements to transpose the matrix
std::swap(sigma_s_[idx1], sigma_s_[idx2]);
}
}
}
}
} // namespace openmc

View file

@ -56,40 +56,30 @@ void LinearSourceDomain::update_neutron_source(double k_eff)
double inverse_k_eff = 1.0 / k_eff;
// Temperature and angle indices, if using multiple temperature
// data sets and/or anisotropic data sets.
// TODO: Currently assumes we are only using single temp/single
// angle data.
const int t = 0;
const int a = 0;
#pragma omp parallel for
for (int sr = 0; sr < n_source_regions_; sr++) {
int material = material_[sr];
MomentMatrix invM = mom_matrix_[sr].inverse();
for (int e_out = 0; e_out < negroups_; e_out++) {
double sigma_t = data::mg.macro_xs_[material].get_xs(
MgxsType::TOTAL, e_out, nullptr, nullptr, nullptr, t, a);
for (int g_out = 0; g_out < negroups_; g_out++) {
double sigma_t = sigma_t_[material * negroups_ + g_out];
double scatter_flat = 0.0f;
double fission_flat = 0.0f;
MomentArray scatter_linear = {0.0, 0.0, 0.0};
MomentArray fission_linear = {0.0, 0.0, 0.0};
for (int e_in = 0; e_in < negroups_; e_in++) {
for (int g_in = 0; g_in < negroups_; g_in++) {
// Handles for the flat and linear components of the flux
double flux_flat = scalar_flux_old_[sr * negroups_ + e_in];
MomentArray flux_linear = flux_moments_old_[sr * negroups_ + e_in];
double flux_flat = scalar_flux_old_[sr * negroups_ + g_in];
MomentArray flux_linear = flux_moments_old_[sr * negroups_ + g_in];
// Handles for cross sections
double sigma_s = data::mg.macro_xs_[material].get_xs(
MgxsType::NU_SCATTER, e_in, &e_out, nullptr, nullptr, t, a);
double nu_sigma_f = data::mg.macro_xs_[material].get_xs(
MgxsType::NU_FISSION, e_in, nullptr, nullptr, nullptr, t, a);
double chi = data::mg.macro_xs_[material].get_xs(
MgxsType::CHI_PROMPT, e_in, &e_out, nullptr, nullptr, t, a);
double sigma_s =
sigma_s_[material * negroups_ * negroups_ + g_out * negroups_ + g_in];
double nu_sigma_f = nu_sigma_f_[material * negroups_ + g_in];
double chi = chi_[material * negroups_ + g_out];
// Compute source terms for flat and linear components of the flux
scatter_flat += sigma_s * flux_flat;
@ -99,7 +89,7 @@ void LinearSourceDomain::update_neutron_source(double k_eff)
}
// Compute the flat source term
source_[sr * negroups_ + e_out] =
source_[sr * negroups_ + g_out] =
(scatter_flat + fission_flat * inverse_k_eff) / sigma_t;
// Compute the linear source terms
@ -107,7 +97,7 @@ void LinearSourceDomain::update_neutron_source(double k_eff)
// are not well known, we will leave the source gradients as zero
// so as to avoid causing any numerical instability.
if (simulation::current_batch > 10) {
source_gradients_[sr * negroups_ + e_out] =
source_gradients_[sr * negroups_ + g_out] =
invM * ((scatter_linear + fission_linear * inverse_k_eff) / sigma_t);
}
}
@ -116,7 +106,7 @@ void LinearSourceDomain::update_neutron_source(double k_eff)
if (settings::run_mode == RunMode::FIXED_SOURCE) {
// Add external source to flat source term if in fixed source mode
#pragma omp parallel for
for (int se = 0; se < n_source_elements_; se++) {
for (int64_t se = 0; se < n_source_elements_; se++) {
source_[se] += external_source_[se];
}
}
@ -133,9 +123,9 @@ void LinearSourceDomain::normalize_scalar_flux_and_volumes(
// Normalize flux to total distance travelled by all rays this iteration
#pragma omp parallel for
for (int64_t e = 0; e < scalar_flux_new_.size(); e++) {
scalar_flux_new_[e] *= normalization_factor;
flux_moments_new_[e] *= normalization_factor;
for (int64_t se = 0; se < scalar_flux_new_.size(); se++) {
scalar_flux_new_[se] *= normalization_factor;
flux_moments_new_[se] *= normalization_factor;
}
// Accumulate cell-wise ray length tallies collected this iteration, then

View file

@ -316,17 +316,9 @@ void RandomRay::attenuate_flux_flat_source(double distance, bool is_active)
int64_t source_element = source_region * negroups_;
int material = this->material();
// Temperature and angle indices, if using multiple temperature
// data sets and/or anisotropic data sets.
// TODO: Currently assumes we are only using single temp/single
// angle data.
const int t = 0;
const int a = 0;
// MOC incoming flux attenuation + source contribution/attenuation equation
for (int g = 0; g < negroups_; g++) {
float sigma_t = data::mg.macro_xs_[material].get_xs(
MgxsType::TOTAL, g, NULL, NULL, NULL, t, a);
float sigma_t = domain_->sigma_t_[material * negroups_ + g];
float tau = sigma_t * distance;
float exponential = cjosey_exponential(tau); // exponential = 1 - exp(-tau)
float new_delta_psi =
@ -388,13 +380,6 @@ void RandomRay::attenuate_flux_linear_source(double distance, bool is_active)
int64_t source_element = source_region * negroups_;
int material = this->material();
// Temperature and angle indices, if using multiple temperature
// data sets and/or anisotropic data sets.
// TODO: Currently assumes we are only using single temp/single
// angle data.
const int t = 0;
const int a = 0;
Position& centroid = domain->centroid_[source_region];
Position midpoint = r() + u() * (distance / 2.0);
@ -422,8 +407,7 @@ void RandomRay::attenuate_flux_linear_source(double distance, bool is_active)
for (int g = 0; g < negroups_; g++) {
// Compute tau, the optical thickness of the ray segment
float sigma_t = data::mg.macro_xs_[material].get_xs(
MgxsType::TOTAL, g, NULL, NULL, NULL, t, a);
float sigma_t = domain_->sigma_t_[material * negroups_ + g];
float tau = sigma_t * distance;
// If tau is very small, set it to zero to avoid numerical issues.

View file

@ -23,6 +23,22 @@ namespace openmc {
void openmc_run_random_ray()
{
//////////////////////////////////////////////////////////
// Run forward simulation
//////////////////////////////////////////////////////////
// Check if adjoint calculation is needed. If it is, we will run the forward
// calculation first and then the adjoint calculation later.
bool adjoint_needed = FlatSourceDomain::adjoint_;
// Configure the domain for forward simulation
FlatSourceDomain::adjoint_ = false;
// If we're going to do an adjoint simulation afterwards, report that this is
// the initial forward flux solve.
if (adjoint_needed && mpi::master)
header("FORWARD FLUX SOLVE", 3);
// Initialize OpenMC general data structures
openmc_simulation_init();
@ -30,26 +46,93 @@ void openmc_run_random_ray()
if (mpi::master)
validate_random_ray_inputs();
// Initialize Random Ray Simulation Object
RandomRaySimulation sim;
// Declare forward flux so that it can be saved for later adjoint simulation
vector<double> forward_flux;
// Begin main simulation timer
simulation::time_total.start();
{
// Initialize Random Ray Simulation Object
RandomRaySimulation sim;
// Execute random ray simulation
sim.simulate();
// Initialize fixed sources, if present
sim.prepare_fixed_sources();
// End main simulation timer
openmc::simulation::time_total.stop();
// Begin main simulation timer
simulation::time_total.start();
// Finalize OpenMC
openmc_simulation_finalize();
// Execute random ray simulation
sim.simulate();
// Reduce variables across MPI ranks
sim.reduce_simulation_statistics();
// End main simulation timer
simulation::time_total.stop();
// Output all simulation results
sim.output_simulation_results();
// Normalize and save the final forward flux
forward_flux = sim.domain()->scalar_flux_final_;
double source_normalization_factor =
sim.domain()->compute_fixed_source_normalization_factor() /
(settings::n_batches - settings::n_inactive);
#pragma omp parallel for
for (uint64_t i = 0; i < forward_flux.size(); i++) {
forward_flux[i] *= source_normalization_factor;
}
// Finalize OpenMC
openmc_simulation_finalize();
// Reduce variables across MPI ranks
sim.reduce_simulation_statistics();
// Output all simulation results
sim.output_simulation_results();
}
//////////////////////////////////////////////////////////
// Run adjoint simulation (if enabled)
//////////////////////////////////////////////////////////
if (adjoint_needed) {
reset_timers();
// Configure the domain for adjoint simulation
FlatSourceDomain::adjoint_ = true;
if (mpi::master)
header("ADJOINT FLUX SOLVE", 3);
// Initialize OpenMC general data structures
openmc_simulation_init();
// Initialize Random Ray Simulation Object
RandomRaySimulation adjoint_sim;
// Initialize adjoint fixed sources, if present
adjoint_sim.prepare_fixed_sources_adjoint(forward_flux);
// Transpose scattering matrix
adjoint_sim.domain()->transpose_scattering_matrix();
// Swap nu_sigma_f and chi
adjoint_sim.domain()->nu_sigma_f_.swap(adjoint_sim.domain()->chi_);
// Begin main simulation timer
simulation::time_total.start();
// Execute random ray simulation
adjoint_sim.simulate();
// End main simulation timer
simulation::time_total.stop();
// Finalize OpenMC
openmc_simulation_finalize();
// Reduce variables across MPI ranks
adjoint_sim.reduce_simulation_statistics();
// Output all simulation results
adjoint_sim.output_simulation_results();
}
}
// Enforces restrictions on inputs in random ray mode. While there are
@ -254,16 +337,31 @@ RandomRaySimulation::RandomRaySimulation()
default:
fatal_error("Unknown random ray source shape");
}
// Convert OpenMC native MGXS into a more efficient format
// internal to the random ray solver
domain_->flatten_xs();
}
void RandomRaySimulation::simulate()
void RandomRaySimulation::prepare_fixed_sources()
{
if (settings::run_mode == RunMode::FIXED_SOURCE) {
// Transfer external source user inputs onto random ray source regions
domain_->convert_external_sources();
domain_->count_external_source_regions();
}
}
void RandomRaySimulation::prepare_fixed_sources_adjoint(
vector<double>& forward_flux)
{
if (settings::run_mode == RunMode::FIXED_SOURCE) {
domain_->set_adjoint_sources(forward_flux);
}
}
void RandomRaySimulation::simulate()
{
// Random ray power iteration loop
while (simulation::current_batch < settings::n_batches) {
@ -314,18 +412,20 @@ void RandomRaySimulation::simulate()
}
// Execute all tallying tasks, if this is an active batch
if (simulation::current_batch > settings::n_inactive && mpi::master) {
// Generate mapping between source regions and tallies
if (!domain_->mapped_all_tallies_) {
domain_->convert_source_regions_to_tallies();
}
// Use above mapping to contribute FSR flux data to appropriate tallies
domain_->random_ray_tally();
if (simulation::current_batch > settings::n_inactive) {
// Add this iteration's scalar flux estimate to final accumulated estimate
domain_->accumulate_iteration_flux();
if (mpi::master) {
// Generate mapping between source regions and tallies
if (!domain_->mapped_all_tallies_) {
domain_->convert_source_regions_to_tallies();
}
// Use above mapping to contribute FSR flux data to appropriate tallies
domain_->random_ray_tally();
}
}
// Set phi_old = phi_new
@ -448,6 +548,9 @@ void RandomRaySimulation::print_results_random_ray(
}
fmt::print(" Volume Estimator Type = {}\n", estimator);
std::string adjoint_true = (FlatSourceDomain::adjoint_) ? "ON" : "OFF";
fmt::print(" Adjoint Flux Mode = {}\n", adjoint_true);
header("Timing Statistics", 4);
show_time("Total time for initialization", time_initialize.elapsed());
show_time("Reading cross sections", time_read_xs.elapsed(), 1);

View file

@ -301,6 +301,10 @@ void get_run_parameters(pugi::xml_node node_base)
FlatSourceDomain::volume_normalized_flux_tallies_ =
get_node_value_bool(random_ray_node, "volume_normalized_flux_tallies");
}
if (check_for_node(random_ray_node, "adjoint")) {
FlatSourceDomain::adjoint_ =
get_node_value_bool(random_ray_node, "adjoint");
}
}
}

View file

@ -0,0 +1,245 @@
<?xml version='1.0' encoding='utf-8'?>
<model>
<materials>
<cross_sections>mgxs.h5</cross_sections>
<material id="1" name="source">
<density units="macro" value="1.0"/>
<macroscopic name="source"/>
</material>
<material id="2" name="void">
<density units="macro" value="1.0"/>
<macroscopic name="void"/>
</material>
<material id="3" name="absorber">
<density units="macro" value="1.0"/>
<macroscopic name="absorber"/>
</material>
</materials>
<geometry>
<cell id="1" material="1" name="infinite source region" universe="1"/>
<cell id="2" material="2" name="infinite void region" universe="2"/>
<cell id="3" material="3" name="infinite absorber region" universe="3"/>
<cell fill="4" id="4" universe="5"/>
<cell fill="5" id="5" name="full domain" region="1 -2 3 -4 5 -6" universe="6"/>
<lattice id="4">
<pitch>2.5 2.5 2.5</pitch>
<dimension>12 12 12</dimension>
<lower_left>0.0 0.0 0.0</lower_left>
<universes>
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 </universes>
</lattice>
<surface boundary="reflective" coeffs="0.0" id="1" type="x-plane"/>
<surface boundary="vacuum" coeffs="30.0" id="2" type="x-plane"/>
<surface boundary="reflective" coeffs="0.0" id="3" type="y-plane"/>
<surface boundary="vacuum" coeffs="30.0" id="4" type="y-plane"/>
<surface boundary="reflective" coeffs="0.0" id="5" type="z-plane"/>
<surface boundary="vacuum" coeffs="30.0" id="6" type="z-plane"/>
</geometry>
<settings>
<run_mode>fixed source</run_mode>
<particles>90</particles>
<batches>10</batches>
<inactive>5</inactive>
<source particle="neutron" strength="3.14" type="independent">
<energy type="discrete">
<parameters>100.0 1.0</parameters>
</energy>
<constraints>
<domain_type>universe</domain_type>
<domain_ids>1</domain_ids>
</constraints>
</source>
<energy_mode>multi-group</energy_mode>
<random_ray>
<distance_active>500.0</distance_active>
<distance_inactive>100.0</distance_inactive>
<source particle="neutron" strength="1.0" type="independent">
<space type="box">
<parameters>0.0 0.0 0.0 30.0 30.0 30.0</parameters>
</space>
</source>
<volume_normalized_flux_tallies>True</volume_normalized_flux_tallies>
<adjoint>True</adjoint>
</random_ray>
</settings>
<tallies>
<filter id="3" type="material">
<bins>1</bins>
</filter>
<filter id="2" type="material">
<bins>2</bins>
</filter>
<filter id="1" type="material">
<bins>3</bins>
</filter>
<tally id="3" name="Source Tally">
<filters>3</filters>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="2" name="Void Tally">
<filters>2</filters>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="1" name="Absorber Tally">
<filters>1</filters>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
</tallies>
</model>

View file

@ -0,0 +1,9 @@
tally 1:
-7.235364E+03
3.367109E+09
tally 2:
4.818311E+05
6.269371E+10
tally 3:
1.515641E+06
4.598791E+11

View file

@ -0,0 +1,20 @@
import os
from openmc.examples import random_ray_three_region_cube
from tests.testing_harness import TolerantPyAPITestHarness
class MGXSTestHarness(TolerantPyAPITestHarness):
def _cleanup(self):
super()._cleanup()
f = 'mgxs.h5'
if os.path.exists(f):
os.remove(f)
def test_random_ray_adjoint_fixed_source():
model = random_ray_three_region_cube()
model.settings.random_ray['adjoint'] = True
harness = MGXSTestHarness('statepoint.10.h5', model)
harness.main()

View file

@ -0,0 +1,110 @@
<?xml version='1.0' encoding='utf-8'?>
<model>
<materials>
<cross_sections>mgxs.h5</cross_sections>
<material id="1" name="UO2 fuel">
<density units="macro" value="1.0"/>
<macroscopic name="UO2"/>
</material>
<material id="2" name="Water">
<density units="macro" value="1.0"/>
<macroscopic name="LWTR"/>
</material>
</materials>
<geometry>
<cell id="1" material="1" name="fuel inner a" region="-2" universe="1"/>
<cell id="2" material="1" name="fuel inner b" region="2 -3" universe="1"/>
<cell id="3" material="1" name="fuel inner c" region="3 -1" universe="1"/>
<cell id="4" material="2" name="moderator inner a" region="1 -4" universe="1"/>
<cell id="5" material="2" name="moderator outer b" region="4 -5" universe="1"/>
<cell id="6" material="2" name="moderator outer c" region="5" universe="1"/>
<cell fill="1" id="7" name="azimuthal_cell_0" region="6 -7" universe="2"/>
<cell fill="1" id="8" name="azimuthal_cell_1" region="7 -8" universe="2"/>
<cell fill="1" id="9" name="azimuthal_cell_2" region="8 -9" universe="2"/>
<cell fill="1" id="10" name="azimuthal_cell_3" region="9 -10" universe="2"/>
<cell fill="1" id="11" name="azimuthal_cell_4" region="10 -11" universe="2"/>
<cell fill="1" id="12" name="azimuthal_cell_5" region="11 -12" universe="2"/>
<cell fill="1" id="13" name="azimuthal_cell_6" region="12 -13" universe="2"/>
<cell fill="1" id="14" name="azimuthal_cell_7" region="13 -6" universe="2"/>
<cell id="15" material="2" name="moderator infinite" universe="3"/>
<cell fill="4" id="16" universe="5"/>
<cell fill="6" id="17" name="assembly" region="14 -15 16 -17" universe="7"/>
<lattice id="4">
<pitch>0.126 0.126</pitch>
<dimension>10 10</dimension>
<lower_left>-0.63 -0.63</lower_left>
<universes>
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 </universes>
</lattice>
<lattice id="6">
<pitch>1.26 1.26</pitch>
<dimension>2 2</dimension>
<lower_left>-1.26 -1.26</lower_left>
<universes>
2 2
2 5 </universes>
</lattice>
<surface coeffs="0.0 0.0 0.54" id="1" name="Fuel OR" type="z-cylinder"/>
<surface coeffs="0.0 0.0 0.33" id="2" name="inner ring a" type="z-cylinder"/>
<surface coeffs="0.0 0.0 0.45" id="3" name="inner ring b" type="z-cylinder"/>
<surface coeffs="0.0 0.0 0.6" id="4" name="outer ring a" type="z-cylinder"/>
<surface coeffs="0.0 0.0 0.69" id="5" name="outer ring b" type="z-cylinder"/>
<surface coeffs="-0.0 1.0 0 0" id="6" type="plane"/>
<surface coeffs="-0.7071067811865475 0.7071067811865476 0 0" id="7" type="plane"/>
<surface coeffs="-1.0 6.123233995736766e-17 0 0" id="8" type="plane"/>
<surface coeffs="-0.7071067811865476 -0.7071067811865475 0 0" id="9" type="plane"/>
<surface coeffs="-1.2246467991473532e-16 -1.0 0 0" id="10" type="plane"/>
<surface coeffs="0.7071067811865475 -0.7071067811865477 0 0" id="11" type="plane"/>
<surface coeffs="1.0 -1.8369701987210297e-16 0 0" id="12" type="plane"/>
<surface coeffs="0.7071067811865477 0.7071067811865474 0 0" id="13" type="plane"/>
<surface boundary="reflective" coeffs="-1.26" id="14" name="minimum x" type="x-plane"/>
<surface boundary="reflective" coeffs="1.26" id="15" name="maximum x" type="x-plane"/>
<surface boundary="reflective" coeffs="-1.26" id="16" name="minimum y" type="y-plane"/>
<surface boundary="reflective" coeffs="1.26" id="17" name="maximum y" type="y-plane"/>
</geometry>
<settings>
<run_mode>eigenvalue</run_mode>
<particles>100</particles>
<batches>10</batches>
<inactive>5</inactive>
<energy_mode>multi-group</energy_mode>
<random_ray>
<distance_active>100.0</distance_active>
<distance_inactive>20.0</distance_inactive>
<source particle="neutron" strength="1.0" type="independent">
<space type="box">
<parameters>-1.26 -1.26 -1 1.26 1.26 1</parameters>
</space>
</source>
<volume_normalized_flux_tallies>True</volume_normalized_flux_tallies>
<adjoint>True</adjoint>
</random_ray>
</settings>
<tallies>
<mesh id="1">
<dimension>2 2</dimension>
<lower_left>-1.26 -1.26</lower_left>
<upper_right>1.26 1.26</upper_right>
</mesh>
<filter id="1" type="mesh">
<bins>1</bins>
</filter>
<filter id="2" type="energy">
<bins>1e-05 0.0635 10.0 100.0 1000.0 500000.0 1000000.0 20000000.0</bins>
</filter>
<tally id="1" name="Mesh tally">
<filters>1 2</filters>
<scores>flux fission nu-fission</scores>
<estimator>analog</estimator>
</tally>
</tallies>
</model>

View file

@ -0,0 +1,171 @@
k-combined:
1.006640E+00 1.812967E-03
tally 1:
6.684129E+00
8.939821E+00
2.685967E+00
1.443592E+00
0.000000E+00
0.000000E+00
6.358774E+00
8.091444E+00
9.687217E-01
1.878029E-01
0.000000E+00
0.000000E+00
5.963160E+00
7.117108E+00
1.932332E-01
7.473914E-03
0.000000E+00
0.000000E+00
5.137593E+00
5.283310E+00
1.714616E-01
5.884834E-03
1.086218E-06
2.361752E-13
4.857253E+00
4.719856E+00
5.689580E-02
6.476286E-04
2.989356E-03
1.787808E-06
4.830516E+00
4.666801E+00
7.203015E-03
1.037676E-05
3.620020E+00
2.620927E+00
5.161382E+00
5.328124E+00
6.786255E-02
9.210763E-04
5.531943E+00
6.120553E+00
5.414034E+00
5.864661E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
5.632338E+00
6.347626E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
5.682608E+00
6.462382E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
5.310716E+00
5.645180E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
4.945409E+00
4.893171E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
4.842688E+00
4.690352E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
5.117198E+00
5.237280E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
6.938711E+00
9.633345E+00
2.835258E+00
1.608212E+00
0.000000E+00
0.000000E+00
6.549505E+00
8.584036E+00
1.015138E+00
2.061993E-01
0.000000E+00
0.000000E+00
6.050651E+00
7.327711E+00
1.992816E-01
7.948424E-03
0.000000E+00
0.000000E+00
5.113981E+00
5.234801E+00
1.732323E-01
6.006619E-03
1.097435E-06
2.410627E-13
4.837033E+00
4.680541E+00
5.760042E-02
6.637112E-04
3.026377E-03
1.832205E-06
4.827049E+00
4.660105E+00
7.319913E-03
1.071647E-05
3.678770E+00
2.706730E+00
5.175337E+00
5.356957E+00
6.923046E-02
9.586177E-04
5.643451E+00
6.370016E+00
6.693323E+00
8.964322E+00
2.753307E+00
1.516683E+00
0.000000E+00
0.000000E+00
6.358384E+00
8.090233E+00
9.912008E-01
1.965868E-01
0.000000E+00
0.000000E+00
5.957484E+00
7.103246E+00
1.974033E-01
7.798286E-03
0.000000E+00
0.000000E+00
5.130744E+00
5.268844E+00
1.749233E-01
6.123348E-03
1.108148E-06
2.457474E-13
4.857340E+00
4.720019E+00
5.816659E-02
6.768049E-04
3.056125E-03
1.868351E-06
4.830629E+00
4.667018E+00
7.366289E-03
1.085264E-05
3.702077E+00
2.741125E+00
5.164864E+00
5.335279E+00
6.947917E-02
9.655086E-04
5.663725E+00
6.415806E+00

View file

@ -0,0 +1,20 @@
import os
from openmc.examples import random_ray_lattice
from tests.testing_harness import TolerantPyAPITestHarness
class MGXSTestHarness(TolerantPyAPITestHarness):
def _cleanup(self):
super()._cleanup()
f = 'mgxs.h5'
if os.path.exists(f):
os.remove(f)
def test_random_ray_basic():
model = random_ray_lattice()
model.settings.random_ray['adjoint'] = True
harness = MGXSTestHarness('statepoint.10.h5', model)
harness.main()