Merge pull request #1047 from paulromano/cpp-thermal

Move thermal scattering data to C++
This commit is contained in:
Paul Romano 2018-08-20 06:28:55 -05:00 committed by GitHub
commit cf299aac73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1153 additions and 1066 deletions

View file

@ -288,7 +288,6 @@ set_target_properties(faddeeva PROPERTIES
add_library(libopenmc SHARED
src/algorithm.F90
src/angleenergy_header.F90
src/bank_header.F90
src/api.F90
src/cmfd_data.F90
@ -338,7 +337,6 @@ add_library(libopenmc SHARED
src/reaction_header.F90
src/relaxng
src/sab_header.F90
src/secondary_correlated.F90
src/set_header.F90
src/settings.F90
src/simulation_header.F90
@ -419,6 +417,7 @@ add_library(libopenmc SHARED
src/state_point.cpp
src/string_functions.cpp
src/surface.cpp
src/thermal.cpp
src/xml_interface.cpp
src/xsdata.cpp)
set_target_properties(libopenmc PROPERTIES

View file

@ -1,6 +1,7 @@
include CMakeLists.txt
include LICENSE
include schemas.xml
include pyproject.toml
include openmc/data/reconstruct.pyx
include docs/source/_templates/layout.html
include docs/sphinxext/LICENSE

2
pyproject.toml Normal file
View file

@ -0,0 +1,2 @@
[build-system]
requires = ["setuptools", "wheel", "numpy", "cython"]

View file

@ -1,38 +0,0 @@
module angleenergy_header
use hdf5_interface, only: HID_T
!===============================================================================
! ANGLEENERGY (abstract) defines a correlated or uncorrelated angle-energy
! distribution that is a function of incoming energy. Each derived type must
! implement a sample() subroutine that returns an outgoing energy and scattering
! cosine given an incoming energy.
!===============================================================================
type, abstract :: AngleEnergy
contains
procedure(angleenergy_sample_), deferred :: sample
procedure(angleenergy_from_hdf5_), deferred :: from_hdf5
end type AngleEnergy
abstract interface
subroutine angleenergy_sample_(this, E_in, E_out, mu)
import AngleEnergy
class(AngleEnergy), intent(in) :: this
real(8), intent(in) :: E_in
real(8), intent(out) :: E_out
real(8), intent(out) :: mu
end subroutine angleenergy_sample_
subroutine angleenergy_from_hdf5_(this, group_id)
import AngleEnergy, HID_T
class(AngleEnergy), intent(inout) :: this
integer(HID_T), intent(in) :: group_id
end subroutine angleenergy_from_hdf5_
end interface
type :: AngleEnergyContainer
class(AngleEnergy), allocatable :: obj
end type AngleEnergyContainer
end module angleenergy_header

View file

@ -130,17 +130,6 @@ constexpr int ANGLE_HISTOGRAM {5};
constexpr int TEMPERATURE_NEAREST {1};
constexpr int TEMPERATURE_INTERPOLATION {2};
// Secondary energy mode for S(a,b) inelastic scattering
// TODO: Convert to enum
constexpr int SAB_SECONDARY_EQUAL {0}; // Equally-likely outgoing energy bins
constexpr int SAB_SECONDARY_SKEWED {1}; // Skewed outgoing energy bins
constexpr int SAB_SECONDARY_CONT {2}; // Continuous, linear-linear interpolation
// Elastic mode for S(a,b) elastic scattering
// TODO: Convert to enum
constexpr int SAB_ELASTIC_DISCRETE {3}; // Sample from discrete cosines
constexpr int SAB_ELASTIC_EXACT {4}; // Exact treatment for coherent elastic
// Reaction types
// TODO: Convert to enum
constexpr int TOTAL_XS {1};
@ -260,6 +249,8 @@ constexpr int N_AC {849};
constexpr int N_2N0 {875};
constexpr int N_2NC {891};
constexpr std::array<int, 6> DEPLETION_RX {N_GAMMA, N_P, N_A, N_2N, N_3N, N_4N};
// Fission neutron emission (nu) type
constexpr int NU_NONE {0}; // No nu values (non-fissionable)
constexpr int NU_POLYNOMIAL {1}; // Nu values given by polynomial

View file

@ -107,6 +107,10 @@ public:
//! Sample a value from the distribution
//! \return Sampled value
double sample() const;
// x property
std::vector<double>& x() { return x_; }
const std::vector<double>& x() const { return x_; }
private:
std::vector<double> x_; //!< tabulated independent variable
std::vector<double> p_; //!< tabulated probability density

View file

@ -144,4 +144,35 @@ double Tabulated1D::operator()(double x) const
}
}
//==============================================================================
// CoherentElasticXS implementation
//==============================================================================
CoherentElasticXS::CoherentElasticXS(hid_t dset)
{
// Read 2D array from dataset
xt::xarray<double> arr;
read_dataset(dset, arr);
// Get views for Bragg edges and structure factors
auto E = xt::view(arr, 0);
auto s = xt::view(arr, 1);
// Copy Bragg edges and partial sums of structure factors
std::copy(E.begin(), E.end(), std::back_inserter(bragg_edges_));
std::copy(s.begin(), s.end(), std::back_inserter(factors_));
}
double CoherentElasticXS::operator()(double E) const
{
if (E < bragg_edges_[0]) {
// If energy is below that of the lowest Bragg peak, the elastic cross
// section will be zero
return 0.0;
} else {
auto i_grid = lower_bound_index(bragg_edges_.begin(), bragg_edges_.end(), E);
return factors_[i_grid] / E;
}
}
} // namespace openmc

View file

@ -73,6 +73,19 @@ private:
std::vector<double> y_; //!< values of ordinate
};
//==============================================================================
//! Coherent elastic scattering data from a crystalline material
//==============================================================================
class CoherentElasticXS : public Function1D {
explicit CoherentElasticXS(hid_t dset);
double operator()(double E) const;
private:
std::vector<double> bragg_edges_; //!< Bragg edges in [eV]
std::vector<double> factors_; //!< Partial sums of structure factors [eV-b]
};
} // namespace openmc
#endif // OPENMC_ENDF_H

View file

@ -327,7 +327,7 @@ get_groups(hid_t group_id, char* name[])
}
std::vector<std::string>
group_names(hid_t group_id)
member_names(hid_t group_id, H5O_type_t type)
{
// Determine number of links in the group
H5G_info_t info;
@ -341,7 +341,7 @@ group_names(hid_t group_id)
// Determine type of object (and skip non-group)
H5Oget_info_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, i, &oinfo,
H5P_DEFAULT);
if (oinfo.type != H5O_TYPE_GROUP) continue;
if (oinfo.type != type) continue;
// Get size of name
size = 1 + H5Lget_name_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC,
@ -356,6 +356,17 @@ group_names(hid_t group_id)
return names;
}
std::vector<std::string>
group_names(hid_t group_id)
{
return member_names(group_id, H5O_TYPE_GROUP);
}
std::vector<std::string>
dataset_names(hid_t group_id)
{
return member_names(group_id, H5O_TYPE_DATASET);
}
bool
object_exists(hid_t object_id, const char* name)

View file

@ -74,6 +74,7 @@ read_nd_vector(hid_t obj_id, const char* name,
bool must_have = false);
std::vector<hsize_t> attribute_shape(hid_t obj_id, const char* name);
std::vector<std::string> dataset_names(hid_t group_id);
void ensure_exists(hid_t group_id, const char* name);
std::vector<std::string> group_names(hid_t group_id);
std::vector<hsize_t> object_shape(hid_t obj_id);
@ -209,12 +210,31 @@ read_attribute(hid_t obj_id, const char* name, std::string& str)
str = std::string{buffer, n};
}
// overload for std::vector<std::string>
inline void
read_attribute(hid_t obj_id, const char* name, std::vector<std::string>& vec)
{
auto dims = attribute_shape(obj_id, name);
auto m = dims[0];
// Allocate a C char array to get strings
auto n = attribute_typesize(obj_id, name);
char buffer[m][n+1];
// Read char data in attribute
read_attr_string(obj_id, name, n, buffer[0]);
for (int i = 0; i < m; ++i) {
vec.emplace_back(&buffer[i][0]);
}
}
//==============================================================================
// Templates/overloads for read_dataset
//==============================================================================
template<typename T>
void read_dataset(hid_t obj_id, const char* name, T buffer, bool indep=false)
void read_dataset(hid_t obj_id, const char* name, T& buffer, bool indep=false)
{
read_dataset(obj_id, name, H5TypeMap<T>::type_id, &buffer, indep);
}

View file

@ -223,7 +223,7 @@ contains
found = .false.
associate (sab => sab_tables(this % i_sab_tables(k)))
FIND_NUCLIDE: do j = 1, size(this % nuclide)
if (any(sab % nuclides == nuclides(this % nuclide(j)) % name)) then
if (sab % has_nuclide(nuclides(this % nuclide(j)) % name)) then
call i_sab_tables % push_back(this % i_sab_tables(k))
call i_sab_nuclides % push_back(j)
call sab_fracs % push_back(this % sab_fracs(k))
@ -377,7 +377,7 @@ contains
! If particle energy is greater than the highest energy for the
! S(a,b) table, then don't use the S(a,b) table
if (p % E > sab_tables(i_sab) % data(1) % threshold_inelastic) then
if (p % E > sab_tables(i_sab) % threshold()) then
i_sab = 0
end if

67
src/nuclide.h Normal file
View file

@ -0,0 +1,67 @@
#ifndef OPENMC_NUCLIDE_H
#define OPENMC_NUCLIDE_H
#include "constants.h"
namespace openmc {
//===============================================================================
//! Cached microscopic cross sections for a particular nuclide at the current
//! energy
//===============================================================================
struct NuclideMicroXS {
// Microscopic cross sections in barns
double total; //!< total cross section
double absorption; //!< absorption (disappearance)
double fission; //!< fission
double nu_fission; //!< neutron production from fission
double elastic; //!< If sab_frac is not 1 or 0, then this value is
//!< averaged over bound and non-bound nuclei
double thermal; //!< Bound thermal elastic & inelastic scattering
double thermal_elastic; //!< Bound thermal elastic scattering
double photon_prod; //!< microscopic photon production xs
// Cross sections for depletion reactions (note that these are not stored in
// macroscopic cache)
double reaction[DEPLETION_RX.size()];
// Indicies and factors needed to compute cross sections from the data tables
int index_grid; //!< Index on nuclide energy grid
int index_temp; //!< Temperature index for nuclide
double interp_factor; //!< Interpolation factor on nuc. energy grid
int index_sab {-1}; //!< Index in sab_tables
int index_temp_sab; //!< Temperature index for sab_tables
double sab_frac; //!< Fraction of atoms affected by S(a,b)
bool use_ptable; //!< In URR range with probability tables?
// Energy and temperature last used to evaluate these cross sections. If
// these values have changed, then the cross sections must be re-evaluated.
double last_E {0.0}; //!< Last evaluated energy
double last_sqrtkT {0.0}; //!< Last temperature in sqrt(Boltzmann constant
//!< * temperature (eV))
};
//===============================================================================
// MATERIALMACROXS contains cached macroscopic cross sections for the material a
// particle is traveling through
//===============================================================================
struct MaterialMacroXS {
double total; //!< macroscopic total xs
double absorption; //!< macroscopic absorption xs
double fission; //!< macroscopic fission xs
double nu_fission; //!< macroscopic production xs
double photon_prod; //!< macroscopic photon production xs
// Photon cross sections
double coherent; //!< macroscopic coherent xs
double incoherent; //!< macroscopic incoherent xs
double photoelectric; //!< macroscopic photoelectric xs
double pair_production; //!< macroscopic pair production xs
};
} // namespace openmc
#endif // OPENMC_NUCLIDE_H

