package gdsc.smlm.function.gaussian;
import gdsc.core.utils.DoubleEquality;
import gdsc.smlm.TestSettings;
import gdsc.smlm.function.gaussian.EllipticalGaussian2DFunction;
import gdsc.smlm.function.gaussian.Gaussian2DFunction;
import gdsc.smlm.function.gaussian.GaussianFunctionFactory;
import java.util.ArrayList;
import java.util.Random;
import org.apache.commons.math3.util.FastMath;
import org.junit.Assert;
import org.junit.Test;
/**
* Contains speed tests for the fastest method for calculating the Hessian and gradient vector
* for use in NonLinearFit
*/
public class SpeedTest
{
int Single = 1;
int Double = 2;
private int MAX_ITER = 20000;
private int blockWidth = 10;
private double Background = 20;
private double Amplitude = 10;
private double Xpos = 5;
private double Ypos = 5;
private double Xwidth = 5;
private Random rand;
private static ArrayList<double[]> paramsListSinglePeak = null;
private static ArrayList<double[]> yListSinglePeak;
private static int[] x;
private static ArrayList<double[]> paramsListDoublePeak;
private static ArrayList<double[]> yListDoublePeak;
public SpeedTest()
{
// TODO - the data generation could be static in the Gaussian2DFunctionTest since
// all gaussian data generation should be similar
rand = new Random(30051977);
if (paramsListSinglePeak == null)
{
paramsListSinglePeak = new ArrayList<double[]>(MAX_ITER);
yListSinglePeak = new ArrayList<double[]>(MAX_ITER);
x = createData(1, MAX_ITER, paramsListSinglePeak, yListSinglePeak);
paramsListDoublePeak = new ArrayList<double[]>(MAX_ITER);
yListDoublePeak = new ArrayList<double[]>(MAX_ITER);
x = createData(2, MAX_ITER, paramsListDoublePeak, yListDoublePeak);
}
}
@Test
public void freeCircularComputesSameAsEllipticalSinglePeak()
{
f1ComputesSameAsf2(Single, GaussianFunctionFactory.FIT_FREE_CIRCLE, GaussianFunctionFactory.FIT_ELLIPTICAL);
}
@Test
public void freeCircularFasterThanEllipticalSinglePeak()
{
f1FasterThanf2(Single, GaussianFunctionFactory.FIT_FREE_CIRCLE, GaussianFunctionFactory.FIT_ELLIPTICAL);
}
@Test
public void circularComputesSameAsFreeCircularSinglePeak()
{
f1ComputesSameAsf2(Single, GaussianFunctionFactory.FIT_CIRCLE, GaussianFunctionFactory.FIT_FREE_CIRCLE);
}
@Test
public void circularFasterThanFreeCircularSinglePeak()
{
f1FasterThanf2(Single, GaussianFunctionFactory.FIT_CIRCLE, GaussianFunctionFactory.FIT_FREE_CIRCLE);
}
@Test
public void fixedComputesSameAsFreeCircularSinglePeak()
{
f1ComputesSameAsf2(Single, GaussianFunctionFactory.FIT_FIXED, GaussianFunctionFactory.FIT_FREE_CIRCLE);
}
@Test
public void fixedFasterThanFreeCircularSinglePeak()
{
f1FasterThanf2(Single, GaussianFunctionFactory.FIT_FIXED, GaussianFunctionFactory.FIT_FREE_CIRCLE);
}
@Test
public void freeCircularComputesSameAsEllipticalSinglePeakNB()
{
f1ComputesSameAsf2(Single, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE,
GaussianFunctionFactory.FIT_SIMPLE_NB_ELLIPTICAL);
}
@Test
public void freeCircularFasterThanEllipticalSinglePeakNB()
{
f1FasterThanf2(Single, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE, GaussianFunctionFactory.FIT_SIMPLE_NB_ELLIPTICAL);
}
@Test
public void circularComputesSameAsFreeCircularSinglePeakNB()
{
f1ComputesSameAsf2(Single, GaussianFunctionFactory.FIT_SIMPLE_NB_CIRCLE, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE);
}
@Test
public void circularFasterThanFreeCircularSinglePeakNB()
{
f1FasterThanf2(Single, GaussianFunctionFactory.FIT_SIMPLE_NB_CIRCLE, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE);
}
@Test
public void fixedComputesSameAsFreeCircularSinglePeakNB()
{
f1ComputesSameAsf2(Single, GaussianFunctionFactory.FIT_SIMPLE_NB_FIXED, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE);
}
@Test
public void fixedFasterThanFreeCircularSinglePeakNB()
{
f1FasterThanf2(Single, GaussianFunctionFactory.FIT_SIMPLE_NB_FIXED, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE);
}
@Test
public void freeCircularComputesSameAsEllipticalDoublePeak()
{
f1ComputesSameAsf2(Double, GaussianFunctionFactory.FIT_FREE_CIRCLE, GaussianFunctionFactory.FIT_ELLIPTICAL);
}
@Test
public void freeCircularFasterThanEllipticalDoublePeak()
{
f1FasterThanf2(Double, GaussianFunctionFactory.FIT_FREE_CIRCLE, GaussianFunctionFactory.FIT_ELLIPTICAL);
}
@Test
public void circularComputesSameAsFreeCircularDoublePeak()
{
f1ComputesSameAsf2(Double, GaussianFunctionFactory.FIT_CIRCLE, GaussianFunctionFactory.FIT_FREE_CIRCLE);
}
@Test
public void circularFasterThanFreeCircularDoublePeak()
{
f1FasterThanf2(Double, GaussianFunctionFactory.FIT_CIRCLE, GaussianFunctionFactory.FIT_FREE_CIRCLE);
}
@Test
public void fixedComputesSameAsFreeCircularDoublePeak()
{
f1ComputesSameAsf2(Double, GaussianFunctionFactory.FIT_FIXED, GaussianFunctionFactory.FIT_FREE_CIRCLE);
}
@Test
public void fixedFasterThanFreeCircularDoublePeak()
{
f1FasterThanf2(Double, GaussianFunctionFactory.FIT_FIXED, GaussianFunctionFactory.FIT_FREE_CIRCLE);
}
@Test
public void freeCircularComputesSameAsEllipticalDoublePeakNB()
{
f1ComputesSameAsf2(Double, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE,
GaussianFunctionFactory.FIT_SIMPLE_NB_ELLIPTICAL);
}
@Test
public void freeCircularFasterThanEllipticalDoublePeakNB()
{
f1FasterThanf2(Double, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE, GaussianFunctionFactory.FIT_SIMPLE_NB_ELLIPTICAL);
}
@Test
public void circularComputesSameAsFreeCircularDoublePeakNB()
{
f1ComputesSameAsf2(Double, GaussianFunctionFactory.FIT_SIMPLE_NB_CIRCLE, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE);
}
@Test
public void circularFasterThanFreeCircularDoublePeakNB()
{
f1FasterThanf2(Double, GaussianFunctionFactory.FIT_SIMPLE_NB_CIRCLE, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE);
}
@Test
public void fixedComputesSameAsFreeCircularDoublePeakNB()
{
f1ComputesSameAsf2(Double, GaussianFunctionFactory.FIT_SIMPLE_NB_FIXED, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE);
}
@Test
public void fixedFasterThanFreeCircularDoublePeakNB()
{
f1FasterThanf2(Double, GaussianFunctionFactory.FIT_SIMPLE_NB_FIXED, GaussianFunctionFactory.FIT_SIMPLE_NB_FREE_CIRCLE);
}
void f1ComputesSameAsf2(int npeaks, int flags1, int flags2)
{
DoubleEquality eq = new DoubleEquality(2, 1e-10);
int iter = 2000;
ArrayList<double[]> paramsList2 = (npeaks == 1) ? copyList(paramsListSinglePeak, iter)
: copyList(paramsListDoublePeak, iter);
Gaussian2DFunction f1 = GaussianFunctionFactory.create2D(1, blockWidth, blockWidth, flags1, null);
Gaussian2DFunction f2 = GaussianFunctionFactory.create2D(1, blockWidth, blockWidth, flags2, null);
double[] dyda1 = new double[1 + npeaks * 6];
double[] dyda2 = new double[1 + npeaks * 6];
int[] gradientIndices = f1.gradientIndices();
int[] g1 = new int[gradientIndices.length];
int[] g2 = new int[gradientIndices.length];
int nparams = 0;
for (int i = 0; i < gradientIndices.length; i++)
{
int index1 = f1.findGradientIndex(g1[i]);
int index2 = f2.findGradientIndex(g2[i]);
if (index1 >= 0 && index2 >= 0)
{
g1[nparams] = index1;
g2[nparams] = index2;
nparams++;
}
}
for (int i = 0; i < paramsList2.size(); i++)
{
f1.initialise(paramsList2.get(i));
f2.initialise(paramsList2.get(i));
for (int j = 0; j < x.length; j++)
{
double y1 = f1.eval(x[j], dyda1);
double y2 = f2.eval(x[j], dyda2);
Assert.assertTrue("Not same y[" + j + "] @ " + i + " " + y1 + " != " + y2,
eq.almostEqualComplement(y1, y2));
for (int ii = 0; ii < nparams; ii++)
Assert.assertTrue("Not same dyda[" + j + "] @ " + gradientIndices[g1[ii]] + ": " + dyda1[g1[ii]] +
" != " + dyda2[g2[ii]], eq.almostEqualComplement(dyda1[g1[ii]], dyda2[g2[ii]]));
}
}
}
void f1FasterThanf2(int npeaks, int flags1, int flags2)
{
org.junit.Assume.assumeTrue(TestSettings.RUN_SPEED_TESTS);
ArrayList<double[]> paramsList2 = (npeaks == 1) ? paramsListSinglePeak : paramsListDoublePeak;
// Use the full list of parameters to build the functions
Gaussian2DFunction f1 = GaussianFunctionFactory.create2D(npeaks, blockWidth, blockWidth, flags1, null);
Gaussian2DFunction f2 = GaussianFunctionFactory.create2D(npeaks, blockWidth, blockWidth, flags2, null);
double[] dyda = new double[1 + npeaks * 6];
for (int i = 0; i < paramsList2.size(); i++)
{
f1.initialise(paramsList2.get(i));
for (int j = 0; j < x.length; j++)
f1.eval(x[j], dyda);
}
long start1 = System.nanoTime();
for (int i = 0; i < paramsList2.size(); i++)
{
f1.initialise(paramsList2.get(i));
for (int j = 0; j < x.length; j++)
f1.eval(x[j], dyda);
}
start1 = System.nanoTime() - start1;
for (int i = 0; i < paramsList2.size(); i++)
{
f2.initialise(paramsList2.get(i));
for (int j = 0; j < x.length; j++)
f2.eval(x[j], dyda);
}
long start2 = System.nanoTime();
for (int i = 0; i < paramsList2.size(); i++)
{
f2.initialise(paramsList2.get(i));
for (int j = 0; j < x.length; j++)
f2.eval(x[j], dyda);
}
start2 = System.nanoTime() - start2;
log("%s = %d : %s = %d : %fx\n", f1.getClass().getName(), start1, f2.getClass().getName(), start2,
(1.0 * start2) / start1);
if (TestSettings.ASSERT_SPEED_TESTS)
Assert.assertTrue(start2 > start1);
}
/**
* Create random elliptical Gaussian data an returns the data plus an estimate of the parameters.
* Only the chosen parameters are randomised and returned for a maximum of (background, amplitude, angle, xpos,
* ypos, xwidth, ywidth }
*
* @param params
* set on output
* @return
*/
private double[] doubleCreateGaussianData(int npeaks, double[] params)
{
int n = blockWidth * blockWidth;
// Generate a 2D Gaussian
EllipticalGaussian2DFunction func = new EllipticalGaussian2DFunction(npeaks, blockWidth, blockWidth);
params[0] = Background + rand.nextFloat() * 5f;
for (int i = 0, j = 1; i < npeaks; i++, j += 6)
{
params[j] = Amplitude + rand.nextFloat() * 5f;
params[j + 1] = 0f; //(double) (Math.PI / 4.0); // Angle
params[j + 2] = Xpos + rand.nextFloat() * 2f;
params[j + 3] = Ypos + rand.nextFloat() * 2f;
params[j + 4] = Xwidth + rand.nextFloat() * 2f;
params[j + 5] = params[j + 4];
}
double[] dy_da = new double[params.length];
double[] y = new double[n];
func.initialise(params);
for (int i = 0; i < y.length; i++)
{
// Add random noise
y[i] = func.eval(i, dy_da) + ((rand.nextFloat() < 0.5f) ? -rand.nextFloat() * 5f : rand.nextFloat() * 5f);
}
// Randomise only the necessary parameters (i.e. not angle and X & Y widths should be the same)
params[0] += ((rand.nextFloat() < 0.5f) ? -rand.nextFloat() : rand.nextFloat());
for (int i = 0, j = 1; i < npeaks; i++, j += 6)
{
params[j + 1] += ((rand.nextFloat() < 0.5f) ? -rand.nextFloat() : rand.nextFloat());
params[j + 3] += ((rand.nextFloat() < 0.5f) ? -rand.nextFloat() : rand.nextFloat());
params[j + 4] += ((rand.nextFloat() < 0.5f) ? -rand.nextFloat() : rand.nextFloat());
params[j + 5] = params[j + 4];
}
return y;
}
protected int[] createData(int npeaks, int iter, ArrayList<double[]> paramsList, ArrayList<double[]> yList)
{
int[] x = new int[blockWidth * blockWidth];
for (int i = 0; i < x.length; i++)
x[i] = i;
for (int i = 0; i < iter; i++)
{
double[] params = new double[1 + 6 * npeaks];
double[] y = doubleCreateGaussianData(npeaks, params);
paramsList.add(params);
yList.add(y);
}
return x;
}
protected ArrayList<double[]> copyList(ArrayList<double[]> paramsList, int iter)
{
iter = FastMath.min(iter, paramsList.size());
ArrayList<double[]> params2List = new ArrayList<double[]>(iter);
for (int i = 0; i < iter; i++)
{
params2List.add(paramsList.get(i));
}
return params2List;
}
void log(String format, Object... args)
{
System.out.printf(format, args);
}
}