Skip to content

Data Fitting

William Wood edited this page Jul 17, 2019 · 8 revisions

Data Fitting

Sometimes you will want to process the raw data you record from an experiment as it is being or immediately after it is recorded. JISA includes the Apache Commons mathematical tools library which offers a diverse toolkit for data-processing.

JISA takes some of these tools and integrates them with ResultTable objects to allow for a more direct approach. We shall cover the instances of this here.

Contents

  1. Function Objects
  2. Linear Interpolation
  3. Polynomial Fitting
  4. General Fitting

Function Objects

Top ↑

The first thing to cover is the idea of Function objects. These represent a (single-variable) mathematical function. They are defined by the Function interface:

public interface Function {
    double value(double x);
    ...
}

Thus you can specify a Function object with a lambda-expression. For example, if I wanted to represent the mathematical function:

Then we would write:

Java

Function f = (x) -> 15.0 * Math.exp(x) - Math.pow(x,2);

Python

f = lambda x : 15.0 * Math.exp(x) - Math.pow(x,2)

We can then evaluate the function at any value of x by calling:

Java

double valueAt3 = f.value(3.0);

Python

valueAt3 = f(3.0)

You can then create another Function that represents its derivative by use of:

Function dfdx = f.derivative();

If we have two functions then you can also combine them into a third:

Function f1 = ...;
Function f2 = ...;

Function added      = f1.add(f2);
Function subtracted = f1.subtract(f2);
Function multiplied = f1.multiply(f2);
Finction divided    = f1.divide(f2);

If the Function came from a fit, then the co-efficients used to create the fit can be obtained by use of:

double[] coeffs = f.getCoefficients();

They either be in the order that you specified them in when defining the fit or in order of ascending power for a polynomial fit.

You can plot a function in a Plot by use of the plotFunction(...) method like so:

plot.plotFunction(Function f, String name, Color colour);

Where f is the function to plot. This will plot the function on the axes as a line that adjusts in range and resolution when changing the limits of the plot.

For example:

Function f      = (x) -> Math.sin(x);
Plot     plot   = new Plot("My Plot", "x", "sin(x)");
Series   series = plot.plotFunction(f, "Sine", Color.BLUE);

plot.show();

will result in:

Linear Interpolation

Top ↑

If you should need to linearly interpolate data from a ResultTable then you can use the asFunction(...) method to create a function of one columns vs another that will linearly interpolate between data-points like so:

Function f = results.asFunction(int xData, int yData);

where xData is the column to use for x-values and yData for y-values (ie for f(x) = y).

For example, let's say we have the following ResultTable:

ResultTable results = new ResultList("Time", "Voltage", "Current");

Then to get "current as a function of voltage":

// x = column 1 (voltage), y = column 2 (current)
Function f = results.asFunction(1, 2);

Now you can get a value of current for any arbitrary value of voltage, eg:

double current = f.value(3.23321);

You can filter which data-points to use by use of a Predicate:

results.asFunction(int xData, int yData, Predicate<Result> filter);

Thus, if we wanted the same again but only for data-points that were recorded after 5 seconds:

Function f = results.asFunction(1,  2, (r) -> r.get(0) > 5.0);

Polynomial Fitting

Top ↑

The most straight-forward fitting option you have is to perform a polynomial fit. You can do this to any ResultTable by use of the polyFit(...) method. For example, let's say we have a ResultTable that has 4 columns: Voltage, Current, Frequency and Temperature.

ResultTable results = new ResultList("Voltage", "Current", "Frequency", "Temperature");

Now, to perform a straight-forward polynomial fit, we can use:

Function fitted = results.polyFit(int xData, int yData, int degree);

where xData is the column number to use for our x-values, yData is the same for our y-values and degree is the degree of the polynomial to fit (ie 1 for linenar, 2 for quadratic, 3 for cubic etc). The returned Function object represents the fitted line.

Let's say we wanted to fit current against voltage linearly:

Function linearFit = results.polyFit(0, 1, 1);

or, the same but a 4th order polynomial instead:

Function fitted = results.polyFit(0, 1, 4);

If you only want to plot a portion of your data, then you can specify a filter by providing a Predicate like so:

Function fitted = results.polyFit(int xData, int yData, Predicate<Result> filter, int degree);

For instance:

Function fitted = results.polyFit(0, 1, (r) -> r.get(3) > 150, 4);

In this above example we have again fitted a 4th order polynomial but only for data-points where the value in the Temperature column is greater than 150.

You can extract the polynomial co-efficients from the Function by use of getCoefficients(). They will be returned in an array, ordered by their order (ie element 0 will correspond to 0th order co-efficient, element 5 corresponds to 5th order etc):

Function fitted       = results.polyfit(0, 1, 4);
double[] coefficients = fitted.getCoefficients();

