LCOV - code coverage report
Current view: top level - src/utils - PolyatomicDisplacementFunctionBase.C (source / functions) Hit Total Coverage
Test: idaholab/magpie: 5710af Lines: 159 162 98.1 %
Date: 2025-07-21 23:34:39 Functions: 20 21 95.2 %
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             : #ifdef GSL_ENABLED
       9             : 
      10             : #include "PolyatomicDisplacementFunctionBase.h"
      11             : 
      12             : // mytrim includes
      13             : #include <mytrim/simconf.h>
      14             : #include <mytrim/ion.h>
      15             : #include <mytrim/element.h>
      16             : 
      17             : // general includes
      18             : #include <assert.h>
      19             : #include <limits>
      20             : 
      21             : // gsl includes
      22             : #include "gsl/gsl_sf_legendre.h"
      23             : #include "gsl/gsl_integration.h"
      24             : 
      25          67 : PolyatomicDisplacementFunctionBase::PolyatomicDisplacementFunctionBase(
      26             :     std::vector<MyTRIM_NS::Element> polyatomic_material,
      27             :     nrt_type damage_function_type,
      28          67 :     std::vector<std::vector<Real>> Ecap)
      29          67 :   : _damage_function_type(damage_function_type),
      30          67 :     _quad_order(4),
      31          67 :     _n_species(polyatomic_material.size()),
      32          67 :     _problem_size(_damage_function_type == ENERGY           ? _n_species
      33          63 :                   : _damage_function_type == NET_DERIVATIVE ? _n_species * _n_species * _n_species
      34             :                                                             : _n_species * _n_species),
      35          67 :     _energy_history({0.0}),
      36         268 :     _displacement_function({std::vector<Real>(_problem_size)})
      37             : {
      38         134 :   _simconf = std::make_unique<MyTRIM_NS::SimconfType>(1);
      39         134 :   _material = std::make_unique<MyTRIM_NS::MaterialBase>(_simconf.get(), 1);
      40         171 :   for (auto & elem : polyatomic_material)
      41             :   {
      42         104 :     _material->_element.push_back(elem);
      43         208 :     _ions.push_back(std::make_unique<MyTRIM_NS::IonBase>(elem._Z, elem._m, 0));
      44             :   }
      45          67 :   _material->prepare();
      46             : 
      47             :   // compute _lambda
      48          67 :   _lambda.resize(_n_species);
      49         171 :   for (unsigned int i = 0; i < _n_species; ++i)
      50             :   {
      51         104 :     _lambda[i].resize(_n_species);
      52         282 :     for (unsigned int j = 0; j < _n_species; ++j)
      53             :     {
      54         178 :       Real Ai = _material->_element[i]._m;
      55         178 :       Real Aj = _material->_element[j]._m;
      56         178 :       _lambda[i][j] = 4.0 * Ai * Aj / (Ai + Aj) / (Ai + Aj);
      57             :     }
      58             :   }
      59             : 
      60          67 :   if (Ecap.size() == 1 && Ecap[0].size() == 0)
      61             :   {
      62             :     // default is Ecap_ij = Ed_j
      63          51 :     _Ecap.resize(_n_species);
      64         123 :     for (unsigned int i = 0; i < _n_species; ++i)
      65             :     {
      66          72 :       _Ecap[i].resize(_n_species);
      67         186 :       for (unsigned int j = 0; j < _n_species; ++j)
      68         114 :         _Ecap[i][j] = _material->_element[j]._Edisp;
      69             :     }
      70             :   }
      71             :   else
      72          16 :     _Ecap = Ecap;
      73             : 
      74             :   // set up the gsl ODE machinery
      75             :   auto func = &PolyatomicDisplacementFunction::gslInterface;
      76          67 :   _sys = {func, NULL, _problem_size, this};
      77          67 :   _ode_driver = gsl_odeiv2_driver_alloc_y_new(&_sys, gsl_odeiv2_step_rk4, 10.0, 1e-2, 1.0e-3);
      78             : 
      79             :   // set up integration rule
      80          67 :   gslQuadRule(_quad_order, _quad_points, _quad_weights);
      81         201 : }
      82             : 
      83          67 : PolyatomicDisplacementFunctionBase::~PolyatomicDisplacementFunctionBase()
      84             : {
      85          67 :   gsl_odeiv2_driver_free(_ode_driver);
      86          67 : }
      87             : 
      88             : int
      89      113653 : PolyatomicDisplacementFunctionBase::gslInterface(Real energy,
      90             :                                                  const Real disp[],
      91             :                                                  Real f[],
      92             :                                                  void * params)
      93             : {
      94             :   (void)disp;
      95             :   PolyatomicDisplacementFunctionBase * padf = (PolyatomicDisplacementFunctionBase *)params;
      96      113653 :   std::vector<Real> rhs = padf->getRHS(energy);
      97      483771 :   for (unsigned int j = 0; j < rhs.size(); ++j)
      98      370118 :     f[j] = rhs[j];
      99      113653 :   return GSL_SUCCESS;
     100             : }
     101             : 
     102             : void
     103        2423 : PolyatomicDisplacementFunctionBase::advanceDisplacements(Real new_energy)
     104             : {
     105        2423 :   if (_energy_history.back() > new_energy)
     106           0 :     return;
     107             : 
     108             :   // add the new energy point to energy history and store old_energy
     109        2423 :   Real old_energy = _energy_history.back();
     110        2423 :   _energy_history.push_back(new_energy);
     111             : 
     112             :   // add new energy step to _displacement_function; initial guess is the old energy
     113        2423 :   _displacement_function.push_back(_displacement_function.back());
     114             : 
     115        2423 :   int status = gsl_odeiv2_driver_apply(
     116             :       _ode_driver, &old_energy, new_energy, _displacement_function.back().data());
     117             : 
     118        2423 :   if (status != GSL_SUCCESS)
     119           0 :     std ::cout << "gsl_odeiv2_driver_apply returned error code  " << status << std::endl;
     120             : }
     121             : 
     122             : void
     123           5 : PolyatomicDisplacementFunctionBase::computeDisplacementFunctionIntegral()
     124             : {
     125             :   // clear the _displacement_function_integral array because this might
     126             :   // have been called before; not the most efficient if the user keeps on calling
     127             :   // this function but they are at their own peril and should know to call it
     128             :   // after finishing the computation of disp function
     129           5 :   _displacement_function_integral.clear();
     130             : 
     131             :   // resize the array; note that initial value is zero (entry for
     132             :   // _displacement_function_integral[0])
     133           5 :   _displacement_function_integral.resize(nEnergySteps(), std::vector<Real>(_problem_size));
     134             : 
     135         256 :   for (unsigned int e = 1; e < nEnergySteps(); ++e)
     136             :   {
     137             :     // initialize the integral to the integral of the previous step
     138         251 :     _displacement_function_integral[e] = _displacement_function_integral[e - 1];
     139             : 
     140             :     // set energy points for integration
     141             :     Real lower = energyPoint(e - 1);
     142             :     Real upper = energyPoint(e);
     143         251 :     Real f = 0.5 * (upper - lower);
     144        1255 :     for (unsigned int qp = 0; qp < _quad_order; ++qp)
     145             :     {
     146        1004 :       Real energy = f * (_quad_points[qp] + 1) + lower;
     147        1004 :       Real w = f * _quad_weights[qp];
     148             : 
     149        5020 :       for (unsigned int n = 0; n < _problem_size; ++n)
     150        4016 :         _displacement_function_integral[e][n] += w * linearInterpolationFromFlatIndex(energy, n);
     151             :     }
     152             :   }
     153           5 : }
     154             : 
     155             : void
     156      218403 : PolyatomicDisplacementFunctionBase::gslQuadRule(unsigned int quad_order,
     157             :                                                 std::vector<Real> & quad_points,
     158             :                                                 std::vector<Real> & quad_weights)
     159             : {
     160             :   // set up integration rule
     161      218403 :   auto * qp_table = gsl_integration_glfixed_table_alloc(quad_order);
     162      218403 :   quad_points.resize(quad_order);
     163      218403 :   quad_weights.resize(quad_order);
     164    22052271 :   for (std::size_t j = 0; j < quad_order; ++j)
     165             :   {
     166             :     Real point, weight;
     167    21833868 :     gsl_integration_glfixed_point(-1.0, 1.0, j, &point, &weight, qp_table);
     168    21833868 :     quad_points[j] = point;
     169    21833868 :     quad_weights[j] = weight;
     170             :   }
     171      218403 :   gsl_integration_glfixed_table_free(qp_table);
     172      218403 : }
     173             : 
     174             : Real
     175      189656 : PolyatomicDisplacementFunctionBase::stoppingPower(unsigned int species, Real energy)
     176             : {
     177             :   assert(species < _n_species);
     178      189656 :   _ions[species]->_E = energy;
     179             :   // need to divide by atomic density because we need Se for PC equations
     180             :   // units are eV * Angstrom**2
     181      189656 :   return _material->getrstop(_ions[species].get()) / _material->_arho;
     182             : }
     183             : 
     184             : Real
     185      135890 : PolyatomicDisplacementFunctionBase::stoppingPowerDerivative(unsigned int species,
     186             :                                                             unsigned int changing_species,
     187             :                                                             Real energy)
     188             : {
     189      135890 :   _ions[species]->_E = energy;
     190      135890 :   return _material->getDrstopDcomp(_ions[species].get(), _material->_element[changing_species]);
     191             : }
     192             : 
     193             : /**
     194             :  * The scattering cross section is computed using Lindhard's universal formula
     195             :  * with f(xi) evaluated using the Thomas Fermi potential. Units are Angstrom**2 / eV
     196             :  */
     197             : Real
     198   152233696 : PolyatomicDisplacementFunctionBase::scatteringCrossSection(unsigned int i,
     199             :                                                            unsigned int j,
     200             :                                                            Real energy,
     201             :                                                            Real recoil_energy) const
     202             : {
     203             :   assert(i < _n_species);
     204             :   assert(j < _n_species);
     205             : 
     206             :   // get the current A & Z for projectile: i and target: j
     207   152233696 :   Real Ai = _material->_element[i]._m;
     208   152233696 :   Real Aj = _material->_element[j]._m;
     209   152233696 :   Real Zi = _material->_element[i]._Z;
     210   152233696 :   Real Zj = _material->_element[j]._Z;
     211             : 
     212             :   // Z only appears as 1/3 power, so it's better to apply the 1/3 power here to get a sqrt
     213   152233696 :   Real Z = std::sqrt(std::pow(Zi, 2.0 / 3.0) + std::pow(Zj, 2.0 / 3.0));
     214   152233696 :   Real a = 0.8853 * _abohr / Z;
     215             : 
     216             :   // compute El and Tm
     217   152233696 :   Real Tm = _lambda[i][j] * energy;
     218   152233696 :   Real El = 30.7514664 * Zi * Zj * Z * (Ai + Aj) / Aj;
     219             : 
     220             :   // compute sqrt(t) and then the cross section using Lindhard's formula
     221   152233696 :   Real sqrt_t = std::sqrt((energy * energy / El / El) * recoil_energy / Tm);
     222   152233696 :   return 0.5 * universalF(sqrt_t) / recoil_energy / sqrt_t * M_PI * a * a;
     223             : }
     224             : 
     225             : Real
     226   152233696 : PolyatomicDisplacementFunctionBase::universalF(Real xi) const
     227             : {
     228             :   Real lp = 1.309;
     229   152233696 :   return lp * std::pow(xi, 1.0 / 3.0) *
     230   152233696 :          std::pow(1.0 + std::pow(2.0 * lp * std::pow(xi, 4.0 / 3.0), 2.0 / 3.0), -1.5);
     231             : }
     232             : 
     233             : unsigned int
     234        8916 : PolyatomicDisplacementFunctionBase::findSpeciesIndex(int atomic_number, Real mass_number) const
     235             : {
     236        9822 :   for (unsigned int j = 0; j < _n_species; ++j)
     237        9822 :     if (atomic_number == _material->_element[j]._Z &&
     238        8916 :         std::abs(mass_number - _material->_element[j]._m) < 1.0e-10)
     239        8916 :       return j;
     240             :   return libMesh::invalid_uint;
     241             : }
     242             : 
     243             : Real
     244   105367424 : PolyatomicDisplacementFunctionBase::displacementProbability(unsigned int k, Real energy) const
     245             : {
     246   105367424 :   return energy < _material->_element[k]._Edisp ? 0 : 1;
     247             : }
     248             : 
     249             : Real
     250    54220888 : PolyatomicDisplacementFunctionBase::nonCaptureProbability(unsigned int i,
     251             :                                                           unsigned int k,
     252             :                                                           Real energy,
     253             :                                                           Real recoil_energy) const
     254             : {
     255             :   // Parkin & Coulter, JNM 101, (1981)
     256             :   return 1 -
     257    54220888 :          displacementProbability(k, recoil_energy) * (energy - recoil_energy < _Ecap[i][k] ? 1 : 0);
     258             : }
     259             : 
     260             : unsigned int
     261   148318925 : PolyatomicDisplacementFunctionBase::energyIndex(Real energy) const
     262             : {
     263   148318925 :   return energy >= _energy_history.back()
     264             :              ? nEnergySteps() - 1
     265   148273255 :              : std::distance(
     266             :                    _energy_history.begin(),
     267   148318925 :                    std::upper_bound(_energy_history.begin(), _energy_history.end(), energy));
     268             : }
     269             : 
     270             : /**
     271             :  * weightedScatteringIntegral computes the integral:
     272             :  * int_0^{energy_limit} d(sigma_ij) / dT T dT
     273             :  */
     274             : Real
     275      908364 : PolyatomicDisplacementFunctionBase::weightedScatteringIntegral(Real energy,
     276             :                                                                Real energy_limit,
     277             :                                                                unsigned int i,
     278             :                                                                unsigned int j) const
     279             : {
     280             :   // uses the t -> 0 limit to WSS form of Lindhard's universal cross section with Thomas-Fermi
     281             :   // potential for recoil_energy < _asymptotic_threshold
     282      908460 :   Real limit = std::min(_asymptotic_threshold, energy_limit);
     283      908364 :   Real Mi = _material->_element[i]._m;
     284      908364 :   Real Mj = _material->_element[j]._m;
     285      908364 :   Real Zi = _material->_element[i]._Z;
     286      908364 :   Real Zj = _material->_element[j]._Z;
     287      908364 :   Real Z = std::pow(std::pow(Zi, 2.0 / 3.0) + std::pow(Zj, 2.0 / 3.0), 1.5);
     288      908364 :   Real a = _abohr * 0.8853 * std::pow(Z, -1.0 / 3.0);
     289      908364 :   Real alpha_ij = 0.5 * 1.309 * M_PI * a * a * std::pow(Mi / Mj, 1.0 / 3.0) *
     290      908364 :                   std::pow(Zi * Zj * 61.5038 * std::pow(Z, 1.0 / 3.0), 2.0 / 3.0);
     291      908364 :   Real integral = 1.5 * std::pow(limit, 2.0 / 3.0) * alpha_ij * std::pow(energy, -1.0 / 3.0);
     292             : 
     293      908364 :   if (energy_limit <= _asymptotic_threshold)
     294             :     return integral;
     295             : 
     296             :   // numerical integration from _asymptotic_threshold to energy_limit
     297       26784 :   Real spacing = std::max(energy_limit / _asymptotic_threshold /
     298       13392 :                               std::ceil(energy_limit / _asymptotic_threshold / 1.05),
     299       13392 :                           1.02);
     300             : 
     301       13392 :   std::vector<Real> energies = {_asymptotic_threshold};
     302       13392 :   Real current_energy = _asymptotic_threshold;
     303             :   for (;;)
     304             :   {
     305     1505088 :     current_energy *= spacing;
     306     1505088 :     if (current_energy >= energy_limit)
     307             :     {
     308       13392 :       energies.push_back(energy_limit);
     309             :       break;
     310             :     }
     311     1491696 :     energies.push_back(current_energy);
     312             :   }
     313             : 
     314     1518480 :   for (unsigned int l = 1; l < energies.size(); ++l)
     315             :   {
     316     1505088 :     Real lower = energies[l - 1];
     317     1505088 :     Real upper = energies[l];
     318     1505088 :     Real scale = 0.5 * (upper - lower);
     319     7525440 :     for (unsigned int qp = 0; qp < _quad_order; ++qp)
     320             :     {
     321     6020352 :       Real recoil_energy = scale * (_quad_points[qp] + 1) + lower;
     322     6020352 :       integral += scale * _quad_weights[qp] * scatteringCrossSection(i, j, energy, recoil_energy) *
     323             :                   recoil_energy;
     324             :     }
     325             :   }
     326             :   return integral;
     327             : }
     328             : 
     329             : Real
     330   148149258 : PolyatomicDisplacementFunctionBase::linearInterpolation(Real energy,
     331             :                                                         unsigned int i,
     332             :                                                         unsigned int j,
     333             :                                                         unsigned int l) const
     334             : {
     335   148149258 :   unsigned int energy_index = energyIndex(energy);
     336   148149258 :   unsigned int index = mapIndex(i, j, l);
     337   148149258 :   if (energy_index == 0)
     338     4556883 :     return _displacement_function[0][index];
     339             : 
     340   143592375 :   return linearInterpolationHelper(energy, energy_index, index);
     341             : }
     342             : 
     343             : Real
     344        4016 : PolyatomicDisplacementFunctionBase::linearInterpolationFromFlatIndex(Real energy,
     345             :                                                                      unsigned int index) const
     346             : {
     347        4016 :   unsigned int energy_index = energyIndex(energy);
     348        4016 :   if (energy_index == 0)
     349           0 :     return _displacement_function[0][index];
     350             : 
     351        4016 :   return linearInterpolationHelper(energy, energy_index, index);
     352             : }
     353             : 
     354             : Real
     355   143596391 : PolyatomicDisplacementFunctionBase::linearInterpolationHelper(Real energy,
     356             :                                                               unsigned int energy_index,
     357             :                                                               unsigned int index) const
     358             : {
     359             :   // linear interpolation
     360   143596391 :   Real e1 = _energy_history[energy_index - 1];
     361   143596391 :   Real e2 = _energy_history[energy_index];
     362   143596391 :   Real v1 = _displacement_function[energy_index - 1][index];
     363   143596391 :   Real v2 = _displacement_function[energy_index][index];
     364             : 
     365   143596391 :   return v1 + (energy - e1) / (e2 - e1) * (v2 - v1);
     366             : }
     367             : 
     368             : Real
     369       29761 : PolyatomicDisplacementFunctionBase::linearInterpolationIntegralDamageFunction(Real energy,
     370             :                                                                               unsigned int i,
     371             :                                                                               unsigned int j,
     372             :                                                                               unsigned int l) const
     373             : {
     374       29761 :   unsigned int energy_index = energyIndex(energy);
     375       29761 :   unsigned int index = mapIndex(i, j, l);
     376             : 
     377       29761 :   if (energy_index == 0)
     378       16112 :     return _displacement_function_integral[0][index];
     379             : 
     380             :   // linear interpolation
     381       13649 :   Real e1 = _energy_history[energy_index - 1];
     382       13649 :   Real e2 = _energy_history[energy_index];
     383       13649 :   Real v1 = _displacement_function_integral[energy_index - 1][index];
     384       13649 :   Real v2 = _displacement_function_integral[energy_index][index];
     385             : 
     386       13649 :   return v1 + (energy - e1) / (e2 - e1) * (v2 - v1);
     387             : }
     388             : 
     389             : unsigned int
     390   148514246 : PolyatomicDisplacementFunctionBase::mapIndex(unsigned int i, unsigned int j, unsigned int l) const
     391             : {
     392             :   assert(_n_indices > 0);
     393             :   assert(j == 0 || _n_indices > 1);
     394             :   assert(l == 0 || _n_indices > 2);
     395   148514246 :   return i + j * _n_species + l * _n_species * _n_species;
     396             : };
     397             : 
     398             : #endif

Generated by: LCOV version 1.14