View Javadoc

1   package de.desy.acop.video.analysis;
2   
3   import java.util.Arrays;
4   
5   import org.apache.commons.math.FunctionEvaluationException;
6   import org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction;
7   import org.apache.commons.math.analysis.MultivariateMatrixFunction;
8   import org.apache.commons.math.optimization.OptimizationException;
9   import org.apache.commons.math.optimization.VectorialPointValuePair;
10  import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
11  
12  /**
13   * 
14   * <code>FittingUtilities</code> provides a set of utilities to fit a 
15   * gaussian function to a set of data
16   *
17   * @author <a href="mailto:jaka.bobnar@cosylab.com">Jaka Bobnar</a>
18   *
19   */
20  public class FittingUtilities {
21  
22  	private static class GaussWithOffset implements DifferentiableMultivariateVectorialFunction {
23  		
24  		private static final long serialVersionUID = 1L;
25  		private double[] x;
26  		
27  		/**
28  		 * Constructs a new GaussWithOffset, where the values are known for the 
29  		 * given horizontal axis.
30  		 * 
31  		 * @param x the horiontal axis values
32  		 */
33  		public GaussWithOffset(double[] x) {
34  			this.x = x;
35  		}
36  				
37  		/*
38  		 * (non-Javadoc)
39  		 * @see org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction#jacobian()
40  		 */
41  		public MultivariateMatrixFunction jacobian() {
42  			return new MultivariateMatrixFunction(){
43  				private static final long serialVersionUID = 1L;
44  				public double[][] value(double[] a) throws FunctionEvaluationException, IllegalArgumentException {
45  					double[][] J = new double[x.length][a.length];
46  					for (int i = 0; i < J.length; i++) {
47  						J[i][0] = Math.exp(-a[1]*(x[i]-a[2])*(x[i]-a[2]));
48  						J[i][1] = -J[i][0]*a[0]*(x[i]-a[2])*(x[i]-a[2]);
49  						J[i][2] = a[0]*J[i][0]*a[1]*2*(x[i]-a[2]);
50  						J[i][3] = 1;
51  					}
52  					return J;
53  				}
54  			};
55  		}
56  				
57  		/*
58  		 * (non-Javadoc)
59  		 * @see org.apache.commons.math.analysis.MultivariateVectorialFunction#value(double[])
60  		 */
61  		public double[] value(double[] a) throws FunctionEvaluationException, IllegalArgumentException {
62  			double[] f = new double[x.length];
63  			for (int i = 0; i < x.length; i++) {
64  				f[i] = a[0] * Math.exp(-a[1]*(x[i]-a[2])*(x[i]-a[2])) + a[3];
65  			}
66  			return f;
67  		}
68  	}
69  	
70  	private static class GaussWithLinear implements DifferentiableMultivariateVectorialFunction {
71  		
72  		private static final long serialVersionUID = 1L;
73  		private double[] x;
74  		
75  		/**
76  		 * Constructs a new GaussWithOffset, where the values are known for the 
77  		 * given horizontal axis.
78  		 * 
79  		 * @param x the horiontal axis values
80  		 */
81  		public GaussWithLinear(double[] x) {
82  			this.x = x;
83  		}
84  				
85  		/*
86  		 * (non-Javadoc)
87  		 * @see org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction#jacobian()
88  		 */
89  		public MultivariateMatrixFunction jacobian() {
90  			return new MultivariateMatrixFunction(){
91  				private static final long serialVersionUID = 1L;
92  				public double[][] value(double[] a) throws FunctionEvaluationException, IllegalArgumentException {
93  					double[][] J = new double[x.length][a.length];
94  					for (int i = 0; i < J.length; i++) {
95  						J[i][0] = Math.exp(-a[1]*(x[i]-a[2])*(x[i]-a[2]));
96  						J[i][1] = -J[i][0]*a[0]*(x[i]-a[2])*(x[i]-a[2]);
97  						J[i][2] = a[0]*J[i][0]*a[1]*2*(x[i]-a[2]);
98  						J[i][3] = 1;
99  						J[i][4] = x[i];
100 					}
101 					return J;
102 				}
103 			};
104 		}
105 				
106 		/*
107 		 * (non-Javadoc)
108 		 * @see org.apache.commons.math.analysis.MultivariateVectorialFunction#value(double[])
109 		 */
110 		public double[] value(double[] a) throws FunctionEvaluationException, IllegalArgumentException {
111 			double[] f = new double[x.length];
112 			for (int i = 0; i < x.length; i++) {
113 				f[i] = a[0] * Math.exp(-a[1]*(x[i]-a[2])*(x[i]-a[2])) + a[3] + a[4]*x[i];
114 			}
115 			return f;
116 		}
117 	}
118 	
119 	private static class Gauss implements DifferentiableMultivariateVectorialFunction {
120 		
121 		private static final long serialVersionUID = 1L;
122 		private double[] x;
123 		
124 		/**
125 		 * Constructs a new GaussWithOffset, where the values are known for the 
126 		 * given horizontal axis.
127 		 * 
128 		 * @param x the horiontal axis values
129 		 */
130 		public Gauss(double[] x) {
131 			this.x = x;
132 		}
133 				
134 		/*
135 		 * (non-Javadoc)
136 		 * @see org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction#jacobian()
137 		 */
138 		public MultivariateMatrixFunction jacobian() {
139 			return new MultivariateMatrixFunction(){
140 				private static final long serialVersionUID = 1L;
141 				public double[][] value(double[] a) throws FunctionEvaluationException, IllegalArgumentException {
142 					double[][] J = new double[x.length][a.length];
143 					for (int i = 0; i < J.length; i++) {
144 						J[i][0] = Math.exp(-a[1]*(x[i]-a[2])*(x[i]-a[2]));
145 						J[i][1] = -J[i][0]*a[0]*(x[i]-a[2])*(x[i]-a[2]);
146 						J[i][2] = a[0]*J[i][0]*a[1]*2*(x[i]-a[2]);
147 					}
148 					return J;
149 				}
150 			};
151 		}
152 				
153 		/*
154 		 * (non-Javadoc)
155 		 * @see org.apache.commons.math.analysis.MultivariateVectorialFunction#value(double[])
156 		 */
157 		public double[] value(double[] a) throws FunctionEvaluationException, IllegalArgumentException {
158 			double[] f = new double[x.length];
159 			for (int i = 0; i < x.length; i++) {
160 				f[i] = a[0] * Math.exp(-a[1]*(x[i]-a[2])*(x[i]-a[2]));
161 			}
162 			return f;
163 		}
164 	}
165 	
166 //	private static class Gauss2D implements DifferentiableMultivariateVectorialFunction {
167 //		
168 //		private static final long serialVersionUID = 1L;
169 //		private double[] x;
170 //		private double[] y;
171 //		
172 //		/**
173 //		 * Constructs a new GaussWithOffset, where the values are known for the 
174 //		 * given horizontal and vertical axes.
175 //		 * 
176 //		 * @param x the horiontal axis values
177 //		 * @param y the vertical axis values
178 //		 */
179 //		public Gauss2D(double[] x, double[] y) {
180 //			this.x = x;
181 //			this.y = y;
182 //		}
183 //				
184 //		/*
185 //		 * (non-Javadoc)
186 //		 * @see org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction#jacobian()
187 //		 */
188 //		public MultivariateMatrixFunction jacobian() {
189 //			return new MultivariateMatrixFunction(){
190 //				private static final long serialVersionUID = 1L;
191 //				public double[][] value(double[] a) throws FunctionEvaluationException, IllegalArgumentException {
192 //					double[][] J = new double[x.length][a.length];
193 //					double d = 0;
194 //					for (int i = 0; i < J.length; i++) {
195 //						d = Math.exp(-a[1]*(x[i]-a[4])*(x[i]-a[4]) - 2*a[2]*(y[i]-a[5])*(x[i]-a[4]) - a[3]*(y[i]-a[5])*(y[i]-a[5]));
196 //						J[i][0] = d;
197 //						d *= a[0];
198 //						J[i][1] = -d*(x[i]-a[4])*(x[i]-a[4]);
199 //						J[i][2] = -d*2*(x[i]-a[4])*(y[i]-a[5]);
200 //						J[i][3] = -d*(y[i]-a[5])*(y[i]-a[5]);
201 //						J[i][4] = d*(a[1]*2*(x[i]-a[4] + 2*a[2]*(y[i]-a[5])));
202 //						J[i][5] = d*(a[3]*2*(y[i]-a[5] + 2*a[2]*(x[i]-a[4])));
203 //						J[i][6] = 1;
204 //					}
205 //					return J;
206 //				}
207 //			};
208 //		}
209 //				
210 //		/*
211 //		 * (non-Javadoc)
212 //		 * @see org.apache.commons.math.analysis.MultivariateVectorialFunction#value(double[])
213 //		 */
214 //		public double[] value(double[] a) throws FunctionEvaluationException, IllegalArgumentException {
215 //			double[] f = new double[x.length];
216 //			for (int i = 0; i < x.length; i++) {
217 //				f[i] = a[0] * Math.exp(-a[1]*(x[i]-a[4])*(x[i]-a[4]) - 2*a[2]*(y[i]-a[5])*(x[i]-a[4]) - a[3]*(y[i]-a[5])*(y[i]-a[5])) + a[6];
218 //			}
219 //			return f;
220 //		}
221 //	}
222 	
223 	/**
224 	 * Fits a gauss with offset y = Ae^(-(x-C)^2/(2B^2)) + D to the given data.
225 	 * @param x the independent variable data
226 	 * @param y the dependent variable data
227 	 * @param weights the weights of the measurements
228 	 * @param startValues starting values for the parameters
229 	 * 
230 	 * @return array of calculated parameters ([A,B,C,D])
231 	 * 
232 	 * @throws OptimizationException
233 	 * @throws FunctionEvaluationException
234 	 * @throws IllegalArgumentException
235 	 * 
236 	 * @deprecated ues {@link FittingUtilities#lmLinear(double[], double[], double[], double[])}
237 	 */
238 	@Deprecated
239 	public static double[] lmOffset(double[] x, double[] y, double[] weights, double[] startValues) throws OptimizationException, FunctionEvaluationException, IllegalArgumentException {
240 		LevenbergMarquardtOptimizer lmo = new LevenbergMarquardtOptimizer();
241 //		GaussNewtonOptimizer lmo = new GaussNewtonOptimizer(true);
242 		lmo.setMaxIterations(500);
243 		GaussWithOffset f = new GaussWithOffset(x);
244 		VectorialPointValuePair pvp = lmo.optimize(f,y,weights,startValues);
245 		double[] a = pvp.getPointRef();
246 		a[1] = 1./Math.sqrt(2*a[1]);
247 		return a;
248 	}	
249 	
250 	/**
251 	 * Fits a gauss with offset y = Ae^(-(x-C)^2/(2B^2)) + D + E x to the given data.
252 	 * @param x the independent variable data
253 	 * @param y the dependent variable data
254 	 * @param weights the weights of the measurements
255 	 * @param startValues starting values for the parameters
256 	 * 
257 	 * @return array of calculated parameters ([A,B,C,D,E])
258 	 * 
259 	 * @throws OptimizationException
260 	 * @throws FunctionEvaluationException
261 	 * @throws IllegalArgumentException
262 	 */
263 	public static double[] lmLinear(double[] x, double[] y, double[] weights, double[] startValues) throws OptimizationException, FunctionEvaluationException, IllegalArgumentException {
264 		LevenbergMarquardtOptimizer lmo = new LevenbergMarquardtOptimizer();
265 		lmo.setInitialStepBoundFactor(0.3);
266 		lmo.setMaxIterations(1000);
267 		startValues[1] = 1/(2*startValues[1]);
268 		GaussWithLinear f = new GaussWithLinear(x);
269 		VectorialPointValuePair pvp = lmo.optimize(f,y,weights,startValues);
270 		double[] a = pvp.getPointRef();
271 		a[1] = 1./Math.sqrt(2*a[1]);		
272 		return a;
273 	}
274 	
275 	/**
276 	 * Fits a gauss with offset y = Ae^(-(x-C)^2/(2B^2)) to the given data.
277 	 * @param x the independent variable data
278 	 * @param y the dependent variable data
279 	 * @param weights the weights of the measurements
280 	 * @param startValues starting values for the parameters
281 	 * 
282 	 * @return array of calculated parameters ([A,B,C])
283 	 * 
284 	 * @throws OptimizationException
285 	 * @throws FunctionEvaluationException
286 	 * @throws IllegalArgumentException
287 	 */
288 	public static double[] lmGauss(double[] x, double[] y, double[] weights, double[] startValues) throws OptimizationException, FunctionEvaluationException, IllegalArgumentException {
289 		LevenbergMarquardtOptimizer lmo = new LevenbergMarquardtOptimizer();
290 		lmo.setInitialStepBoundFactor(0.3);
291 		lmo.setMaxIterations(1000);
292 		startValues[1] = 1/(2*startValues[1]);
293 		Gauss f = new Gauss(x);
294 		VectorialPointValuePair pvp = lmo.optimize(f,y,weights,startValues);
295 		double[] a = pvp.getPointRef();
296 		a[1] = 1./Math.sqrt(2*a[1]);
297 		return a;
298 	}
299 		
300 	public static void main(String[] args) throws MathException {
301 		int n = 1000;
302 		double[] x = new double[n];
303 		for (int i = 0; i < x.length; i++) {
304 			x[i] = 5*Math.exp(-(i-n/2)*(i-n/2)/(2*n*n/100));
305 		}
306 		double[] res = fit(x,0);
307 		System.out.println(Arrays.toString(res));
308 	}
309 	
310 	/**
311 	 * Performs a linear fit of exponential function to the y data.
312 	 * 
313 	 * @param y
314 	 * @param constY
315 	 * @return
316 	 */
317 	public static double[] fit(double[] y, double constY) throws MathException {
318 		double[][] A = new double[3][3];
319 		double[] b = new double[3];
320 		double val;
321 		double length = y.length;
322 		try {
323 			int i = 0;
324 			double v;
325 			double x;
326     		for (;;) {
327     			//normalize to eliminate overflow
328     			val = (y[i]-constY)/length;
329     			if (val <= 0) {
330     				val = 0;
331     			} else {
332     				val = Math.log(val);
333     			}
334     			//extra weight to boost large values instead of small
335     			v = y[i]-constY;
336     			//normalize to eliminate overflow
337     			x = i/length;
338     			A[0][0] += x*x*x*x*v;
339     			A[0][1] += x*x*x*v;
340     			A[0][2] += x*x*v;
341     			A[1][2] += x*v;
342     			A[2][2] += v;
343     			b[0] += val*x*x*v;
344     			b[1] += val*x*v;
345     			b[2] += val*v;
346     			i++;
347     		}
348 		} catch (ArrayIndexOutOfBoundsException e) {}
349 		A[1][1] = A[0][2];
350 		A[1][0] = A[0][1];
351 		A[2][0] = A[0][2];
352 		A[2][1] = A[1][2];
353 
354 		LUDecomposition lu = new LUDecomposition(A);
355 		double[] r = lu.solve(b);
356 		double sigma = Math.sqrt(-0.5/r[0])*length;
357 		double mean = -0.5*r[1]*length/r[0];
358 		double amp = Math.exp(r[2] -0.25*r[1]*r[1]/r[0])*length;
359 		
360 		return new double[]{amp,sigma,mean,constY,0};
361 	}
362 	
363 	/**
364 	 * Checks if any of the values in the arrays is NaN.
365 	 * 
366 	 * @param values
367 	 * @return
368 	 */
369 	public static boolean isNaN(double[] values) {
370 		for (int i = 0; i < values.length; i++) {
371 			if (Double.isNaN(values[i])) return true;
372 		}
373 		return false;
374 	}
375 	
376 	/**
377 	 * Smooths the data with averaging.
378 	 * 
379 	 * @param x data to be smoothed
380 	 * @return smoothed data
381 	 */
382 	public static double[] smooth(double[] x) {
383 		return smooth(x,false);
384 	}
385 		
386 	/**
387 	 * Smooths the values in the array with averaging and applying a low pass filter.
388 	 * 
389 	 * @param x the data to smooth
390 	 * @param lowPassFilterOn true if low pass filtering should be used
391 	 * @return smoothed array
392 	 */
393 	public static double[] smooth(double[] x, boolean lowPassFilterOn) {
394 				
395 		int l = x.length;
396 		int n = 6;
397 		double[] xx = new double[l];
398 		for (int i = n; i < l; i++) {
399 			for (int j = 0; j < n; j++) { 
400 				xx[i] += x[i-j];
401 			}
402 			xx[i] /= n;
403 		}
404 		for (int i = 0; i < n; i++) {
405 			xx[i] = xx[n];
406 		}
407 		if (!lowPassFilterOn) return xx;
408 		
409 		if (l < 16)	l = 16;
410 		else if (l < 32) l = 32;
411 		else if (l < 64) l = 64;
412 		else if (l < 128) l = 128;
413 		else if (l < 256) l = 256;
414 		else if (l < 512) l = 512;
415 		else if (l < 1024) l = 1024;
416 		else if (l < 2048) l = 2048;
417 		else if (l < 4096) l = 4096;
418 		else if (l < 8192) l = 8192;
419 		
420 		double[] d = new double[l];
421 		double[] d1 = new double[l];
422 		System.arraycopy(xx,0,d1,0,xx.length);
423 		double[][] dd = new double[][]{d1,d};
424 		dd = FFT1D(dd,true);
425 		int q = (int)(dd[0].length*0.05 + 0.5);
426 		int h = dd[0].length/2;
427 		for (int i = q; i < h; i++) {
428 			dd[0][i] = 0;
429 			dd[0][h + i] = 0;
430 			dd[1][i] = 0;
431 			dd[1][h+i] = 0;
432 		}
433 		
434 		dd = FFT1D(dd,false);
435 		double[] a = new double[xx.length];
436 		System.arraycopy(dd[0],0,a,0,a.length);
437 		return a;
438 	}
439 	
440 	/**
441 	 * @param x array of values. First array are real values, second imaginary
442 	 * @param forward true for forward and false for inverse transform
443 	 * @return Fourier transform of x
444 	 * @throws Exception
445 	 */
446 	public static double[][] FFT1D(double[][] x, boolean forward) {
447 		if (x == null)
448 			return null;
449 		if (x.length != 2 || x[0].length != x[1].length) {
450 			throw new IllegalArgumentException("bad x lengths");
451 		}
452 		int n = x[0].length;
453 		int n2 = 1;
454 		while (n2 < n) {
455 			n2 *= 2;
456 			if (n2 > n) {
457 				throw new IllegalArgumentException("x length must be power of 2");
458 			}
459 		}
460 		n2 = n / 2;
461 		double[][] temp = new double[2][n2];
462 		double angle = (-2.0 * Math.PI / n);
463 		if (!forward)
464 			angle = -angle;
465 		for (int i = 0; i < n2; i++) {
466 			temp[0][i] = Math.cos(i * angle);
467 			temp[1][i] = Math.sin(i * angle);
468 		}
469 		double[][] y = FFT1D(x, temp);
470 		if (!forward) {
471 			for (int i = 0; i < n; i++) {
472 				y[0][i] /= n;
473 				y[1][i] /= n;
474 			}
475 		}
476 		return y;
477 	}
478 
479 	private static double[][] FFT1D(double[][] x, double[][] temp) {
480 		int n = x[0].length;
481 		int n2 = n / 2;
482 		int k = 0;
483 		int b;
484 		int c = 0;
485 		if (n == 1) {
486 			double[][] z1 = {{x[0][0]}, {x[1][0]}};
487 			return z1;
488 		}
489 
490 		b = (temp[0].length / n2);
491 
492 		double[][] z = new double[2][n2];
493 		double[][] w = new double[2][n2];
494 
495 		for (k = 0; k < n2; k++) {
496 			int k2 = 2 * k;
497 			z[0][k] = x[0][k2];
498 			z[1][k] = x[1][k2];
499 			w[0][k] = x[0][k2 + 1];
500 			w[1][k] = x[1][k2 + 1];
501 		}
502 
503 		z = FFT1D(z, temp);
504 		w = FFT1D(w, temp);
505 
506 		double[][] y = new double[2][n];
507 		for (k = 0; k < n2; k++) {
508 			y[0][k] = z[0][k];
509 			y[1][k] = z[1][k];
510 
511 			double re = w[0][k] * temp[0][c] - w[1][k] * temp[1][c];
512 			double im = w[0][k] * temp[1][c] + w[1][k] * temp[0][c];
513 			w[0][k] = re;
514 			w[1][k] = im;
515 
516 			y[0][k] += w[0][k];
517 			y[1][k] += w[1][k];
518 			y[0][k + n2] = z[0][k] - w[0][k];
519 			y[1][k + n2] = z[1][k] - w[1][k];
520 			c += b;
521 		}
522 		return y;
523 	}
524 }