mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-25 04:25:29 -04:00
912 lines
28 KiB
C++
912 lines
28 KiB
C++
#include "openmc/scattdata.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <numeric>
|
|
|
|
#include "xtensor/xbuilder.hpp"
|
|
#include "xtensor/xview.hpp"
|
|
|
|
#include "openmc/constants.h"
|
|
#include "openmc/error.h"
|
|
#include "openmc/math_functions.h"
|
|
#include "openmc/random_lcg.h"
|
|
#include "openmc/settings.h"
|
|
|
|
namespace openmc {
|
|
|
|
//==============================================================================
|
|
// ScattData base-class methods
|
|
//==============================================================================
|
|
|
|
void ScattData::base_init(int order, const xt::xtensor<int, 1>& in_gmin,
|
|
const xt::xtensor<int, 1>& in_gmax, const double_2dvec& in_energy,
|
|
const double_2dvec& in_mult)
|
|
{
|
|
size_t groups = in_energy.size();
|
|
|
|
gmin = in_gmin;
|
|
gmax = in_gmax;
|
|
energy.resize(groups);
|
|
mult.resize(groups);
|
|
dist.resize(groups);
|
|
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
// Store the inputted data
|
|
energy[gin] = in_energy[gin];
|
|
mult[gin] = in_mult[gin];
|
|
|
|
// Make sure the multiplicity does not have 0s
|
|
unsigned long int num_converted = 0;
|
|
for (int go = 0; go < mult[gin].size(); go++) {
|
|
if (mult[gin][go] == 0.) {
|
|
num_converted += 1;
|
|
mult[gin][go] = 1.;
|
|
}
|
|
}
|
|
|
|
if (num_converted > 0) {
|
|
// Raise a warning to the user if we did have to do the conversion
|
|
std::string msg =
|
|
std::to_string(num_converted) +
|
|
" entries in the Multiplicity Matrix were changed from 0 to 1";
|
|
warning(msg);
|
|
}
|
|
|
|
// Make sure the energy is normalized
|
|
double norm = std::accumulate(energy[gin].begin(), energy[gin].end(), 0.);
|
|
|
|
if (norm != 0.) {
|
|
for (auto& n : energy[gin])
|
|
n /= norm;
|
|
}
|
|
|
|
// Initialize the distribution data
|
|
dist[gin].resize(in_gmax[gin] - in_gmin[gin] + 1);
|
|
for (auto& v : dist[gin]) {
|
|
v.resize(order);
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void ScattData::base_combine(size_t max_order, size_t order_dim,
|
|
const vector<ScattData*>& those_scatts, const vector<double>& scalars,
|
|
xt::xtensor<int, 1>& in_gmin, xt::xtensor<int, 1>& in_gmax,
|
|
double_2dvec& sparse_mult, double_3dvec& sparse_scatter)
|
|
{
|
|
size_t groups = those_scatts[0]->energy.size();
|
|
|
|
// Now allocate and zero our storage spaces
|
|
xt::xtensor<double, 3> this_nuscatt_matrix({groups, groups, order_dim}, 0.);
|
|
xt::xtensor<double, 2> this_nuscatt_P0({groups, groups}, 0.);
|
|
xt::xtensor<double, 2> this_scatt_P0({groups, groups}, 0.);
|
|
xt::xtensor<double, 2> this_mult({groups, groups}, 1.);
|
|
|
|
// Build the dense scattering and multiplicity matrices
|
|
for (int i = 0; i < those_scatts.size(); i++) {
|
|
ScattData* that = those_scatts[i];
|
|
|
|
// Build the dense matrix for that object
|
|
xt::xtensor<double, 3> that_matrix = that->get_matrix(max_order);
|
|
|
|
// Now add that to this for the nu-scatter matrix
|
|
this_nuscatt_matrix += scalars[i] * that_matrix;
|
|
|
|
// Do the same with the P0 matrices
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
for (int go = 0; go < groups; go++) {
|
|
this_nuscatt_P0(gin, go) +=
|
|
scalars[i] * that->get_xs(MgxsType::NU_SCATTER, gin, &go, nullptr);
|
|
this_scatt_P0(gin, go) +=
|
|
scalars[i] * that->get_xs(MgxsType::SCATTER, gin, &go, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now we have the dense nuscatt and scatt, we can easily compute the
|
|
// multiplicity matrix by dividing the two and fixing any nans
|
|
this_mult = xt::nan_to_num(this_nuscatt_P0 / this_scatt_P0);
|
|
|
|
// We have the data, now we need to convert to a jagged array and then use
|
|
// the initialize function to store it on the object.
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
// Find the minimum and maximum group boundaries
|
|
int gmin_;
|
|
for (gmin_ = 0; gmin_ < groups; gmin_++) {
|
|
bool non_zero = false;
|
|
for (int l = 0; l < this_nuscatt_matrix.shape()[2]; l++) {
|
|
if (this_nuscatt_matrix(gin, gmin_, l) != 0.) {
|
|
non_zero = true;
|
|
break;
|
|
}
|
|
}
|
|
if (non_zero)
|
|
break;
|
|
}
|
|
int gmax_;
|
|
for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) {
|
|
bool non_zero = false;
|
|
for (int l = 0; l < this_nuscatt_matrix.shape()[2]; l++) {
|
|
if (this_nuscatt_matrix(gin, gmax_, l) != 0.) {
|
|
non_zero = true;
|
|
break;
|
|
}
|
|
}
|
|
if (non_zero)
|
|
break;
|
|
}
|
|
|
|
// treat the case of all values being 0
|
|
if (gmin_ > gmax_) {
|
|
gmin_ = gin;
|
|
gmax_ = gin;
|
|
}
|
|
|
|
// Store the group bounds
|
|
in_gmin[gin] = gmin_;
|
|
in_gmax[gin] = gmax_;
|
|
|
|
// Store the data in the compressed format
|
|
sparse_scatter[gin].resize(gmax_ - gmin_ + 1);
|
|
sparse_mult[gin].resize(gmax_ - gmin_ + 1);
|
|
int i_gout = 0;
|
|
for (int gout = gmin_; gout <= gmax_; gout++) {
|
|
sparse_scatter[gin][i_gout].resize(this_nuscatt_matrix.shape()[2]);
|
|
for (int l = 0; l < this_nuscatt_matrix.shape()[2]; l++) {
|
|
sparse_scatter[gin][i_gout][l] = this_nuscatt_matrix(gin, gout, l);
|
|
}
|
|
sparse_mult[gin][i_gout] = this_mult(gin, gout);
|
|
i_gout++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void ScattData::sample_energy(int gin, int& gout, int& i_gout, uint64_t* seed)
|
|
{
|
|
// Sample the outgoing group
|
|
double xi = prn(seed);
|
|
double prob = 0.;
|
|
i_gout = 0;
|
|
for (gout = gmin[gin]; gout < gmax[gin]; ++gout) {
|
|
prob += energy[gin][i_gout];
|
|
if (xi < prob)
|
|
break;
|
|
++i_gout;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
double ScattData::get_xs(
|
|
MgxsType xstype, int gin, const int* gout, const double* mu)
|
|
{
|
|
// Set the outgoing group offset index as needed
|
|
int i_gout = 0;
|
|
if (gout != nullptr) {
|
|
// short circuit the function if gout is from a zero portion of the
|
|
// scattering matrix
|
|
if ((*gout < gmin[gin]) || (*gout > gmax[gin])) { // > gmax?
|
|
return 0.;
|
|
}
|
|
i_gout = *gout - gmin[gin];
|
|
}
|
|
|
|
double val = scattxs[gin];
|
|
switch (xstype) {
|
|
case MgxsType::NU_SCATTER:
|
|
if (gout != nullptr)
|
|
val *= energy[gin][i_gout];
|
|
break;
|
|
case MgxsType::SCATTER:
|
|
if (gout != nullptr) {
|
|
val *= energy[gin][i_gout] / mult[gin][i_gout];
|
|
} else {
|
|
val /= std::inner_product(
|
|
mult[gin].begin(), mult[gin].end(), energy[gin].begin(), 0.0);
|
|
}
|
|
break;
|
|
case MgxsType::NU_SCATTER_FMU:
|
|
if ((gout != nullptr) && (mu != nullptr)) {
|
|
val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu);
|
|
} else {
|
|
// This is not an expected path (asking for f_mu without asking for a
|
|
// group or mu is not useful
|
|
fatal_error("Invalid call to get_xs");
|
|
}
|
|
break;
|
|
case MgxsType::SCATTER_FMU:
|
|
if ((gout != nullptr) && (mu != nullptr)) {
|
|
val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu) / mult[gin][i_gout];
|
|
} else {
|
|
// This is not an expected path (asking for f_mu without asking for a
|
|
// group or mu is not useful
|
|
fatal_error("Invalid call to get_xs");
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
//==============================================================================
|
|
// ScattDataLegendre methods
|
|
//==============================================================================
|
|
|
|
void ScattDataLegendre::init(const xt::xtensor<int, 1>& in_gmin,
|
|
const xt::xtensor<int, 1>& in_gmax, const double_2dvec& in_mult,
|
|
const double_3dvec& coeffs)
|
|
{
|
|
size_t groups = coeffs.size();
|
|
size_t order = coeffs[0][0].size();
|
|
|
|
// make a copy of coeffs that we can use to both extract data and normalize
|
|
double_3dvec matrix = coeffs;
|
|
|
|
// Get the scattering cross section value by summing the un-normalized P0
|
|
// coefficient in the variable matrix over all outgoing groups.
|
|
scattxs = xt::zeros<double>({groups});
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
|
|
for (int i_gout = 0; i_gout < num_groups; i_gout++) {
|
|
scattxs[gin] += matrix[gin][i_gout][0];
|
|
}
|
|
}
|
|
|
|
// Build the energy transfer matrix from data in the variable matrix while
|
|
// also normalizing the variable matrix itself
|
|
// (forcing the CDF of f(mu=1) == 1)
|
|
double_2dvec in_energy;
|
|
in_energy.resize(groups);
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
|
|
in_energy[gin].resize(num_groups);
|
|
for (int i_gout = 0; i_gout < num_groups; i_gout++) {
|
|
double norm = matrix[gin][i_gout][0];
|
|
in_energy[gin][i_gout] = norm;
|
|
if (norm != 0.) {
|
|
for (auto& n : matrix[gin][i_gout])
|
|
n /= norm;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize the base class attributes
|
|
ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult);
|
|
|
|
// Set the distribution (sdata.dist) values and initialize max_val
|
|
max_val.resize(groups);
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
int num_groups = gmax[gin] - gmin[gin] + 1;
|
|
for (int i_gout = 0; i_gout < num_groups; i_gout++) {
|
|
dist[gin][i_gout] = matrix[gin][i_gout];
|
|
}
|
|
max_val[gin].resize(num_groups);
|
|
for (auto& n : max_val[gin])
|
|
n = 0.;
|
|
}
|
|
|
|
// Now update the maximum value
|
|
update_max_val();
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void ScattDataLegendre::update_max_val()
|
|
{
|
|
size_t groups = max_val.size();
|
|
// Step through the polynomial with fixed number of points to identify the
|
|
// maximal value
|
|
int Nmu = 1001;
|
|
double dmu = 2. / (Nmu - 1);
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
int num_groups = gmax[gin] - gmin[gin] + 1;
|
|
for (int i_gout = 0; i_gout < num_groups; i_gout++) {
|
|
for (int imu = 0; imu < Nmu; imu++) {
|
|
double mu;
|
|
if (imu == 0) {
|
|
mu = -1.;
|
|
} else if (imu == (Nmu - 1)) {
|
|
mu = 1.;
|
|
} else {
|
|
mu = -1. + (imu - 1) * dmu;
|
|
}
|
|
|
|
// Calculate probability
|
|
double f = evaluate_legendre(
|
|
dist[gin][i_gout].size() - 1, dist[gin][i_gout].data(), mu);
|
|
|
|
// if this is a new maximum, store it
|
|
if (f > max_val[gin][i_gout])
|
|
max_val[gin][i_gout] = f;
|
|
} // end imu loop
|
|
|
|
// Since we may not have caught the true max, add 10% margin
|
|
max_val[gin][i_gout] *= 1.1;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
double ScattDataLegendre::calc_f(int gin, int gout, double mu)
|
|
{
|
|
double f;
|
|
if ((gout < gmin[gin]) || (gout > gmax[gin])) {
|
|
f = 0.;
|
|
} else {
|
|
int i_gout = gout - gmin[gin];
|
|
f = evaluate_legendre(
|
|
dist[gin][i_gout].size() - 1, dist[gin][i_gout].data(), mu);
|
|
}
|
|
return f;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void ScattDataLegendre::sample(
|
|
int gin, int& gout, double& mu, double& wgt, uint64_t* seed)
|
|
{
|
|
// Sample the outgoing energy using the base-class method
|
|
int i_gout;
|
|
sample_energy(gin, gout, i_gout, seed);
|
|
|
|
// Now we can sample mu using the scattering kernel using rejection
|
|
// sampling from a rectangular bounding box
|
|
double M = max_val[gin][i_gout];
|
|
int samples;
|
|
for (samples = 0; samples < MAX_SAMPLE; ++samples) {
|
|
mu = 2. * prn(seed) - 1.;
|
|
double f = calc_f(gin, gout, mu);
|
|
if (f > 0.) {
|
|
double u = prn(seed) * M;
|
|
if (u <= f)
|
|
break;
|
|
}
|
|
}
|
|
if (samples == MAX_SAMPLE) {
|
|
fatal_error("Maximum number of Legendre expansion samples reached!");
|
|
}
|
|
|
|
// Update the weight to reflect neutron multiplicity
|
|
wgt *= mult[gin][i_gout];
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void ScattDataLegendre::combine(
|
|
const vector<ScattData*>& those_scatts, const vector<double>& scalars)
|
|
{
|
|
// Find the max order in the data set and make sure we can combine the sets
|
|
size_t max_order = 0;
|
|
for (int i = 0; i < those_scatts.size(); i++) {
|
|
// Lets also make sure these items are combineable
|
|
ScattDataLegendre* that = dynamic_cast<ScattDataLegendre*>(those_scatts[i]);
|
|
if (!that) {
|
|
fatal_error("Cannot combine the ScattData objects!");
|
|
}
|
|
size_t that_order = that->get_order();
|
|
if (that_order > max_order)
|
|
max_order = that_order;
|
|
}
|
|
|
|
size_t groups = those_scatts[0]->energy.size();
|
|
|
|
xt::xtensor<int, 1> in_gmin({groups}, 0);
|
|
xt::xtensor<int, 1> in_gmax({groups}, 0);
|
|
double_3dvec sparse_scatter(groups);
|
|
double_2dvec sparse_mult(groups);
|
|
|
|
// The rest of the steps do not depend on the type of angular representation
|
|
// so we use a base class method to sum up xs and create new energy and mult
|
|
// matrices
|
|
size_t order_dim = max_order + 1;
|
|
ScattData::base_combine(max_order, order_dim, those_scatts, scalars, in_gmin,
|
|
in_gmax, sparse_mult, sparse_scatter);
|
|
|
|
// Got everything we need, store it.
|
|
init(in_gmin, in_gmax, sparse_mult, sparse_scatter);
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
xt::xtensor<double, 3> ScattDataLegendre::get_matrix(size_t max_order)
|
|
{
|
|
// Get the sizes and initialize the data to 0
|
|
size_t groups = energy.size();
|
|
size_t order_dim = max_order + 1;
|
|
xt::xtensor<double, 3> matrix({groups, groups, order_dim}, 0.);
|
|
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
|
|
int gout = i_gout + gmin[gin];
|
|
for (int l = 0; l < order_dim; l++) {
|
|
matrix(gin, gout, l) =
|
|
scattxs[gin] * energy[gin][i_gout] * dist[gin][i_gout][l];
|
|
}
|
|
}
|
|
}
|
|
return matrix;
|
|
}
|
|
|
|
//==============================================================================
|
|
// ScattDataHistogram methods
|
|
//==============================================================================
|
|
|
|
void ScattDataHistogram::init(const xt::xtensor<int, 1>& in_gmin,
|
|
const xt::xtensor<int, 1>& in_gmax, const double_2dvec& in_mult,
|
|
const double_3dvec& coeffs)
|
|
{
|
|
size_t groups = coeffs.size();
|
|
size_t order = coeffs[0][0].size();
|
|
|
|
// make a copy of coeffs that we can use to both extract data and normalize
|
|
double_3dvec matrix = coeffs;
|
|
|
|
// Get the scattering cross section value by summing the distribution
|
|
// over all the histogram bins in angle and outgoing energy groups
|
|
scattxs = xt::zeros<double>({groups});
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) {
|
|
scattxs[gin] += std::accumulate(
|
|
matrix[gin][i_gout].begin(), matrix[gin][i_gout].end(), 0.);
|
|
}
|
|
}
|
|
|
|
// Build the energy transfer matrix from data in the variable matrix
|
|
double_2dvec in_energy;
|
|
in_energy.resize(groups);
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
|
|
in_energy[gin].resize(num_groups);
|
|
for (int i_gout = 0; i_gout < num_groups; i_gout++) {
|
|
double norm = std::accumulate(
|
|
matrix[gin][i_gout].begin(), matrix[gin][i_gout].end(), 0.);
|
|
in_energy[gin][i_gout] = norm;
|
|
if (norm != 0.) {
|
|
for (auto& n : matrix[gin][i_gout])
|
|
n /= norm;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize the base class attributes
|
|
ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult);
|
|
|
|
// Build the angular distribution mu values
|
|
mu = xt::linspace(-1., 1., order + 1);
|
|
dmu = 2. / order;
|
|
|
|
// Calculate f(mu) and integrate it so we can avoid rejection sampling
|
|
fmu.resize(groups);
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
int num_groups = gmax[gin] - gmin[gin] + 1;
|
|
fmu[gin].resize(num_groups);
|
|
for (int i_gout = 0; i_gout < num_groups; i_gout++) {
|
|
fmu[gin][i_gout].resize(order);
|
|
// The variable matrix contains f(mu); so directly assign it
|
|
fmu[gin][i_gout] = matrix[gin][i_gout];
|
|
|
|
// Integrate the histogram
|
|
dist[gin][i_gout][0] = dmu * matrix[gin][i_gout][0];
|
|
for (int imu = 1; imu < order; imu++) {
|
|
dist[gin][i_gout][imu] =
|
|
dmu * matrix[gin][i_gout][imu] + dist[gin][i_gout][imu - 1];
|
|
}
|
|
|
|
// Now re-normalize for integral to unity
|
|
double norm = dist[gin][i_gout][order - 1];
|
|
if (norm > 0.) {
|
|
for (int imu = 0; imu < order; imu++) {
|
|
fmu[gin][i_gout][imu] /= norm;
|
|
dist[gin][i_gout][imu] /= norm;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
double ScattDataHistogram::calc_f(int gin, int gout, double mu)
|
|
{
|
|
double f;
|
|
if ((gout < gmin[gin]) || (gout > gmax[gin])) {
|
|
f = 0.;
|
|
} else {
|
|
// Find mu bin
|
|
int i_gout = gout - gmin[gin];
|
|
int imu;
|
|
if (mu == 1.) {
|
|
// use size -2 to have the index one before the end
|
|
imu = this->mu.shape()[0] - 2;
|
|
} else {
|
|
imu = std::floor((mu + 1.) / dmu + 1.) - 1;
|
|
}
|
|
|
|
f = fmu[gin][i_gout][imu];
|
|
}
|
|
return f;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void ScattDataHistogram::sample(
|
|
int gin, int& gout, double& mu, double& wgt, uint64_t* seed)
|
|
{
|
|
// Sample the outgoing energy using the base-class method
|
|
int i_gout;
|
|
sample_energy(gin, gout, i_gout, seed);
|
|
|
|
// Determine the outgoing cosine bin
|
|
double xi = prn(seed);
|
|
|
|
int imu;
|
|
if (xi < dist[gin][i_gout][0]) {
|
|
imu = 0;
|
|
} else {
|
|
imu =
|
|
std::upper_bound(dist[gin][i_gout].begin(), dist[gin][i_gout].end(), xi) -
|
|
dist[gin][i_gout].begin();
|
|
}
|
|
|
|
// Randomly select mu within the imu bin
|
|
mu = prn(seed) * dmu + this->mu[imu];
|
|
|
|
if (mu < -1.) {
|
|
mu = -1.;
|
|
} else if (mu > 1.) {
|
|
mu = 1.;
|
|
}
|
|
|
|
// Update the weight to reflect neutron multiplicity
|
|
wgt *= mult[gin][i_gout];
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
xt::xtensor<double, 3> ScattDataHistogram::get_matrix(size_t max_order)
|
|
{
|
|
// Get the sizes and initialize the data to 0
|
|
size_t groups = energy.size();
|
|
// We ignore the requested order for Histogram and Tabular representations
|
|
size_t order_dim = get_order();
|
|
xt::xtensor<double, 3> matrix({groups, groups, order_dim}, 0);
|
|
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
|
|
int gout = i_gout + gmin[gin];
|
|
for (int l = 0; l < order_dim; l++) {
|
|
matrix(gin, gout, l) =
|
|
scattxs[gin] * energy[gin][i_gout] * fmu[gin][i_gout][l];
|
|
}
|
|
}
|
|
}
|
|
return matrix;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void ScattDataHistogram::combine(
|
|
const vector<ScattData*>& those_scatts, const vector<double>& scalars)
|
|
{
|
|
// Find the max order in the data set and make sure we can combine the sets
|
|
size_t max_order = those_scatts[0]->get_order();
|
|
for (int i = 0; i < those_scatts.size(); i++) {
|
|
// Lets also make sure these items are combineable
|
|
ScattDataHistogram* that =
|
|
dynamic_cast<ScattDataHistogram*>(those_scatts[i]);
|
|
if (!that) {
|
|
fatal_error("Cannot combine the ScattData objects!");
|
|
}
|
|
if (max_order != that->get_order()) {
|
|
fatal_error("Cannot combine the ScattData objects!");
|
|
}
|
|
}
|
|
|
|
size_t groups = those_scatts[0]->energy.size();
|
|
|
|
xt::xtensor<int, 1> in_gmin({groups}, 0);
|
|
xt::xtensor<int, 1> in_gmax({groups}, 0);
|
|
double_3dvec sparse_scatter(groups);
|
|
double_2dvec sparse_mult(groups);
|
|
|
|
// The rest of the steps do not depend on the type of angular representation
|
|
// so we use a base class method to sum up xs and create new energy and mult
|
|
// matrices
|
|
size_t order_dim = max_order;
|
|
ScattData::base_combine(max_order, order_dim, those_scatts, scalars, in_gmin,
|
|
in_gmax, sparse_mult, sparse_scatter);
|
|
|
|
// Got everything we need, store it.
|
|
init(in_gmin, in_gmax, sparse_mult, sparse_scatter);
|
|
}
|
|
|
|
//==============================================================================
|
|
// ScattDataTabular methods
|
|
//==============================================================================
|
|
|
|
void ScattDataTabular::init(const xt::xtensor<int, 1>& in_gmin,
|
|
const xt::xtensor<int, 1>& in_gmax, const double_2dvec& in_mult,
|
|
const double_3dvec& coeffs)
|
|
{
|
|
size_t groups = coeffs.size();
|
|
size_t order = coeffs[0][0].size();
|
|
|
|
// make a copy of coeffs that we can use to both extract data and normalize
|
|
double_3dvec matrix = coeffs;
|
|
|
|
// Build the angular distribution mu values
|
|
mu = xt::linspace(-1., 1., order);
|
|
dmu = 2. / (order - 1);
|
|
|
|
// Get the scattering cross section value by integrating the distribution
|
|
// over all mu points and then combining over all outgoing groups
|
|
scattxs = xt::zeros<double>({groups});
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) {
|
|
for (int imu = 1; imu < order; imu++) {
|
|
scattxs[gin] +=
|
|
0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build the energy transfer matrix from data in the variable matrix
|
|
double_2dvec in_energy(groups);
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
|
|
in_energy[gin].resize(num_groups);
|
|
for (int i_gout = 0; i_gout < num_groups; i_gout++) {
|
|
double norm = 0.;
|
|
for (int imu = 1; imu < order; imu++) {
|
|
norm +=
|
|
0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
|
|
}
|
|
in_energy[gin][i_gout] = norm;
|
|
}
|
|
}
|
|
|
|
// Initialize the base class attributes
|
|
ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult);
|
|
|
|
// Calculate f(mu) and integrate it so we can avoid rejection sampling
|
|
fmu.resize(groups);
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
int num_groups = gmax[gin] - gmin[gin] + 1;
|
|
fmu[gin].resize(num_groups);
|
|
for (int i_gout = 0; i_gout < num_groups; i_gout++) {
|
|
fmu[gin][i_gout].resize(order);
|
|
// The variable matrix contains f(mu); so directly assign it
|
|
fmu[gin][i_gout] = matrix[gin][i_gout];
|
|
|
|
// Ensure positivity
|
|
for (auto& val : fmu[gin][i_gout]) {
|
|
if (val < 0.)
|
|
val = 0.;
|
|
}
|
|
|
|
// Now re-normalize for numerical integration issues and to take care of
|
|
// the above negative fix-up. Also accrue the CDF
|
|
double norm = 0.;
|
|
for (int imu = 1; imu < order; imu++) {
|
|
norm += 0.5 * dmu * (fmu[gin][i_gout][imu - 1] + fmu[gin][i_gout][imu]);
|
|
// incorporate to the CDF
|
|
dist[gin][i_gout][imu] = norm;
|
|
}
|
|
|
|
// now do the normalization
|
|
if (norm > 0.) {
|
|
for (int imu = 0; imu < order; imu++) {
|
|
fmu[gin][i_gout][imu] /= norm;
|
|
dist[gin][i_gout][imu] /= norm;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
double ScattDataTabular::calc_f(int gin, int gout, double mu)
|
|
{
|
|
double f;
|
|
if ((gout < gmin[gin]) || (gout > gmax[gin])) {
|
|
f = 0.;
|
|
} else {
|
|
// Find mu bin
|
|
int i_gout = gout - gmin[gin];
|
|
int imu;
|
|
if (mu == 1.) {
|
|
// use size -2 to have the index one before the end
|
|
imu = this->mu.shape()[0] - 2;
|
|
} else {
|
|
imu = std::floor((mu + 1.) / dmu + 1.) - 1;
|
|
}
|
|
|
|
double r = (mu - this->mu[imu]) / (this->mu[imu + 1] - this->mu[imu]);
|
|
f = (1. - r) * fmu[gin][i_gout][imu] + r * fmu[gin][i_gout][imu + 1];
|
|
}
|
|
return f;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void ScattDataTabular::sample(
|
|
int gin, int& gout, double& mu, double& wgt, uint64_t* seed)
|
|
{
|
|
// Sample the outgoing energy using the base-class method
|
|
int i_gout;
|
|
sample_energy(gin, gout, i_gout, seed);
|
|
|
|
// Determine the outgoing cosine bin
|
|
int NP = this->mu.shape()[0];
|
|
double xi = prn(seed);
|
|
|
|
double c_k = dist[gin][i_gout][0];
|
|
int k;
|
|
for (k = 0; k < NP - 1; k++) {
|
|
double c_k1 = dist[gin][i_gout][k + 1];
|
|
if (xi < c_k1)
|
|
break;
|
|
c_k = c_k1;
|
|
}
|
|
|
|
// Check to make sure k is <= NP - 1
|
|
k = std::min(k, NP - 2);
|
|
|
|
// Find the pdf values we want
|
|
double p0 = fmu[gin][i_gout][k];
|
|
double mu0 = this->mu[k];
|
|
double p1 = fmu[gin][i_gout][k + 1];
|
|
double mu1 = this->mu[k + 1];
|
|
|
|
if (p0 == p1) {
|
|
mu = mu0 + (xi - c_k) / p0;
|
|
} else {
|
|
double frac = (p1 - p0) / (mu1 - mu0);
|
|
mu =
|
|
mu0 +
|
|
(std::sqrt(std::max(0., p0 * p0 + 2. * frac * (xi - c_k))) - p0) / frac;
|
|
}
|
|
|
|
if (mu < -1.) {
|
|
mu = -1.;
|
|
} else if (mu > 1.) {
|
|
mu = 1.;
|
|
}
|
|
|
|
// Update the weight to reflect neutron multiplicity
|
|
wgt *= mult[gin][i_gout];
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
xt::xtensor<double, 3> ScattDataTabular::get_matrix(size_t max_order)
|
|
{
|
|
// Get the sizes and initialize the data to 0
|
|
size_t groups = energy.size();
|
|
// We ignore the requested order for Histogram and Tabular representations
|
|
size_t order_dim = get_order();
|
|
xt::xtensor<double, 3> matrix({groups, groups, order_dim}, 0.);
|
|
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
|
|
int gout = i_gout + gmin[gin];
|
|
for (int l = 0; l < order_dim; l++) {
|
|
matrix(gin, gout, l) =
|
|
scattxs[gin] * energy[gin][i_gout] * fmu[gin][i_gout][l];
|
|
}
|
|
}
|
|
}
|
|
return matrix;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void ScattDataTabular::combine(
|
|
const vector<ScattData*>& those_scatts, const vector<double>& scalars)
|
|
{
|
|
// Find the max order in the data set and make sure we can combine the sets
|
|
size_t max_order = those_scatts[0]->get_order();
|
|
for (int i = 0; i < those_scatts.size(); i++) {
|
|
// Lets also make sure these items are combineable
|
|
ScattDataTabular* that = dynamic_cast<ScattDataTabular*>(those_scatts[i]);
|
|
if (!that) {
|
|
fatal_error("Cannot combine the ScattData objects!");
|
|
}
|
|
if (max_order != that->get_order()) {
|
|
fatal_error("Cannot combine the ScattData objects!");
|
|
}
|
|
}
|
|
|
|
size_t groups = those_scatts[0]->energy.size();
|
|
|
|
xt::xtensor<int, 1> in_gmin({groups}, 0);
|
|
xt::xtensor<int, 1> in_gmax({groups}, 0);
|
|
double_3dvec sparse_scatter(groups);
|
|
double_2dvec sparse_mult(groups);
|
|
|
|
// The rest of the steps do not depend on the type of angular representation
|
|
// so we use a base class method to sum up xs and create new energy and mult
|
|
// matrices
|
|
size_t order_dim = max_order;
|
|
ScattData::base_combine(max_order, order_dim, those_scatts, scalars, in_gmin,
|
|
in_gmax, sparse_mult, sparse_scatter);
|
|
|
|
// Got everything we need, store it.
|
|
init(in_gmin, in_gmax, sparse_mult, sparse_scatter);
|
|
}
|
|
|
|
//==============================================================================
|
|
// module-level methods
|
|
//==============================================================================
|
|
|
|
void convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab)
|
|
{
|
|
// See if the user wants us to figure out how many points to use
|
|
int n_mu = settings::legendre_to_tabular_points;
|
|
if (n_mu == C_NONE) {
|
|
// then we will use 2 pts if its P0, or the default if a higher order
|
|
// TODO use an error minimization algorithm that also picks n_mu
|
|
if (leg.get_order() == 0) {
|
|
n_mu = 2;
|
|
} else {
|
|
n_mu = DEFAULT_NMU;
|
|
}
|
|
}
|
|
|
|
tab.base_init(n_mu, leg.gmin, leg.gmax, leg.energy, leg.mult);
|
|
tab.scattxs = leg.scattxs;
|
|
|
|
// Build mu and dmu
|
|
tab.mu = xt::linspace(-1., 1., n_mu);
|
|
tab.dmu = 2. / (n_mu - 1);
|
|
|
|
// Calculate f(mu) and integrate it so we can avoid rejection sampling
|
|
size_t groups = tab.energy.size();
|
|
tab.fmu.resize(groups);
|
|
for (int gin = 0; gin < groups; gin++) {
|
|
int num_groups = tab.gmax[gin] - tab.gmin[gin] + 1;
|
|
tab.fmu[gin].resize(num_groups);
|
|
for (int i_gout = 0; i_gout < num_groups; i_gout++) {
|
|
tab.fmu[gin][i_gout].resize(n_mu);
|
|
for (int imu = 0; imu < n_mu; imu++) {
|
|
tab.fmu[gin][i_gout][imu] =
|
|
evaluate_legendre(leg.dist[gin][i_gout].size() - 1,
|
|
leg.dist[gin][i_gout].data(), tab.mu[imu]);
|
|
}
|
|
|
|
// Ensure positivity
|
|
for (auto& val : tab.fmu[gin][i_gout]) {
|
|
if (val < 0.)
|
|
val = 0.;
|
|
}
|
|
|
|
// Now re-normalize for numerical integration issues and to take care of
|
|
// the above negative fix-up. Also accrue the CDF
|
|
double norm = 0.;
|
|
tab.dist[gin][i_gout][0] = 0.;
|
|
for (int imu = 1; imu < n_mu; imu++) {
|
|
norm += 0.5 * tab.dmu *
|
|
(tab.fmu[gin][i_gout][imu - 1] + tab.fmu[gin][i_gout][imu]);
|
|
// incorporate to the CDF
|
|
tab.dist[gin][i_gout][imu] = norm;
|
|
}
|
|
|
|
// now do the normalization
|
|
if (norm > 0.) {
|
|
for (int imu = 0; imu < n_mu; imu++) {
|
|
tab.fmu[gin][i_gout][imu] /= norm;
|
|
tab.dist[gin][i_gout][imu] /= norm;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace openmc
|