umontreal-simul/ssj

Evaluate the spline function using knots and control points at a given point in time

Closed this issue · 5 comments

Hi there,
We are trying to use the SSJ library to fit time series data using B-splines. The goal is simply to reduce the number of data points we are storing instead of storing the complete time series.
We are storing control points and knots in a DB and then with the use of those params we want to get estimated original time series. We have an evaluate function in BSpline class which we can use to get original time series but problem is one needs b-spline object created through createApproxBSpline function to use that function.

Is there any way by which we can make use of knots and control points (parameters we get by calling createApproxBSpline function) to create b-spline object which in turn can be used to call evaluate function and get estimate of original time series? If you have an example that shows how to actually evaluate the spline function using knots and control points at a given point in time it would
really help us. We are not sure right now if this is not possible in SSJ, or if we are using it incorrectly.Thanks for taking a look.Really appreciate it.

Hi @karanshah03,
I coded quickly a simple example on how to call the BSpline constructor with arguments.
In this example, I use createAproxBSpline to create a BSpline. Then, I save the control points x,y and the knots.
Afterward, I create a new BSpline using the constructor and the saved parameters. See method testBSpline in the code below.
The function we try to approximate in this example is defined in function functionEval.

Note that there was a bug with the BSpline class in the version <= 3.2 of SSJ, see issue #2 . We have yet to release the new jar (and version) on Maven Central. You need to compile the jar from master branch of SSJ to have the bug fix.

Does this answer your question?

import umontreal.ssj.functionfit.BSpline;
import java.util.*;

public class TestBSpline {
    double[] times;
    double[] values;

    public double maxTime = 100;
    public int bsplineDegree = 4;
    public int bsplineNumCtrlPoints = 30;

    public TestBSpline() {}
    
    /**
     * Defines the function to approximate here.
     */
    public double functionEval(double x) {
        return Math.sin(0.2*x)*0.5*x;
    }
    
    private void generateData(int numPoints) {
        times = new double[numPoints];
        values = new double[numPoints];
        
        for(int i = 0; i < numPoints; i++) {
            times[i] = ((double) i)/numPoints * maxTime;
            values[i] = functionEval(times[i]);
        }
    }
    
    public void printData() {
        System.out.printf("%10s, %10s%n", "Time", "Value");
        for(int i = 0; i < times.length; i++)
            System.out.printf("%10f, %10f%n", times[i], values[i]);
    }    
    
    /**
     * Runs the test.
     *
     * @param numDataPoints number of data points to generate from the
     * function defined in {@link #functionEval}. Use these points to
     * create the BSpline object.
     *
     * @param numTestPoints number of points to generate from the BSpline,
     * and compare the error with the real function {@link #functionEval}.
     */
    public void testBSpline(int numDataPoints, int numTestPoints) {
        generateData(numDataPoints);
        BSpline bspline = BSpline.createApproxBSpline(times, values, bsplineDegree, bsplineNumCtrlPoints);
    
        // save the parameters
        double[] xs = bspline.getX();
        double[] ys = bspline.getY();
        double[] knots = bspline.getKnots();
        
        // How to create BSpline from constructor
        BSpline bspline2 = new BSpline(xs, ys, knots);
        
        double[] xTest = new double[numTestPoints];
        double[] yTest = new double[numTestPoints];
        for(int i = 0; i < numTestPoints; i++) {
            xTest[i] = ((double) i)/numTestPoints * maxTime;
            yTest[i] = bspline2.evaluate(xTest[i]);
        }
        
        // print results
        System.out.println("\nOriginal data:");
        printData();
        
        System.out.println("\nNew BSpline:");
        System.out.printf("%10s, %10s, %20s%n", "Time", "Value", "Error(Approx-Real)");
        for(int i = 0; i < xTest.length; i++)
            System.out.printf("%10f, %10f, %20f%n", xTest[i], yTest[i], (functionEval(xTest[i])-yTest[i]));
    }
    
    public static void main(String[] args){
        TestBSpline test = new TestBSpline();
        test.testBSpline(300, 200); 
    }
}

This works. Thanks for the such a quick reply. Cheers.

@chwyean Could you please tell me when you can push the bug fix out to maven central?

For the next release, I will ask @pierrelecuyer.

There was a major bug in the method createApproxBSpline. I don't recommend using this method in SSJ <= 3.2.

The bug in BSpline is minor. It was caused by the root finder that was unstable at the extreme knots. If you try to evaluate the first or last point, there may be instability error. But one can still use BSpline if he does not evaluate the extreme points.

We just released SSJ version 3.2.1 with a few bug fixes on Maven Central. It should be available soon.