LCOV - code coverage report
Current view: top level - src/userobjects - DPAUserObjectBase.C (source / functions) Hit Total Coverage
Test: idaholab/magpie: 5710af Lines: 156 188 83.0 %
Date: 2025-07-21 23:34:39 Functions: 12 13 92.3 %
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             : #ifdef GSL_ENABLED
      10             : 
      11             : #include "DPAUserObjectBase.h"
      12             : #include "PolyatomicDisplacementFunction.h"
      13             : 
      14             : InputParameters
      15          32 : DPAUserObjectBase::validParams()
      16             : {
      17          32 :   InputParameters params = GeneralUserObject::validParams();
      18          64 :   params.addParam<Real>("irradiation_time", "Irradiation time used ");
      19          32 :   MultiMooseEnum damage_reaction_types("elastic inelastic");
      20          64 :   params.addParam<MultiMooseEnum>("damage_reaction_types",
      21             :                                   damage_reaction_types,
      22             :                                   "The neutron reaction causing radiation damage");
      23          64 :   params.addParam<std::vector<Real>>("Z", {}, "The atomic numbers of all present isotopes");
      24          64 :   params.addParam<std::vector<Real>>("A", {}, "The mass numbers of all present isotopes");
      25          64 :   params.addParam<std::vector<Real>>(
      26             :       "number_densities", {}, "The number densities of all present isotopes");
      27          64 :   params.addParam<std::vector<Real>>("energy_group_boundaries",
      28             :                                      {},
      29             :                                      "The neutron flux energy group boundaries in units of eV "
      30             :                                      "starting with the highest energy group");
      31          64 :   params.addParam<std::vector<Real>>("scalar_flux",
      32             :                                      {},
      33             :                                      "The values of the neutron scalar flux by energy group "
      34             :                                      "starting with the highest energy group");
      35          64 :   params.addParam<std::vector<std::vector<Real>>>(
      36             :       "cross_section",
      37             :       "The values of the cross sections. Each semicolon separated vector contains cross sections "
      38             :       "for a particular nuclide and reaction type. Each entry must be number of energy groups "
      39             :       "entries long. One vector must be provided for each "
      40             :       "nuclide/reaction type combination. The ordering is as follows: if there are reaction types"
      41             :       "a and b, and nuclides i, k, and l, the ordering will be xs_ai; xs_ak; xs_al; xs_bi; xs_bk, "
      42             :       "xs_bl");
      43          64 :   params.addParam<std::vector<std::vector<Real>>>(
      44             :       "Q", "The Q values by reaction type and then isotope. Assumed zero if not provided.");
      45          32 :   params.set<ExecFlagEnum>("execute_on") = EXEC_TIMESTEP_BEGIN;
      46          32 :   params.suppressParameter<ExecFlagEnum>("execute_on");
      47          32 :   return params;
      48          32 : }
      49             : 
      50          16 : DPAUserObjectBase::DPAUserObjectBase(const InputParameters & parameters)
      51             :   : GeneralUserObject(parameters),
      52          32 :     _is_transient_irradiation(!isParamValid("irradiation_time")),
      53          32 :     _irradiation_time(_is_transient_irradiation ? 0 : getParam<Real>("irradiation_time")),
      54          32 :     _neutron_reaction_types(getParam<MultiMooseEnum>("damage_reaction_types")),
      55          16 :     _nr(_neutron_reaction_types.size()),
      56          32 :     _atomic_numbers(getParam<std::vector<Real>>("Z")),
      57          32 :     _mass_numbers(getParam<std::vector<Real>>("A")),
      58          32 :     _number_densities(getParam<std::vector<Real>>("number_densities")),
      59          32 :     _energy_group_boundaries(getParam<std::vector<Real>>("energy_group_boundaries")),
      60          64 :     _scalar_flux(getParam<std::vector<Real>>("scalar_flux"))
      61             : {
      62          16 :   if (_nr == 0)
      63           0 :     paramError("damage_reaction_types", "At least one damage mechanism must be provided");
      64             : 
      65          32 :   if (isParamValid("cross_section"))
      66             :   {
      67          48 :     std::vector<std::vector<Real>> xs = getParam<std::vector<std::vector<Real>>>("cross_section");
      68             : 
      69             :     // we know _nr so the length of cross sections should be divisible by _nr
      70          16 :     unsigned int n = xs.size();
      71          16 :     if (n % _nr != 0)
      72           0 :       paramError("cross_section",
      73             :                  "The number of entries in the cross_section param must be divisible by number of "
      74             :                  "reaction types ",
      75             :                  _nr);
      76             : 
      77          16 :     unsigned int n_nuc = n / _nr;
      78          16 :     _cross_sections.resize(_nr);
      79             :     unsigned int p = 0;
      80          40 :     for (unsigned int r = 0; r < _nr; ++r)
      81             :     {
      82          24 :       _cross_sections[r].resize(n_nuc);
      83          64 :       for (unsigned int j = 0; j < n_nuc; ++j)
      84             :       {
      85          40 :         _cross_sections[r][j] = xs[p];
      86          40 :         ++p;
      87             :       }
      88             :     }
      89          16 :   }
      90          16 : }
      91             : 
      92             : void
      93          16 : DPAUserObjectBase::prepare()
      94             : {
      95             :   // This object support dynamic changes of number densities
      96             :   // appearance and disappearance of isotopes, and change in
      97             :   // cross sections. Therefore, this function needs to check
      98             :   // and redo a lot of things that would usually be done in
      99             :   // the constructor.
     100             : 
     101             :   // checks on scalar flux & energy groups
     102          16 :   _ng = _scalar_flux.size();
     103          16 :   if (_ng == 0)
     104           0 :     mooseError("The number of provided scalar fluxes is zero. Provide using parameter scalar_flux "
     105             :                "or set programmatically");
     106             : 
     107          16 :   if (_energy_group_boundaries.size() != _ng + 1)
     108           0 :     mooseError("The size of the energy group boundary is ",
     109           0 :                _energy_group_boundaries.size(),
     110             :                " but it must have exactly number of energy groups plus one entry ",
     111           0 :                _ng + 1);
     112             : 
     113         120 :   for (unsigned int j = 1; j < _energy_group_boundaries.size(); ++j)
     114         104 :     if (_energy_group_boundaries[j - 1] <= _energy_group_boundaries[j])
     115           0 :       mooseError("Entries of energy_group_boundaries must be strictly decreasing");
     116             : 
     117             :   // check on A, Z, number densities
     118          16 :   initAZNHelper();
     119             : 
     120             :   // checks on cross sections
     121          16 :   if (_cross_sections.size() != _nr)
     122           0 :     mooseError("Leading dimension of _cross_sections is ",
     123           0 :                _cross_sections.size(),
     124             :                " but should be equal to number of damage reaction types ",
     125           0 :                _nr);
     126          40 :   for (unsigned int i = 0; i < _nr; ++i)
     127             :   {
     128          24 :     if (_cross_sections[i].size() != _atomic_numbers.size())
     129           0 :       mooseError("Second dimension of _cross_sections for index ",
     130             :                  i,
     131             :                  " does not match number of isotope ",
     132           0 :                  _atomic_numbers.size());
     133          64 :     for (unsigned int j = 0; j < _atomic_numbers.size(); ++j)
     134          40 :       if (_cross_sections[i][j].size() != _ng)
     135           0 :         mooseError("Third dimension of _cross_sections for indices ",
     136             :                    i,
     137             :                    " ",
     138             :                    j,
     139             :                    " has ",
     140           0 :                    _cross_sections[i][j].size(),
     141             :                    " entries when number of groups is ",
     142           0 :                    _ng);
     143             :   }
     144             : 
     145             :   // get Q values, need to do this here to be sure that number of isotopes is set
     146          32 :   if (isParamValid("Q"))
     147             :   {
     148          24 :     _q_values = getParam<std::vector<std::vector<Real>>>("Q");
     149           8 :     if (_q_values.size() != _nr)
     150           0 :       paramError("Q", "Leading dimension must be of the same size as reaction types.");
     151          24 :     for (unsigned int j = 0; j < _nr; ++j)
     152          16 :       if (_q_values[j].size() != _atomic_numbers.size())
     153           0 :         paramError("Q", "Second dimension must be of same size as isotopes.");
     154             :   }
     155             :   else
     156             :   {
     157           8 :     _q_values.resize(_nr);
     158          16 :     for (unsigned int j = 0; j < _nr; ++j)
     159           8 :       _q_values[j].resize(_atomic_numbers.size());
     160             :   }
     161          16 : }
     162             : 
     163             : void
     164          32 : DPAUserObjectBase::initAZNHelper()
     165             : {
     166          32 :   unsigned int ns = getAtomicNumbers().size();
     167          32 :   if (ns == 0)
     168           0 :     mooseError("The size of the atomic number array is zero. Set Z parameter or set atomic number "
     169             :                "programmatically");
     170             : 
     171          32 :   if (_mass_numbers.size() != ns)
     172           0 :     mooseError("Size of mass number array does not match size of atomic number array. Size of mass "
     173             :                "numbers ",
     174           0 :                _mass_numbers.size(),
     175             :                ", size of atomic numbers ",
     176             :                ns);
     177             : 
     178          32 :   if (_number_densities.size() != ns)
     179           0 :     mooseError("Size of mass number array does not match size of atomic number array. Size of mass "
     180             :                "numbers ",
     181           0 :                _number_densities.size(),
     182             :                ", size of atomic numbers ",
     183             :                ns);
     184          32 : }
     185             : 
     186             : std::vector<unsigned int>
     187          36 : DPAUserObjectBase::getAtomicNumbers() const
     188             : {
     189             :   // in this kind we need to work a bit since
     190             :   // VPPs are Reals and we need to return unsigned int
     191          36 :   std::vector<unsigned int> Zs(_atomic_numbers.size());
     192          92 :   for (unsigned int j = 0; j < _atomic_numbers.size(); ++j)
     193             :   {
     194          56 :     unsigned int i = static_cast<unsigned int>(_atomic_numbers[j]);
     195          56 :     if (std::abs(_atomic_numbers[j] - i) > 1e-12)
     196           0 :       mooseError("Entry ", j, ":", _atomic_numbers[j], " is not a non-negative integer.");
     197          56 :     Zs[j] = i;
     198             :   }
     199          36 :   return Zs;
     200             : }
     201             : 
     202             : std::vector<Real>
     203           4 : DPAUserObjectBase::getMassNumbers() const
     204             : {
     205           4 :   return _mass_numbers;
     206             : }
     207             : 
     208             : std::vector<Real>
     209           4 : DPAUserObjectBase::getNumberFractions() const
     210             : {
     211             :   // need to normalize here
     212             :   Real sum = 0;
     213          12 :   for (unsigned int j = 0; j < _number_densities.size(); ++j)
     214           8 :     sum += _number_densities[j];
     215           4 :   std::vector<Real> nf(_number_densities.size());
     216          12 :   for (unsigned int j = 0; j < _number_densities.size(); ++j)
     217           8 :     nf[j] = _number_densities[j] / sum;
     218           4 :   return nf;
     219             : }
     220             : 
     221             : Real
     222          28 : DPAUserObjectBase::getMaxEnergy() const
     223             : {
     224             :   // between elastic & inelastic scattering, elastic scattering
     225             :   // allows the largest recoil energy = gamma * E_neutron
     226          28 :   Real max_neutron_energy = _energy_group_boundaries[0];
     227             : 
     228             :   // find maximum gamma
     229             :   Real min_mass = std::numeric_limits<Real>::max();
     230          76 :   for (unsigned int j = 0; j < _mass_numbers.size(); ++j)
     231          48 :     if (min_mass > _mass_numbers[j])
     232             :       min_mass = _mass_numbers[j];
     233          28 :   Real gamma = 4 * min_mass / (min_mass + 1) / (min_mass + 1);
     234          28 :   return gamma * max_neutron_energy;
     235             : }
     236             : 
     237             : bool
     238          16 : DPAUserObjectBase::changed() const
     239             : {
     240             :   // the size could have changed
     241           0 :   if (_atomic_numbers.size() != _atomic_numbers_old.size() ||
     242          16 :       _mass_numbers.size() != _mass_numbers_old.size() ||
     243             :       _number_densities.size() != _number_densities_old.size())
     244             :     return true;
     245             : 
     246             :   // the size is still the same
     247           0 :   for (unsigned int j = 0; j < _atomic_numbers.size(); ++j)
     248           0 :     if (!MooseUtils::absoluteFuzzyEqual(_atomic_numbers[j], _atomic_numbers_old[j], _tol) ||
     249           0 :         !MooseUtils::absoluteFuzzyEqual(_mass_numbers[j], _mass_numbers_old[j], _tol) ||
     250             :         !MooseUtils::absoluteFuzzyEqual(_number_densities[j], _number_densities_old[j], _tol))
     251             :       return true;
     252             : 
     253             :   return false;
     254             : }
     255             : 
     256             : void
     257          16 : DPAUserObjectBase::accumulateDamage()
     258             : {
     259          16 :   if (changed())
     260             :   {
     261          16 :     initAZNHelper();
     262          16 :     onCompositionChanged();
     263             : 
     264             :     // copy over A, Z, N to A, Z, N_old
     265          16 :     _atomic_numbers_old.resize(_atomic_numbers.size());
     266          16 :     _mass_numbers_old.resize(_atomic_numbers.size());
     267          16 :     _number_densities_old.resize(_atomic_numbers.size());
     268          40 :     for (unsigned int j = 0; j < _atomic_numbers.size(); ++j)
     269             :     {
     270          24 :       _atomic_numbers_old[j] = _atomic_numbers[j];
     271          24 :       _mass_numbers_old[j] = _mass_numbers[j];
     272          24 :       _number_densities_old[j] = _number_densities[j];
     273             :     }
     274             :   }
     275             : 
     276             :   // compute total number density
     277             :   Real total_number_density = 0;
     278          40 :   for (unsigned int j = 0; j < _atomic_numbers.size(); ++j)
     279          24 :     total_number_density += _number_densities[j];
     280             : 
     281             :   // damage function have been computed
     282             :   // so now we can start computing dpa
     283          16 :   if (!_is_transient_irradiation)
     284             :   {
     285          16 :     _dpa = 0;
     286             :     _partial_dpa.clear();
     287             :   }
     288             : 
     289          16 :   Real del_t = _is_transient_irradiation ? _dt : _irradiation_time;
     290             : 
     291          40 :   for (unsigned int x = 0; x < _nr; ++x)
     292             :   {
     293         200 :     for (unsigned int g = 0; g < _ng; ++g)
     294             :     {
     295         176 :       Real del_E = _energy_group_boundaries[g] - _energy_group_boundaries[g + 1];
     296             : 
     297         496 :       for (unsigned int i = 0; i < _atomic_numbers.size(); ++i)
     298         928 :         for (unsigned int j = 0; j < _atomic_numbers.size(); ++j)
     299             :         {
     300         608 :           Real v = del_t / del_E * _scalar_flux[g] * _number_densities[i] / total_number_density *
     301         608 :                    _cross_sections[x][i][g] * neutronDamageEfficiency(i, j, g, x);
     302             : 
     303             :           // total dpa is incremented regardless
     304         608 :           _dpa += v;
     305             : 
     306             :           // partial dpa: if the entry exists, add to it, otherwise create it
     307        1216 :           const auto & p = _partial_dpa.find(zaidHelper(_atomic_numbers[i], _mass_numbers[i]));
     308         608 :           if (p == _partial_dpa.end())
     309          24 :             _partial_dpa[zaidHelper(_atomic_numbers[i], _mass_numbers[i])] = v;
     310             :           else
     311         584 :             p->second += v;
     312             :         }
     313             :     }
     314             :   }
     315          16 : }
     316             : 
     317             : Real
     318         608 : DPAUserObjectBase::neutronDamageEfficiency(unsigned int i,
     319             :                                            unsigned int j,
     320             :                                            unsigned int g,
     321             :                                            unsigned int x) const
     322             : {
     323             :   std::vector<Real> points;
     324             :   std::vector<Real> weights;
     325         608 :   PolyatomicDisplacementFunction::gslQuadRule(100, points, weights);
     326             : 
     327        1216 :   if (_neutron_reaction_types[x] == "elastic")
     328             :   {
     329         320 :     Real gamma = 4 * _mass_numbers[i] / (_mass_numbers[i] + 1) / (_mass_numbers[i] + 1);
     330         320 :     Real from_E = _energy_group_boundaries[g + 1];
     331         320 :     Real to_E = _energy_group_boundaries[g];
     332         320 :     Real delta_E = to_E - from_E;
     333             : 
     334             :     // integral over E
     335             :     Real integral = 0;
     336       32320 :     for (unsigned int qp = 0; qp < points.size(); ++qp)
     337             :     {
     338       32000 :       Real energy = 0.5 * (points[qp] + 1) * delta_E + from_E;
     339       32000 :       Real w = 0.5 * weights[qp] * delta_E;
     340       32000 :       integral += w * integralDamageFunction(gamma * energy, i, j) / energy;
     341             :     }
     342             : 
     343         320 :     return integral / gamma;
     344             :   }
     345         576 :   else if (_neutron_reaction_types[x] == "inelastic")
     346             :   {
     347             :     // warn if q value does not make sense
     348         288 :     if (_q_values[x][i] >= 0)
     349         144 :       mooseDoOnce(mooseWarning("Q value is greater or equal to zero for inelastic scattering."));
     350             : 
     351         288 :     Real d = std::abs(_q_values[x][i]) * (_mass_numbers[i] + 1) / _mass_numbers[i];
     352         288 :     Real gamma = 4 * _mass_numbers[i] / (_mass_numbers[i] + 1) / (_mass_numbers[i] + 1);
     353         288 :     Real from_E = _energy_group_boundaries[g + 1];
     354         288 :     Real to_E = _energy_group_boundaries[g];
     355         288 :     Real delta_E = to_E - from_E;
     356             : 
     357             :     // integral over E
     358             :     Real integral = 0;
     359       29088 :     for (unsigned int qp = 0; qp < points.size(); ++qp)
     360             :     {
     361       28800 :       Real energy = 0.5 * (points[qp] + 1) * delta_E + from_E;
     362       28800 :       Real Delta = d / energy;
     363             : 
     364             :       // make sure threshold energy is honored
     365       28800 :       if (Delta > 1)
     366       13440 :         continue;
     367             : 
     368       15360 :       Real sr = std::sqrt(1 - Delta);
     369       15360 :       Real Tmin = gamma / 2 * energy * (1 - sr - 0.5 * Delta);
     370       15360 :       Real Tmax = gamma / 2 * energy * (1 + sr - 0.5 * Delta);
     371       15360 :       Real w = 0.5 * weights[qp] * delta_E;
     372       15360 :       integral += w * (integralDamageFunction(Tmax, i, j) - integralDamageFunction(Tmin, i, j)) /
     373       15360 :                   (energy * sr);
     374             :     }
     375             : 
     376         288 :     return integral / gamma;
     377             :   }
     378             : 
     379           0 :   mooseError("Neutron reaction type not recognized. Should never get here.");
     380             :   return 0;
     381             : }
     382             : 
     383             : std::string
     384         632 : DPAUserObjectBase::zaidHelper(unsigned int Z, Real A) const
     385             : {
     386         632 :   std::stringstream ss;
     387             :   ss << std::setprecision(4) << Z << A;
     388         632 :   return ss.str();
     389         632 : }
     390             : 
     391             : Real
     392           0 : DPAUserObjectBase::getPartialDPA(unsigned int Z, Real A) const
     393             : {
     394           0 :   const auto & p = _partial_dpa.find(zaidHelper(Z, A));
     395           0 :   if (p == _partial_dpa.end())
     396             :     return 0;
     397           0 :   return p->second;
     398             : }
     399             : 
     400             : #endif

Generated by: LCOV version 1.14