View file

@ -126,36 +126,36 @@ module nuclide_header
! (NuclideMicroXS % elastic)
real(8), parameter :: CACHE_INVALID = dble(Z"FFE0000000000000")
type NuclideMicroXS
type, bind(C) :: NuclideMicroXS
! Microscopic cross sections in barns
real(8) :: total
real(8) :: absorption ! absorption (disappearance)
real(8) :: fission ! fission
real(8) :: nu_fission ! neutron production from fission
real(C_DOUBLE) :: total
real(C_DOUBLE) :: absorption ! absorption (disappearance)
real(C_DOUBLE) :: fission ! fission
real(C_DOUBLE) :: nu_fission ! neutron production from fission
real(8) :: elastic ! If sab_frac is not 1 or 0, then this value is
real(C_DOUBLE) :: elastic ! If sab_frac is not 1 or 0, then this value is
! averaged over bound and non-bound nuclei
real(8) :: thermal ! Bound thermal elastic & inelastic scattering
real(8) :: thermal_elastic ! Bound thermal elastic scattering
real(8) :: photon_prod ! microscopic photon production xs
real(C_DOUBLE) :: thermal ! Bound thermal elastic & inelastic scattering
real(C_DOUBLE) :: thermal_elastic ! Bound thermal elastic scattering
real(C_DOUBLE) :: photon_prod ! microscopic photon production xs
! Cross sections for depletion reactions (note that these are not stored in
! macroscopic cache)
real(8) :: reaction(size(DEPLETION_RX))
real(C_DOUBLE) :: reaction(size(DEPLETION_RX))
! Indicies and factors needed to compute cross sections from the data tables
integer :: index_grid ! Index on nuclide energy grid
integer :: index_temp ! Temperature index for nuclide
real(8) :: interp_factor ! Interpolation factor on nuc. energy grid
integer :: index_sab = NONE ! Index in sab_tables
integer :: index_temp_sab ! Temperature index for sab_tables
real(8) :: sab_frac ! Fraction of atoms affected by S(a,b)
logical :: use_ptable ! In URR range with probability tables?
integer(C_INT) :: index_grid ! Index on nuclide energy grid
integer(C_INT) :: index_temp ! Temperature index for nuclide
real(C_DOUBLE) :: interp_factor ! Interpolation factor on nuc. energy grid
integer(C_INT) :: index_sab = NONE ! Index in sab_tables
integer(C_INT) :: index_temp_sab ! Temperature index for sab_tables
real(C_DOUBLE) :: sab_frac ! Fraction of atoms affected by S(a,b)
logical(C_BOOL) :: use_ptable ! In URR range with probability tables?
! Energy and temperature last used to evaluate these cross sections. If
! these values have changed, then the cross sections must be re-evaluated.
real(8) :: last_E = ZERO ! Last evaluated energy
real(8) :: last_sqrtkT = ZERO ! Last temperature in sqrt(Boltzmann
real(C_DOUBLE) :: last_E = ZERO ! Last evaluated energy
real(C_DOUBLE) :: last_sqrtkT = ZERO ! Last temperature in sqrt(Boltzmann
! constant * temperature (eV))
end type NuclideMicroXS
@ -164,7 +164,7 @@ module nuclide_header
! particle is traveling through
!===============================================================================
type MaterialMacroXS
type, bind(C) :: MaterialMacroXS
real(C_DOUBLE) :: total ! macroscopic total xs
real(C_DOUBLE) :: absorption ! macroscopic absorption xs
real(C_DOUBLE) :: fission ! macroscopic fission xs
@ -198,7 +198,7 @@ module nuclide_header
type(DictCharInt) :: nuclide_dict
! Cross section caches
type(NuclideMicroXS), allocatable :: micro_xs(:) ! Cache for each nuclide
type(NuclideMicroXS), allocatable, target :: micro_xs(:) ! Cache for each nuclide
type(MaterialMacroXS) :: material_xs ! Cache for current material
!$omp threadprivate(micro_xs, material_xs)
@ -1096,9 +1096,9 @@ contains
real(8), intent(in) :: sab_frac ! fraction of atoms affected by S(a,b)
type(NuclideMicroXS), intent(inout) :: micro_xs ! Cross section cache
integer :: i_temp ! temperature index
real(8) :: inelastic ! S(a,b) inelastic cross section
real(8) :: elastic ! S(a,b) elastic cross section
integer(C_INT) :: i_temp ! temperature index
real(C_DOUBLE) :: inelastic ! S(a,b) inelastic cross section
real(C_DOUBLE) :: elastic ! S(a,b) elastic cross section
! Set flag that S(a,b) treatment should be used for scattering
micro_xs % index_sab = i_sab

View file

@ -884,263 +884,16 @@ contains
real(8), intent(inout) :: uvw(3) ! directional cosines
real(8), intent(out) :: mu ! scattering cosine
integer :: i ! incoming energy bin
integer :: j ! outgoing energy bin
integer :: k ! outgoing cosine bin
integer :: i_temp ! temperature index
integer :: n_energy_out ! number of outgoing energy bins
real(8) :: f ! interpolation factor
real(8) :: r ! used for skewed sampling & continuous
real(8) :: E_ij ! outgoing energy j for E_in(i)
real(8) :: E_i1j ! outgoing energy j for E_in(i+1)
real(8) :: mu_ijk ! outgoing cosine k for E_in(i) and E_out(j)
real(8) :: mu_i1jk ! outgoing cosine k for E_in(i+1) and E_out(j)
real(8) :: prob ! probability for sampling Bragg edge
! Following are needed only for SAB_SECONDARY_CONT scattering
integer :: l ! sampled incoming E bin (is i or i + 1)
real(8) :: E_i_1, E_i_J ! endpoints on outgoing grid i
real(8) :: E_i1_1, E_i1_J ! endpoints on outgoing grid i+1
real(8) :: E_1, E_J ! endpoints interpolated between i and i+1
real(8) :: E_l_j, E_l_j1 ! adjacent E on outgoing grid l
real(8) :: p_l_j, p_l_j1 ! adjacent p on outgoing grid l
real(8) :: c_j, c_j1 ! cumulative probability
real(8) :: frac ! interpolation factor on outgoing energy
real(8) :: r1 ! RNG for outgoing energy
real(8) :: mu_left, mu_right ! adjacent mu values
real(C_DOUBLE) :: E_out
type(C_PTR) :: ptr
i_temp = micro_xs(i_nuclide) % index_temp_sab
! Sample from C++ side
ptr = C_LOC(micro_xs(i_nuclide))
call sab_tables(i_sab) % sample(ptr, E, E_out, mu)
! Get pointer to S(a,b) table
associate (sab => sab_tables(i_sab) % data(i_temp))
! Determine whether inelastic or elastic scattering will occur
if (prn() < micro_xs(i_nuclide) % thermal_elastic / &
micro_xs(i_nuclide) % thermal) then
! elastic scattering
! Get index and interpolation factor for elastic grid
if (E < sab % elastic_e_in(1)) then
i = 1
f = ZERO
else
i = binary_search(sab % elastic_e_in, sab % n_elastic_e_in, E)
f = (E - sab%elastic_e_in(i)) / &
(sab%elastic_e_in(i+1) - sab%elastic_e_in(i))
end if
! Select treatment based on elastic mode
if (sab % elastic_mode == SAB_ELASTIC_DISCRETE) then
! With this treatment, we interpolate between two discrete cosines
! corresponding to neighboring incoming energies. This is used for
! data derived in the incoherent approximation
! Sample outgoing cosine bin
k = 1 + int(prn() * sab % n_elastic_mu)
! Determine outgoing cosine corresponding to E_in(i) and E_in(i+1)
mu_ijk = sab % elastic_mu(k,i)
mu_i1jk = sab % elastic_mu(k,i+1)
! Cosine of angle between incoming and outgoing neutron
mu = (1 - f)*mu_ijk + f*mu_i1jk
elseif (sab % elastic_mode == SAB_ELASTIC_EXACT) then
! This treatment is used for data derived in the coherent
! approximation, i.e. for crystalline structures that have Bragg
! edges.
! Sample a Bragg edge between 1 and i
prob = prn() * sab % elastic_P(i+1)
if (prob < sab % elastic_P(1)) then
k = 1
else
k = binary_search(sab % elastic_P(1:i+1), i+1, prob)
end if
! Characteristic scattering cosine for this Bragg edge
mu = ONE - TWO*sab % elastic_e_in(k) / E
end if
! Outgoing energy is same as incoming energy -- no need to do anything
else
! Perform inelastic calculations
! Get index and interpolation factor for inelastic grid
if (E < sab % inelastic_e_in(1)) then
i = 1
f = ZERO
else
i = binary_search(sab % inelastic_e_in, sab % n_inelastic_e_in, E)
f = (E - sab%inelastic_e_in(i)) / &
(sab%inelastic_e_in(i+1) - sab%inelastic_e_in(i))
end if
! Now that we have an incoming energy bin, we need to determine the
! outgoing energy bin. This will depend on the "secondary energy
! mode". If the mode is 0, then the outgoing energy bin is chosen from a
! set of equally-likely bins. If the mode is 1, then the first
! two and last two bins are skewed to have lower probabilities than the
! other bins (0.1 for the first and last bins and 0.4 for the second and
! second to last bins, relative to a normal bin probability of 1).
! Finally, if the mode is 2, then a continuous distribution (with
! accompanying PDF and CDF is utilized)
if ((sab_tables(i_sab) % secondary_mode == SAB_SECONDARY_EQUAL) .or. &
(sab_tables(i_sab) % secondary_mode == SAB_SECONDARY_SKEWED)) then
if (sab_tables(i_sab) % secondary_mode == SAB_SECONDARY_EQUAL) then
! All bins equally likely
j = 1 + int(prn() * sab % n_inelastic_e_out)
elseif (sab_tables(i_sab) % secondary_mode == SAB_SECONDARY_SKEWED) then
! Distribution skewed away from edge points
! Determine number of outgoing energy and angle bins
n_energy_out = sab % n_inelastic_e_out
r = prn() * (n_energy_out - 3)
if (r > ONE) then
! equally likely N-4 middle bins
j = int(r) + 2
elseif (r > 0.6_8) then
! second to last bin has relative probability of 0.4
j = n_energy_out - 1
elseif (r > HALF) then
! last bin has relative probability of 0.1
j = n_energy_out
elseif (r > 0.1_8) then
! second bin has relative probability of 0.4
j = 2
else
! first bin has relative probability of 0.1
j = 1
end if
end if
! Determine outgoing energy corresponding to E_in(i) and E_in(i+1)
E_ij = sab % inelastic_e_out(j,i)
E_i1j = sab % inelastic_e_out(j,i+1)
! Outgoing energy
E = (1 - f)*E_ij + f*E_i1j
! Sample outgoing cosine bin
k = 1 + int(prn() * sab % n_inelastic_mu)
! Determine outgoing cosine corresponding to E_in(i) and E_in(i+1)
mu_ijk = sab % inelastic_mu(k,j,i)
mu_i1jk = sab % inelastic_mu(k,j,i+1)
! Cosine of angle between incoming and outgoing neutron
mu = (1 - f)*mu_ijk + f*mu_i1jk
else if (sab_tables(i_sab) % secondary_mode == SAB_SECONDARY_CONT) then
! Continuous secondary energy - this is to be similar to
! Law 61 interpolation on outgoing energy
! Sample between ith and (i+1)th bin
r = prn()
if (f > r) then
l = i + 1
else
l = i
end if
! Determine endpoints on grid i
n_energy_out = sab % inelastic_data(i) % n_e_out
E_i_1 = sab % inelastic_data(i) % e_out(1)
E_i_J = sab % inelastic_data(i) % e_out(n_energy_out)
! Determine endpoints on grid i + 1
n_energy_out = sab % inelastic_data(i + 1) % n_e_out
E_i1_1 = sab % inelastic_data(i + 1) % e_out(1)
E_i1_J = sab % inelastic_data(i + 1) % e_out(n_energy_out)
E_1 = E_i_1 + f * (E_i1_1 - E_i_1)
E_J = E_i_J + f * (E_i1_J - E_i_J)
! Determine outgoing energy bin
! (First reset n_energy_out to the right value)
n_energy_out = sab % inelastic_data(l) % n_e_out
r1 = prn()
c_j = sab % inelastic_data(l) % e_out_cdf(1)
do j = 1, n_energy_out - 1
c_j1 = sab % inelastic_data(l) % e_out_cdf(j + 1)
if (r1 < c_j1) exit
c_j = c_j1
end do
! check to make sure k is <= n_energy_out - 1
j = min(j, n_energy_out - 1)
! Get the data to interpolate between
E_l_j = sab % inelastic_data(l) % e_out(j)
p_l_j = sab % inelastic_data(l) % e_out_pdf(j)
! Next part assumes linear-linear interpolation in standard
E_l_j1 = sab % inelastic_data(l) % e_out(j + 1)
p_l_j1 = sab % inelastic_data(l) % e_out_pdf(j + 1)
! Find secondary energy (variable E)
frac = (p_l_j1 - p_l_j) / (E_l_j1 - E_l_j)
if (frac == ZERO) then
E = E_l_j + (r1 - c_j) / p_l_j
else
E = E_l_j + (sqrt(max(ZERO, p_l_j * p_l_j + &
TWO * frac * (r1 - c_j))) - p_l_j) / frac
end if
! Now interpolate between incident energy bins i and i + 1
if (l == i) then
E = E_1 + (E - E_i_1) * (E_J - E_1) / (E_i_J - E_i_1)
else
E = E_1 + (E - E_i1_1) * (E_J - E_1) / (E_i1_J - E_i1_1)
end if
! Sample outgoing cosine bin
k = 1 + int(prn() * sab % n_inelastic_mu)
! Rather than use the sampled discrete mu directly, it is smeared over
! a bin of width min(mu[k] - mu[k-1], mu[k+1] - mu[k]) centered on the
! discrete mu value itself.
associate (mu_l => sab % inelastic_data(l) % mu)
f = (r1 - c_j)/(c_j1 - c_j)
! Determine (k-1)th mu value
if (k == 1) then
mu_left = -ONE
else
mu_left = mu_l(k-1, j) + f*(mu_l(k-1, j+1) - mu_l(k-1,j))
end if
! Determine kth mu value
mu = mu_l(k, j) + f*(mu_l(k, j+1) - mu_l(k, j))
! Determine (k+1)th mu value
if (k == sab % n_inelastic_mu) then
mu_right = ONE - mu
else
mu_right = mu_l(k+1, j) + f*(mu_l(k+1, j+1) - mu_l(k+1,j)) - mu
end if
end associate
! Smear angle
mu = mu + min(mu - mu_left, mu_right - mu)*(prn() - HALF)
end if ! (inelastic secondary energy treatment)
end if ! (elastic or inelastic)
end associate
! Because of floating-point roundoff, it may be possible for mu to be
! outside of the range [-1,1). In these cases, we just set mu to exactly
! -1 or 1
if (abs(mu) > ONE) mu = sign(ONE,mu)
! change direction of particle
! Set energy to outgoing, change direction of particle
E = E_out
uvw = rotate_angle(uvw, mu)
end subroutine sab_scatter
!===============================================================================

View file

@ -1,468 +1,172 @@
module sab_header
use, intrinsic :: ISO_C_BINDING
use, intrinsic :: ISO_FORTRAN_ENV
use algorithm, only: find, sort, binary_search
use constants
use dict_header, only: DictIntInt, DictCharInt
use distribution_univariate, only: Tabular
use error, only: warning, fatal_error
use dict_header, only: DictCharInt
use hdf5_interface
use random_lcg, only: prn
use secondary_correlated, only: CorrelatedAngleEnergy
use settings
use stl_vector, only: VectorInt, VectorReal
use string, only: to_str, str_to_int
use stl_vector, only: VectorReal
use string, only: to_c_string
implicit none
private
!===============================================================================
! DISTENERGYSAB contains the secondary energy/angle distributions for inelastic
! thermal scattering collisions which utilize a continuous secondary energy
! representation.
!===============================================================================
type DistEnergySab
integer :: n_e_out
real(8), allocatable :: e_out(:)
real(8), allocatable :: e_out_pdf(:)
real(8), allocatable :: e_out_cdf(:)
real(8), allocatable :: mu(:,:)
end type DistEnergySab
public :: free_memory_sab
!===============================================================================
! SALPHABETA contains S(a,b) data for thermal neutron scattering, typically off
! of light isotopes such as water, graphite, Be, etc
!===============================================================================
type SabData
! threshold for S(a,b) treatment (usually ~4 eV)
real(8) :: threshold_inelastic
real(8) :: threshold_elastic = ZERO
! Inelastic scattering data
integer :: n_inelastic_e_in ! # of incoming E for inelastic
integer :: n_inelastic_e_out ! # of outgoing E for inelastic
integer :: n_inelastic_mu ! # of outgoing angles for inelastic
real(8), allocatable :: inelastic_e_in(:)
real(8), allocatable :: inelastic_sigma(:)
! The following are used only if secondary_mode is 0 or 1
real(8), allocatable :: inelastic_e_out(:,:)
real(8), allocatable :: inelastic_mu(:,:,:)
! The following is used only if secondary_mode is 3
! The different implementation is necessary because the continuous
! representation has a variable number of outgoing energy points for each
! incoming energy
type(DistEnergySab), allocatable :: inelastic_data(:) ! One for each Ein
! Elastic scattering data
integer :: elastic_mode ! elastic mode (discrete/exact)
integer :: n_elastic_e_in ! # of incoming E for elastic
integer :: n_elastic_mu ! # of outgoing angles for elastic
real(8), allocatable :: elastic_e_in(:)
real(8), allocatable :: elastic_P(:)
real(8), allocatable :: elastic_mu(:,:)
end type SabData
type SAlphaBeta
character(150) :: name ! name of table, e.g. lwtr.10t
real(8) :: awr ! weight of nucleus in neutron masses
real(8), allocatable :: kTs(:) ! temperatures in eV (k*T)
character(10), allocatable :: nuclides(:) ! List of valid nuclides
integer :: secondary_mode ! secondary mode (equal/skewed/continuous)
! cross sections and distributions at each temperature
type(SabData), allocatable :: data(:)
type, public :: SAlphaBeta
type(C_PTR) :: ptr
contains
procedure :: from_hdf5 => salphabeta_from_hdf5
procedure :: calculate_xs => sab_calculate_xs
procedure :: from_hdf5
procedure :: calculate_xs
procedure :: free
procedure :: has_nuclide
procedure :: sample
procedure :: threshold
end type SAlphaBeta
! S(a,b) tables
type(SAlphaBeta), allocatable, target :: sab_tables(:)
integer(C_INT), bind(C) :: n_sab_tables
type(DictCharInt) :: sab_dict
type(SAlphaBeta), public, allocatable, target :: sab_tables(:)
integer(C_INT), public, bind(C) :: n_sab_tables
type(DictCharInt), public :: sab_dict
interface
function sab_from_hdf5(group_id, temperature, n, method, &
tolerance, minmax) result(ptr) bind(C)
import HID_T, C_DOUBLE, C_INT, C_PTR
integer(HID_T), value :: group_id
real(C_DOUBLE), intent(in) :: temperature
integer(C_INT), value :: n
integer(C_INT), value :: method
real(C_DOUBLE), value :: tolerance
real(C_DOUBLE), intent(in) :: minmax(2)
type(C_PTR) :: ptr
end function
subroutine sab_calculate_xs(ptr, E, sqrtkT, i_temp, elastic, &
inelastic) bind(C)
import C_PTR, C_DOUBLE, C_INT
type(C_PTR), value :: ptr
real(C_DOUBLE), value :: E
real(C_DOUBLE), value :: sqrtkT
integer(C_INT), intent(out) :: i_temp
real(C_DOUBLE), intent(out) :: elastic
real(C_DOUBLE), intent(out) :: inelastic
end subroutine
subroutine sab_free(ptr) bind(C)
import C_PTR
type(C_PTR), value :: ptr
end subroutine
function sab_has_nuclide(ptr, name) result(val) bind(C)
import C_PTR, C_CHAR, C_BOOL
type(C_PTR), value :: ptr
character(kind=C_CHAR), intent(in) :: name(*)
logical(C_BOOL) :: val
end function
subroutine sab_sample(ptr, micro_xs, E_in, E_out, mu) bind(C)
import C_PTR, C_INT, C_DOUBLE
type(C_PTR), value :: ptr
type(C_PTR), value :: micro_xs
real(C_DOUBLE), value :: E_in
real(C_DOUBLE), intent(out) :: E_out
real(C_DOUBLE), intent(out) :: mu
end subroutine
function sab_threshold(ptr) result(threshold) bind(C)
import C_PTR, C_double
type(C_PTR), value :: ptr
real(C_DOUBLE) :: threshold
end function
end interface
contains
subroutine salphabeta_from_hdf5(this, group_id, temperature, method, &
subroutine from_hdf5(this, group_id, temperature, method, &
tolerance, minmax)
class(SAlphaBeta), intent(inout) :: this
integer(HID_T), intent(in) :: group_id
type(VectorReal), intent(in) :: temperature ! list of temperatures
integer, intent(in) :: method
real(8), intent(in) :: tolerance
real(8), intent(in) :: minmax(2)
integer(C_INT), intent(in) :: method
real(C_DOUBLE), intent(in) :: tolerance
real(C_DOUBLE), intent(in) :: minmax(2)
integer :: i, j
integer :: t
integer :: n_energy, n_energy_out, n_mu
integer :: i_closest
integer :: n_temperature
integer(HID_T) :: T_group
integer(HID_T) :: elastic_group
integer(HID_T) :: inelastic_group
integer(HID_T) :: dset_id
integer(HID_T) :: kT_group
integer(HSIZE_T) :: dims2(2)
integer(HSIZE_T) :: dims3(3)
real(8), allocatable :: temp(:,:)
character(20) :: type
type(CorrelatedAngleEnergy) :: correlated_dist
real(C_DOUBLE) :: dummy
integer(C_INT) :: n
character(MAX_WORD_LEN) :: temp_str
character(MAX_WORD_LEN), allocatable :: dset_names(:)
real(8), allocatable :: temps_available(:) ! temperatures available
real(8) :: temp_desired
real(8) :: temp_actual
type(VectorInt) :: temps_to_read
! Get name of table from group
this % name = get_name(group_id)
! Get rid of leading '/'
this % name = trim(this % name(2:))
call read_attribute(this % awr, group_id, 'atomic_weight_ratio')
call read_attribute(this % nuclides, group_id, 'nuclides')
call read_attribute(type, group_id, 'secondary_mode')
select case (type)
case ('equal')
this % secondary_mode = SAB_SECONDARY_EQUAL
case ('skewed')
this % secondary_mode = SAB_SECONDARY_SKEWED
case ('continuous')
this % secondary_mode = SAB_SECONDARY_CONT
end select
! Read temperatures
kT_group = open_group(group_id, 'kTs')
! Determine temperatures available
call get_datasets(kT_group, dset_names)
allocate(temps_available(size(dset_names)))
do i = 1, size(dset_names)
! Read temperature value
call read_dataset(temps_available(i), kT_group, trim(dset_names(i)))
temps_available(i) = temps_available(i) / K_BOLTZMANN
end do
call sort(temps_available)
! Determine actual temperatures to read -- start by checking whether a
! temperature range was given, in which case all temperatures in the range
! are loaded irrespective of what temperatures actually appear in the model
if (minmax(2) > ZERO) then
do i = 1, size(temps_available)
temp_actual = temps_available(i)
if (minmax(1) <= temp_actual .and. temp_actual <= minmax(2)) then
call temps_to_read % push_back(nint(temp_actual))
end if
end do
n = temperature % size()
if (n > 0) then
this % ptr = sab_from_hdf5(group_id, temperature % data(1), n, method, tolerance, minmax)
else
! In this case, temperatures % data(1) doesn't exist, so we just pass a
! dummy value
this % ptr = sab_from_hdf5(group_id, dummy, n, method, tolerance, minmax)
end if
select case (method)
case (TEMPERATURE_NEAREST)
! Determine actual temperatures to read
do i = 1, temperature % size()
temp_desired = temperature % data(i)
i_closest = minloc(abs(temps_available - temp_desired), dim=1)
temp_actual = temps_available(i_closest)
if (abs(temp_actual - temp_desired) < tolerance) then
if (find(temps_to_read, nint(temp_actual)) == -1) then
call temps_to_read % push_back(nint(temp_actual))
end if
else
call fatal_error("Nuclear data library does not contain cross sections &
&for " // trim(this % name) // " at or near " // &
trim(to_str(nint(temp_desired))) // " K.")
end if
end do
case (TEMPERATURE_INTERPOLATION)
! If temperature interpolation or multipole is selected, get a list of
! bounding temperatures for each actual temperature present in the model
TEMP_LOOP: do i = 1, temperature % size()
temp_desired = temperature % data(i)
do j = 1, size(temps_available) - 1
if (temps_available(j) <= temp_desired .and. &
temp_desired < temps_available(j + 1)) then
if (find(temps_to_read, nint(temps_available(j))) == -1) then
call temps_to_read % push_back(nint(temps_available(j)))
end if
if (find(temps_to_read, nint(temps_available(j + 1))) == -1) then
call temps_to_read % push_back(nint(temps_available(j + 1)))
end if
cycle TEMP_LOOP
end if
end do
call fatal_error("Nuclear data library does not contain cross sections &
&for " // trim(this % name) // " at temperatures that bound " // &
trim(to_str(nint(temp_desired))) // " K.")
end do TEMP_LOOP
end select
! Sort temperatures to read
call sort(temps_to_read)
n_temperature = temps_to_read % size()
allocate(this % kTs(n_temperature))
allocate(this % data(n_temperature))
do t = 1, n_temperature
! Get temperature as a string
temp_str = trim(to_str(temps_to_read % data(t))) // "K"
! Read exact temperature value
call read_dataset(this % kTs(t), kT_group, temp_str)
! Open group for temperature i
T_group = open_group(group_id, temp_str)
! Coherent elastic data
if (object_exists(T_group, 'elastic')) then
! Read cross section data
elastic_group = open_group(T_group, 'elastic')
dset_id = open_dataset(elastic_group, 'xs')
call read_attribute(type, dset_id, 'type')
call get_shape(dset_id, dims2)
allocate(temp(dims2(1), dims2(2)))
call read_dataset(temp, dset_id)
call close_dataset(dset_id)
! Set cross section data and type
this % data(t) % n_elastic_e_in = int(dims2(1), 4)
allocate(this % data(t) % elastic_e_in(this % data(t) % n_elastic_e_in))
allocate(this % data(t) % elastic_P(this % data(t) % n_elastic_e_in))
this % data(t) % elastic_e_in(:) = temp(:, 1)
this % data(t) % elastic_P(:) = temp(:, 2)
select case (type)
case ('tab1')
this % data(t) % elastic_mode = SAB_ELASTIC_DISCRETE
case ('bragg')
this % data(t) % elastic_mode = SAB_ELASTIC_EXACT
end select
deallocate(temp)
! Set elastic threshold
this % data(t) % threshold_elastic = this % data(t) % elastic_e_in(&
this % data(t) % n_elastic_e_in)
! Read angle distribution
if (this % data(t) % elastic_mode /= SAB_ELASTIC_EXACT) then
dset_id = open_dataset(elastic_group, 'mu_out')
call get_shape(dset_id, dims2)
this % data(t) % n_elastic_mu = int(dims2(1), 4)
allocate(this % data(t) % elastic_mu(dims2(1), dims2(2)))
call read_dataset(this % data(t) % elastic_mu, dset_id)
call close_dataset(dset_id)
end if
call close_group(elastic_group)
end if
! Inelastic data
if (object_exists(T_group, 'inelastic')) then
! Read type of inelastic data
inelastic_group = open_group(T_group, 'inelastic')
! Read cross section data
dset_id = open_dataset(inelastic_group, 'xs')
call get_shape(dset_id, dims2)
allocate(temp(dims2(1), dims2(2)))
call read_dataset(temp, dset_id)
call close_dataset(dset_id)
! Set cross section data
this % data(t) % n_inelastic_e_in = int(dims2(1), 4)
allocate(this % data(t) % inelastic_e_in(this % data(t) % n_inelastic_e_in))
allocate(this % data(t) % inelastic_sigma(this % data(t) % n_inelastic_e_in))
this % data(t) % inelastic_e_in(:) = temp(:, 1)
this % data(t) % inelastic_sigma(:) = temp(:, 2)
deallocate(temp)
! Set inelastic threshold
this % data(t) % threshold_inelastic = this % data(t) % inelastic_e_in(&
this % data(t) % n_inelastic_e_in)
if (this % secondary_mode /= SAB_SECONDARY_CONT) then
! Read energy distribution
dset_id = open_dataset(inelastic_group, 'energy_out')
call get_shape(dset_id, dims2)
this % data(t) % n_inelastic_e_out = int(dims2(1), 4)
allocate(this % data(t) % inelastic_e_out(dims2(1), dims2(2)))
call read_dataset(this % data(t) % inelastic_e_out, dset_id)
call close_dataset(dset_id)
! Read angle distribution
dset_id = open_dataset(inelastic_group, 'mu_out')
call get_shape(dset_id, dims3)
this % data(t) % n_inelastic_mu = int(dims3(1), 4)
allocate(this % data(t) % inelastic_mu(dims3(1), dims3(2), dims3(3)))
call read_dataset(this % data(t) % inelastic_mu, dset_id)
call close_dataset(dset_id)
else
! Read correlated angle-energy distribution
call correlated_dist % from_hdf5(inelastic_group)
! Convert to S(a,b) native format
n_energy = size(correlated_dist % energy)
allocate(this % data(t) % inelastic_data(n_energy))
do i = 1, n_energy
associate (edist => correlated_dist % distribution(i))
! Get number of outgoing energies for incoming energy i
n_energy_out = size(edist % e_out)
this % data(t) % inelastic_data(i) % n_e_out = n_energy_out
allocate(this % data(t) % inelastic_data(i) % e_out(n_energy_out))
allocate(this % data(t) % inelastic_data(i) % e_out_pdf(n_energy_out))
allocate(this % data(t) % inelastic_data(i) % e_out_cdf(n_energy_out))
! Copy outgoing energy distribution
this % data(t) % inelastic_data(i) % e_out(:) = edist % e_out
this % data(t) % inelastic_data(i) % e_out_pdf(:) = edist % p
this % data(t) % inelastic_data(i) % e_out_cdf(:) = edist % c
do j = 1, n_energy_out
select type (adist => edist % angle(j) % obj)
type is (Tabular)
! On first pass, allocate space for angles
if (j == 1) then
n_mu = size(adist % x)
this % data(t) % n_inelastic_mu = n_mu
allocate(this % data(t) % inelastic_data(i) % mu(&
n_mu, n_energy_out))
end if
! Copy outgoing angles
this % data(t) % inelastic_data(i) % mu(:, j) = adist % x
end select
end do
end associate
end do
! Clear data on correlated angle-energy object
deallocate(correlated_dist % breakpoints)
deallocate(correlated_dist % interpolation)
deallocate(correlated_dist % energy)
deallocate(correlated_dist % distribution)
end if
call close_group(inelastic_group)
end if
call close_group(T_group)
end do
call close_group(kT_group)
end subroutine salphabeta_from_hdf5
end subroutine from_hdf5
!===============================================================================
! SAB_CALCULATE_XS determines the elastic and inelastic scattering
! cross-sections in the thermal energy range.
!===============================================================================
subroutine sab_calculate_xs(this, E, sqrtkT, i_temp, elastic, inelastic)
subroutine calculate_xs(this, E, sqrtkT, i_temp, elastic, inelastic)
class(SAlphaBeta), intent(in) :: this ! S(a,b) object
real(8), intent(in) :: E ! energy
real(8), intent(in) :: sqrtkT ! temperature
real(C_DOUBLE), intent(in) :: E ! energy
real(C_DOUBLE), intent(in) :: sqrtkT ! temperature
integer, intent(out) :: i_temp ! index in the S(a,b)'s temperature
real(8), intent(out) :: elastic ! thermal elastic cross section
real(8), intent(out) :: inelastic ! thermal inelastic cross section
real(C_DOUBLE), intent(out) :: elastic ! thermal elastic cross section
real(C_DOUBLE), intent(out) :: inelastic ! thermal inelastic cross section
integer :: i_grid ! index on S(a,b) energy grid
real(8) :: f ! interp factor on S(a,b) energy grid
real(8) :: kT
call sab_calculate_xs(this % ptr, E, sqrtkT, i_temp, elastic, inelastic)
end subroutine
! Determine temperature for S(a,b) table
kT = sqrtkT**2
if (temperature_method == TEMPERATURE_NEAREST) then
! If using nearest temperature, do linear search on temperature
do i_temp = 1, size(this % kTs)
if (abs(this % kTs(i_temp) - kT) < &
K_BOLTZMANN*temperature_tolerance) exit
end do
else
! Find temperatures that bound the actual temperature
do i_temp = 1, size(this % kTs) - 1
if (this % kTs(i_temp) <= kT .and. &
kT < this % kTs(i_temp + 1)) exit
end do
subroutine free(this)
class(SAlphaBeta), intent(inout) :: this
call sab_free(this % ptr)
end subroutine
! Randomly sample between temperature i and i+1
f = (kT - this % kTs(i_temp)) / &
(this % kTs(i_temp + 1) - this % kTs(i_temp))
if (f > prn()) i_temp = i_temp + 1
end if
function has_nuclide(this, name) result(val)
class(SAlphaBeta), intent(in) :: this
character(len=*), intent(in) :: name
logical(C_BOOL) :: val
val = sab_has_nuclide(this % ptr, to_c_string(name))
end function
! Get pointer to S(a,b) table
associate (sab => this % data(i_temp))
subroutine sample(this, micro_xs, E_in, E_out, mu)
class(SAlphaBeta), intent(in) :: this
type(C_PTR), value :: micro_xs
real(C_DOUBLE), value :: E_in
real(C_DOUBLE), intent(out) :: E_out
real(C_DOUBLE), intent(out) :: mu
! Get index and interpolation factor for inelastic grid
if (E < sab % inelastic_e_in(1)) then
i_grid = 1
f = ZERO
else
i_grid = binary_search(sab % inelastic_e_in, sab % n_inelastic_e_in, E)
f = (E - sab%inelastic_e_in(i_grid)) / &
(sab%inelastic_e_in(i_grid+1) - sab%inelastic_e_in(i_grid))
end if
! Calculate S(a,b) inelastic scattering cross section
inelastic = (ONE - f) * sab % inelastic_sigma(i_grid) + &
f * sab % inelastic_sigma(i_grid + 1)
! Check for elastic data
if (E < sab % threshold_elastic) then
! Determine whether elastic scattering is given in the coherent or
! incoherent approximation. For coherent, the cross section is
! represented as P/E whereas for incoherent, it is simply P
if (sab % elastic_mode == SAB_ELASTIC_EXACT) then
if (E < sab % elastic_e_in(1)) then
! If energy is below that of the lowest Bragg peak, the elastic
! cross section will be zero
elastic = ZERO
else
i_grid = binary_search(sab % elastic_e_in, &
sab % n_elastic_e_in, E)
elastic = sab % elastic_P(i_grid) / E
end if
else
! Determine index on elastic energy grid
if (E < sab % elastic_e_in(1)) then
i_grid = 1
else
i_grid = binary_search(sab % elastic_e_in, &
sab % n_elastic_e_in, E)
end if
! Get interpolation factor for elastic grid
f = (E - sab%elastic_e_in(i_grid))/(sab%elastic_e_in(i_grid+1) - &
sab%elastic_e_in(i_grid))
! Calculate S(a,b) elastic scattering cross section
elastic = (ONE - f) * sab % elastic_P(i_grid) + &
f * sab % elastic_P(i_grid + 1)
end if
else
! No elastic data
elastic = ZERO
end if
end associate
end subroutine sab_calculate_xs
call sab_sample(this % ptr, micro_xs, E_in, E_out, mu)
end subroutine
function threshold(this)
class(SAlphaBeta), intent(in) :: this
real(C_DOUBLE) :: threshold
threshold = sab_threshold(this % ptr)
end function
!===============================================================================
! FREE_MEMORY_SAB deallocates global arrays defined in this module
!===============================================================================
subroutine free_memory_sab()
integer :: i
n_sab_tables = 0
if (allocated(sab_tables)) deallocate(sab_tables)
if (allocated(sab_tables)) then
do i = 1, size(sab_tables)
call sab_tables(i) % free()
end do
deallocate(sab_tables)
end if
call sab_dict % clear()
end subroutine free_memory_sab

View file

@ -1,302 +0,0 @@
module secondary_correlated
use algorithm, only: binary_search
use angleenergy_header, only: AngleEnergy
use constants, only: ZERO, ONE, HALF, TWO, HISTOGRAM, LINEAR_LINEAR
use distribution_univariate, only: DistributionContainer, Tabular
use hdf5_interface
use random_lcg, only: prn
!===============================================================================
! CORRELATEDANGLEENERGY represents a correlated angle-energy distribution. This
! corresponds to ACE law 61 and ENDF File 6, LAW=1, LANG/=2.
!===============================================================================
type AngleEnergyTable
integer :: interpolation
integer :: n_discrete
real(8), allocatable :: e_out(:)
real(8), allocatable :: p(:)
real(8), allocatable :: c(:)
type(DistributionContainer), allocatable :: angle(:)
end type AngleEnergyTable
type, extends(AngleEnergy) :: CorrelatedAngleEnergy
integer :: n_region ! number of interpolation regions
integer, allocatable :: breakpoints(:) ! breakpoints of interpolation regions
integer, allocatable :: interpolation(:) ! interpolation region codes
real(8), allocatable :: energy(:) ! incoming energies
type(AngleEnergyTable), allocatable :: distribution(:) ! outgoing E/mu distributions
contains
procedure :: sample => correlated_sample
procedure :: from_hdf5 => correlated_from_hdf5
end type CorrelatedAngleEnergy
contains
subroutine correlated_sample(this, E_in, E_out, mu)
class(CorrelatedAngleEnergy), intent(in) :: this
real(8), intent(in) :: E_in ! incoming energy
real(8), intent(out) :: E_out ! sampled outgoing energy
real(8), intent(out) :: mu ! sapmled scattering cosine
integer :: i, k, l ! indices
integer :: n_energy_in ! number of incoming energies
integer :: n_energy_out ! number of outgoing energies
real(8) :: r ! interpolation factor on incoming energy
real(8) :: r1 ! random number on [0,1)
real(8) :: frac ! interpolation factor on outgoing energy
real(8) :: E_i_1, E_i_K ! endpoints on outgoing grid i
real(8) :: E_i1_1, E_i1_K ! endpoints on outgoing grid i+1
real(8) :: E_1, E_K ! endpoints interpolated between i and i+1
real(8) :: E_l_k, E_l_k1 ! adjacent E on outgoing grid l
real(8) :: p_l_k, p_l_k1 ! adjacent p on outgoing grid l
real(8) :: c_k, c_k1 ! cumulative probability
! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< REMOVE THIS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
! Before the secondary distribution refactor, an isotropic polar cosine was
! always sampled but then overwritten with the polar cosine sampled from the
! correlated distribution. To preserve the random number stream, we keep
! this dummy sampling here but can remove it later (will change answers)
mu = TWO*prn() - ONE
! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< REMOVE THIS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
! find energy bin and calculate interpolation factor -- if the energy is
! outside the range of the tabulated energies, choose the first or last bins
n_energy_in = size(this%energy)
if (E_in < this%energy(1)) then
i = 1
r = ZERO
elseif (E_in > this%energy(n_energy_in)) then
i = n_energy_in - 1
r = ONE
else
i = binary_search(this%energy, n_energy_in, E_in)
r = (E_in - this%energy(i)) / &
(this%energy(i+1) - this%energy(i))
end if
! Sample between the ith and (i+1)th bin
if (r > prn()) then
l = i + 1
else
l = i
end if
! interpolation for energy E1 and EK
n_energy_out = size(this%distribution(i)%e_out)
E_i_1 = this%distribution(i)%e_out(1)
E_i_K = this%distribution(i)%e_out(n_energy_out)
n_energy_out = size(this%distribution(i+1)%e_out)
E_i1_1 = this%distribution(i+1)%e_out(1)
E_i1_K = this%distribution(i+1)%e_out(n_energy_out)
E_1 = E_i_1 + r*(E_i1_1 - E_i_1)
E_K = E_i_K + r*(E_i1_K - E_i_K)
! determine outgoing energy bin
n_energy_out = size(this%distribution(l)%e_out)
r1 = prn()
c_k = this%distribution(l)%c(1)
do k = 1, n_energy_out - 1
c_k1 = this%distribution(l)%c(k+1)
if (r1 < c_k1) exit
c_k = c_k1
end do
! check to make sure k is <= NP - 1
k = min(k, n_energy_out - 1)
E_l_k = this%distribution(l)%e_out(k)
p_l_k = this%distribution(l)%p(k)
if (this%distribution(l)%interpolation == HISTOGRAM) then
! Histogram interpolation
if (p_l_k > ZERO) then
E_out = E_l_k + (r1 - c_k)/p_l_k
else
E_out = E_l_k
end if
elseif (this%distribution(l)%interpolation == LINEAR_LINEAR) then
! Linear-linear interpolation
E_l_k1 = this%distribution(l)%e_out(k+1)
p_l_k1 = this%distribution(l)%p(k+1)
frac = (p_l_k1 - p_l_k)/(E_l_k1 - E_l_k)
if (frac == ZERO) then
E_out = E_l_k + (r1 - c_k)/p_l_k
else
E_out = E_l_k + (sqrt(max(ZERO, p_l_k*p_l_k + &
TWO*frac*(r1 - c_k))) - p_l_k)/frac
end if
end if
! Now interpolate between incident energy bins i and i + 1
if (l == i) then
E_out = E_1 + (E_out - E_i_1)*(E_K - E_1)/(E_i_K - E_i_1)
else
E_out = E_1 + (E_out - E_i1_1)*(E_K - E_1)/(E_i1_K - E_i1_1)
end if
! Find correlated angular distribution for closest outgoing energy bin
if (r1 - c_k < c_k1 - r1) then
mu = this%distribution(l)%angle(k)%obj%sample()
else
mu = this%distribution(l)%angle(k + 1)%obj%sample()
end if
end subroutine correlated_sample
subroutine correlated_from_hdf5(this, group_id)
class(CorrelatedAngleEnergy), intent(inout) :: this
integer(HID_T), intent(in) :: group_id
integer :: i, j, k
integer :: n_energy
integer :: m, n
integer :: offset_mu
integer :: interp_mu
integer(HID_T) :: dset_id
integer(HSIZE_T) :: dims(1), dims2(2)
integer, allocatable :: temp(:,:)
integer, allocatable :: offsets(:)
integer, allocatable :: interp(:)
integer, allocatable :: n_discrete(:)
real(8), allocatable :: eout(:,:)
real(8), allocatable :: mu(:,:)
! Open incoming energy dataset
dset_id = open_dataset(group_id, 'energy')
! Get interpolation parameters
call read_attribute(temp, dset_id, 'interpolation')
allocate(this%breakpoints(size(temp, 1)))
allocate(this%interpolation(size(temp, 1)))
this%breakpoints(:) = temp(:, 1)
this%interpolation(:) = temp(:, 2)
this%n_region = size(this%breakpoints)
deallocate(temp)
! Get incoming energies
call get_shape(dset_id, dims)
n_energy = int(dims(1), 4)
allocate(this%energy(n_energy))
allocate(this%distribution(n_energy))
call read_dataset(this%energy, dset_id)
call close_dataset(dset_id)
! Get outgoing energy distribution data
dset_id = open_dataset(group_id, 'energy_out')
call read_attribute(offsets, dset_id, 'offsets')
call read_attribute(interp, dset_id, 'interpolation')
call read_attribute(n_discrete, dset_id, 'n_discrete_lines')
call get_shape(dset_id, dims2)
allocate(eout(dims2(1), dims2(2)))
call read_dataset(eout, dset_id)
call close_dataset(dset_id)
! Get outgoing angle data
dset_id = open_dataset(group_id, 'mu')
call get_shape(dset_id, dims2)
allocate(mu(dims2(1), dims2(2)))
call read_dataset(mu, dset_id)
call close_dataset(dset_id)
do i = 1, n_energy
! Determine number of outgoing energies
j = offsets(i)
if (i < n_energy) then
n = offsets(i+1) - j
else
n = size(eout, 1) - j
end if
associate (d => this % distribution(i))
! Assign interpolation scheme and number of discrete lines
d % interpolation = interp(i)
d % n_discrete = n_discrete(i)
! Allocate arrays for energies and PDF/CDF
allocate(d % e_out(n))
allocate(d % p(n))
allocate(d % c(n))
allocate(d % angle(n))
! Copy data
d % e_out(:) = eout(j+1:j+n, 1)
d % p(:) = eout(j+1:j+n, 2)
d % c(:) = eout(j+1:j+n, 3)
! To get answers that match ACE data, for now we still use the tabulated
! CDF values that were passed through to the HDF5 library. At a later
! time, we can remove the CDF values from the HDF5 library and
! reconstruct them using the PDF
if (.false.) then
! Calculate cumulative distribution function -- discrete portion
do k = 1, d % n_discrete
if (k == 1) then
d % c(k) = d % p(k)
else
d % c(k) = d % c(k-1) + d % p(k)
end if
end do
! Continuous portion
do k = d % n_discrete + 1, n
if (k == d % n_discrete + 1) then
d % c(k) = sum(d % p(1:d % n_discrete))
else
if (d % interpolation == HISTOGRAM) then
d % c(k) = d % c(k-1) + d % p(k-1) * &
(d % e_out(k) - d % e_out(k-1))
elseif (d % interpolation == LINEAR_LINEAR) then
d % c(k) = d % c(k-1) + HALF*(d % p(k-1) + d % p(k)) * &
(d % e_out(k) - d % e_out(k-1))
end if
end if
end do
! Normalize density and distribution functions
d % p(:) = d % p(:)/d % c(n)
d % c(:) = d % c(:)/d % c(n)
end if
end associate
do j = 1, n
allocate(Tabular :: this%distribution(i)%angle(j)%obj)
select type(mudist => this%distribution(i)%angle(j)%obj)
type is (Tabular)
! Get interpolation scheme
interp_mu = nint(eout(offsets(i)+j, 4))
! Determine offset and size of distribution
offset_mu = nint(eout(offsets(i)+j, 5))
if (offsets(i) + j < size(eout, 1)) then
m = nint(eout(offsets(i)+j+1, 5)) - offset_mu
else
m = size(mu, 1) - offset_mu
end if
! To get answers that match ACE data, for now we still use the tabulated
! CDF values that were passed through to the HDF5 library. At a later
! time, we can remove the CDF values from the HDF5 library and
! reconstruct them using the PDF
if (.true.) then
mudist % interpolation = interp_mu
allocate(mudist % x(m))
allocate(mudist % p(m))
allocate(mudist % c(m))
mudist % x(:) = mu(offset_mu+1:offset_mu+m, 1)
mudist % p(:) = mu(offset_mu+1:offset_mu+m, 2)
mudist % c(:) = mu(offset_mu+1:offset_mu+m, 3)
else
! Initialize tabular distribution
call mudist % initialize(mu(offset_mu+1:offset_mu+m, 1), &
mu(offset_mu+1:offset_mu+m, 2), interp_mu)
end if
end select
end do
end do
end subroutine correlated_from_hdf5
end module secondary_correlated

View file

@ -21,14 +21,6 @@ namespace openmc {
class CorrelatedAngleEnergy : public AngleEnergy {
public:
explicit CorrelatedAngleEnergy(hid_t group);
//! Sample distribution for an angle and energy
//! \param[in] E_in Incoming energy in [eV]
//! \param[out] E_out Outgoing energy in [eV]
//! \param[out] mu Outgoing cosine with respect to current direction
void sample(double E_in, double& E_out, double& mu) const;
private:
//! Outgoing energy/angle at a single incoming energy
struct CorrTable {
int n_discrete; //!< Number of discrete lines
@ -39,6 +31,22 @@ private:
std::vector<UPtrDist> angle; //!< Angle distribution
};
explicit CorrelatedAngleEnergy(hid_t group);
//! Sample distribution for an angle and energy
//! \param[in] E_in Incoming energy in [eV]
//! \param[out] E_out Outgoing energy in [eV]
//! \param[out] mu Outgoing cosine with respect to current direction
void sample(double E_in, double& E_out, double& mu) const;
// energy property
std::vector<double>& energy() { return energy_; }
const std::vector<double>& energy() const { return energy_; }
// distribution property
std::vector<CorrTable>& distribution() { return distribution_; }
const std::vector<CorrTable>& distribution() const { return distribution_; }
private:
int n_region_; //!< Number of interpolation regions
std::vector<int> breakpoints_; //!< Breakpoints between regions
std::vector<Interpolation> interpolation_; //!< Interpolation laws

View file

@ -1,5 +1,6 @@
#include "settings.h"
#include "constants.h"
#include "error.h"
#include "openmc.h"
#include "string_utils.h"
@ -20,6 +21,12 @@ std::string path_multipole;
std::string path_output;
std::string path_source;
int temperature_method {TEMPERATURE_NEAREST};
bool temperature_multipole {false};
double temperature_tolerance {10.0};
double temperature_default {293.6};
std::array<double, 2> temperature_range {0.0, 0.0};
//==============================================================================
// Functions
//==============================================================================
@ -65,6 +72,32 @@ void read_settings(pugi::xml_node* root)
}
}
}
// Get temperature settings
if (check_for_node(*root, "temperature_default")) {
temperature_default = std::stod(get_node_value(*root, "temperature_default"));
}
if (check_for_node(*root, "temperature_method")) {
auto temp_str = get_node_value(*root, "temperature_method", true, true);
if (temp_str == "nearest") {
temperature_method = TEMPERATURE_NEAREST;
} else if (temp_str == "interpolation") {
temperature_method = TEMPERATURE_INTERPOLATION;
} else {
fatal_error("Unknown temperature method: " + temp_str);
}
}
if (check_for_node(*root, "temperature_tolerance")) {
temperature_tolerance = std::stod(get_node_value(*root, "temperature_tolerance"));
}
if (check_for_node(*root, "temperature_multipole")) {
temperature_multipole = get_node_value_bool(*root, "temperature_multipole");
}
if (check_for_node(*root, "temperature_range")) {
auto range = get_node_array<double>(*root, "temperature_range");
temperature_range[0] = range[0];
temperature_range[1] = range[1];
}
}
} // namespace openmc
} // namespace openmc

