LCOV - code coverage report
Current view: top level - src/utils - DiscreteFissionPKAPDF.C (source / functions) Hit Total Coverage
Test: idaholab/magpie: 535c54 Lines: 110 128 85.9 %
Date: 2025-10-02 22:23:54 Functions: 7 8 87.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /**********************************************************************/
       2             : /*                     DO NOT MODIFY THIS HEADER                      */
       3             : /* MAGPIE - Mesoscale Atomistic Glue Program for Integrated Execution */
       4             : /*                                                                    */
       5             : /*            Copyright 2017 Battelle Energy Alliance, LLC            */
       6             : /*                        ALL RIGHTS RESERVED                         */
       7             : /**********************************************************************/
       8             : 
       9             : #include "DiscreteFissionPKAPDF.h"
      10             : #include "MooseError.h"
      11             : #include "MooseRandom.h"
      12             : #include "MagpieUtils.h"
      13             : #include "MooseUtils.h"
      14             : 
      15             : #include "libmesh/utility.h"
      16             : 
      17             : #include <fstream>
      18             : #include <map>
      19             : 
      20           0 : DiscreteFissionPKAPDF::DiscreteFissionPKAPDF()
      21             :   : DiscretePKAPDFBase(),
      22           0 :     _marginal_cdf_target(MultiIndex<Real>({1})),
      23           0 :     _conditional_cdf_energy(MultiIndex<Real>({1}))
      24             : {
      25           0 : }
      26             : 
      27           1 : DiscreteFissionPKAPDF::DiscreteFissionPKAPDF(const std::vector<unsigned int> & ZAID,
      28             :                                              const std::vector<Real> & energies,
      29           1 :                                              const MultiIndex<Real> & probabilities)
      30             :   : DiscretePKAPDFBase(ZAID, energies),
      31           1 :     _marginal_cdf_target(probabilities),
      32           1 :     _conditional_cdf_energy(probabilities)
      33             : {
      34             :   // check the size of _probabilities
      35           1 :   if (probabilities.dim() != 2)
      36           0 :     mooseError("probabilities MultiIndex object has wrong dimensions.");
      37             : 
      38           1 :   MultiIndex<Real>::size_type shape = probabilities.size();
      39           1 :   if (shape[0] != _nZA || shape[1] != _ng)
      40           0 :     mooseError("Size of probabilities is inconsistent with random variable input.");
      41             : 
      42           1 :   precomputeCDF(probabilities);
      43           1 :   readFissionData(ZAID);
      44           1 :   computeMagnitude(probabilities);
      45           1 : }
      46             : 
      47             : void
      48           1 : DiscreteFissionPKAPDF::precomputeCDF(MultiIndex<Real> probabilities)
      49             : {
      50             :   // Step 1: Compute the marginal distribution w.r.t. incident energy of the neutron
      51           1 :   MultiIndex<Real>::size_type shape(1);
      52           1 :   MultiIndex<Real>::size_type index(2);
      53             : 
      54           1 :   shape[0] = _nZA;
      55           1 :   _marginal_cdf_target = MultiIndex<Real>(shape);
      56           3 :   for (unsigned int jZA = 0; jZA < _nZA; ++jZA)
      57             :   {
      58             :     Real integral_value = 0.0;
      59           6 :     for (unsigned int jE = 0; jE < _ng; ++jE)
      60           4 :       integral_value += probabilities({jZA, jE});
      61           2 :     _marginal_cdf_target({jZA}) = integral_value;
      62             :   }
      63             : 
      64             :   // Step 2: Compute cdf
      65           2 :   for (auto target : _marginal_cdf_target)
      66             :   {
      67           2 :     index = target.first;
      68           2 :     index[0] -= 1;
      69           2 :     if (target.first[0] > 0)
      70           1 :       target.second += _marginal_cdf_target(index);
      71             :   }
      72             : 
      73             :   // Step 3: Renormalize to ensure that cdf[-1] == 1
      74           1 :   index[0] = _nZA - 1;
      75           1 :   Real last_value = _marginal_cdf_target(index);
      76           2 :   for (auto target : _marginal_cdf_target)
      77           2 :     target.second /= last_value;
      78             : 
      79             :   // Step 1: Compute the conditional distribution of the target w.r.t the sampled energy
      80           1 :   _conditional_cdf_energy = probabilities;
      81             : 
      82             :   // Step 2: Compute cdf
      83           4 :   for (auto energy : _conditional_cdf_energy)
      84             :   {
      85           4 :     index = energy.first;
      86           4 :     index[1] -= 1;
      87           4 :     if (energy.first[1] > 0)
      88           2 :       energy.second += _conditional_cdf_energy(index);
      89             :   }
      90             : 
      91             :   // Step 3: Renormalize to ensure that cdf[-1] == 1
      92             :   std::vector<Real> last_element_per_target;
      93           3 :   for (unsigned int j = 0; j < _conditional_cdf_energy.size()[0]; ++j)
      94           2 :     last_element_per_target.push_back(_conditional_cdf_energy({j, _ng - 1}));
      95           4 :   for (auto energy : _conditional_cdf_energy)
      96           4 :     energy.second /= last_element_per_target[energy.first[0]];
      97           1 : }
      98             : 
      99             : void
     100      500000 : DiscreteFissionPKAPDF::drawSample(std::vector<MyTRIM_NS::IonBase> & initial_state) const
     101             : {
     102             :   // resize initial_state
     103      500000 :   initial_state.resize(2);
     104             : 
     105             :   // first sample the target ZAID and the energy group of the incident neutron
     106             :   std::vector<unsigned int> sampled_indices;
     107      500000 :   sampled_indices.push_back(sampleHelper(_marginal_cdf_target));
     108      500000 :   sampled_indices.push_back(sampleHelper(_conditional_cdf_energy, sampled_indices));
     109             : 
     110             :   // first we need to compute Z and m from ZAID
     111      500000 :   auto target_Z = MagpieUtils::getZFromZAID(_zaids[sampled_indices[0]]);
     112      500000 :   auto target_A = MagpieUtils::getAFromZAID(_zaids[sampled_indices[0]]);
     113             : 
     114             :   // the real random variables also need to be resampled uniformly
     115             :   // within bin index[j]
     116             :   auto neutron_energy =
     117      500000 :       (_energies[sampled_indices[1]] - _energies[sampled_indices[1] + 1]) * MooseRandom::rand() +
     118      500000 :       _energies[sampled_indices[1] + 1];
     119             : 
     120             :   // pull the right fission yield table and sample Z, A, and energy
     121      500000 :   auto energy = MagpieUtils::determineNeutronType(neutron_energy);
     122      500000 :   auto key = _zaids[sampled_indices[0]];
     123             : 
     124             :   // pull the map based on the neutron energy
     125      500000 :   auto zaid_map = _fission_zaids[energy];
     126             :   auto cdf_map = _fission_cdf[energy];
     127             : 
     128             :   /// check if key exists
     129      500000 :   if (zaid_map.find(key) == zaid_map.end())
     130           0 :     mooseError("Fission product ZAID's not found for this target isotope, ",
     131             :                key,
     132             :                " at this energy, ",
     133             :                energy);
     134             : 
     135      500000 :   if (cdf_map.find(key) == cdf_map.end())
     136           0 :     mooseError("Sum yield not found for this target isotope, ", key, " at this energy, ", energy);
     137             : 
     138             :   // now we know the key1/key2 pair exists
     139      500000 :   auto zaid_products = zaid_map[key];
     140      500000 :   auto cdf_products = cdf_map[key];
     141             : 
     142             :   // calculate the total kinetic energy of the fission products
     143      500000 :   auto total_ke = determineFragmentsEnergy(target_Z, target_A);
     144             : 
     145             :   // sample the fission yield cdf to determine the first fission product
     146      500000 :   auto ffindex = sampleHelper(cdf_products);
     147      500000 :   auto ffzaid = zaid_products[ffindex];
     148      500000 :   initial_state[0]._Z = MagpieUtils::getZFromZAID(ffzaid);
     149      500000 :   initial_state[0]._m = MagpieUtils::getAFromZAID(ffzaid);
     150             : 
     151             :   // sample neutrons per fission
     152      500000 :   auto neutrons_per_fission = sampleNu(energy, key);
     153             : 
     154             :   // mass balance to find Z and A of second fission product
     155      500000 :   initial_state[1]._Z = target_Z - initial_state[0]._Z;
     156      500000 :   initial_state[1]._m = target_A - initial_state[0]._m - neutrons_per_fission;
     157             : 
     158             :   // calculate the kinetic energy of each fission product
     159      500000 :   initial_state[0]._E = total_ke / (1.0 + (initial_state[0]._m / initial_state[1]._m));
     160      500000 :   initial_state[1]._E = (initial_state[0]._m / initial_state[1]._m) * initial_state[0]._E;
     161     1000000 : }
     162             : 
     163             : void
     164           1 : DiscreteFissionPKAPDF::readFissionData(const std::vector<unsigned int> & zaid_list)
     165             : {
     166           1 :   _fission_zaids.resize(MagpieUtils::NET_MAX);
     167           1 :   _fission_cdf.resize(MagpieUtils::NET_MAX);
     168             : 
     169             :   // iterate over all neutron energy types
     170           5 :   for (unsigned int energy = 0; energy < MagpieUtils::NET_MAX; ++energy)
     171             :   {
     172             :     // Dummy maps
     173             :     std::map<unsigned int, std::vector<unsigned int>> zaid_map;
     174             :     std::map<unsigned int, std::vector<Real>> cdf_map;
     175          12 :     for (auto & zaid : zaid_list)
     176             :     {
     177             :       // check if $ENDF_FP_DIR is set
     178           8 :       auto path = std::getenv("ENDF_FP_DIR");
     179           8 :       if (path == NULL)
     180           0 :         mooseError("Set $ENDF_FP_DIR to the directory holding the sum yield data files.");
     181             : 
     182             :       // check if file exists, if not continue
     183             :       std::string filename =
     184          16 :           path + std::to_string(zaid) + "_" + MagpieUtils::neutronEnergyName(energy) + ".txt";
     185           8 :       if (!MooseUtils::checkFileReadable(filename, false, false))
     186             :       {
     187             :         // most isotopes have fission data for High but not for Fast wich is weird
     188             :         // if fast is not available, then try to load High instead
     189           1 :         if (MagpieUtils::neutronEnergyName(energy) == "Fast")
     190             :         {
     191           1 :           std::string alt_filename = path + std::to_string(zaid) + "_" + "High" + ".txt";
     192           1 :           if (!MooseUtils::checkFileReadable(alt_filename, false, false))
     193             :             continue;
     194             :           filename = alt_filename;
     195             :         }
     196             :         else
     197           0 :           continue;
     198             :       }
     199             : 
     200             :       // read the data
     201           8 :       std::ifstream infile(filename.c_str());
     202             :       std::vector<unsigned int> zaid_target;
     203             :       std::vector<Real> fission_probabilities;
     204             :       int aa;
     205             :       Real bb;
     206             :       Real prob_prev = 0.0;
     207       15628 :       while (infile >> aa >> bb)
     208             :       {
     209        7806 :         zaid_target.push_back(aa);
     210        7806 :         fission_probabilities.push_back(bb + prob_prev);
     211             : 
     212             :         // accumulate CDF as it reads from the file
     213        7806 :         prob_prev += bb;
     214             :       }
     215             : 
     216             :       // renormalize to ensure that cdf[-1] == 1
     217           8 :       unsigned int last_index = fission_probabilities.size() - 1;
     218           8 :       Real last_value = fission_probabilities[last_index];
     219        7814 :       for (auto & probability : fission_probabilities)
     220        7806 :         probability /= last_value;
     221             : 
     222             :       // Step 4: Store std::vectors into maps
     223           8 :       zaid_map[zaid] = zaid_target;
     224           8 :       cdf_map[zaid] = fission_probabilities;
     225           8 :     }
     226             : 
     227             :     // store map into vector for given energy type
     228           4 :     _fission_zaids[energy] = zaid_map;
     229             :     _fission_cdf[energy] = cdf_map;
     230             :   }
     231           1 : }
     232             : 
     233             : Real
     234      500000 : DiscreteFissionPKAPDF::determineFragmentsEnergy(unsigned int Z, unsigned int A) const
     235             : {
     236             :   // MyTRIM expects energies to be in eV, hence the 1.0e6 factor
     237      500000 :   return (0.1178 * (Utility::pow<2>(Z) / std::cbrt(A)) + 5.8) * 1.0e6;
     238             : }
     239             : 
     240             : /**
     241             :  * Given the target isotope and neutron energy,
     242             :  * determine the avg. number of neutrons per fission.
     243             :  */
     244             : unsigned int
     245      500000 : DiscreteFissionPKAPDF::sampleNu(MagpieUtils::NeutronEnergyType energy_type, unsigned int zaid) const
     246             : {
     247             :   Real nu_bar;
     248      500000 :   if (zaid == 922350 && energy_type == MagpieUtils::Thermal)
     249             :     nu_bar = 2.4355;
     250      500000 :   else if (zaid == 922330 && energy_type == MagpieUtils::Epithermal)
     251             :     nu_bar = 2.4968;
     252      500000 :   else if (zaid == 902320 && energy_type == MagpieUtils::Epithermal)
     253             :     nu_bar = 2.456;
     254      500000 :   else if (zaid == 942390 && energy_type == MagpieUtils::Thermal)
     255             :     nu_bar = 2.8836;
     256           0 :   else if (zaid == 942390 && energy_type == MagpieUtils::Epithermal)
     257             :     nu_bar = 2.8836;
     258           0 :   else if (zaid == 942400 && energy_type == MagpieUtils::Fast)
     259             :     nu_bar = 3.086;
     260           0 :   else if (zaid == 942410 && energy_type == MagpieUtils::Thermal)
     261             :     nu_bar = 2.4979;
     262           0 :   else if (zaid == 942420 && energy_type == MagpieUtils::Epithermal)
     263             :     nu_bar = 3.189;
     264           0 :   else if (zaid == 952410 && energy_type == MagpieUtils::Thermal)
     265             :     nu_bar = 3.239;
     266           0 :   else if (zaid == 962420 && energy_type == MagpieUtils::Epithermal)
     267             :     nu_bar = 2.529;
     268           0 :   else if (zaid == 962430 && energy_type == MagpieUtils::Thermal)
     269             :     nu_bar = 3.433;
     270           0 :   else if (zaid == 962430 && energy_type == MagpieUtils::Epithermal)
     271             :     nu_bar = 3.433;
     272             :   else
     273             :     nu_bar = 2.5;
     274             : 
     275      500000 :   if (nu_bar <= std::floor(nu_bar) + MooseRandom::rand())
     276       58301 :     return std::floor(nu_bar);
     277             :   else
     278      441699 :     return std::ceil(nu_bar);
     279             : }
     280             : 
     281             : void
     282           1 : DiscreteFissionPKAPDF::computeMagnitude(MultiIndex<Real> probabilities)
     283             : {
     284           1 :   _magnitude = 0.0;
     285           4 :   for (auto it : probabilities)
     286             :   {
     287           4 :     MultiIndex<Real>::size_type index = it.first;
     288           4 :     Real delE = _energies[index[1]] - _energies[index[1] + 1];
     289           4 :     _magnitude += delE * it.second;
     290           4 :   }
     291           1 : }

Generated by: LCOV version 1.14