#include "openmc/scattdata.h" #include #include #include #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& in_gmin, const xt::xtensor& 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& those_scatts, const vector& scalars, xt::xtensor& in_gmin, xt::xtensor& 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 this_nuscatt_matrix({groups, groups, order_dim}, 0.); xt::xtensor this_nuscatt_P0({groups, groups}, 0.); xt::xtensor this_scatt_P0({groups, groups}, 0.); xt::xtensor 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 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& in_gmin, const xt::xtensor& 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({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& those_scatts, const vector& 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(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 in_gmin({groups}, 0); xt::xtensor 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 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 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& in_gmin, const xt::xtensor& 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({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 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 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& those_scatts, const vector& 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(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 in_gmin({groups}, 0); xt::xtensor 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& in_gmin, const xt::xtensor& 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({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 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 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& those_scatts, const vector& 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(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 in_gmin({groups}, 0); xt::xtensor 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