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