// f(x) = ax^4 + bx^3 + cx^2 + dx + e
double a = coefficients[4];
double b = coefficients[3];
double c = coefficients[2];
double d = coefficients[1];
double e = coefficients[0];

Introducing a Plot into the mix:

SMU         smu     = new K2450(...);
ResultTable results = new ResultList("V", "I", "f", "T");
Plot        plot    = new Plot("Results", "Voltage [V]", "Current [A]");
Series      data    = plot.watchList(results, 0, 1, "Data", Color.BLACK);

data.setLineWidth(0.0); // Hide the line, only show markers for raw data

plot.show();

smu.turnOn();
for (double v : Util.makeLinearArray(-10, +10, 21)) {
    smu.setVoltage(v);
    results.addData(smu.getVoltage(), smu.getCurrent());
}
smu.turnOff();

Function fitted = results.polyFit(0,1,1);
Series   fit    = plot.plotFunction(fitted, -12, +12, 100, "Fit", Color.RED);

double[] coeffs   = fitted.getCoefficients();
double   gradient = coeffs[1]; // First-order co-efficient
System.out.printf("Gradient is %e A/V\n", gradient);

This will give us:

Terminal Output:

Gradient is 2.800547e-03 A/V

Or if we change to a 4th-order fit...

Function fitted = results.polyFit(0,1,4);

...then run it again, we get:

General Fitting

Top ↑

If you have a more general function to fit, then you can use the fit(...) method. To define a function to fit you need to create a PFunction object. These are almost the same as a Function object except that they have some variable parameters. For instance, if we want to fit:

Where a and b are parameters to be fitted, then we would create the following:

PFunction toFit = (x, params) -> params[0]*Math.sin(params[1]*x);

So the difference for a PFunction is that it takes an extra argument (params as we called it here) which is an array of the fitting parameters.

We can then perform a fit by using:

Function fitted = results.fit(int xData, int yData, PFunction f, double... guesses);

where as before xData and yData define which columns we're fitting, f is the function to fit to and guesses are our initial guesses for each fitting parameter. Let's say we guess a = 5.0 and b = 1.0, then we'd write:

ResultTable results = new ResultList("Time", "Voltage", "Current");
PFunction   toFit   = (x, params)  -> params[0]*Math.sin(params[1]*x);

// Fit Voltage vs Time, guessing a=5.0 and b=1.0 to start with
Function    fitted  = results.fit(0, 1, toFit, 5.0, 1.0);

Again, if you want to filter which data to use you can specify a Predicate:

results.fit(int xData, int yData, Predicate<Result> filter, PFunction f, double... guesses);

Looking at a full example:

Java

SMU         smu     = new K2450(...);
ResultTable results = new ResultList("Time", "Voltage", "Current");
Plot        plot    = new Plot("Results", "Time [s]", "Voltage [V]");
Series      data    = plot.watchList(results, 0, 1, "Data", Color.BLACK);

data.setLineWidth(0.0); // Hide the line, only show markers for raw data

plot.show();

smu.turnOn();
// Take a measurement every second for 10 seconds
for (double t = 0; t <= 10.0; t += 1.0) {
    results.addData(t, smu.getVoltage(), smu.getCurrent());
    Util.sleep(1000);
}
smu.turnOff();

PFunction toFit  = (t, p) -> p[0]*Math.sin(p[1]*t);
Function  fitted = results.fit(0, 1, toFit, 5.0, 1.0);
Series    fit    = plot.plotFunction(fitted, 0, 10, 100, "Fit", Color.RED);

double[] coeffs   = fitted.getCoefficients();
double   a = coeffs[0];
double   b = coeffs[1];
System.out.printf("a = %e, b = %e\n", a, b);

Python

smu     = K2450(...)
results = ResultList(["Time", "Voltage", "Current"])
plot    = Plot("Results", "Time [s]", "Voltage [V]")
data    = plot.watchList(results, 0, 1, "Data", Color.BLACK)

data.setLineWidth(0.0)

plot.show()

smu.turnOn()

t = 0;
while t <= 10.0:
    results.addData([t, smu.getVoltage(), smu.getCurrent()])
    Util.sleep(1000)
    t += 1.0

smu.turnOff();

toFit  = lambda t, p : p[0]*Math.sin(p[1]*t)
fitted = results.fit(0, 1, toFit, 5.0, 1.0)
fit    = plot.plotFunction(fitted, 0, 10, 100, "Fit", Color.RED)

coeffs = fitted.getCoefficients()
a      = coeffs[0]
b      = coeffs[1]
print("a = %e, b = %e" % (a, b));

If the signal was actually:

Then we get:

a = 4.300000e+00, b = 8.000000e-01
Clone this wiki locally