package org.das2.qds.util; import org.das2.datum.Datum; import org.das2.datum.Units; import org.das2.qds.DataSetUtil; import org.das2.qds.FDataSet; import org.das2.qds.QDataSet; import org.das2.qds.SemanticOps; /** * Linear Fit routine. This will allow errors on the Y values, and will report * chi squared. * * Borrowed from pamguard, https://sourceforge.net/projects/pamguard/. * * @author Doug Gillespie Simple linear regression. Fitting line y = a + bx * * Modified to use QDataSet, Jeremy Faden Singleton class */ public class LinFit { private static final int ITMAX = 100; private static final double EPS = 3.0e-7; private static final double FPMIN = 1.0e-30; private double a, b, siga, sigb, chi2, q; private QDataSet x, y, sig; private int nData; private boolean doneErrors; double wt, t, sxoss, sx = 0., sy = 0., st2 = 0., ss, sigdat; int mwt = 0; /** * do fit with uniform weights or weight=0 where fill is found. * * @param x the x data * @param y the y data */ public LinFit(QDataSet x, QDataSet y) { FDataSet wds = FDataSet.createRank1(x.length()); QDataSet wdsx = DataSetUtil.weightsDataSet(x); QDataSet wdsy = DataSetUtil.weightsDataSet(y); for (int i = 0; i < x.length(); i++) { wds.putValue(i, (wdsx.value(i) == 0 || wdsy.value(i) == 0 ? 0 : 1)); } doFit(x, y, wds); } /** * do fit with weights. X and Y must not contain fill where sig>0. * * @param x the x data * @param y the y data * @param sig the error bar, or zero for fill. */ public LinFit(QDataSet x, QDataSet y, QDataSet sig) { doFit(x, y, sig); } private void doFit(QDataSet x, QDataSet y, QDataSet sig) { if (x.rank() != 1 || y.rank() != 1) { throw new IllegalArgumentException("x and y must be rank 1"); } this.x = x; this.y = y; this.nData = x.length(); this.sig = sig; doneErrors = false; int i; if (sig != null) { mwt = nData; } else { mwt = 0; } b = 0.; if (mwt > 0) { // Accumalative sums ss = 0.; for (i = 0; i < nData; i++) { // with weights if (sig.value(i) > 0) { wt = 1. / SQR(sig.value(i)); ss += wt; sx += x.value(i) * wt; sy += y.value(i) * wt; } } } else { for (i = 0; i < nData; i++) { sx += x.value(i); // without weights sy += y.value(i); } ss = nData; } sxoss = sx / ss; if (mwt > 0) { for (i = 0; i < nData; i++) { t = (x.value(i) - sxoss) / sig.value(i); st2 += t * t; b += t * y.value(i) / sig.value(i); } } else { for (i = 0; i < nData; i++) { t = x.value(i) - sxoss; st2 += t * t; b += t * y.value(i); } } b /= st2; a = (sy - sx * b) / ss; } private void doErrors() { int i; siga = Math.sqrt((1. + sx * sx / (ss * st2)) / ss); sigb = Math.sqrt(1. / st2); chi2 = q = 0.; if (nData <= 2) { return; } if (mwt == 0) { for (i = 0; i < nData; i++) { chi2 += SQR(y.value(i) - (a) - (b) * x.value(i)); } q = 1.; sigdat = Math.sqrt((chi2) / (nData - 2)); siga *= sigdat; sigb *= sigdat; } else { for (i = 0; i < nData; i++) { chi2 += SQR((y.value(i) - a - b * x.value(i)) / sig.value(i)); } q = gammq(0.5 * (nData - 2), 0.5 * (chi2)); } doneErrors = true; } /** * return the result A, the intercept, of the fit y = A + B * x * * @return the result A of the fit y = A + B * x */ public double getA() { if (doneErrors == false) { doErrors(); } return a; } /** * return the result B, the slope, of the fit y = A + B * x * * @return the result B of the fit y = A + B * x */ public double getB() { if (doneErrors == false) { doErrors(); } return b; } /** * return the slope (B of linear fit equation) as a datum with units of Yunits/Xunits. Note the current * version of the library is unable to do many unit calculations. * * @return the slope as a Datum with units. */ public Datum getSlope() { Units xunits = SemanticOps.getUnits(x).getOffsetUnits(); Units yunits = SemanticOps.getUnits(y).getOffsetUnits(); return yunits.divide(getB(), 1, xunits); } /** * return the intercept (A of linear fit equation) as a datum with units of y. * * @return */ public Datum getIntercept() { Units yunits = SemanticOps.getUnits(y).getOffsetUnits(); return yunits.createDatum(getA()); } /** * return the Chi-Squared result from the fit. * @return the Chi-Squared result from the fit. */ public double getChi2() { if (doneErrors == false) { doErrors(); } return chi2; } public double getQ() { if (doneErrors == false) { doErrors(); } return q; } public double getSiga() { if (doneErrors == false) { doErrors(); } return siga; } public double getSigb() { if (doneErrors == false) { doErrors(); } return sigb; } double gammq(double a, double x) { // incomplete gamma function Q(a,x) = 1 - P(a,x) Double gamser = 0.; Double gammcf = 0.; Double gln = 0.; if (x < 0. || a <= 0.) { // MessageBox(NULL, "Invalid arguments in Gamma function", // "Routine gammq", MB_ICONHAND); return 0; } if (x < (a + 1.0)) { gamser = gser(gamser, a, x, gln); return 1.0 - gamser; } else { gammcf = gcf(gammcf, a, x, gln); return gammcf; } } double gser(Double gamser, double a, double x, Double gln) { int n; double sum, del, ap; gln = gammln(a); if (x <= 0.0) { if (x < 0.0) { throw new IllegalArgumentException("x less than 0 in routine gser"); } gamser = 0.0; return gamser; } else { ap = a; del = sum = 1.0 / a; for (n = 1; n <= ITMAX; n++) { ++ap; del *= x / ap; sum += del; if (Math.abs(del) < Math.abs(sum) * EPS) { gamser = sum * Math.exp(-x + a * Math.log(x) - (gln)); return gamser; } } throw new IllegalArgumentException("a too large, ITMAX too small in routine gser"); } } double gcf(Double gammcf, double a, double x, Double gln) { int i; double an, b, c, d, del, h; gln = gammln(a); b = x + 1.0 - a; c = 1.0 / FPMIN; d = 1.0 / b; h = d; for (i = 1; i <= ITMAX; i++) { an = -i * (i - a); b += 2.0; d = an * d + b; if (Math.abs(d) < FPMIN) { d = FPMIN; } c = b + an / c; if (Math.abs(c) < FPMIN) { c = FPMIN; } d = 1.0 / d; del = d * c; h *= del; if (Math.abs(del - 1.0) < EPS) { break; } } //if (i > ITMAX) nrerror("a too large, ITMAX too small in gcf"); gammcf = Math.exp(-x + a * Math.log(x) - (gln)) * h; return gammcf; } double gammln(double xx) { double x, y, tmp, ser; final double[] cof = {76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5 }; int j; y = x = xx; tmp = x + 5.5; tmp -= (x + 0.5) * Math.log(tmp); ser = 1.000000000190015; for (j = 0; j <= 5; j++) { ser += cof[j] / ++y; } return -tmp + Math.log(2.5066282746310005 * ser / x); } private final double SQR(double f) { return f * f; } }