LCOV - code coverage report
Current view: top level - src/criticality - BoratedWater.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: ddd5f2 Lines: 80 85 94.1 %
Date: 2026-06-07 19:35:24 Functions: 4 4 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /********************************************************************/
       2             : /*                  SOFTWARE COPYRIGHT NOTIFICATION                 */
       3             : /*                             Cardinal                             */
       4             : /*                                                                  */
       5             : /*                  (c) 2021 UChicago Argonne, LLC                  */
       6             : /*                        ALL RIGHTS RESERVED                       */
       7             : /*                                                                  */
       8             : /*                 Prepared by UChicago Argonne, LLC                */
       9             : /*               Under Contract No. DE-AC02-06CH11357               */
      10             : /*                With the U. S. Department of Energy               */
      11             : /*                                                                  */
      12             : /*             Prepared by Battelle Energy Alliance, LLC            */
      13             : /*               Under Contract No. DE-AC07-05ID14517               */
      14             : /*                With the U. S. Department of Energy               */
      15             : /*                                                                  */
      16             : /*                 See LICENSE for full restrictions                */
      17             : /********************************************************************/
      18             : 
      19             : #ifdef ENABLE_OPENMC_COUPLING
      20             : 
      21             : #define NUCLEAR_DATA_IMPLEMENTATION
      22             : 
      23             : #include "BoratedWater.h"
      24             : #include "UserErrorChecking.h"
      25             : #include "NuclearData.h"
      26             : #include "openmc/capi.h"
      27             : #include "openmc/constants.h"
      28             : #include "openmc/nuclide.h"
      29             : 
      30             : registerMooseObject("CardinalApp", BoratedWater);
      31             : 
      32             : InputParameters
      33          24 : BoratedWater::validParams()
      34             : {
      35          24 :   auto params = OpenMCMaterialSearch::validParams();
      36          48 :   params.addParam<std::vector<std::string>>(
      37             :       "absent_nuclides",
      38             :       "Natural nuclides of hydrogen, oxygen, or boron which are missing from your cross section "
      39             :       "library; some cross section libraries do not have entries for O17 and O18. If your library "
      40             :       "does not have these nuclides you will get an error from this object trying to add them. For "
      41             :       "these missing nuclides, specify them here and their abundance will be applied to the main "
      42             :       "isotope of each element. Currently, only O18 is supported.");
      43          24 :   params.addClassDescription(
      44             :       "Searches for criticality using natural boron ppm in water in units of weight ppm");
      45          24 :   return params;
      46           0 : }
      47             : 
      48          16 : BoratedWater::BoratedWater(const InputParameters & parameters) : OpenMCMaterialSearch(parameters)
      49             : {
      50             :   // apply additional checks on the minimum and maximum
      51          16 :   if (_minimum < 0)
      52           4 :     paramError("minimum",
      53           2 :                "The 'minimum' boron ppm (" + std::to_string(_minimum) + ") must be positive!");
      54          14 :   if (_maximum > 50000)
      55           4 :     paramError("maximum",
      56             :                "The borated water composition is computed using a dilute species approximation. "
      57             :                "Results will not be accurate if the boron species is no longer dilute, which we "
      58           2 :                "take as 5\% weight concentration or higher. Please decrease 'maximum' (" +
      59           0 :                    std::to_string(_maximum) + ") to an upper limit which is in the dilute regime.");
      60             : 
      61             :   // We take the provided material, retaining its density, but then overwriting
      62             :   // any nuclides there to be H2O + boron weight ppm and add the S(a,b) tables
      63             :   // for hydrogen. Therefore, we perform a check here if any nuclides in the
      64             :   // material are *not* those we are going to add back when we wipe the material,
      65             :   // to notify the user they probably provided the wrong material ID or they need
      66             :   // to use a more general material modification object that allows full control
      67             :   // over additional nuclides (e.g., if their water includes corrosion products
      68             :   // that they do want to be there).
      69             :   // TODO: add the S(a,b) tables
      70             : 
      71             :   const int * nuclides;
      72             :   const double * densities;
      73             :   int n;
      74          12 :   int err = openmc_material_get_densities(_material_index, &nuclides, &densities, &n);
      75          12 :   catchOpenMCError(err, "get nuclide densities from material " + std::to_string(_material_id));
      76             : 
      77             :   // get all the natural isotopes of H, O, B
      78          12 :   _hydrogen_natural = NuclearData::Nuclide::getAbundances("H");
      79          12 :   _boron_natural = NuclearData::Nuclide::getAbundances("B");
      80          24 :   _oxygen_natural = NuclearData::Nuclide::getAbundances("O");
      81             : 
      82             :   std::vector<std::string> allowable;
      83          36 :   for (const auto & i : _hydrogen_natural)
      84          24 :     allowable.push_back(i.first);
      85          36 :   for (const auto & i : _boron_natural)
      86          24 :     allowable.push_back(i.first);
      87          48 :   for (const auto & i : _oxygen_natural)
      88          36 :     allowable.push_back(i.first);
      89             : 
      90          12 :   applyAbsentNuclides(allowable);
      91             : 
      92             :   // check if any nuclides already defined on the material do not intersect with
      93             :   // natural isotopes of H, O, B
      94          10 :   std::string full_names = "";
      95          66 :   for (int i = 0; i < n; ++i)
      96             :   {
      97          56 :     auto idx = nuclides[i];
      98          56 :     std::string name = openmc::data::nuclides[idx]->name_;
      99          56 :     if (std::find(allowable.begin(), allowable.end(), name) == allowable.end())
     100          16 :       full_names += name + " ";
     101             :   }
     102             : 
     103          10 :   if (!full_names.empty())
     104             :   {
     105           2 :     std::ostringstream msg;
     106             :     msg << "The criticality search will clear out all nuclides in material "
     107           2 :         << std::to_string(_material_id)
     108             :         << " and replace them with the naturally-abundant nuclides in hydrogen, oxygen, and boron. "
     109           4 :            "Any other nuclides which existed in the material will be deleted."
     110             :         << std::endl;
     111             :     msg << "\nThe material you provided contains nuclides which are not the natural isotopes of H, "
     112             :            "B, and O: "
     113           2 :         << full_names.substr(0, full_names.length() - 1) << ". These will be deleted from material "
     114           8 :         << std::to_string(_material_id) << " when the boron concentration is changed." << std::endl;
     115             :     msg << "\nFor general criticality searches based on material composition, please contact the "
     116           2 :            "Cardinal developer team.";
     117           2 :     mooseWarning(msg.str());
     118           0 :   }
     119             : 
     120             :   // Compute the molar mass of pure water
     121           8 :   _M_H2O = 0.0;
     122          24 :   for (const auto & h : _hydrogen_natural)
     123          16 :     _M_H2O += 2.0 * h.second * NuclearData::Nuclide::getAtomicMass(h.first);
     124          24 :   for (const auto & o : _oxygen_natural)
     125          16 :     _M_H2O += 1.0 * o.second * NuclearData::Nuclide::getAtomicMass(o.first);
     126             : 
     127             :   // Compute the molar mass of pure boron
     128           8 :   _M_B = 0.0;
     129          24 :   for (const auto & b : _boron_natural)
     130          16 :     _M_B += 1.0 * b.second * NuclearData::Nuclide::getAtomicMass(b.first);
     131           8 : }
     132             : 
     133             : void
     134          96 : BoratedWater::updateOpenMCModel(const Real & ppm)
     135             : {
     136          96 :   _console << " OpenMC will run with next guess for boron = " << ppm << " [ppm] ..." << std::endl;
     137             : 
     138             :   // A coupled thermal-fluid app may set the material density just before we enter
     139             :   // this routine; we will preserve that density which may be set and we interpret
     140             :   // it to be the SOLUTION density to be fully consistent with energy conservation.
     141             : 
     142             :   // Compute the number fractions of each element
     143          96 :   Real frac_H2O = (1 - ppm * 1e-6) / _M_H2O;
     144          96 :   Real frac_H = 2 * frac_H2O;
     145             :   Real frac_O = frac_H2O;
     146          96 :   Real frac_B = ppm * 1e-6 / _M_B;
     147             : 
     148             :   double rho;
     149          96 :   int err = openmc_material_get_density(_material_index, &rho);
     150         192 :   catchOpenMCError(err, "get density for material " + std::to_string(_material_id));
     151             : 
     152             :   std::vector<std::string> names;
     153             :   std::vector<double> densities;
     154         288 :   for (const auto & h : _hydrogen_natural)
     155             :   {
     156         192 :     names.push_back(h.first);
     157         192 :     densities.push_back(h.second * frac_H * rho * openmc::N_AVOGADRO);
     158             :   }
     159         288 :   for (const auto & o : _oxygen_natural)
     160             :   {
     161         192 :     names.push_back(o.first);
     162         192 :     densities.push_back(o.second * frac_O * rho * openmc::N_AVOGADRO);
     163             :   }
     164         288 :   for (const auto & b : _boron_natural)
     165             :   {
     166         192 :     names.push_back(b.first);
     167         192 :     densities.push_back(b.second * frac_B * rho * openmc::N_AVOGADRO);
     168             :   }
     169             : 
     170          96 :   openmc::model::materials[_material_index]->set_densities(names, densities /* atom/b-cm */);
     171          96 : }
     172             : 
     173             : void
     174          12 : BoratedWater::applyAbsentNuclides(const std::vector<std::string> & allowable)
     175             : {
     176          24 :   if (isParamValid("absent_nuclides"))
     177             :   {
     178          24 :     const auto & absent = getParam<std::vector<std::string>>("absent_nuclides");
     179             : 
     180          22 :     for (const auto & a : absent)
     181             :     {
     182             :       // only missing nuclides for H, O, B are meaningful
     183          12 :       if (std::find(allowable.begin(), allowable.end(), a) == allowable.end())
     184           2 :         paramWarning("absent_nuclides",
     185             :                      "Only absent isotopes of hydrogen, oxygen, or boron will be used to adjust "
     186           2 :                      "natural abundances. The entry '" +
     187           0 :                          a + "' will be unused.");
     188             : 
     189             :       // adjust the natural abundances if nuclides are missing from the cross section library
     190             :       // TODO: implement in a general fashion, this only works for O18 which is also the only
     191             :       // absent nuclide we expect in practice
     192          10 :       if (a == "O18")
     193             :       {
     194             :         // find which entry in _oxygen_natural has O16 and O18; this allows these isotopes
     195             :         // to appear in any order
     196             :         unsigned int idx16;
     197             :         unsigned int idx18;
     198             :         unsigned int idx = 0;
     199          40 :         for (const auto & o : _oxygen_natural)
     200             :         {
     201          30 :           if (o.first == "O16")
     202             :             idx16 = idx;
     203          30 :           if (o.first == "O18")
     204             :             idx18 = idx;
     205          30 :           idx++;
     206             :         }
     207             : 
     208          10 :         _oxygen_natural[idx16].second += _oxygen_natural[idx18].second;
     209          10 :         _oxygen_natural.erase(_oxygen_natural.begin() + idx18);
     210             :       }
     211             :       else
     212           0 :         paramError(
     213             :             "absent_nuclides",
     214             :             "Cardinal currently only assumes that O18 may be missing from your cross section "
     215             :             "library; please contact the Cardinal developer team to generalize this capability");
     216             :     }
     217             :   }
     218          10 : }
     219             : #endif

Generated by: LCOV version 1.14