/*-
* Copyright (c) 2012 Diamond Light Source Ltd.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package uk.ac.diamond.scisoft.analysis.fitting.functions;
import org.eclipse.dawnsci.analysis.api.fitting.functions.IParameter;
import org.eclipse.january.dataset.DoubleDataset;
/**
* Basically an implementation of a simple cubic spline calculator
*/
public class CubicSpline extends AFunction {
private static String NAME = "CubicSpline";
private static String DESC = "Construct a cubic spline given points - this is not a fitting function";
private transient double[] a = null, b = null, c = null, d = null;
private transient double[] x = null, y = null;
public CubicSpline() {
this(4);
}
public CubicSpline(int numberOfParameters) {
super(numberOfParameters);
}
public CubicSpline(IParameter... params) {
super(params);
}
/**
* Constructor for use with Global Optimisers
* @param xpoints The x positions for the control points
* @param ystartpoints the start y positions for the control points
* @param deviation the amount the optimiser can go from the specified y value
*/
public CubicSpline(double[] xpoints, double[] ystartpoints, double deviation) {
super(ystartpoints);
setNames();
for(int i = 0; i < parameters.length; i++) {
IParameter p = parameters[i];
p.setLimits(p.getValue() - deviation, p.getValue() + deviation);
}
x = xpoints;
}
/**
* Constructor for normal use
* @param xpoints The x positions for the control points
* @param ystartpoints the start y positions for the control points
*/
public CubicSpline(double[] xpoints, double[] ystartpoints) {
super(ystartpoints);
setNames();
x = xpoints;
}
@Override
protected void setNames() {
setNames(NAME, DESC);
}
protected void generateSpline(double[] newx, double[] newy) throws IllegalArgumentException {
x = newx;
y = newy;
int n = 0;
if (x == null)
return;
if (x.length == y.length) {
n = x.length-1;
} else {
throw new IllegalArgumentException("x and y arrays are not the same length");
}
double[] h = new double[n];
double[] bb = new double[n];
for (int i = 0; i < n; i++) {
h[i] = x[i + 1] - x[i];
bb[i] = (y[i + 1] - y[i]) / h[i];
}
double[] u = new double[n];
double[] v = new double[n];
u[1] = 2.0*(h[0]+h[1]);
v[1] = 6.0*(bb[1]-bb[0]);
for (int i = 2; i < n; i++) {
u[i] = 2.0 * (h[i - 1] + h[i]) - (h[i - 1] * h[i - 1]) / u[i - 1];
v[i] = 6.0 * (bb[i] - bb[i - 1]) - (h[i - 1] * v[i - 1]) / u[i - 1];
}
double[] z = new double[n+1];
z[n] = 0;
for(int i = n-1; i > 0; i--) {
z[i] = (v[i] - h[i] * z[i + 1]) / u[i];
}
// now calculate the parameters
a = new double[n];
b = new double[n];
c = new double[n];
d = new double[n];
for (int i = 0; i < n; i++) {
a[i] = y[i];
b[i] = (-h[i] / 6.0) * z[i + 1] - (h[i] / 3.0) * z[i] + (y[i + 1] - y[i]) / h[i];
c[i] = z[i] / 2.0;
d[i] = (z[i + 1] - z[i]) / (6.0 * h[i]);
}
setDirty(false);
}
protected int getRegion(double xvalue) {
// get the region
if (x == null)
return 0;
for (int i = 0; i < x.length-1; i++) {
if (xvalue < x[i+1]) {
return i;
}
}
return x.length-2;
}
protected double evaluateSpline(double xvalue) {
int i = getRegion(xvalue);
if (x == null)
return 0;
double dx = (xvalue - x[i]);
double result = a[i] + dx * (b[i] + (dx * (c[i] + (dx * d[i]))));
return result;
}
@Override
public boolean isDirty() {
if (super.isDirty())
return true;
//check the values to see if they have changed
for (int i = 0; i < y.length; i++) {
if (parameters[i].getValue() != y[i]) {
setDirty(true);
return true;
}
}
return false;
}
@Override
public double val(double... values) {
if (isDirty()) {
// build the spline
generateSpline(x, getParameterValues());
}
return evaluateSpline(values[0]);
}
@Override
public void fillWithValues(DoubleDataset data, CoordinatesIterator it) {
if (isDirty()) {
// build the spline
generateSpline(x, getParameterValues());
}
it.reset();
double[] coords = it.getCoordinates();
int i = 0;
double[] buffer = data.getData();
while (it.hasNext()) {
buffer[i++] = evaluateSpline(coords[0]);
}
}
}