View file

@ -4,6 +4,7 @@
//! \file settings.h
//! \brief Settings for OpenMC
#include <array>
#include <string>
#include "pugixml.hpp"
@ -31,6 +32,12 @@ extern std::string path_multipole;
extern std::string path_output;
extern std::string path_source;
extern int temperature_method;
extern bool temperature_multipole;
extern double temperature_tolerance;
extern double temperature_default;
extern std::array<double, 2> temperature_range;
//==============================================================================
//! Read settings from XML file
//! \param[in] root XML node for <settings>
@ -40,4 +47,4 @@ extern "C" void read_settings(pugi::xml_node* root);
} // namespace openmc
#endif // OPENMC_SETTINGS_H
#endif // OPENMC_SETTINGS_H

623
src/thermal.cpp Normal file
View file

@ -0,0 +1,623 @@
#include "thermal.h"
#include <algorithm> // for sort, move, min, max, find
#include <cmath> // for round, sqrt, fabs
#include <sstream> // for stringstream
#include "xtensor/xarray.hpp"
#include "xtensor/xbuilder.hpp"
#include "xtensor/xmath.hpp"
#include "xtensor/xsort.hpp"
#include "xtensor/xtensor.hpp"
#include "xtensor/xview.hpp"
#include "constants.h"
#include "error.h"
#include "random_lcg.h"
#include "search.h"
#include "secondary_correlated.h"
#include "settings.h"
namespace openmc {
//==============================================================================
// ThermalScattering implementation
//==============================================================================
ThermalScattering::ThermalScattering(hid_t group, const std::vector<double>& temperature,
int method, double tolerance, const double* minmax)
{
// Get name of table from group
name_ = object_name(group);
// Get rid of leading '/'
name_ = name_.substr(1);
read_attribute(group, "atomic_weight_ratio", awr_);
read_attribute(group, "nuclides", nuclides_);
std::string sec_mode;
read_attribute(group, "secondary_mode", sec_mode);
int secondary_mode;
if (sec_mode == "equal") {
secondary_mode = SAB_SECONDARY_EQUAL;
} else if (sec_mode == "skewed") {
secondary_mode = SAB_SECONDARY_SKEWED;
} else if (sec_mode == "continuous") {
secondary_mode = SAB_SECONDARY_CONT;
}
// Read temperatures
hid_t kT_group = open_group(group, "kTs");
// Determine temperatures available
auto dset_names = dataset_names(kT_group);
auto n = dset_names.size();
auto temps_available = xt::empty<double>({n});
for (int i = 0; i < dset_names.size(); ++i) {
// Read temperature value
double T;
read_dataset(kT_group, dset_names[i].data(), T);
temps_available[i] = T / K_BOLTZMANN;
}
std::sort(temps_available.begin(), temps_available.end());
// Determine actual temperatures to read -- start by checking whether a
// temperature range was given, in which case all temperatures in the range
// are loaded irrespective of what temperatures actually appear in the model
std::vector<int> temps_to_read;
if (minmax[1] > 0.0) {
for (const auto& T : temps_available) {
if (minmax[0] <= T && T <= minmax[1]) {
temps_to_read.push_back(std::round(T));
}
}
}
switch (method) {
case TEMPERATURE_NEAREST:
// Determine actual temperatures to read
for (const auto& T : temperature) {
auto i_closest = xt::argmin(xt::abs(temps_available - T))[0];
auto temp_actual = temps_available[i_closest];
if (std::fabs(temp_actual - T) < tolerance) {
if (std::find(temps_to_read.begin(), temps_to_read.end(), std::round(temp_actual))
== temps_to_read.end()) {
temps_to_read.push_back(std::round(temp_actual));
}
} else {
std::stringstream msg;
msg << "Nuclear data library does not contain cross sections for "
<< name_ << " at or near " << std::round(T) << " K.";
fatal_error(msg);
}
}
break;
case TEMPERATURE_INTERPOLATION:
// If temperature interpolation or multipole is selected, get a list of
// bounding temperatures for each actual temperature present in the model
for (const auto& T : temperature) {
bool found = false;
for (int j = 0; j < temps_available.size() - 1; ++j) {
if (temps_available[j] <= T && T < temps_available[j + 1]) {
int T_j = std::round(temps_available[j]);
int T_j1 = std::round(temps_available[j + 1]);
if (std::find(temps_to_read.begin(), temps_to_read.end(), T_j) == temps_to_read.end()) {
temps_to_read.push_back(T_j);
}
if (std::find(temps_to_read.begin(), temps_to_read.end(), T_j1) == temps_to_read.end()) {
temps_to_read.push_back(T_j1);
}
found = true;
}
}
if (!found) {
std::stringstream msg;
msg << "Nuclear data library does not contain cross sections for "
<< name_ << " at temperatures that bound " << std::round(T) << " K.";
fatal_error(msg);
}
}
}
// Sort temperatures to read
std::sort(temps_to_read.begin(), temps_to_read.end());
auto n_temperature = temps_to_read.size();
kTs_.reserve(n_temperature);
data_.reserve(n_temperature);
for (auto T : temps_to_read) {
// Get temperature as a string
std::string temp_str = std::to_string(T) + "K";
// Read exact temperature value
double kT;
read_dataset(kT_group, temp_str.data(), kT);
kTs_.push_back(kT);
// Open group for temperature i
hid_t T_group = open_group(group, temp_str.data());
data_.emplace_back(T_group, secondary_mode);
close_group(T_group);
}
close_group(kT_group);
}
void
ThermalScattering::calculate_xs(double E, double sqrtkT, int* i_temp,
double* elastic, double* inelastic) const
{
// Determine temperature for S(a,b) table
double kT = sqrtkT*sqrtkT;
int i;
if (temperature_method == TEMPERATURE_NEAREST) {
// If using nearest temperature, do linear search on temperature
for (i = 0; i < kTs_.size(); ++i) {
if (abs(kTs_[i] - kT) < K_BOLTZMANN*temperature_tolerance) {
break;
}
}
} else {
// Find temperatures that bound the actual temperature
for (i = 0; i < kTs_.size() - 1; ++i) {
if (kTs_[i] <= kT && kT < kTs_[i+1]) {
break;
}
}
// Randomly sample between temperature i and i+1
double f = (kT - kTs_[i]) / (kTs_[i+1] - kTs_[i]);
if (f > prn()) ++i;
}
// Set temperature index
*i_temp = i;
// Get pointer to S(a,b) table
auto& sab = data_[i];
// Get index and interpolation factor for inelastic grid
int i_grid = 0;
double f = 0.0;
if (E >= sab.inelastic_e_in_.front()) {
auto& E_in = sab.inelastic_e_in_;
i_grid = lower_bound_index(E_in.begin(), E_in.end(), E);
f = (E - E_in[i_grid]) / (E_in[i_grid+1] - E_in[i_grid]);
}
// Calculate S(a,b) inelastic scattering cross section
auto& xs = sab.inelastic_sigma_;
*inelastic = (1.0 - f) * xs[i_grid] + f * xs[i_grid + 1];
// Check for elastic data
if (E < sab.threshold_elastic_) {
// Determine whether elastic scattering is given in the coherent or
// incoherent approximation. For coherent, the cross section is
// represented as P/E whereas for incoherent, it is simply P
auto& E_in = sab.elastic_e_in_;
if (sab.elastic_mode_ == SAB_ELASTIC_COHERENT) {
if (E < E_in.front()) {
// If energy is below that of the lowest Bragg peak, the elastic
// cross section will be zero
*elastic = 0.0;
} else {
i_grid = lower_bound_index(E_in.begin(), E_in.end(), E);
*elastic = sab.elastic_P_[i_grid] / E;
}
} else {
// Determine index on elastic energy grid
if (E < E_in.front()) {
i_grid = 0;
} else {
i_grid = lower_bound_index(E_in.begin(), E_in.end(), E);
}
// Get interpolation factor for elastic grid
f = (E - E_in[i_grid]) / (E_in[i_grid+1] - E_in[i_grid]);
// Calculate S(a,b) elastic scattering cross section
auto& xs = sab.elastic_P_;
*elastic = (1.0 - f) * xs[i_grid] + f * xs[i_grid + 1];
}
} else {
// No elastic data
*elastic = 0.0;
}
}
bool
ThermalScattering::has_nuclide(const char* name) const
{
std::string nuc {name};
return std::find(nuclides_.begin(), nuclides_.end(), nuc) != nuclides_.end();
}
//==============================================================================
// ThermalData implementation
//==============================================================================
ThermalData::ThermalData(hid_t group, int secondary_mode)
: inelastic_mode_{secondary_mode}
{
// Coherent elastic data
if (object_exists(group, "elastic")) {
// Read cross section data
hid_t elastic_group = open_group(group, "elastic");
// Read elastic cross section
xt::xarray<double> temp;
hid_t dset = open_dataset(elastic_group, "xs");
read_dataset(dset, temp);
// Get view on energies and cross section/probability values
auto E_in = xt::view(temp, 0);
auto P = xt::view(temp, 1);
// Set cross section data and type
std::copy(E_in.begin(), E_in.end(), std::back_inserter(elastic_e_in_));
std::copy(P.begin(), P.end(), std::back_inserter(elastic_P_));
n_elastic_e_in_ = elastic_e_in_.size();
// Determine whether elastic scattering is incoherent or coherent
std::string type;
read_attribute(dset, "type", type);
if (type == "tab1") {
elastic_mode_ = SAB_ELASTIC_INCOHERENT;
} else if (type == "bragg") {
elastic_mode_ = SAB_ELASTIC_COHERENT;
}
close_dataset(dset);
// Set elastic threshold
threshold_elastic_ = elastic_e_in_.back();
// Read angle distribution
if (elastic_mode_ == SAB_ELASTIC_INCOHERENT) {
xt::xarray<double> mu_out;
read_dataset(elastic_group, "mu_out", mu_out);
elastic_mu_ = mu_out;
}
close_group(elastic_group);
}
// Inelastic data
if (object_exists(group, "inelastic")) {
// Read type of inelastic data
hid_t inelastic_group = open_group(group, "inelastic");
// Read cross section data
xt::xarray<double> temp;
read_dataset(inelastic_group, "xs", temp);
// Get view of inelastic cross section and energy grid
auto E_in = xt::view(temp, 0);
auto xs = xt::view(temp, 1);
// Set cross section data
std::copy(E_in.begin(), E_in.end(), std::back_inserter(inelastic_e_in_));
std::copy(xs.begin(), xs.end(), std::back_inserter(inelastic_sigma_));
n_inelastic_e_in_ = inelastic_e_in_.size();
// Set inelastic threshold
threshold_inelastic_ = inelastic_e_in_.back();
if (secondary_mode != SAB_SECONDARY_CONT) {
// Read energy distribution
xt::xarray<double> E_out;
read_dataset(inelastic_group, "energy_out", E_out);
inelastic_e_out_ = E_out;
n_inelastic_e_out_ = inelastic_e_out_.shape()[1];
// Read angle distribution
xt::xarray<double> mu_out;
read_dataset(inelastic_group, "mu_out", mu_out);
inelastic_mu_ = mu_out;
n_inelastic_mu_ = inelastic_mu_.shape()[2];
} else {
// Read correlated angle-energy distribution
CorrelatedAngleEnergy dist {inelastic_group};
// Convert to S(a,b) native format
for (const auto& edist : dist.distribution()) {
// Create temporary distribution
DistEnergySab d;
// Copy outgoing energy distribution
d.n_e_out = edist.e_out.size();
d.e_out = edist.e_out;
d.e_out_pdf = edist.p;
d.e_out_cdf = edist.c;
for (int j = 0; j < d.n_e_out; ++j) {
auto adist = dynamic_cast<Tabular*>(edist.angle[j].get());
if (adist) {
// On first pass, allocate space for angles
if (j == 0) {
auto n_mu = adist->x().size();
n_inelastic_mu_ = n_mu;
d.mu = xt::empty<double>({d.n_e_out, n_mu});
}
// Copy outgoing angles
auto mu_j = xt::view(d.mu, j);
std::copy(adist->x().begin(), adist->x().end(), mu_j.begin());
}
}
inelastic_data_.push_back(std::move(d));
}
}
close_group(inelastic_group);
}
}
void
ThermalData::sample(const NuclideMicroXS* micro_xs, double E,
double* E_out, double* mu)
{
// Determine whether inelastic or elastic scattering will occur
if (prn() < micro_xs->thermal_elastic / micro_xs->thermal) {
// elastic scattering
// Get index and interpolation factor for elastic grid
int i = 0;
double f = 0.0;
if (E >= elastic_e_in_.front()) {
auto& E_in = elastic_e_in_;
i = lower_bound_index(E_in.begin(), E_in.end(), E);
f = (E - E_in[i]) / (E_in[i+1] - E_in[i]);
}
// Select treatment based on elastic mode
if (elastic_mode_ == SAB_ELASTIC_INCOHERENT) {
// With this treatment, we interpolate between two discrete cosines
// corresponding to neighboring incoming energies. This is used for
// data derived in the incoherent approximation
// Sample outgoing cosine bin
int k = prn() * n_elastic_mu_;
// Determine outgoing cosine corresponding to E_in[i] and E_in[i+1]
double mu_ijk = elastic_mu_(i, k);
double mu_i1jk = elastic_mu_(i+1, k);
// Cosine of angle between incoming and outgoing neutron
*mu = (1 - f)*mu_ijk + f*mu_i1jk;
} else if (elastic_mode_ == SAB_ELASTIC_COHERENT) {
// This treatment is used for data derived in the coherent
// approximation, i.e. for crystalline structures that have Bragg
// edges.
// Sample a Bragg edge between 1 and i
double prob = prn() * elastic_P_[i+1];
int k = 0;
if (prob >= elastic_P_.front()) {
k = lower_bound_index(elastic_P_.begin(), elastic_P_.begin() + (i+1), prob);
}
// Characteristic scattering cosine for this Bragg edge
*mu = 1.0 - 2.0*elastic_e_in_[k] / E;
}
// Outgoing energy is same as incoming energy
*E_out = E;
} else {
// Perform inelastic calculations
// Get index and interpolation factor for inelastic grid
int i = 0;
double f = 0.0;
if (E >= inelastic_e_in_.front()) {
auto& E_in = inelastic_e_in_;
i = lower_bound_index(E_in.begin(), E_in.end(), E);
f = (E - E_in[i]) / (E_in[i+1] - E_in[i]);
}
// Now that we have an incoming energy bin, we need to determine the
// outgoing energy bin. This will depend on the "secondary energy
// mode". If the mode is 0, then the outgoing energy bin is chosen from a
// set of equally-likely bins. If the mode is 1, then the first
// two and last two bins are skewed to have lower probabilities than the
// other bins (0.1 for the first and last bins and 0.4 for the second and
// second to last bins, relative to a normal bin probability of 1).
// Finally, if the mode is 2, then a continuous distribution (with
// accompanying PDF and CDF is utilized)
if (inelastic_mode_ == SAB_SECONDARY_EQUAL ||
inelastic_mode_ == SAB_SECONDARY_SKEWED) {
int j;
if (inelastic_mode_ == SAB_SECONDARY_EQUAL) {
// All bins equally likely
j = prn() * n_inelastic_e_out_;
} else if (inelastic_mode_ == SAB_SECONDARY_SKEWED) {
// Distribution skewed away from edge points
double r = prn() * (n_inelastic_e_out_ - 3);
if (r > 1.0) {
// equally likely N-4 middle bins
j = r + 1;
} else if (r > 0.6) {
// second to last bin has relative probability of 0.4
j = n_inelastic_e_out_ - 2;
} else if (r > 0.5) {
// last bin has relative probability of 0.1
j = n_inelastic_e_out_ - 1;
} else if (r > 0.1) {
// second bin has relative probability of 0.4
j = 1;
} else {
// first bin has relative probability of 0.1
j = 0;
}
}
// Determine outgoing energy corresponding to E_in[i] and E_in[i+1]
double E_ij = inelastic_e_out_(i, j);
double E_i1j = inelastic_e_out_(i+1, j);
// Outgoing energy
*E_out = (1 - f)*E_ij + f*E_i1j;
// Sample outgoing cosine bin
int k = prn() * n_inelastic_mu_;
// Determine outgoing cosine corresponding to E_in[i] and E_in[i+1]
double mu_ijk = inelastic_mu_(i, j, k);
double mu_i1jk = inelastic_mu_(i+1, j, k);
// Cosine of angle between incoming and outgoing neutron
*mu = (1 - f)*mu_ijk + f*mu_i1jk;
} else if (inelastic_mode_ == SAB_SECONDARY_CONT) {
// Continuous secondary energy - this is to be similar to
// Law 61 interpolation on outgoing energy
// Sample between ith and [i+1]th bin
int l = f > prn() ? i + 1 : i;
// Determine endpoints on grid i
auto n = inelastic_data_[i].e_out.size();
double E_i_1 = inelastic_data_[i].e_out(0);
double E_i_J = inelastic_data_[i].e_out(n);
// Determine endpoints on grid i + 1
n = inelastic_data_[i].e_out.size();
double E_i1_1 = inelastic_data_[i + 1].e_out(0);
double E_i1_J = inelastic_data_[i + 1].e_out(n);
double E_1 = E_i_1 + f * (E_i1_1 - E_i_1);
double E_J = E_i_J + f * (E_i1_J - E_i_J);
// Determine outgoing energy bin
// (First reset n_energy_out to the right value)
n = inelastic_data_[l].n_e_out;
double r1 = prn();
double c_j = inelastic_data_[l].e_out_cdf[0];
double c_j1;
std::size_t j;
for (j = 0; j < n - 2; ++j) {
c_j1 = inelastic_data_[l].e_out_cdf[j + 1];
if (r1 < c_j1) break;
c_j = c_j1;
}
// check to make sure j is <= n_energy_out - 2
j = std::min(j, n - 2);
// Get the data to interpolate between
double E_l_j = inelastic_data_[l].e_out[j];
double p_l_j = inelastic_data_[l].e_out_pdf[j];
// Next part assumes linear-linear interpolation in standard
double E_l_j1 = inelastic_data_[l].e_out[j + 1];
double p_l_j1 = inelastic_data_[l].e_out_pdf[j + 1];
// Find secondary energy (variable E)
double frac = (p_l_j1 - p_l_j) / (E_l_j1 - E_l_j);
if (frac == 0.0) {
*E_out = E_l_j + (r1 - c_j) / p_l_j;
} else {
*E_out = E_l_j + (std::sqrt(std::max(0.0, p_l_j*p_l_j +
2.0*frac*(r1 - c_j))) - p_l_j) / frac;
}
// Now interpolate between incident energy bins i and i + 1
if (l == i) {
*E_out = E_1 + (E - E_i_1) * (E_J - E_1) / (E_i_J - E_i_1);
} else {
*E_out = E_1 + (E - E_i1_1) * (E_J - E_1) / (E_i1_J - E_i1_1);
}
// Sample outgoing cosine bin
std::size_t k = prn() * n_inelastic_mu_;
// Rather than use the sampled discrete mu directly, it is smeared over
// a bin of width min(mu[k] - mu[k-1], mu[k+1] - mu[k]) centered on the
// discrete mu value itself.
const auto& mu_l = inelastic_data_[l].mu;
f = (r1 - c_j)/(c_j1 - c_j);
// Determine (k-1)th mu value
double mu_left;
if (k == 0) {
mu_left = -1.0;
} else {
mu_left = mu_l(j, k-1) + f*(mu_l(j+1, k-1) - mu_l(j, k-1));
}
// Determine kth mu value
*mu = mu_l(j, k) + f*(mu_l(j+1, k) - mu_l(j, k));
// Determine (k+1)th mu value
double mu_right;
if (k == n_inelastic_mu_ - 1) {
mu_right = 1.0 - *mu;
} else {
mu_right = mu_l(j, k+1) + f*(mu_l(j+1, k+1) - mu_l(j, k+1)) - *mu;
}
// Smear angle
*mu += std::min(*mu - mu_left, mu_right - *mu)*(prn() - 0.5);
} // (inelastic secondary energy treatment)
} // (elastic or inelastic)
// Because of floating-point roundoff, it may be possible for mu to be
// outside of the range [-1,1). In these cases, we just set mu to exactly
// -1 or 1
if (std::fabs(*mu) > 1.0) *mu = std::copysign(1.0, *mu);
}
//==============================================================================
// Fortran compatibility functions
//==============================================================================
ThermalScattering*
sab_from_hdf5(hid_t group, const double* temperature, int n,
int method, double tolerance, const double* minmax)
{
// Convert temperatures to a vector
std::vector<double> T {temperature, temperature + n};
// Create new object and return it
return new ThermalScattering{group, T, method, tolerance, minmax};
}
void sab_calculate_xs(ThermalScattering* data, double E, double sqrtkT,
int* i_temp, double* elastic, double* inelastic)
{
// Calculate cross section
int t;
data->calculate_xs(E, sqrtkT, &t, elastic, inelastic);
// Fortran needs index plus one
*i_temp = t + 1;
}
void sab_free(ThermalScattering* data) { delete data; }
bool sab_has_nuclide(ThermalScattering* data, const char* name)
{
return data->has_nuclide(name);
}
void sab_sample(ThermalScattering* data, const NuclideMicroXS* micro_xs,
double E_in, double* E_out, double* mu)
{
int i_temp = micro_xs->index_temp_sab;
data->data_[i_temp - 1].sample(micro_xs, E_in, E_out, mu);
}
double sab_threshold(ThermalScattering* data) { return data->threshold(); }
} // namespace openmc

