Search code examples
c++eigenspline

c++ Eigen: spline derivatives() gives strange derivatives


I'm trying to get a hold on how to work with splines in Eigen, specifically I want do find the value of the spline interpolation and its first and second derivatives in some point. Finding the interpolated value is easy, but when I try to calculate the derivative I get strange values.

I tried following the instructions for the derivatives command in the manual (http://eigen.tuxfamily.org/dox/unsupported/classEigen_1_1Spline.html#af3586ab1929959e0161bfe7da40155c6), and this is my attempt in code:

#include <iostream>
#include <Eigen/Core>
#include <unsupported/Eigen/Splines>

using namespace Eigen;
using namespace std;

double scaling(double x, double min, double max)  // for scaling numbers
{
    return (x - min)/(max - min);
}

VectorXd scale(VectorXd xvals)  // for scaling vectors
{
    const double min = xvals.minCoeff();
    const double max = xvals.maxCoeff();

    for (int k = 0; k < xvals.size(); k++)
        xvals(k) = scaling(xvals(k),min,max);

    return xvals;
}

int main()
{
    typedef Spline<double,1,3> spline;

    VectorXd xvals = (VectorXd(4) << 0,1,2,4).finished();
    VectorXd yvals = xvals.array().square();  // x^2

    spline testspline = SplineFitting<spline>::Interpolate(yvals.transpose(), 3,
scale(xvals).transpose());

    cout << "derivative at x = 0: " << testspline.derivatives(0.00,2) << endl;
    cout << "derivative at x = 1: " << testspline.derivatives(0.25,2) << endl;
    cout << "derivative at x = 2: " << testspline.derivatives(0.50,2) << endl;
    cout << "derivative at x = 3: " << testspline.derivatives(0.75,2) << endl;
    cout << "derivative at x = 4: " << testspline.derivatives(1.00,2) << endl;
}

it outputs

derivative at x = 0:  0  0 32
derivative at x = 1:  1  8 32
derivative at x = 2:  4 16 32
derivative at x = 3:  9 24 32
derivative at x = 4: 16 32 32

That is, the interpolation is correct (c.f. x = 3), but the derivatives are not, and they are off in a systematic way, so I'm thinking I'm doing something wrong. Since these follow x^2, the derivatives should be 0,2,4,6,8 and the second order derivative should be 2.

Any ideas on how to solve this?

Edit 1

Changing x^2 to x^2 + 1 yields the same derivatives, so that checks out at least. But changing x^2 to x^3 is wrong, but wrong in a slightly different way, output would then be:

derivative at x = 2:  8 48  192
derivative at x = 3: 27 108 288
derivative at x = 4: 64 192 384

Which is wrong, it should be 6, 9, 12.

Also running the x^2 case, but changing he input vector to 0,1,...9 yields the same derivative as using the original input vector, but the second order derivative becomes a steady 200, which too is wrong. I fail to see why the second order derivative should depend on the number of input points.


Solution

  • Solved it. You were very close. All you had to do was scale the derivatives with

    • 1 / (x_max - x_min) (first derivative)
    • 1 / (x_max - x_min)^2 (second derivative).

    TLDR: You normalized the x values to be between 0 and 1 while fitting the spline, but you didn't scale the y values.

    Instead of the spline fitting x^2, you actually fitted:

    x_norm = (x - x_min) / (x_max - x_min)
    y = x_norm**2
    

    So using the chain rule the first derivative of y = x_norm**2 would be 2x / (x_max - x_min) and the second derivative would be 2 / (x_max - x_min)**2.

    Full example code:

    #include <iostream>
    #include <Eigen/Core>
    #include <unsupported/Eigen/Splines>
    
    using namespace Eigen;
    using namespace std;
    
    VectorXd normalize(const VectorXd &x) {
      VectorXd x_norm;
      x_norm.resize(x.size());
    
      const double min = x.minCoeff();
      const double max = x.maxCoeff();
      for (int k = 0; k < x.size(); k++) {
        x_norm(k) = (x(k) - min)/(max - min);
      }
    
      return x_norm;
    }
    
    int main() {
      typedef Spline<double, 1, 3> Spline1D;
      typedef SplineFitting<Spline1D> Spline1DFitting;
    
      const Vector4d x{0, 1, 2, 4};
      const Vector4d y = (x.array().square());  // x^2
    
      const auto knots = normalize(x);  // Normalize x to be between 0 and 1
      const double scale = 1 / (x.maxCoeff() -  x.minCoeff());
      const double scale_sq = scale * scale;
      Spline1D spline = Spline1DFitting::Interpolate(y.transpose(), 3, knots);
    
      cout << "1st deriv at x = 0: " << spline.derivatives(0.00, 1)(1) * scale << endl;
      cout << "1st deriv at x = 1: " << spline.derivatives(0.25, 1)(1) * scale << endl;
      cout << "1st deriv at x = 2: " << spline.derivatives(0.50, 1)(1) * scale << endl;
      cout << "1st deriv at x = 3: " << spline.derivatives(0.75, 1)(1) * scale << endl;
      cout << "1st deriv at x = 4: " << spline.derivatives(1.00, 1)(1) * scale << endl;
      cout << endl;
    
      /**
       * IMPORTANT NOTE: Eigen's spline module is not documented well. Once you fit a spline
       * to find the derivative of the fitted spline at any point u [0, 1] you call:
       *
       *     spline.derivatives(u, 1)(1)
       *                        ^  ^  ^
       *                        |  |  |
       *                        |  |  +------- Access the result
       *                        |  +---------- Derivative order
       *                        +------------- Parameter u [0, 1]
       *
       * The last bit `(1)` is if the spline is 1D. And value of `1` for the first
       * order. `2` for the second order. Do not forget to scale the result.
       *
       * For higher dimensions, treat the return as a matrix and grab the 1st or
       * 2nd column for the first and second derivative.
       */
    
      cout << "2nd deriv at x = 0: " << spline.derivatives(0.00, 2)(2) * scale_sq << endl;
      cout << "2nd deriv at x = 1: " << spline.derivatives(0.25, 2)(2) * scale_sq << endl;
      cout << "2nd deriv at x = 2: " << spline.derivatives(0.50, 2)(2) * scale_sq << endl;
      cout << "2nd deriv at x = 3: " << spline.derivatives(0.75, 2)(2) * scale_sq << endl;
      cout << "2nd deriv at x = 4: " << spline.derivatives(1.00, 2)(2) * scale_sq << endl;
    
      return 0;
    }
    

    Example output:

    1st deriv at x = 0: 4.52754e-16
    1st deriv at x = 1: 2
    1st deriv at x = 2: 4
    1st deriv at x = 3: 6
    1st deriv at x = 4: 8
    
    2nd deriv at x = 0: 2
    2nd deriv at x = 1: 2
    2nd deriv at x = 2: 2
    2nd deriv at x = 3: 2
    2nd deriv at x = 4: 2