LCOV - code coverage report
Current view: top level - src/utils - MastodonUtils.C (source / functions) Hit Total Coverage
Test: idaholab/mastodon: 55510a Lines: 185 197 93.9 %
Date: 2025-08-26 23:09:31 Functions: 19 19 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // System includes
       2             : #include <glob.h>
       3             : 
       4             : // STL includes
       5             : #include <cmath>
       6             : #include <numeric>
       7             : 
       8             : // MOOSE contrib
       9             : #include "pcrecpp.h"
      10             : #include "tinydir.h"
      11             : #include "MooseUtils.h"
      12             : #include "MooseRandom.h"
      13             : #include "Lognormal.h"
      14             : #include "Normal.h"
      15             : 
      16             : // MASTODON includes
      17             : #include "MastodonUtils.h"
      18             : 
      19             : std::vector<std::vector<Real>>
      20         354 : MastodonUtils::responseSpectrum(const Real & freq_start,
      21             :                                 const Real & freq_end,
      22             :                                 const unsigned int & freq_num,
      23             :                                 const std::vector<Real> & history_acc,
      24             :                                 const Real & xi,
      25             :                                 const Real & reg_dt)
      26             : {
      27             :   std::vector<Real> freq_vec, per_vec, aspec_vec, vspec_vec, dspec_vec;
      28             :   Real logdf, om_n, om_d, dt2, dis1, vel1, acc1, dis2, vel2, acc2, pdmax, kd;
      29      125208 :   for (std::size_t n = 0; n < freq_num; ++n)
      30             :   {
      31             :     // Building the frequency vector and the period vector.
      32             :     // Frequencies are distributed uniformly in the log scale.
      33             :     // Periods are calculated as inverse of frequency
      34      124854 :     logdf = (std::log10(freq_end) - std::log10(freq_start)) / (freq_num - 1);
      35      124854 :     freq_vec.push_back(pow(10.0, std::log10(freq_start) + n * logdf));
      36      124854 :     per_vec.push_back(1.0 / freq_vec[n]);
      37      124854 :     om_n = 2.0 * 3.141593 * freq_vec[n]; // om_n = 2*pi*f
      38      124854 :     om_d = om_n * xi;
      39             :     dis1 = 0.0;
      40             :     vel1 = 0.0;
      41      124854 :     dt2 = reg_dt * reg_dt;
      42      124854 :     pdmax = 0.0;
      43      124854 :     acc1 = -1.0 * history_acc[0] - 2.0 * om_d * vel1 - om_n * om_n * dis1;
      44      124854 :     kd = 1.0 + om_d * reg_dt + dt2 * om_n * om_n / 4.0;
      45     8010876 :     for (std::size_t j = 0; j < history_acc.size(); ++j)
      46             :     {
      47     7886022 :       dis2 = ((1.0 + om_d * reg_dt) * dis1 + (reg_dt + 1.0 / 2.0 * om_d * dt2) * vel1 +
      48     7886022 :               dt2 / 4.0 * acc1 - dt2 / 4.0 * history_acc[j]) /
      49             :              kd;
      50     7886022 :       acc2 = 4.0 / dt2 * (dis2 - dis1) - 4.0 / reg_dt * vel1 - acc1;
      51     7886022 :       vel2 = vel1 + reg_dt / 2.0 * (acc1 + acc2);
      52     7886022 :       if (std::abs(dis2) > pdmax)
      53     3448719 :         pdmax = std::abs(dis2);
      54             :       dis1 = dis2;
      55             :       vel1 = vel2;
      56             :       acc1 = acc2;
      57             :     }
      58      124854 :     dspec_vec.push_back(pdmax);
      59      124854 :     vspec_vec.push_back(pdmax * om_n);
      60      124854 :     aspec_vec.push_back(pdmax * om_n * om_n);
      61             :   }
      62        2478 :   return {freq_vec, per_vec, dspec_vec, vspec_vec, aspec_vec};
      63         708 : }
      64             : 
      65             : std::vector<std::vector<Real>>
      66         354 : MastodonUtils::regularize(const std::vector<Real> & history_acc,
      67             :                           const std::vector<Real> & history_time,
      68             :                           const Real & reg_dt)
      69             : {
      70             :   std::vector<Real> reg_acc;
      71             :   std::vector<Real> reg_tme;
      72         354 :   Real cur_tme = history_time[0];
      73             :   Real cur_acc;
      74       28134 :   for (std::size_t i = 0; i < history_time.size() - 1; ++i)
      75             :   {
      76       55782 :     while (cur_tme >= history_time[i] && cur_tme <= history_time[i + 1])
      77             :     {
      78       28002 :       cur_acc = history_acc[i] + (cur_tme - history_time[i]) /
      79       28002 :                                      (history_time[i + 1] - history_time[i]) *
      80       28002 :                                      (history_acc[i + 1] - history_acc[i]);
      81       28002 :       reg_acc.push_back(cur_acc);
      82       28002 :       reg_tme.push_back(cur_tme);
      83       28002 :       cur_tme += reg_dt;
      84             :     }
      85             :   }
      86        1416 :   return {reg_tme, reg_acc};
      87         708 : }
      88             : 
      89             : bool
      90         274 : MastodonUtils::checkEqualSize(const std::vector<std::vector<Real>> & vectors)
      91             : {
      92         553 :   for (const auto & v : vectors)
      93             :   {
      94         280 :     if (v.size() != vectors[0].size())
      95             :       return false;
      96             :   }
      97             :   return true;
      98             : }
      99             : 
     100             : bool
     101          17 : MastodonUtils::checkEqualSize(const std::vector<std::vector<Real> *> & vectors)
     102             : {
     103          68 :   for (std::size_t i = 0; i < vectors.size(); i++)
     104             :   {
     105          51 :     if ((*vectors[i]).size() != (*vectors[0]).size())
     106             :       return false;
     107             :   }
     108             :   return true;
     109             : }
     110             : 
     111             : bool
     112         249 : MastodonUtils::checkEqual(const std::vector<Real> & vector1,
     113             :                           const std::vector<Real> & vector2,
     114             :                           const Real percent_error)
     115             : {
     116         249 :   if (vector1.size() != vector2.size())
     117             :     return false;
     118         952 :   for (std::size_t i = 0; i < vector1.size(); ++i)
     119             :   {
     120         714 :     if (!MooseUtils::absoluteFuzzyEqual(
     121         714 :             vector1[i], vector2[i], std::abs(vector1[i] * percent_error / 100)))
     122             :       return false;
     123             :   }
     124             :   return true;
     125             : }
     126             : 
     127             : bool
     128      811779 : MastodonUtils::isNegativeOrZero(const std::vector<Real> & vector)
     129             : {
     130     4418932 :   for (const auto & element : vector)
     131     3607154 :     if (element <= 0)
     132             :       return true;
     133             :   return false;
     134             : }
     135             : 
     136             : Real
     137        1553 : MastodonUtils::mean(const std::vector<Real> & vector)
     138             : {
     139             :   Real sum = std::accumulate(vector.begin(), vector.end(), 0.0);
     140        1553 :   return sum / vector.size();
     141             : }
     142             : 
     143             : std::vector<Real>
     144          17 : MastodonUtils::mean(const std::vector<std::vector<Real> *> & history_acc)
     145             : {
     146             :   // bool check = ;
     147          17 :   if (MastodonUtils::checkEqualSize(history_acc) < 1)
     148           0 :     mooseError("Input vectors are all not of equal size.");
     149             :   else
     150             :   {
     151             :     std::vector<Real> mean_acc;
     152          17 :     mean_acc.resize((*history_acc[0]).size());
     153             :     Real tmp_var = 0;
     154         118 :     for (std::size_t i = 0; i < (*history_acc[0]).size(); i++)
     155             :     {
     156         404 :       for (std::size_t j = 0; j < history_acc.size(); j++)
     157             :       {
     158         303 :         tmp_var = tmp_var + (*history_acc[j])[i];
     159             :       }
     160         101 :       mean_acc[i] = tmp_var / history_acc.size();
     161             :       tmp_var = 0;
     162             :     }
     163          17 :     return mean_acc;
     164          17 :   }
     165             : }
     166             : 
     167             : Real
     168          46 : MastodonUtils::median(const std::vector<Real> & vector, const std::string & interpolation)
     169             : {
     170          46 :   std::vector<Real> sorted_vector = vector;
     171          46 :   std::sort(sorted_vector.begin(), sorted_vector.end());
     172          46 :   if (sorted_vector.size() % 2 != 0)
     173          25 :     return sorted_vector[(sorted_vector.size() - 1) / 2];
     174          21 :   else if (interpolation == "linear")
     175          19 :     return (sorted_vector[sorted_vector.size() / 2] + sorted_vector[sorted_vector.size() / 2 - 1]) /
     176          19 :            2.0;
     177           2 :   else if (interpolation == "lower")
     178           1 :     return sorted_vector[sorted_vector.size() / 2 - 1];
     179           1 :   else if (interpolation == "higher")
     180           1 :     return sorted_vector[sorted_vector.size() / 2];
     181             :   else
     182           0 :     mooseError("Invalid interpolation type in median calculation.");
     183          46 : }
     184             : 
     185             : Real
     186           6 : MastodonUtils::percentile(const std::vector<Real> & vector,
     187             :                           const Real & percent,
     188             :                           const std::string & interpolation)
     189             : {
     190           6 :   std::vector<Real> sorted_vector = vector;
     191           6 :   std::sort(sorted_vector.begin(), sorted_vector.end());
     192           6 :   if (percent < 0.0 || percent > 100.0)
     193           0 :     mooseError("Percent should be between 0 and 100.\n");
     194             :   std::size_t low_index;
     195           6 :   if (floor(percent / 100 * sorted_vector.size()) == 0)
     196             :     low_index = 0;
     197             :   else
     198           5 :     low_index = floor(percent / 100 * sorted_vector.size()) - 1;
     199           6 :   if (interpolation == "lower")
     200           1 :     return sorted_vector[low_index];
     201           5 :   else if (interpolation == "higher")
     202           1 :     return sorted_vector[low_index + 1];
     203           4 :   else if (interpolation == "linear")
     204             :   {
     205           4 :     Real index_remainder = fmod(percent / 100.0 * sorted_vector.size(), 1.0);
     206             :     Real percentile_value =
     207           4 :         sorted_vector[low_index] +
     208           4 :         index_remainder * (sorted_vector[low_index + 1] - sorted_vector[low_index]);
     209           4 :     return percentile_value;
     210             :   }
     211             :   else
     212           0 :     mooseError("Invalid interpolation type in percentile calculation.");
     213           6 : }
     214             : 
     215             : Real
     216          44 : MastodonUtils::standardDeviation(const std::vector<Real> & vector)
     217             : {
     218             :   Real sum = 0;
     219         820 :   for (std::size_t i = 0; i < vector.size(); i++)
     220             :   {
     221         776 :     sum += (vector[i] - MastodonUtils::mean(vector)) * (vector[i] - MastodonUtils::mean(vector));
     222             :   }
     223          44 :   Real sigma = sqrt(sum / (vector.size() - 1));
     224          44 :   return sigma;
     225             : }
     226             : 
     227             : Real
     228          43 : MastodonUtils::lognormalStandardDeviation(const std::vector<Real> & vector)
     229             : {
     230          43 :   if (MastodonUtils::isNegativeOrZero(vector))
     231           0 :     mooseError("One or more elements in the sample for calculating beta are non positive.\n");
     232             :   std::vector<Real> logvector;
     233         809 :   for (const auto & element : vector)
     234         766 :     logvector.push_back(log(element));
     235          86 :   return MastodonUtils::standardDeviation(logvector);
     236          43 : }
     237             : 
     238             : std::string
     239         218 : MastodonUtils::zeropad(const unsigned int n, const unsigned int n_tot)
     240             : {
     241         218 :   std::size_t zeropadsize = (std::to_string(n_tot)).length() - (std::to_string(n)).length();
     242         218 :   std::string pad = "";
     243         403 :   for (std::size_t i = 0; i < zeropadsize; i++)
     244             :   {
     245             :     pad += "0";
     246             :   }
     247         436 :   return pad + std::to_string(n);
     248             : }
     249             : 
     250             : std::vector<std::string>
     251          22 : MastodonUtils::glob(const std::string & pattern)
     252             : {
     253             :   glob_t glob_result;
     254          22 :   glob(pattern.c_str(), GLOB_TILDE, NULL, &glob_result);
     255             :   std::vector<string> output;
     256         198 :   for (unsigned int i = 0; i < glob_result.gl_pathc; ++i)
     257         352 :     output.push_back(string(glob_result.gl_pathv[i]));
     258          22 :   globfree(&glob_result);
     259          22 :   return output;
     260           0 : }
     261             : 
     262             : std::vector<Real>
     263        1027 : MastodonUtils::adjust(const std::vector<Real> & input, const Real & scale, const Real & offset)
     264             : {
     265        1027 :   std::vector<Real> output(input.size());
     266       11156 :   for (std::size_t i = 0; i < input.size(); ++i)
     267       10129 :     output[i] = scale * input[i] + offset;
     268        1027 :   return output;
     269             : }
     270             : 
     271             : std::vector<Real>
     272          16 : MastodonUtils::log10(const std::vector<Real> & input)
     273             : {
     274          16 :   std::vector<Real> output(input.size());
     275          88 :   for (std::size_t i = 0; i < input.size(); ++i)
     276             :   {
     277          74 :     if (input[i] <= 0)
     278           2 :       ::mooseError("Cannot take the log of ", input[i], ".");
     279          72 :     output[i] = std::log10(input[i]);
     280             :   }
     281          14 :   return output;
     282           2 : }
     283             : 
     284             : Real
     285          43 : MastodonUtils::greaterProbability(Real demand_median,
     286             :                                   Real demand_scale,
     287             :                                   Real capacity_median,
     288             :                                   Real capacity_scale)
     289             : {
     290             :   // Need P(D-C > 0) = P(lnD-lnC > 0)
     291             :   // lnD and lnC are Normally distributed
     292             :   // Evaluating the mean and std dev of lnD-lnC as
     293             :   // mean = mean(lnD) - mean(lnC) = ln(thetaD) - ln(thetaC)
     294             :   // std dev = srss(betaD, betaC)
     295             :   // now, greater probability = 1 - CDF(0.0)
     296          43 :   Real greater_prob_location = log(demand_median) - log(capacity_median);
     297             :   Real greater_prob_scale =
     298          43 :       std::sqrt(demand_scale * demand_scale + capacity_scale * capacity_scale);
     299          43 :   return (1.0 - Normal::cdf(0.0, greater_prob_location, greater_prob_scale));
     300             : }
     301             : 
     302             : Real
     303      405597 : MastodonUtils::calcLogLikelihood(const std::vector<Real> & im,
     304             :                                  const std::vector<Real> & pf,
     305             :                                  const Real & loc,
     306             :                                  const Real & sca,
     307             :                                  const unsigned int & n)
     308             : {
     309             :   // error check
     310      405597 :   if (im.size() != pf.size())
     311           0 :     mooseError("While calculating loglikelihood, intensity measure and failure probability vectors "
     312             :                "should be of the same size.");
     313      405597 :   if (MastodonUtils::isNegativeOrZero(im) || MastodonUtils::isNegativeOrZero(pf))
     314           0 :     mooseError("While calculating loglikelihood, intensity measure or failure probability has a "
     315             :                "non-positive value.");
     316     2207988 :   for (std::size_t i = 0; i < pf.size(); ++i)
     317             :   {
     318     1802391 :     if (pf[i] > 1.0)
     319           0 :       mooseError("While calculating loglikelihood, a value greater than 1 is found in the failure "
     320             :                  "probability vector.");
     321             :   }
     322      405597 :   if (sca <= 0)
     323           0 :     mooseError("While calculating loglikelihood, scale parameter should be positive.");
     324      405597 :   if (loc <= 0)
     325           0 :     mooseError("While calculating loglikelihood, location parameter should be positive.");
     326             : 
     327             :   // Calculating the likelihood at each IM value
     328             :   Real loglikelihood = 0;
     329     2207988 :   for (std::size_t i = 0; i < im.size(); ++i)
     330             :   {
     331             :     // Binomial pdf in the below calculation is made in the log scale due to
     332             :     // numerical errors (+inf) in calculating nCr for large trial sizes (n).
     333             :     // Calculating log10(nCr)
     334     1802391 :     unsigned int r = floor(n * pf[i]);
     335             :     Real log10_nCr = 0.0;
     336   259605552 :     for (std::size_t k = 1; k <= r; k++)
     337   257803161 :       log10_nCr += std::log10(n - r + k) - std::log10(k);
     338             : 
     339     1802391 :     Real p = Lognormal::cdf(im[i], log(loc), sca);
     340     1802391 :     if (p == 0)
     341             :       p = std::numeric_limits<Real>::min();
     342             : 
     343             :     // calculating sum of loglikelihoods. Each loglikelihood is the log(binomial pdf),
     344             :     // which ends up to be the summation below.
     345     1802391 :     loglikelihood += log10_nCr + r * std::log10(p) + (n - r) * std::log10(1.0 - p);
     346             :   }
     347      405597 :   return loglikelihood;
     348             : }
     349             : 
     350             : std::vector<Real>
     351          11 : MastodonUtils::maximizeLogLikelihood(const std::vector<Real> & im,
     352             :                                      const std::vector<Real> & pf,
     353             :                                      const std::vector<Real> & loc_space,
     354             :                                      const std::vector<Real> & sca_space,
     355             :                                      const unsigned int & n,
     356             :                                      const bool & brute_force,
     357             :                                      const Real tolerance,
     358             :                                      const Real gamma,
     359             :                                      const int num_rnd,
     360             :                                      const int seed)
     361             : {
     362          11 :   std::vector<Real> params_return = {0, 0};
     363          11 :   if (brute_force)
     364             :   {
     365           4 :     Real loglikelihoodmax = MastodonUtils::calcLogLikelihood(im, pf, loc_space[0], sca_space[0], n);
     366        1547 :     for (Real loc = loc_space[0]; loc < loc_space[1]; loc += 0.01)
     367      304846 :       for (Real sca = sca_space[0]; sca < sca_space[1]; sca += 0.01)
     368      303303 :         if (MastodonUtils::calcLogLikelihood(im, pf, loc, sca, n) >= loglikelihoodmax)
     369             :         {
     370         776 :           loglikelihoodmax = MastodonUtils::calcLogLikelihood(im, pf, loc, sca, n);
     371         776 :           params_return = {loc, sca};
     372             :         }
     373             :   }
     374             :   else
     375             :   // Using Randomized Gradient Descent to maximize likelihood (as defined above) or minimize
     376             :   // -loglikelihood
     377             :   {
     378             :     Real loc_rand;
     379             :     Real sca_rand;
     380           7 :     std::vector<Real> params_now = {0, 0};     // Initializing new parameter vector here.
     381           7 :     std::vector<Real> params_before = {0, 0};  // Initializing old parameter vector here.
     382           7 :     std::vector<Real> gradient_now = {-1, -2}; // Initializing the gradient vector here.
     383             :     // This variable will get updated within each iteration of the Gradient Descent algorithm.
     384             :     Real dparam =
     385             :         0.01; // Initilizing an arbitrarily small deviation to the random seed parameter vector.
     386             :               // Note that Gradient Descent algorithm requires two likelihood values from two seeds.
     387             :     Real likelihood_now;           // Initializing a variable.
     388             :     Real likelihood_before;        // Initializing a variable.
     389             :     Real likelihood_base = std::numeric_limits<Real>::max();
     390             :     // This variable will get updated if a parameter vector has greater likelihood.
     391           7 :     MooseRandom::seed(seed); // Setting up the random number generator.
     392       18341 :     for (int index = 0; index < num_rnd; index++)
     393             :     {
     394       18334 :       loc_rand = loc_space[0] + (loc_space[1] - loc_space[0]) * MooseRandom::rand();
     395       18334 :       sca_rand = sca_space[0] + (sca_space[1] - sca_space[0]) * MooseRandom::rand();
     396       18334 :       likelihood_now = -MastodonUtils::calcLogLikelihood(im, pf, loc_rand, sca_rand, n);
     397       18334 :       likelihood_before =
     398       18334 :           -MastodonUtils::calcLogLikelihood(im, pf, loc_rand + dparam, sca_rand + dparam, n);
     399       18334 :       params_now = {loc_rand, sca_rand};
     400       18334 :       params_before = {loc_rand + dparam, sca_rand + dparam};
     401       18334 :       if (likelihood_now > likelihood_before) // Swap params and likelihoods before and now
     402             :       {
     403       10406 :         likelihood_now =
     404       10406 :             -MastodonUtils::calcLogLikelihood(im, pf, loc_rand + dparam, sca_rand + dparam, n);
     405       10406 :         likelihood_before = -MastodonUtils::calcLogLikelihood(im, pf, loc_rand, sca_rand, n);
     406       10406 :         params_now = {loc_rand + dparam, sca_rand + dparam};
     407       10406 :         params_before = {loc_rand, sca_rand};
     408             :       }
     409       62358 :       while (std::abs(likelihood_now - likelihood_before) > tolerance)
     410             :       {
     411       56258 :         gradient_now[0] = (likelihood_now - likelihood_before) / (params_now[0] - params_before[0]);
     412       56258 :         gradient_now[1] = (likelihood_now - likelihood_before) / (params_now[1] - params_before[1]);
     413             :         likelihood_before = likelihood_now;
     414       56258 :         params_now[0] = (params_now[0] - gamma * gradient_now[0]);
     415       56258 :         params_now[1] = (params_now[1] - gamma * gradient_now[1]);
     416       52110 :         if ((params_now[0] < loc_space[0]) || (params_now[0] > loc_space[1]) ||
     417      101739 :             (params_now[1] < sca_space[0]) || (params_now[1] > sca_space[1]))
     418             :         {
     419       12234 :           index = index - 1;
     420       12234 :           break;
     421             :         }
     422             :         else
     423             :           {
     424       44024 :             likelihood_now =
     425       44024 :                 -MastodonUtils::calcLogLikelihood(im, pf, params_now[0], params_now[1], n);
     426       44024 :             if (likelihood_now < likelihood_base)
     427             :             {
     428             :               likelihood_base = likelihood_now;
     429         128 :               params_return = params_now;
     430             :             }
     431             :           }
     432             :         }
     433             :     }
     434           7 :   }
     435          11 :   return params_return;
     436           0 : }

Generated by: LCOV version 1.14