LCOV - code coverage report
Current view: top level - src/utils - BicubicInterpolation.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 219 223 98.2 %
Date: 2025-07-17 01:28:37 Functions: 21 26 80.8 %
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 "BicubicInterpolation.h"
      11             : #include "MooseError.h"
      12             : #include "MathUtils.h"
      13             : #include "MooseUtils.h"
      14             : 
      15             : #include "libmesh/int_range.h"
      16             : 
      17           1 : BicubicInterpolation::BicubicInterpolation(const std::vector<Real> & x1,
      18             :                                            const std::vector<Real> & x2,
      19           1 :                                            const std::vector<std::vector<Real>> & y)
      20          18 :   : BidimensionalInterpolation(x1, x2), _y(y)
      21             : {
      22           1 :   errorCheck();
      23             : 
      24           1 :   auto m = _x1.size();
      25           1 :   auto n = _x2.size();
      26             : 
      27           1 :   _bicubic_coeffs.resize(m - 1);
      28           9 :   for (const auto i : index_range(_bicubic_coeffs))
      29             :   {
      30           8 :     _bicubic_coeffs[i].resize(n - 1);
      31          72 :     for (const auto j : index_range(_bicubic_coeffs[i]))
      32             :     {
      33          64 :       _bicubic_coeffs[i][j].resize(4);
      34         320 :       for (const auto k : make_range(4))
      35         256 :         _bicubic_coeffs[i][j][k].resize(4);
      36             :     }
      37             :   }
      38             : 
      39             :   // Precompute the coefficients
      40           1 :   precomputeCoefficients();
      41           3 : }
      42             : 
      43             : Real
      44           2 : BicubicInterpolation::sample(const Real x1, const Real x2) const
      45             : {
      46           2 :   return sampleInternal(x1, x2);
      47             : }
      48             : 
      49             : ADReal
      50           1 : BicubicInterpolation::sample(const ADReal & x1, const ADReal & x2) const
      51             : {
      52           1 :   return sampleInternal(x1, x2);
      53             : }
      54             : 
      55             : ChainedReal
      56           1 : BicubicInterpolation::sample(const ChainedReal & x1, const ChainedReal & x2) const
      57             : {
      58           1 :   return sampleInternal(x1, x2);
      59             : }
      60             : 
      61             : template <typename T>
      62             : T
      63           4 : BicubicInterpolation::sampleInternal(const T & x1, const T & x2) const
      64             : {
      65             :   unsigned int x1l, x1u, x2l, x2u;
      66           2 :   T t, u;
      67           4 :   findInterval(_x1, x1, x1l, x1u, t);
      68           4 :   findInterval(_x2, x2, x2l, x2u, u);
      69             : 
      70           4 :   T sample = 0.0;
      71          20 :   for (const auto i : make_range(4))
      72          80 :     for (const auto j : make_range(4))
      73          64 :       sample += _bicubic_coeffs[x1l][x2l][i][j] * MathUtils::pow(t, i) * MathUtils::pow(u, j);
      74             : 
      75           6 :   return sample;
      76           2 : }
      77             : 
      78             : Real
      79           4 : BicubicInterpolation::sampleDerivative(Real x1, Real x2, unsigned int deriv_var) const
      80             : {
      81             :   unsigned int x1l, x1u, x2l, x2u;
      82             :   Real t, u;
      83           4 :   findInterval(_x1, x1, x1l, x1u, t);
      84           4 :   findInterval(_x2, x2, x2l, x2u, u);
      85             : 
      86             :   // Take derivative along x1 axis
      87             :   // Note: sum from i = 1 as the first term is zero
      88           4 :   if (deriv_var == 1)
      89             :   {
      90           2 :     Real sample_deriv = 0.0;
      91           8 :     for (const auto i : make_range(1, 4))
      92          30 :       for (const auto j : make_range(4))
      93          24 :         sample_deriv +=
      94          24 :             i * _bicubic_coeffs[x1l][x2l][i][j] * MathUtils::pow(t, i - 1) * MathUtils::pow(u, j);
      95             : 
      96           2 :     Real d = _x1[x1u] - _x1[x1l];
      97             : 
      98           2 :     if (!MooseUtils::absoluteFuzzyEqual(d, 0.0))
      99           2 :       sample_deriv /= d;
     100             : 
     101           2 :     return sample_deriv;
     102             :   }
     103             : 
     104             :   // Take derivative along x2 axis
     105             :   // Note: sum from j = 1 as the first term is zero
     106           2 :   else if (deriv_var == 2)
     107             :   {
     108           2 :     Real sample_deriv = 0.0;
     109             : 
     110          10 :     for (const auto i : make_range(4))
     111          32 :       for (const auto j : make_range(1, 4))
     112          24 :         sample_deriv +=
     113          24 :             j * _bicubic_coeffs[x1l][x2l][i][j] * MathUtils::pow(t, i) * MathUtils::pow(u, j - 1);
     114             : 
     115           2 :     Real d = _x2[x2u] - _x2[x2l];
     116             : 
     117           2 :     if (!MooseUtils::absoluteFuzzyEqual(d, Real(0.0)))
     118           2 :       sample_deriv /= d;
     119             : 
     120           2 :     return sample_deriv;
     121             :   }
     122             : 
     123             :   else
     124           0 :     mooseError("deriv_var must be either 1 or 2 in BicubicInterpolation");
     125             : }
     126             : 
     127             : Real
     128           6 : BicubicInterpolation::sample2ndDerivative(Real x1, Real x2, unsigned int deriv_var) const
     129             : {
     130             :   unsigned int x1l, x1u, x2l, x2u;
     131             :   Real t, u;
     132           6 :   findInterval(_x1, x1, x1l, x1u, t);
     133           6 :   findInterval(_x2, x2, x2l, x2u, u);
     134             : 
     135             :   // Take derivative along x1 axis
     136             :   // Note: sum from i = 2 as the first two terms are zero
     137           6 :   if (deriv_var == 1)
     138             :   {
     139           2 :     Real sample_deriv = 0.0;
     140           6 :     for (const auto i : make_range(2, 4))
     141          20 :       for (const auto j : make_range(4))
     142          32 :         sample_deriv += i * (i - 1) * _bicubic_coeffs[x1l][x2l][i][j] * MathUtils::pow(t, i - 2) *
     143          16 :                         MathUtils::pow(u, j);
     144             : 
     145           2 :     Real d = _x1[x1u] - _x1[x1l];
     146             : 
     147           2 :     if (!MooseUtils::absoluteFuzzyEqual(d, Real(0.0)))
     148           2 :       sample_deriv /= (d * d);
     149             : 
     150           2 :     return sample_deriv;
     151             :   }
     152             : 
     153             :   // Take derivative along x2 axis
     154             :   // Note: sum from j = 2 as the first two terms are zero
     155           4 :   else if (deriv_var == 2)
     156             :   {
     157           2 :     Real sample_deriv = 0.0;
     158          10 :     for (const auto i : make_range(4))
     159          24 :       for (const auto j : make_range(2, 4))
     160          32 :         sample_deriv += j * (j - 1) * _bicubic_coeffs[x1l][x2l][i][j] * MathUtils::pow(t, i) *
     161          16 :                         MathUtils::pow(u, j - 2);
     162             : 
     163           2 :     Real d = _x2[x2u] - _x2[x2l];
     164             : 
     165           2 :     if (!MooseUtils::absoluteFuzzyEqual(d, Real(0.0)))
     166           2 :       sample_deriv /= (d * d);
     167             : 
     168           2 :     return sample_deriv;
     169             :   }
     170             : 
     171             :   // Take mixed derivative along x1 and x2 axes
     172             :   // Note: sum from i = 1 and j = 1 as the first terms are zero
     173           2 :   else if (deriv_var == 3)
     174             :   {
     175           2 :     Real sample_deriv = 0.0;
     176           8 :     for (const auto i : make_range(1, 4))
     177          24 :       for (const auto j : make_range(1, 4))
     178          36 :         sample_deriv += i * j * _bicubic_coeffs[x1l][x2l][i][j] * MathUtils::pow(t, i - 1) *
     179          18 :                         MathUtils::pow(u, j - 1);
     180             : 
     181           2 :     const Real d1 = _x1[x1u] - _x1[x1l];
     182           2 :     const Real d2 = _x2[x2u] - _x2[x2l];
     183             : 
     184           4 :     if (!MooseUtils::absoluteFuzzyEqual(d1, Real(0.0)) &&
     185           4 :         !MooseUtils::absoluteFuzzyEqual(d2, Real(0.0)))
     186           2 :       sample_deriv /= (d1 * d2);
     187             : 
     188           2 :     return sample_deriv;
     189             :   }
     190             : 
     191             :   else
     192           0 :     mooseError("deriv_var must be either 1, 2 or 3 in BicubicInterpolation");
     193             : }
     194             : 
     195             : void
     196           1 : BicubicInterpolation::sampleValueAndDerivatives(
     197             :     Real x1, Real x2, Real & y, Real & dy1, Real & dy2) const
     198             : {
     199           1 :   sampleValueAndDerivativesInternal(x1, x2, y, dy1, dy2);
     200           1 : }
     201             : 
     202             : void
     203           1 : BicubicInterpolation::sampleValueAndDerivatives(
     204             :     const ADReal & x1, const ADReal & x2, ADReal & y, ADReal & dy1, ADReal & dy2) const
     205             : {
     206           1 :   sampleValueAndDerivativesInternal(x1, x2, y, dy1, dy2);
     207           1 : }
     208             : void
     209           1 : BicubicInterpolation::sampleValueAndDerivatives(const ChainedReal & x1,
     210             :                                                 const ChainedReal & x2,
     211             :                                                 ChainedReal & y,
     212             :                                                 ChainedReal & dy1,
     213             :                                                 ChainedReal & dy2) const
     214             : {
     215           1 :   sampleValueAndDerivativesInternal(x1, x2, y, dy1, dy2);
     216           1 : }
     217             : 
     218             : template <typename T>
     219             : void
     220           3 : BicubicInterpolation::sampleValueAndDerivativesInternal(T x1, T x2, T & y, T & dy1, T & dy2) const
     221             : {
     222             :   unsigned int x1l, x1u, x2l, x2u;
     223           2 :   T t, u;
     224           3 :   findInterval(_x1, x1, x1l, x1u, t);
     225           3 :   findInterval(_x2, x2, x2l, x2u, u);
     226             : 
     227           3 :   y = 0.0;
     228          15 :   for (const auto i : make_range(4))
     229          60 :     for (const auto j : make_range(4))
     230          48 :       y += _bicubic_coeffs[x1l][x2l][i][j] * MathUtils::pow(t, i) * MathUtils::pow(u, j);
     231             : 
     232             :   // Note: sum from i = 1 as the first term is zero
     233           3 :   dy1 = 0.0;
     234          12 :   for (const auto i : make_range(1, 4))
     235          45 :     for (const auto j : make_range(4))
     236          36 :       dy1 += i * _bicubic_coeffs[x1l][x2l][i][j] * MathUtils::pow(t, i - 1) * MathUtils::pow(u, j);
     237             : 
     238             :   // Note: sum from j = 1 as the first term is zero
     239           3 :   dy2 = 0.0;
     240          15 :   for (const auto i : make_range(4))
     241          48 :     for (const auto j : make_range(1, 4))
     242          36 :       dy2 += j * _bicubic_coeffs[x1l][x2l][i][j] * MathUtils::pow(t, i) * MathUtils::pow(u, j - 1);
     243             : 
     244           3 :   T d1 = _x1[x1u] - _x1[x1l];
     245             : 
     246           3 :   if (!MooseUtils::absoluteFuzzyEqual(d1, 0.0))
     247           3 :     dy1 /= d1;
     248             : 
     249           3 :   T d2 = _x2[x2u] - _x2[x2l];
     250             : 
     251           3 :   if (!MooseUtils::absoluteFuzzyEqual(d2, 0.0))
     252           3 :     dy2 /= d2;
     253           3 : }
     254             : 
     255             : void
     256           1 : BicubicInterpolation::precomputeCoefficients()
     257             : {
     258             :   // Calculate the first derivatives in each direction at each point, and the
     259             :   // mixed second derivative
     260           1 :   std::vector<std::vector<Real>> dy_dx1, dy_dx2, d2y_dx1x2;
     261           1 :   tableDerivatives(dy_dx1, dy_dx2, d2y_dx1x2);
     262             : 
     263             :   // Now solve for the coefficients at each point in the grid
     264           9 :   for (const auto i : index_range(_bicubic_coeffs))
     265          72 :     for (const auto j : index_range(_bicubic_coeffs[i]))
     266             :     {
     267             :       // Distance between corner points in each direction
     268          64 :       const Real d1 = _x1[i + 1] - _x1[i];
     269          64 :       const Real d2 = _x2[j + 1] - _x2[j];
     270             : 
     271             :       // Values of function and derivatives of the four corner points
     272          64 :       std::vector<Real> y = {_y[i][j], _y[i + 1][j], _y[i + 1][j + 1], _y[i][j + 1]};
     273             :       std::vector<Real> y1 = {
     274          64 :           dy_dx1[i][j], dy_dx1[i + 1][j], dy_dx1[i + 1][j + 1], dy_dx1[i][j + 1]};
     275             :       std::vector<Real> y2 = {
     276          64 :           dy_dx2[i][j], dy_dx2[i + 1][j], dy_dx2[i + 1][j + 1], dy_dx2[i][j + 1]};
     277             :       std::vector<Real> y12 = {
     278          64 :           d2y_dx1x2[i][j], d2y_dx1x2[i + 1][j], d2y_dx1x2[i + 1][j + 1], d2y_dx1x2[i][j + 1]};
     279             : 
     280          64 :       std::vector<Real> cl(16), x(16);
     281             :       Real xx;
     282          64 :       unsigned int count = 0;
     283             : 
     284             :       // Temporary vector used in the matrix multiplication
     285         320 :       for (const auto k : make_range(4))
     286             :       {
     287         256 :         x[k] = y[k];
     288         256 :         x[k + 4] = y1[k] * d1;
     289         256 :         x[k + 8] = y2[k] * d2;
     290         256 :         x[k + 12] = y12[k] * d1 * d2;
     291             :       }
     292             : 
     293             :       // Multiply by the matrix of constants
     294        1088 :       for (const auto k : make_range(16))
     295             :       {
     296        1024 :         xx = 0.0;
     297       17408 :         for (const auto q : make_range(16))
     298       16384 :           xx += _wt[k][q] * x[q];
     299             : 
     300        1024 :         cl[k] = xx;
     301             :       }
     302             : 
     303             :       // Unpack results into coefficient table
     304         320 :       for (const auto k : make_range(4))
     305        1280 :         for (const auto q : make_range(4))
     306             :         {
     307        1024 :           _bicubic_coeffs[i][j][k][q] = cl[count];
     308        1024 :           count++;
     309             :         }
     310          64 :     }
     311           1 : }
     312             : 
     313             : void
     314           1 : BicubicInterpolation::tableDerivatives(std::vector<std::vector<Real>> & dy_dx1,
     315             :                                        std::vector<std::vector<Real>> & dy_dx2,
     316             :                                        std::vector<std::vector<Real>> & d2y_dx1x2)
     317             : {
     318           1 :   const auto m = _x1.size();
     319           1 :   const auto n = _x2.size();
     320           1 :   dy_dx1.resize(m);
     321           1 :   dy_dx2.resize(m);
     322           1 :   d2y_dx1x2.resize(m);
     323             : 
     324          10 :   for (const auto i : make_range(m))
     325             :   {
     326           9 :     dy_dx1[i].resize(n);
     327           9 :     dy_dx2[i].resize(n);
     328           9 :     d2y_dx1x2[i].resize(n);
     329             :   }
     330             : 
     331             :   // Central difference calculations of derivatives
     332          10 :   for (const auto i : make_range(m))
     333          90 :     for (const auto j : make_range(n))
     334             :     {
     335             :       // Derivative wrt x1
     336          81 :       if (i == 0)
     337             :       {
     338           9 :         dy_dx1[i][j] = (_y[i + 1][j] - _y[i][j]) / (_x1[i + 1] - _x1[i]);
     339             :       }
     340          72 :       else if (i == m - 1)
     341           9 :         dy_dx1[i][j] = (_y[i][j] - _y[i - 1][j]) / (_x1[i] - _x1[i - 1]);
     342             :       else
     343          63 :         dy_dx1[i][j] = (_y[i + 1][j] - _y[i - 1][j]) / (_x1[i + 1] - _x1[i - 1]);
     344             : 
     345             :       // Derivative wrt x2
     346          81 :       if (j == 0)
     347           9 :         dy_dx2[i][j] = (_y[i][j + 1] - _y[i][j]) / (_x2[j + 1] - _x2[j]);
     348          72 :       else if (j == n - 1)
     349           9 :         dy_dx2[i][j] = (_y[i][j] - _y[i][j - 1]) / (_x2[j] - _x2[j - 1]);
     350             :       else
     351          63 :         dy_dx2[i][j] = (_y[i][j + 1] - _y[i][j - 1]) / (_x2[j + 1] - _x2[j - 1]);
     352             : 
     353             :       // Mixed derivative d2y/dx1x2
     354          81 :       if (i == 0 && j == 0)
     355           2 :         d2y_dx1x2[i][j] = (_y[i + 1][j + 1] - _y[i + 1][j] - _y[i][j + 1] + _y[i][j]) /
     356           1 :                           (_x1[i + 1] - _x1[i]) / (_x2[j + 1] - _x2[j]);
     357          80 :       else if (i == 0 && j == n - 1)
     358           2 :         d2y_dx1x2[i][j] = (_y[i + 1][j] - _y[i + 1][j - 1] - _y[i][j] + _y[i][j - 1]) /
     359           1 :                           (_x1[i + 1] - _x1[i]) / (_x2[j] - _x2[j - 1]);
     360          79 :       else if (i == m - 1 && j == 0)
     361           2 :         d2y_dx1x2[i][j] = (_y[i][j + 1] - _y[i][j] - _y[i - 1][j + 1] + _y[i - 1][j]) /
     362           1 :                           (_x1[i] - _x1[i - 1]) / (_x2[j + 1] - _x2[j]);
     363          78 :       else if (i == m - 1 && j == n - 1)
     364           2 :         d2y_dx1x2[i][j] = (_y[i][j] - _y[i][j - 1] - _y[i - 1][j] + _y[i - 1][j - 1]) /
     365           1 :                           (_x1[i] - _x1[i - 1]) / (_x2[j] - _x2[j - 1]);
     366          77 :       else if (i == 0)
     367          14 :         d2y_dx1x2[i][j] = (_y[i + 1][j + 1] - _y[i + 1][j - 1] - _y[i][j + 1] + _y[i][j - 1]) /
     368           7 :                           (_x1[i + 1] - _x1[i]) / (_x2[j + 1] - _x2[j - 1]);
     369          70 :       else if (i == m - 1)
     370          14 :         d2y_dx1x2[i][j] = (_y[i][j + 1] - _y[i][j - 1] - _y[i - 1][j + 1] + _y[i - 1][j - 1]) /
     371           7 :                           (_x1[i] - _x1[i - 1]) / (_x2[j + 1] - _x2[j - 1]);
     372          63 :       else if (j == 0)
     373          14 :         d2y_dx1x2[i][j] = (_y[i + 1][j + 1] - _y[i + 1][j] - _y[i - 1][j + 1] + _y[i - 1][j]) /
     374           7 :                           (_x1[i + 1] - _x1[i - 1]) / (_x2[j + 1] - _x2[j]);
     375          56 :       else if (j == n - 1)
     376          14 :         d2y_dx1x2[i][j] = (_y[i + 1][j] - _y[i + 1][j - 1] - _y[i - 1][j] + _y[i - 1][j - 1]) /
     377           7 :                           (_x1[i + 1] - _x1[i - 1]) / (_x2[j] - _x2[j - 1]);
     378             :       else
     379          49 :         d2y_dx1x2[i][j] =
     380          49 :             (_y[i + 1][j + 1] - _y[i + 1][j - 1] - _y[i - 1][j + 1] + _y[i - 1][j - 1]) /
     381          49 :             (_x1[i + 1] - _x1[i - 1]) / (_x2[j + 1] - _x2[j - 1]);
     382             :     }
     383           1 : }
     384             : 
     385             : template <typename T>
     386             : void
     387          34 : BicubicInterpolation::findInterval(
     388             :     const std::vector<Real> & x, const T & xi, unsigned int & klo, unsigned int & khi, T & xs) const
     389             : {
     390             :   // Find the indices that bracket the point xi
     391          34 :   klo = 0;
     392             :   mooseAssert(x.size() >= 2,
     393             :               "There must be at least two points in the table in BicubicInterpolation");
     394             : 
     395          34 :   khi = x.size() - 1;
     396         136 :   while (khi - klo > 1)
     397             :   {
     398         102 :     unsigned int kmid = (khi + klo) / 2;
     399         102 :     if (x[kmid] > xi)
     400          51 :       khi = kmid;
     401             :     else
     402          51 :       klo = kmid;
     403             :   }
     404             : 
     405             :   // Now find the scaled position, normalized to [0,1]
     406          34 :   Real d = x[khi] - x[klo];
     407          34 :   xs = xi - x[klo];
     408             : 
     409          34 :   if (!MooseUtils::absoluteFuzzyEqual(d, Real(0.0)))
     410          34 :     xs /= d;
     411          34 : }
     412             : 
     413             : template void BicubicInterpolation::findInterval(const std::vector<Real> & x,
     414             :                                                  const Real & xi,
     415             :                                                  unsigned int & klo,
     416             :                                                  unsigned int & khi,
     417             :                                                  Real & xs) const;
     418             : template void BicubicInterpolation::findInterval(const std::vector<Real> & x,
     419             :                                                  const ADReal & xi,
     420             :                                                  unsigned int & klo,
     421             :                                                  unsigned int & khi,
     422             :                                                  ADReal & xs) const;
     423             : 
     424             : void
     425           1 : BicubicInterpolation::errorCheck()
     426             : {
     427           1 :   auto m = _x1.size(), n = _x2.size();
     428             : 
     429           1 :   if (_y.size() != m)
     430           0 :     mooseError("y row dimension does not match the size of x1 in BicubicInterpolation");
     431             :   else
     432          10 :     for (const auto i : index_range(_y))
     433           9 :       if (_y[i].size() != n)
     434           0 :         mooseError("y column dimension does not match the size of x2 in BicubicInterpolation");
     435           1 : }

Generated by: LCOV version 1.14