147
src/thermal.h Normal file
View file

@ -0,0 +1,147 @@
#ifndef OPENMC_THERMAL_SCATTERING_H
#define OPENMC_THERMAL_SCATTERING_H
#include <cstddef>
#include <string>
#include <vector>
#include "xtensor/xtensor.hpp"
#include "hdf5_interface.h"
#include "nuclide.h"
namespace openmc {
//==============================================================================
// Constants
//==============================================================================
// Secondary energy mode for S(a,b) inelastic scattering
// TODO: Convert to enum
constexpr int SAB_SECONDARY_EQUAL {0}; // Equally-likely outgoing energy bins
constexpr int SAB_SECONDARY_SKEWED {1}; // Skewed outgoing energy bins
constexpr int SAB_SECONDARY_CONT {2}; // Continuous, linear-linear interpolation
// Elastic mode for S(a,b) elastic scattering
// TODO: Convert to enum
constexpr int SAB_ELASTIC_INCOHERENT {3}; // Incoherent elastic scattering
constexpr int SAB_ELASTIC_COHERENT {4}; // Coherent elastic scattering (Bragg edges)
//==============================================================================
//! Secondary angle-energy data for thermal neutron scattering at a single
//! temperature
//==============================================================================
class ThermalData {
public:
ThermalData(hid_t group, int secondary_mode);
// Sample an outgoing energy and angle
void sample(const NuclideMicroXS* micro_xs, double E_in,
double* E_out, double* mu);
private:
//! Secondary energy/angle distributions for inelastic thermal scattering
//! collisions which utilize a continuous secondary energy representation.
struct DistEnergySab {
std::size_t n_e_out; //!< Number of outgoing energies
xt::xtensor<double, 1> e_out; //!< Outgoing energies
xt::xtensor<double, 1> e_out_pdf; //!< Probability density function
xt::xtensor<double, 1> e_out_cdf; //!< Cumulative distribution function
xt::xtensor<double, 2> mu; //!< Equiprobable angles at each outgoing energy
};
//! Upper threshold for incoherent inelastic scattering (usually ~4 eV)
double threshold_inelastic_;
//! Upper threshold for coherent/incoherent elastic scattering
double threshold_elastic_ {0.0};
// Inelastic scattering data
int inelastic_mode_; //!< distribution type (equal/skewed/continuous)
std::size_t n_inelastic_e_in_; //!< number of incoming E for inelastic
std::size_t n_inelastic_e_out_; //!< number of outgoing E for inelastic
std::size_t n_inelastic_mu_; //!< number of outgoing angles for inelastic
std::vector<double> inelastic_e_in_; //!< incoming E grid for inelastic
std::vector<double> inelastic_sigma_; //!< inelastic scattering cross section
// The following are used only for equal/skewed distributions
xt::xtensor<double, 2> inelastic_e_out_;
xt::xtensor<double, 3> inelastic_mu_;
// The following is used only for continuous S(a,b) distributions. The
// different implementation is necessary because the continuous representation
// has a variable number of outgoing energy points for each incoming energy
std::vector<DistEnergySab> inelastic_data_; //!< Secondary angle-energy at
//!< each incoming energy
// Elastic scattering data
int elastic_mode_; //!< type of elastic (incoherent/coherent)
std::size_t n_elastic_e_in_; //!< number of incoming E for elastic
std::size_t n_elastic_mu_; //!< number of outgoing angles for elastic
std::vector<double> elastic_e_in_; //!< incoming E grid for elastic
std::vector<double> elastic_P_; //!< elastic scattering cross section
xt::xtensor<double, 2> elastic_mu_; //!< equi-probable angles at each incoming E
// ThermalScattering needs access to private data members
friend class ThermalScattering;
};
//==============================================================================
//! Data for thermal neutron scattering, typically off light isotopes in
//! moderating materials such as water, graphite, BeO, etc.
//==============================================================================
class ThermalScattering {
public:
ThermalScattering(hid_t group, const std::vector<double>& temperature, int method,
double tolerance, const double* minmax);
//! Determine inelastic/elastic cross section at given energy
//!
//! \param[in] E incoming energy in [eV]
//! \param[in] sqrtkT square-root of temperature multipled by Boltzmann's constant
//! \param[out] i_temp corresponding temperature index
//! \param[out] elastic Thermal elastic scattering cross section
//! \param[out] inelastic Thermal inelastic scattering cross section
void calculate_xs(double E, double sqrtkT, int* i_temp, double* elastic,
double* inelastic) const;
//! Determine whether table applies to a particular nuclide
//!
//! \param[in] name Name of the nuclide, e.g., "H1"
//! \return Whether table applies to the nuclide
bool has_nuclide(const char* name) const;
// Sample an outgoing energy and angle
void sample(const NuclideMicroXS* micro_xs, double E_in,
double* E_out, double* mu);
double threshold() const { return data_[0].threshold_inelastic_; }
std::string name_; //!< name of table, e.g. "c_H_in_H2O"
double awr_; //!< weight of nucleus in neutron masses
std::vector<double> kTs_; //!< temperatures in [eV] (k*T)
std::vector<std::string> nuclides_; //!< Valid nuclides
//! cross sections and distributions at each temperature
std::vector<ThermalData> data_;
};
//==============================================================================
// Fortran compatibility functions
//==============================================================================
extern "C" {
ThermalScattering* sab_from_hdf5(hid_t group, const double* temperature,
int n, int method, double tolerance, const double* minmax);
void sab_calculate_xs(ThermalScattering* data, double E, double sqrtkT,
int* i_temp, double* elastic, double* inelastic);
void sab_free(ThermalScattering* data);
bool sab_has_nuclide(ThermalScattering* data, const char* name);
void sab_sample(ThermalScattering* data, const NuclideMicroXS* micro_xs,
double E_in, double* E_out, double* mu);
double sab_threshold(ThermalScattering* data);
}
} // namespace openmc
#endif // OPENMC_THERMAL_SCATTERING_H

