LCOV - code coverage report
Current view: top level - src/utils - MultiDimensionalInterpolation.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 90 113 79.6 %
Date: 2025-07-17 01:28:37 Functions: 7 21 33.3 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "MultiDimensionalInterpolation.h"
      11             : 
      12             : #include <cassert>
      13             : #include <fstream>
      14             : #include <stdexcept>
      15             : 
      16             : template <typename T>
      17           4 : MultiDimensionalInterpolationTempl<T>::MultiDimensionalInterpolationTempl(
      18             :     const std::vector<std::vector<Real>> & base_points, const MultiIndex<Real> & data)
      19           4 :   : _base_points(std::vector<std::vector<Real>>()), _data(MultiIndex<Real>({}))
      20             : {
      21           4 :   setData(base_points, data);
      22           4 : }
      23             : 
      24             : template <typename T>
      25           1 : MultiDimensionalInterpolationTempl<T>::MultiDimensionalInterpolationTempl()
      26           1 :   : _base_points(std::vector<std::vector<Real>>()), _data(MultiIndex<Real>({}))
      27             : {
      28           1 : }
      29             : 
      30             : template <typename T>
      31             : void
      32           6 : MultiDimensionalInterpolationTempl<T>::setData(const std::vector<std::vector<Real>> & base_points,
      33             :                                                const MultiIndex<Real> & data)
      34             : {
      35           6 :   _degenerate_interpolation = false;
      36           6 :   _setup_complete = false;
      37             : 
      38             :   // stash away original dimension of data because we might modify it
      39           6 :   _original_dim = data.dim();
      40           6 :   _degenerate_index.resize(_original_dim, false);
      41             : 
      42             :   // need to check this here to make sure the next loop is safe
      43           6 :   if (data.dim() != base_points.size())
      44             :   {
      45           0 :     std::ostringstream oss;
      46           0 :     oss << "Leading dimension of base_points " << base_points.size() << " and data dimensionality "
      47           0 :         << data.dim() << " are inconsistent.";
      48           0 :     throw std::domain_error(oss.str());
      49           0 :   }
      50             : 
      51             :   // first a corner case is addressed; if the size of the MultiIndex in one
      52             :   // dimension is 1, then finding the interpolation "stencil" becomes much
      53             :   // harder. It is easier to slice the data array and keep track of which values
      54             :   // to discard in the interpolation routines
      55           6 :   MultiIndex<Real>::size_type slice_dimensions;
      56           6 :   MultiIndex<Real>::size_type slice_indices;
      57           6 :   MultiIndex<Real>::size_type size = data.size();
      58           6 :   std::vector<std::vector<Real>> modified_base_points;
      59          28 :   for (unsigned int j = 0; j < data.dim(); ++j)
      60          22 :     if (size[j] == 1)
      61             :     {
      62           7 :       slice_dimensions.push_back(j);
      63           7 :       slice_indices.push_back(0);
      64           7 :       _degenerate_index[j] = true;
      65             :     }
      66             :     else
      67          15 :       modified_base_points.push_back(base_points[j]);
      68             : 
      69             :   // if all dimensions have size 1, then there is only one value to return
      70             :   // this is a weird corner case that we should support to make life easier
      71             :   // for whoever uses this object
      72             :   // In this case, we can just use the passed base_points and data objects
      73             :   // and short-cut the computation in the interpolation function
      74           6 :   if (modified_base_points.size() == 0)
      75             :   {
      76           1 :     _degenerate_interpolation = true;
      77           1 :     _base_points = base_points;
      78           1 :     _data = data;
      79             :   }
      80             :   else
      81             :   {
      82             :     // set the data
      83           5 :     _base_points = modified_base_points;
      84           5 :     _data = data.slice(slice_dimensions, slice_indices);
      85             :   }
      86           6 :   errorCheck();
      87           6 : }
      88             : 
      89             : template <typename T>
      90             : void
      91           6 : MultiDimensionalInterpolationTempl<T>::errorCheck()
      92             : {
      93           6 :   if (_base_points.size() != _data.dim())
      94           0 :     throw std::domain_error("Size of the leading dimension of base points must be equal to "
      95             :                             "dimensionality of data array");
      96             : 
      97           6 :   MultiIndex<Real>::size_type shape = _data.size();
      98          26 :   for (unsigned int j = 0; j < _data.dim(); ++j)
      99             :   {
     100          20 :     if (shape[j] != _base_points[j].size())
     101             :     {
     102           0 :       std::ostringstream oss;
     103           0 :       oss << "Dimension " << j << " has " << _base_points[j].size()
     104           0 :           << " base points but dimension of data array is " << shape[j];
     105           0 :       throw std::domain_error(oss.str());
     106           0 :     }
     107             : 
     108          65 :     for (unsigned int i = 1; i < _base_points[j].size(); ++i)
     109             :     {
     110          45 :       if (_base_points[j][i - 1] >= _base_points[j][i])
     111             :       {
     112           0 :         std::ostringstream oss;
     113           0 :         oss << "Base point values for dimension " << j << " are not strictly increasing: bp["
     114           0 :             << i - 1 << "]: " << _base_points[j][i - 1] << " bc[" << i
     115           0 :             << "]: " << _base_points[j][i];
     116           0 :         throw std::domain_error(oss.str());
     117           0 :       }
     118             :     }
     119             :   }
     120             : 
     121             :   // now the setup is complete
     122           6 :   _setup_complete = true;
     123           6 : }
     124             : 
     125             : template <typename T>
     126             : void
     127        2667 : MultiDimensionalInterpolationTempl<T>::linearSearch(std::vector<T> & values,
     128             :                                                     MultiIndex<Real>::size_type & indices) const
     129             : {
     130             :   assert(values.size() == _data.dim());
     131             : 
     132        2667 :   indices.resize(_data.dim());
     133       10668 :   for (unsigned int d = 0; d < _data.dim(); ++d)
     134        8001 :     indices[d] = linearSearchHelper(values[d], _base_points[d]);
     135        2667 : }
     136             : 
     137             : template <typename T>
     138             : unsigned int
     139        8001 : MultiDimensionalInterpolationTempl<T>::linearSearchHelper(T & x,
     140             :                                                           const std::vector<Real> & vector) const
     141             : {
     142             : // check order of vector only in debug
     143             : #ifndef NDEBUG
     144             :   for (unsigned int i = 1; i < vector.size(); ++i)
     145             :   {
     146             :     if (vector[i - 1] >= vector[i])
     147             :     {
     148             :       std::ostringstream oss;
     149             :       oss << "In linearSearchHelper vector is not strictly increasing: vector[" << i - 1
     150             :           << "]: " << vector[i - 1] << " vector[" << i << "]: " << vector[i];
     151             :       throw std::domain_error(oss.str());
     152             :     }
     153             :   }
     154             : #endif
     155             : 
     156             :   // smaller/larger cases for tabulation
     157        8001 :   if (x < vector[0])
     158             :   {
     159           2 :     x = vector[0];
     160           2 :     return 0;
     161             :   }
     162        7999 :   else if (x >= vector.back())
     163             :   {
     164             :     // this requires explanation because it looks like a bug (I swear it isn't, it's a feature)
     165             :     // if x >= largest base point value and the index i = vector.size() - 1 is returned, then during
     166             :     // the interpolation, i + 1 will be accessed, causing a segementation fault. Logic in the
     167             :     // interpolation is also bad because it may slow it down. By returning i = vector.size() - 2,
     168             :     // the last two values in the base point array are used, but x is reset to be equal to the last
     169             :     // value. This will give the right interpolation behavior.
     170         610 :     x = vector.back();
     171         610 :     return vector.size() - 2;
     172             :   }
     173             : 
     174       15499 :   for (unsigned int j = 0; j < vector.size() - 2; ++j)
     175       12230 :     if (x >= vector[j] && x < vector[j + 1])
     176        4120 :       return j;
     177        3269 :   return vector.size() - 2;
     178             : }
     179             : 
     180             : template <typename T>
     181             : T
     182        2664 : MultiDimensionalInterpolationTempl<T>::multiLinearInterpolation(const std::vector<T> & x) const
     183             : {
     184             :   // ensure that object has been set up properly
     185        2664 :   if (!_setup_complete)
     186           0 :     throw std::domain_error(
     187             :         "Object has been constructed properly. This happens when the empty constructor is called "
     188             :         "and setData is not called before interpolation.");
     189             : 
     190             :   // assert does not seem to be enough to check data consistency here
     191             :   // because this function is exposed to user and will be frequently used
     192        2664 :   if (x.size() != _original_dim)
     193             :   {
     194           0 :     std::ostringstream oss;
     195           0 :     oss << "In sample the parameter x has size " << x.size() << " but data has dimension "
     196           0 :         << _data.dim();
     197           0 :     throw std::domain_error(oss.str());
     198           0 :   }
     199             : 
     200             :   // in this case there is only one entry that resides at flat index 0
     201        2664 :   if (_degenerate_interpolation)
     202           1 :     return _data[0];
     203             : 
     204             :   // make a copy of x because linearSearch needs to be able to modify it
     205             :   // also adjust for degenerate indices that were removed
     206        2663 :   std::vector<T> y(_data.dim());
     207        2663 :   unsigned int l = 0;
     208       10654 :   for (unsigned int j = 0; j < _original_dim; ++j)
     209        7991 :     if (!_degenerate_index[j])
     210             :     {
     211        7989 :       y[l] = x[j];
     212        7989 :       ++l;
     213             :     }
     214             : 
     215             :   // obtain the indices using linearSearch & get flat index in _data array
     216        2663 :   MultiIndex<Real>::size_type indices;
     217        2663 :   linearSearch(y, indices);
     218             : 
     219             :   // pre-compute the volume of the hypercube and weights used for the interpolation
     220        2663 :   Real volume = 1;
     221        2663 :   std::vector<std::vector<T>> weights(_data.dim());
     222       10652 :   for (unsigned int j = 0; j < _data.dim(); ++j)
     223             :   {
     224        7989 :     volume *= _base_points[j][indices[j] + 1] - _base_points[j][indices[j]];
     225             : 
     226             :     // now compute weight for each dimension, note that
     227             :     // weight[j][0] = bp[j][high] - y[j]
     228             :     // weight[j][0] = y[j] - bp[j][low]
     229             :     // because in the interpolation the value on the "left" is weighted with the
     230             :     // distance of y to the "right"
     231        7989 :     weights[j].resize(2);
     232        7989 :     weights[j][0] = _base_points[j][indices[j] + 1] - y[j];
     233        7989 :     weights[j][1] = y[j] - _base_points[j][indices[j]];
     234             :   }
     235             : 
     236             :   // get flat index and stride
     237        2663 :   unsigned int flat_index = _data.flatIndex(indices);
     238        2663 :   MultiIndex<Real>::size_type stride = _data.stride();
     239             : 
     240             :   // this is the interpolation routine, evaluation can be expensive if dim is large
     241             : 
     242             :   // first we retrieve the data around the point y
     243             :   // we use a 2^dim MultiIndex hypercube for generating the permulations
     244        2663 :   MultiIndex<Real>::size_type shape(_data.dim(), 2);
     245        2663 :   MultiIndex<Real> hypercube = MultiIndex<Real>(shape);
     246        2663 :   T interpolated_value = 0;
     247       26630 :   for (const auto & p : hypercube)
     248             :   {
     249             :     // this loop computes the flat index for the point in the
     250             :     // hypercube and accumulates the total weight for this point
     251             :     // the selection of the dimensional weight is based on the swapping
     252             :     // done in the pre-computing loop before
     253       21304 :     unsigned int index = flat_index;
     254       21304 :     T w = 1;
     255       85216 :     for (unsigned int j = 0; j < p.first.size(); ++j)
     256             :     {
     257       63912 :       index += stride[j] * p.first[j];
     258       63912 :       w *= weights[j][p.first[j]];
     259             :     }
     260             : 
     261             :     // add the contribution of this base point to the interpolated value
     262       21304 :     interpolated_value += w * _data[index];
     263             :   }
     264             : 
     265        2663 :   return interpolated_value / volume;
     266        2663 : }
     267             : 
     268             : template class MultiDimensionalInterpolationTempl<Real>;
     269             : template class MultiDimensionalInterpolationTempl<ADReal>;

Generated by: LCOV version 1.14