-
Notifications
You must be signed in to change notification settings - Fork 9
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.
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:
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);
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:
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
- Getting Started
- Object Orientation
- Choosing a Language
- Using JISA in Java
- Using JISA in Python
- Using JISA in Kotlin
- Exceptions
- Functions as Objects
- Instrument Basics
- SMUs
- Thermometers (and old TCs)
- PID and Temperature Controllers
- Lock-Ins
- Power Supplies
- Pre-Amplifiers
- Writing New Drivers