View file

@ -40,4 +40,19 @@ get_node_value(pugi::xml_node node, const char* name, bool lowercase,
return value;
}
bool
get_node_value_bool(pugi::xml_node node, const char* name)
{
if (node.attribute(name)) {
return node.attribute(name).as_bool();
} else if (node.child(name)) {
return node.child(name).text().as_bool();
} else {
std::stringstream err_msg;
err_msg << "Node \"" << name << "\" is not a member of the \""
<< node.name() << "\" XML node";
fatal_error(err_msg);
}
}
} // namespace openmc

View file

@ -16,9 +16,11 @@ check_for_node(pugi::xml_node node, const char *name)
return node.attribute(name) || node.child(name);
}
std::string get_node_value(pugi::xml_node node, const char *name,
std::string get_node_value(pugi::xml_node node, const char* name,
bool lowercase=false, bool strip=false);
bool get_node_value_bool(pugi::xml_node node, const char* name);
template <typename T>
std::vector<T> get_node_array(pugi::xml_node node, const char* name,
bool lowercase=false)

View file

@ -7,10 +7,6 @@ set -ex
# Upgrade pip before doing anything else
pip install --upgrade pip
# Running OpenMC's setup.py requires numpy/cython already
pip install numpy
pip install cython
# pytest installed by default -- make sure we get latest
pip install --upgrade pytest