List of usage examples for org.apache.commons.math3.optim PointValuePair getPointRef
public double[] getPointRef()
From source file:gdsc.smlm.fitting.BinomialFitter.java
/** * Fit the binomial distribution (n,p) to the input data. Performs fitting assuming a fixed n value and attempts to * optimise p. All n from minN to maxN are evaluated. If maxN is zero then all possible n from minN are evaluated * until the fit is worse./*from w w w .ja va 2 s. co m*/ * * @param data * The input data (all value must be positive) * @param minN * The minimum n to evaluate * @param maxN * The maximum n to evaluate. Set to zero to evaluate all possible values. * @param zeroTruncated * True if the model should ignore n=0 (zero-truncated binomial) * @return The best fit (n, p) * @throws IllegalArgumentException * If any of the input data values are negative */ public double[] fitBinomial(int[] data, int minN, int maxN, boolean zeroTruncated) { double[] histogram = getHistogram(data, false); final double initialSS = Double.POSITIVE_INFINITY; double bestSS = initialSS; double[] parameters = null; int worse = 0; int N = (int) histogram.length - 1; if (minN < 1) minN = 1; if (maxN > 0) { if (N > maxN) { // Limit the number fitted to maximum N = maxN; } else if (N < maxN) { // Expand the histogram to the maximum histogram = Arrays.copyOf(histogram, maxN + 1); N = maxN; } } if (minN > N) minN = N; final double mean = getMean(histogram); String name = (zeroTruncated) ? "Zero-truncated Binomial distribution" : "Binomial distribution"; log("Mean cluster size = %s", Utils.rounded(mean)); log("Fitting cumulative " + name); // Since varying the N should be done in integer steps do this // for n=1,2,3,... until the SS peaks then falls off (is worse than the best // score several times in succession) for (int n = minN; n <= N; n++) { PointValuePair solution = fitBinomial(histogram, mean, n, zeroTruncated); if (solution == null) continue; double p = solution.getPointRef()[0]; log("Fitted %s : N=%d, p=%s. SS=%g", name, n, Utils.rounded(p), solution.getValue()); if (bestSS > solution.getValue()) { bestSS = solution.getValue(); parameters = new double[] { n, p }; worse = 0; } else if (bestSS != initialSS) { if (++worse >= 3) break; } } return parameters; }
From source file:gdsc.smlm.ij.plugins.pcpalm.PCPALMClusters.java
/** * Fit a zero-truncated Binomial to the cumulative histogram * // w ww. j a v a 2 s .c o m * @param histogramData * @return */ private double[] fitBinomial(HistogramData histogramData) { // Get the mean and sum of the input histogram double mean; double sum = 0; count = 0; for (int i = 0; i < histogramData.histogram[1].length; i++) { count += histogramData.histogram[1][i]; sum += histogramData.histogram[1][i] * i; } mean = sum / count; String name = "Zero-truncated Binomial distribution"; Utils.log("Mean cluster size = %s", Utils.rounded(mean)); Utils.log("Fitting cumulative " + name); // Convert to a normalised double array for the binomial fitter double[] histogram = new double[histogramData.histogram[1].length]; for (int i = 0; i < histogramData.histogram[1].length; i++) histogram[i] = histogramData.histogram[1][i] / count; // Plot the cumulative histogram String title = TITLE + " Cumulative Distribution"; Plot2 plot = null; if (showCumulativeHistogram) { // Create a cumulative histogram for fitting double[] cumulativeHistogram = new double[histogram.length]; sum = 0; for (int i = 0; i < histogram.length; i++) { sum += histogram[i]; cumulativeHistogram[i] = sum; } double[] values = Utils.newArray(histogram.length, 0.0, 1.0); plot = new Plot2(title, "N", "Cumulative Probability", values, cumulativeHistogram); plot.setLimits(0, histogram.length - 1, 0, 1.05); plot.addPoints(values, cumulativeHistogram, Plot2.CIRCLE); Utils.display(title, plot); } // Do fitting for different N double bestSS = Double.POSITIVE_INFINITY; double[] parameters = null; int worse = 0; int N = histogram.length - 1; int min = minN; final boolean customRange = (minN > 1) || (maxN > 0); if (min > N) min = N; if (maxN > 0 && N > maxN) N = maxN; Utils.log("Fitting N from %d to %d%s", min, N, (customRange) ? " (custom-range)" : ""); // Since varying the N should be done in integer steps do this // for n=1,2,3,... until the SS peaks then falls off (is worse then the best // score several times in succession) BinomialFitter bf = new BinomialFitter(new IJLogger()); bf.setMaximumLikelihood(maximumLikelihood); for (int n = min; n <= N; n++) { PointValuePair solution = bf.fitBinomial(histogram, mean, n, true); if (solution == null) continue; double p = solution.getPointRef()[0]; Utils.log("Fitted %s : N=%d, p=%s. SS=%g", name, n, Utils.rounded(p), solution.getValue()); if (bestSS > solution.getValue()) { bestSS = solution.getValue(); parameters = new double[] { n, p }; worse = 0; } else if (bestSS < Double.POSITIVE_INFINITY) { if (++worse >= 3) break; } if (showCumulativeHistogram) addToPlot(n, p, title, plot, new Color((float) n / N, 0, 1f - (float) n / N)); } // Add best it in magenta if (showCumulativeHistogram && parameters != null) addToPlot((int) parameters[0], parameters[1], title, plot, Color.magenta); return parameters; }
From source file:gdsc.smlm.ij.plugins.EMGainAnalysis.java
/** * Fit the EM-gain distribution (Gaussian * Gamma) * // w ww .j a v a 2 s .com * @param h * The distribution */ private void fit(int[] h) { final int[] limits = limits(h); final double[] x = getX(limits); final double[] y = getY(h, limits); Plot2 plot = new Plot2(TITLE, "ADU", "Frequency"); double yMax = Maths.max(y); plot.setLimits(limits[0], limits[1], 0, yMax); plot.setColor(Color.black); plot.addPoints(x, y, Plot2.DOT); Utils.display(TITLE, plot); // Estimate remaining parameters. // Assuming a gamma_distribution(shape,scale) then mean = shape * scale // scale = gain // shape = Photons = mean / gain double mean = getMean(h) - bias; // Note: if the bias is too high then the mean will be negative. Just move the bias. while (mean < 0) { bias -= 1; mean += 1; } double photons = mean / gain; if (simulate) Utils.log("Simulated bias=%d, gain=%s, noise=%s, photons=%s", (int) _bias, Utils.rounded(_gain), Utils.rounded(_noise), Utils.rounded(_photons)); Utils.log("Estimate bias=%d, gain=%s, noise=%s, photons=%s", (int) bias, Utils.rounded(gain), Utils.rounded(noise), Utils.rounded(photons)); final int max = (int) x[x.length - 1]; double[] g = pdf(max, photons, gain, noise, (int) bias); plot.setColor(Color.blue); plot.addPoints(x, g, Plot2.LINE); Utils.display(TITLE, plot); // Perform a fit CustomPowellOptimizer o = new CustomPowellOptimizer(1e-6, 1e-16, 1e-6, 1e-16); double[] startPoint = new double[] { photons, gain, noise, bias }; final int maxEval = 3000; // Set bounds double[] lower = new double[] { 0, 0.5 * gain, 0, bias - noise }; double[] upper = new double[] { (limits[1] - limits[0]) / gain, 2 * gain, gain, bias + noise }; // Restart until converged. // TODO - Maybe fix this with a better optimiser. This needs to be tested on real data. PointValuePair solution = null; for (int iter = 0; iter < 3; iter++) { IJ.showStatus("Fitting histogram ... Iteration " + iter); try { // Basic Powell optimiser MultivariateFunction fun = getFunction(limits, y, max, maxEval); PointValuePair optimum = o.optimize(new MaxEval(maxEval), new ObjectiveFunction(fun), GoalType.MINIMIZE, new InitialGuess((solution == null) ? startPoint : solution.getPointRef())); if (solution == null || optimum.getValue() < solution.getValue()) { solution = optimum; } } catch (Exception e) { } try { // Bounded Powell optimiser MultivariateFunction fun = getFunction(limits, y, max, maxEval); MultivariateFunctionMappingAdapter adapter = new MultivariateFunctionMappingAdapter(fun, lower, upper); PointValuePair optimum = o.optimize(new MaxEval(maxEval), new ObjectiveFunction(adapter), GoalType.MINIMIZE, new InitialGuess(adapter .boundedToUnbounded((solution == null) ? startPoint : solution.getPointRef()))); double[] point = adapter.unboundedToBounded(optimum.getPointRef()); optimum = new PointValuePair(point, optimum.getValue()); if (solution == null || optimum.getValue() < solution.getValue()) { solution = optimum; } } catch (Exception e) { } } IJ.showStatus(""); IJ.showProgress(1); if (solution == null) { Utils.log("Failed to fit the distribution"); return; } double[] point = solution.getPointRef(); photons = point[0]; gain = point[1]; noise = point[2]; bias = (int) Math.round(point[3]); String label = String.format("Fitted bias=%d, gain=%s, noise=%s, photons=%s", (int) bias, Utils.rounded(gain), Utils.rounded(noise), Utils.rounded(photons)); Utils.log(label); if (simulate) { Utils.log("Relative Error bias=%s, gain=%s, noise=%s, photons=%s", Utils.rounded(relativeError(bias, _bias)), Utils.rounded(relativeError(gain, _gain)), Utils.rounded(relativeError(noise, _noise)), Utils.rounded(relativeError(photons, _photons))); } // Show the PoissonGammaGaussian approximation double[] f = null; if (showApproximation) { f = new double[x.length]; PoissonGammaGaussianFunction fun = new PoissonGammaGaussianFunction(1.0 / gain, noise); final double expected = photons * gain; for (int i = 0; i < f.length; i++) { f[i] = fun.likelihood(x[i] - bias, expected); //System.out.printf("x=%d, g=%f, f=%f, error=%f\n", (int) x[i], g[i], f[i], // gdsc.smlm.fitting.utils.DoubleEquality.relativeError(g[i], f[i])); } yMax = Maths.maxDefault(yMax, f); } // Replot g = pdf(max, photons, gain, noise, (int) bias); plot = new Plot2(TITLE, "ADU", "Frequency"); plot.setLimits(limits[0], limits[1], 0, yMax * 1.05); plot.setColor(Color.black); plot.addPoints(x, y, Plot2.DOT); plot.setColor(Color.red); plot.addPoints(x, g, Plot2.LINE); plot.addLabel(0, 0, label); if (showApproximation) { plot.setColor(Color.blue); plot.addPoints(x, f, Plot2.LINE); } Utils.display(TITLE, plot); }
From source file:gdsc.smlm.ij.plugins.pcpalm.PCPALMMolecules.java
private double[] optimiseSimplex(float[] x, float[] y, double[] initialSolution) { // Simplex optimisation SkewNormalMultivariateFunction sn2 = new SkewNormalMultivariateFunction(initialSolution); sn2.addData(x, y);//from w w w . j a v a 2s. c om NelderMeadSimplex simplex = new NelderMeadSimplex(4); SimplexOptimizer opt = new SimplexOptimizer(1e-6, 1e-10); PointValuePair solution = opt.optimize(new MaxEval(1000), new InitialGuess(initialSolution), simplex, new ObjectiveFunction(sn2), GoalType.MINIMIZE); double[] skewParameters2 = solution.getPointRef(); return skewParameters2; }
From source file:gdsc.smlm.ij.plugins.pcpalm.PCPALMFitting.java
/** * Fits the correlation curve with r>0 to the clustered model using the estimated density and precision. Parameters * must be fit within a tolerance of the starting values. * // w w w . j a va 2s . com * @param gr * @param sigmaS * The estimated precision * @param proteinDensity * The estimated protein density * @return The fitted parameters [precision, density, clusterRadius, clusterDensity] */ private double[] fitClusteredModel(double[][] gr, double sigmaS, double proteinDensity, String resultColour) { final ClusteredModelFunctionGradient myFunction = new ClusteredModelFunctionGradient(); clusteredModel = myFunction; log("Fitting %s: Estimated precision = %f nm, estimated protein density = %g um^-2", clusteredModel.getName(), sigmaS, proteinDensity * 1e6); clusteredModel.setLogging(true); for (int i = offset; i < gr[0].length; i++) { // Only fit the curve above the estimated resolution (points below it will be subject to error) if (gr[0][i] > sigmaS * fitAboveEstimatedPrecision) clusteredModel.addPoint(gr[0][i], gr[1][i]); } double[] parameters; // The model is: sigma, density, range, amplitude double[] initialSolution = new double[] { sigmaS, proteinDensity, sigmaS * 5, 1 }; int evaluations = 0; // Constrain the fitting to be close to the estimated precision (sigmaS) and protein density. // LVM fitting does not support constrained fitting so use a bounded optimiser. SumOfSquaresModelFunction clusteredModelMulti = new SumOfSquaresModelFunction(clusteredModel); double[] x = clusteredModelMulti.x; // Put some bounds around the initial guess. Use the fitting tolerance (in %) if provided. double limit = (fittingTolerance > 0) ? 1 + fittingTolerance / 100 : 2; double[] lB = new double[] { initialSolution[0] / limit, initialSolution[1] / limit, 0, 0 }; // The amplitude and range should not extend beyond the limits of the g(r) curve. double[] uB = new double[] { initialSolution[0] * limit, initialSolution[1] * limit, Maths.max(x), Maths.max(gr[1]) }; log("Fitting %s using a bounded search: %s < precision < %s & %s < density < %s", clusteredModel.getName(), Utils.rounded(lB[0], 4), Utils.rounded(uB[0], 4), Utils.rounded(lB[1] * 1e6, 4), Utils.rounded(uB[1] * 1e6, 4)); PointValuePair constrainedSolution = runBoundedOptimiser(gr, initialSolution, lB, uB, clusteredModelMulti); if (constrainedSolution == null) return null; parameters = constrainedSolution.getPointRef(); evaluations = boundedEvaluations; // Refit using a LVM if (useLSE) { log("Re-fitting %s using a gradient optimisation", clusteredModel.getName()); LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer(); PointVectorValuePair lvmSolution; try { lvmSolution = optimizer.optimize(new MaxIter(3000), new MaxEval(Integer.MAX_VALUE), new ModelFunctionJacobian(new MultivariateMatrixFunction() { public double[][] value(double[] point) throws IllegalArgumentException { return myFunction.jacobian(point); } }), new ModelFunction(myFunction), new Target(myFunction.getY()), new Weight(myFunction.getWeights()), new InitialGuess(parameters)); evaluations += optimizer.getEvaluations(); double ss = 0; double[] obs = clusteredModel.getY(); double[] exp = lvmSolution.getValue(); for (int i = 0; i < obs.length; i++) ss += (obs[i] - exp[i]) * (obs[i] - exp[i]); if (ss < constrainedSolution.getValue()) { log("Re-fitting %s improved the SS from %s to %s (-%s%%)", clusteredModel.getName(), Utils.rounded(constrainedSolution.getValue(), 4), Utils.rounded(ss, 4), Utils.rounded( 100 * (constrainedSolution.getValue() - ss) / constrainedSolution.getValue(), 4)); parameters = lvmSolution.getPoint(); } } catch (TooManyIterationsException e) { log("Failed to re-fit %s: Too many iterations (%d)", clusteredModel.getName(), optimizer.getIterations()); } catch (ConvergenceException e) { log("Failed to re-fit %s: %s", clusteredModel.getName(), e.getMessage()); } } clusteredModel.setLogging(false); // Ensure the width is positive parameters[0] = Math.abs(parameters[0]); //parameters[2] = Math.abs(parameters[2]); double ss = 0; double[] obs = clusteredModel.getY(); double[] exp = clusteredModel.value(parameters); for (int i = 0; i < obs.length; i++) ss += (obs[i] - exp[i]) * (obs[i] - exp[i]); ic2 = Maths.getInformationCriterion(ss, clusteredModel.size(), parameters.length); final double fitSigmaS = parameters[0]; final double fitProteinDensity = parameters[1]; final double domainRadius = parameters[2]; //The radius of the cluster domain final double domainDensity = parameters[3]; //The density of the cluster domain // This is from the PC-PALM paper. However that paper fits the g(r)protein exponential convolved in 2D // with the g(r)PSF. In this method we have just fit the exponential final double nCluster = 2 * domainDensity * Math.PI * domainRadius * domainRadius * fitProteinDensity; double e1 = parameterDrift(sigmaS, fitSigmaS); double e2 = parameterDrift(proteinDensity, fitProteinDensity); log(" %s fit: SS = %f. cAIC = %f. %d evaluations", clusteredModel.getName(), ss, ic2, evaluations); log(" %s parameters:", clusteredModel.getName()); log(" Average precision = %s nm (%s%%)", Utils.rounded(fitSigmaS, 4), Utils.rounded(e1, 4)); log(" Average protein density = %s um^-2 (%s%%)", Utils.rounded(fitProteinDensity * 1e6, 4), Utils.rounded(e2, 4)); log(" Domain radius = %s nm", Utils.rounded(domainRadius, 4)); log(" Domain density = %s", Utils.rounded(domainDensity, 4)); log(" nCluster = %s", Utils.rounded(nCluster, 4)); // Check the fitted parameters are within tolerance of the initial estimates valid2 = true; if (fittingTolerance > 0 && (Math.abs(e1) > fittingTolerance || Math.abs(e2) > fittingTolerance)) { log(" Failed to fit %s within tolerance (%s%%): Average precision = %f nm (%s%%), average protein density = %g um^-2 (%s%%)", clusteredModel.getName(), Utils.rounded(fittingTolerance, 4), fitSigmaS, Utils.rounded(e1, 4), fitProteinDensity * 1e6, Utils.rounded(e2, 4)); valid2 = false; } // Check extra parameters. Domain radius should be higher than the precision. Density should be positive if (domainRadius < fitSigmaS) { log(" Failed to fit %s: Domain radius is smaller than the average precision (%s < %s)", clusteredModel.getName(), Utils.rounded(domainRadius, 4), Utils.rounded(fitSigmaS, 4)); valid2 = false; } if (domainDensity < 0) { log(" Failed to fit %s: Domain density is negative (%s)", clusteredModel.getName(), Utils.rounded(domainDensity, 4)); valid2 = false; } if (ic2 > ic1) { log(" Failed to fit %s - Information Criterion has increased %s%%", clusteredModel.getName(), Utils.rounded((100 * (ic2 - ic1) / ic1), 4)); valid2 = false; } addResult(clusteredModel.getName(), resultColour, valid2, fitSigmaS, fitProteinDensity, domainRadius, domainDensity, nCluster, 0, ic2); return parameters; }
From source file:gdsc.smlm.ij.plugins.pcpalm.PCPALMFitting.java
/** * Fits the correlation curve with r>0 to the clustered model using the estimated density and precision. Parameters * must be fit within a tolerance of the starting values. * //from w ww. jav a 2 s. co m * @param gr * @param sigmaS * The estimated precision * @param proteinDensity * The estimated protein density * @return The fitted parameters [precision, density, clusterRadius, clusterDensity] */ private double[] fitEmulsionModel(double[][] gr, double sigmaS, double proteinDensity, String resultColour) { final EmulsionModelFunctionGradient myFunction = new EmulsionModelFunctionGradient(); emulsionModel = myFunction; log("Fitting %s: Estimated precision = %f nm, estimated protein density = %g um^-2", emulsionModel.getName(), sigmaS, proteinDensity * 1e6); emulsionModel.setLogging(true); for (int i = offset; i < gr[0].length; i++) { // Only fit the curve above the estimated resolution (points below it will be subject to error) if (gr[0][i] > sigmaS * fitAboveEstimatedPrecision) emulsionModel.addPoint(gr[0][i], gr[1][i]); } double[] parameters; // The model is: sigma, density, range, amplitude, alpha double[] initialSolution = new double[] { sigmaS, proteinDensity, sigmaS * 5, 1, sigmaS * 5 }; int evaluations = 0; // Constrain the fitting to be close to the estimated precision (sigmaS) and protein density. // LVM fitting does not support constrained fitting so use a bounded optimiser. SumOfSquaresModelFunction emulsionModelMulti = new SumOfSquaresModelFunction(emulsionModel); double[] x = emulsionModelMulti.x; double[] y = emulsionModelMulti.y; // Range should be equal to the first time the g(r) curve crosses 1 for (int i = 0; i < x.length; i++) if (y[i] < 1) { initialSolution[4] = initialSolution[2] = (i > 0) ? (x[i - 1] + x[i]) * 0.5 : x[i]; break; } // Put some bounds around the initial guess. Use the fitting tolerance (in %) if provided. double limit = (fittingTolerance > 0) ? 1 + fittingTolerance / 100 : 2; double[] lB = new double[] { initialSolution[0] / limit, initialSolution[1] / limit, 0, 0, 0 }; // The amplitude and range should not extend beyond the limits of the g(r) curve. // TODO - Find out the expected range for the alpha parameter. double[] uB = new double[] { initialSolution[0] * limit, initialSolution[1] * limit, Maths.max(x), Maths.max(gr[1]), Maths.max(x) * 2 }; log("Fitting %s using a bounded search: %s < precision < %s & %s < density < %s", emulsionModel.getName(), Utils.rounded(lB[0], 4), Utils.rounded(uB[0], 4), Utils.rounded(lB[1] * 1e6, 4), Utils.rounded(uB[1] * 1e6, 4)); PointValuePair constrainedSolution = runBoundedOptimiser(gr, initialSolution, lB, uB, emulsionModelMulti); if (constrainedSolution == null) return null; parameters = constrainedSolution.getPointRef(); evaluations = boundedEvaluations; // Refit using a LVM if (useLSE) { log("Re-fitting %s using a gradient optimisation", emulsionModel.getName()); LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer(); PointVectorValuePair lvmSolution; try { lvmSolution = optimizer.optimize(new MaxIter(3000), new MaxEval(Integer.MAX_VALUE), new ModelFunctionJacobian(new MultivariateMatrixFunction() { public double[][] value(double[] point) throws IllegalArgumentException { return myFunction.jacobian(point); } }), new ModelFunction(myFunction), new Target(myFunction.getY()), new Weight(myFunction.getWeights()), new InitialGuess(parameters)); evaluations += optimizer.getEvaluations(); double ss = 0; double[] obs = emulsionModel.getY(); double[] exp = lvmSolution.getValue(); for (int i = 0; i < obs.length; i++) ss += (obs[i] - exp[i]) * (obs[i] - exp[i]); if (ss < constrainedSolution.getValue()) { log("Re-fitting %s improved the SS from %s to %s (-%s%%)", emulsionModel.getName(), Utils.rounded(constrainedSolution.getValue(), 4), Utils.rounded(ss, 4), Utils.rounded( 100 * (constrainedSolution.getValue() - ss) / constrainedSolution.getValue(), 4)); parameters = lvmSolution.getPoint(); } } catch (TooManyIterationsException e) { log("Failed to re-fit %s: Too many iterations (%d)", emulsionModel.getName(), optimizer.getIterations()); } catch (ConvergenceException e) { log("Failed to re-fit %s: %s", emulsionModel.getName(), e.getMessage()); } } emulsionModel.setLogging(false); // Ensure the width is positive parameters[0] = Math.abs(parameters[0]); //parameters[2] = Math.abs(parameters[2]); double ss = 0; double[] obs = emulsionModel.getY(); double[] exp = emulsionModel.value(parameters); for (int i = 0; i < obs.length; i++) ss += (obs[i] - exp[i]) * (obs[i] - exp[i]); ic3 = Maths.getInformationCriterion(ss, emulsionModel.size(), parameters.length); final double fitSigmaS = parameters[0]; final double fitProteinDensity = parameters[1]; final double domainRadius = parameters[2]; //The radius of the cluster domain final double domainDensity = parameters[3]; //The density of the cluster domain final double coherence = parameters[4]; //The coherence length between circles // This is from the PC-PALM paper. It may not be correct for the emulsion model. final double nCluster = 2 * domainDensity * Math.PI * domainRadius * domainRadius * fitProteinDensity; double e1 = parameterDrift(sigmaS, fitSigmaS); double e2 = parameterDrift(proteinDensity, fitProteinDensity); log(" %s fit: SS = %f. cAIC = %f. %d evaluations", emulsionModel.getName(), ss, ic3, evaluations); log(" %s parameters:", emulsionModel.getName()); log(" Average precision = %s nm (%s%%)", Utils.rounded(fitSigmaS, 4), Utils.rounded(e1, 4)); log(" Average protein density = %s um^-2 (%s%%)", Utils.rounded(fitProteinDensity * 1e6, 4), Utils.rounded(e2, 4)); log(" Domain radius = %s nm", Utils.rounded(domainRadius, 4)); log(" Domain density = %s", Utils.rounded(domainDensity, 4)); log(" Domain coherence = %s", Utils.rounded(coherence, 4)); log(" nCluster = %s", Utils.rounded(nCluster, 4)); // Check the fitted parameters are within tolerance of the initial estimates valid2 = true; if (fittingTolerance > 0 && (Math.abs(e1) > fittingTolerance || Math.abs(e2) > fittingTolerance)) { log(" Failed to fit %s within tolerance (%s%%): Average precision = %f nm (%s%%), average protein density = %g um^-2 (%s%%)", emulsionModel.getName(), Utils.rounded(fittingTolerance, 4), fitSigmaS, Utils.rounded(e1, 4), fitProteinDensity * 1e6, Utils.rounded(e2, 4)); valid2 = false; } // Check extra parameters. Domain radius should be higher than the precision. Density should be positive if (domainRadius < fitSigmaS) { log(" Failed to fit %s: Domain radius is smaller than the average precision (%s < %s)", emulsionModel.getName(), Utils.rounded(domainRadius, 4), Utils.rounded(fitSigmaS, 4)); valid2 = false; } if (domainDensity < 0) { log(" Failed to fit %s: Domain density is negative (%s)", emulsionModel.getName(), Utils.rounded(domainDensity, 4)); valid2 = false; } if (ic3 > ic1) { log(" Failed to fit %s - Information Criterion has increased %s%%", emulsionModel.getName(), Utils.rounded((100 * (ic3 - ic1) / ic1), 4)); valid2 = false; } addResult(emulsionModel.getName(), resultColour, valid2, fitSigmaS, fitProteinDensity, domainRadius, domainDensity, nCluster, coherence, ic3); return parameters; }
From source file:gdsc.smlm.fitting.BinomialFitter.java
/** * Fit the binomial distribution (n,p) to the cumulative histogram. Performs fitting assuming a fixed n value and * attempts to optimise p./*from w ww .j a va2s.co m*/ * * @param histogram * The input histogram * @param mean * The histogram mean (used to estimate p). Calculated if NaN. * @param n * The n to evaluate * @param zeroTruncated * True if the model should ignore n=0 (zero-truncated binomial) * @return The best fit (n, p) * @throws IllegalArgumentException * If any of the input data values are negative * @throws IllegalArgumentException * If any fitting a zero truncated binomial and there are no values above zero */ public PointValuePair fitBinomial(double[] histogram, double mean, int n, boolean zeroTruncated) { if (Double.isNaN(mean)) mean = getMean(histogram); if (zeroTruncated && histogram[0] > 0) { log("Fitting zero-truncated histogram but there are zero values - Renormalising to ignore zero"); double cumul = 0; for (int i = 1; i < histogram.length; i++) cumul += histogram[i]; if (cumul == 0) throw new IllegalArgumentException( "Fitting zero-truncated histogram but there are no non-zero values"); histogram[0] = 0; for (int i = 1; i < histogram.length; i++) histogram[i] /= cumul; } int nFittedPoints = Math.min(histogram.length, n + 1) - ((zeroTruncated) ? 1 : 0); if (nFittedPoints < 1) { log("No points to fit (%d): Histogram.length = %d, n = %d, zero-truncated = %b", nFittedPoints, histogram.length, n, zeroTruncated); return null; } // The model is only fitting the probability p // For a binomial n*p = mean => p = mean/n double[] initialSolution = new double[] { FastMath.min(mean / n, 1) }; // Create the function BinomialModelFunction function = new BinomialModelFunction(histogram, n, zeroTruncated); double[] lB = new double[1]; double[] uB = new double[] { 1 }; SimpleBounds bounds = new SimpleBounds(lB, uB); // Fit // CMAESOptimizer or BOBYQAOptimizer support bounds // CMAESOptimiser based on Matlab code: // https://www.lri.fr/~hansen/cmaes.m // Take the defaults from the Matlab documentation int maxIterations = 2000; double stopFitness = 0; //Double.NEGATIVE_INFINITY; boolean isActiveCMA = true; int diagonalOnly = 0; int checkFeasableCount = 1; RandomGenerator random = new Well19937c(); boolean generateStatistics = false; ConvergenceChecker<PointValuePair> checker = new SimpleValueChecker(1e-6, 1e-10); // The sigma determines the search range for the variables. It should be 1/3 of the initial search region. OptimizationData sigma = new CMAESOptimizer.Sigma(new double[] { (uB[0] - lB[0]) / 3 }); OptimizationData popSize = new CMAESOptimizer.PopulationSize((int) (4 + Math.floor(3 * Math.log(2)))); try { PointValuePair solution = null; boolean noRefit = maximumLikelihood; if (n == 1 && zeroTruncated) { // No need to fit solution = new PointValuePair(new double[] { 1 }, 0); noRefit = true; } else { GoalType goalType = (maximumLikelihood) ? GoalType.MAXIMIZE : GoalType.MINIMIZE; // Iteratively fit CMAESOptimizer opt = new CMAESOptimizer(maxIterations, stopFitness, isActiveCMA, diagonalOnly, checkFeasableCount, random, generateStatistics, checker); for (int iteration = 0; iteration <= fitRestarts; iteration++) { try { // Start from the initial solution PointValuePair result = opt.optimize(new InitialGuess(initialSolution), new ObjectiveFunction(function), goalType, bounds, sigma, popSize, new MaxIter(maxIterations), new MaxEval(maxIterations * 2)); //System.out.printf("CMAES Iter %d initial = %g (%d)\n", iteration, result.getValue(), // opt.getEvaluations()); if (solution == null || result.getValue() < solution.getValue()) { solution = result; } } catch (TooManyEvaluationsException e) { } catch (TooManyIterationsException e) { } if (solution == null) continue; try { // Also restart from the current optimum PointValuePair result = opt.optimize(new InitialGuess(solution.getPointRef()), new ObjectiveFunction(function), goalType, bounds, sigma, popSize, new MaxIter(maxIterations), new MaxEval(maxIterations * 2)); //System.out.printf("CMAES Iter %d restart = %g (%d)\n", iteration, result.getValue(), // opt.getEvaluations()); if (result.getValue() < solution.getValue()) { solution = result; } } catch (TooManyEvaluationsException e) { } catch (TooManyIterationsException e) { } } if (solution == null) return null; } if (noRefit) { // Although we fit the log-likelihood, return the sum-of-squares to allow // comparison across different n double p = solution.getPointRef()[0]; double ss = 0; double[] obs = function.p; double[] exp = function.getP(p); for (int i = 0; i < obs.length; i++) ss += (obs[i] - exp[i]) * (obs[i] - exp[i]); return new PointValuePair(solution.getPointRef(), ss); } // We can do a LVM refit if the number of fitted points is more than 1 else if (nFittedPoints > 1) { // Improve SS fit with a gradient based LVM optimizer LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer(); try { final BinomialModelFunctionGradient gradientFunction = new BinomialModelFunctionGradient( histogram, n, zeroTruncated); PointVectorValuePair lvmSolution = optimizer.optimize(new MaxIter(3000), new MaxEval(Integer.MAX_VALUE), new ModelFunctionJacobian(new MultivariateMatrixFunction() { public double[][] value(double[] point) throws IllegalArgumentException { return gradientFunction.jacobian(point); } }), new ModelFunction(gradientFunction), new Target(gradientFunction.p), new Weight(gradientFunction.getWeights()), new InitialGuess(solution.getPointRef())); double ss = 0; double[] obs = gradientFunction.p; double[] exp = lvmSolution.getValue(); for (int i = 0; i < obs.length; i++) ss += (obs[i] - exp[i]) * (obs[i] - exp[i]); // Check the pValue is valid since the LVM is not bounded. double p = lvmSolution.getPointRef()[0]; if (ss < solution.getValue() && p <= 1 && p >= 0) { //log("Re-fitting improved the SS from %s to %s (-%s%%)", // Utils.rounded(solution.getValue(), 4), Utils.rounded(ss, 4), // Utils.rounded(100 * (solution.getValue() - ss) / solution.getValue(), 4)); return new PointValuePair(lvmSolution.getPoint(), ss); } } catch (TooManyIterationsException e) { log("Failed to re-fit: Too many iterations (%d)", optimizer.getIterations()); } catch (ConvergenceException e) { log("Failed to re-fit: %s", e.getMessage()); } catch (Exception e) { // Ignore this ... } } return solution; } catch (Exception e) { log("Failed to fit Binomial distribution with N=%d : %s", n, e.getMessage()); } return null; }
From source file:gdsc.smlm.ij.plugins.pcpalm.PCPALMFitting.java
private PointValuePair runBoundedOptimiser(double[][] gr, double[] initialSolution, double[] lB, double[] uB, SumOfSquaresModelFunction function) { // Create the functions to optimise ObjectiveFunction objective = new ObjectiveFunction(new SumOfSquaresMultivariateFunction(function)); ObjectiveFunctionGradient gradient = new ObjectiveFunctionGradient( new SumOfSquaresMultivariateVectorFunction(function)); final boolean debug = false; // Try a BFGS optimiser since this will produce a deterministic solution and can respect bounds. PointValuePair optimum = null; boundedEvaluations = 0;/*from w w w . j ava2 s.c o m*/ final MaxEval maxEvaluations = new MaxEval(2000); MultivariateOptimizer opt = null; for (int iteration = 0; iteration <= fitRestarts; iteration++) { try { opt = new BFGSOptimizer(); final double relativeThreshold = 1e-6; // Configure maximum step length for each dimension using the bounds double[] stepLength = new double[lB.length]; for (int i = 0; i < stepLength.length; i++) stepLength[i] = (uB[i] - lB[i]) * 0.3333333; // The GoalType is always minimise so no need to pass this in optimum = opt.optimize(maxEvaluations, gradient, objective, new InitialGuess((optimum == null) ? initialSolution : optimum.getPointRef()), new SimpleBounds(lB, uB), new BFGSOptimizer.GradientTolerance(relativeThreshold), new BFGSOptimizer.StepLength(stepLength)); if (debug) System.out.printf("BFGS Iter %d = %g (%d)\n", iteration, optimum.getValue(), opt.getEvaluations()); } catch (TooManyEvaluationsException e) { break; // No need to restart } catch (RuntimeException e) { break; // No need to restart } finally { boundedEvaluations += opt.getEvaluations(); } } // Try a CMAES optimiser which is non-deterministic. To overcome this we perform restarts. // CMAESOptimiser based on Matlab code: // https://www.lri.fr/~hansen/cmaes.m // Take the defaults from the Matlab documentation double stopFitness = 0; //Double.NEGATIVE_INFINITY; boolean isActiveCMA = true; int diagonalOnly = 0; int checkFeasableCount = 1; RandomGenerator random = new Well44497b(); //Well19937c(); boolean generateStatistics = false; ConvergenceChecker<PointValuePair> checker = new SimpleValueChecker(1e-6, 1e-10); // The sigma determines the search range for the variables. It should be 1/3 of the initial search region. double[] range = new double[lB.length]; for (int i = 0; i < lB.length; i++) range[i] = (uB[i] - lB[i]) / 3; OptimizationData sigma = new CMAESOptimizer.Sigma(range); OptimizationData popSize = new CMAESOptimizer.PopulationSize( (int) (4 + Math.floor(3 * Math.log(initialSolution.length)))); SimpleBounds bounds = new SimpleBounds(lB, uB); opt = new CMAESOptimizer(maxEvaluations.getMaxEval(), stopFitness, isActiveCMA, diagonalOnly, checkFeasableCount, random, generateStatistics, checker); // Restart the optimiser several times and take the best answer. for (int iteration = 0; iteration <= fitRestarts; iteration++) { try { // Start from the initial solution PointValuePair constrainedSolution = opt.optimize(new InitialGuess(initialSolution), objective, GoalType.MINIMIZE, bounds, sigma, popSize, maxEvaluations); if (debug) System.out.printf("CMAES Iter %d initial = %g (%d)\n", iteration, constrainedSolution.getValue(), opt.getEvaluations()); boundedEvaluations += opt.getEvaluations(); if (optimum == null || constrainedSolution.getValue() < optimum.getValue()) { optimum = constrainedSolution; } } catch (TooManyEvaluationsException e) { } catch (TooManyIterationsException e) { } finally { boundedEvaluations += maxEvaluations.getMaxEval(); } if (optimum == null) continue; try { // Also restart from the current optimum PointValuePair constrainedSolution = opt.optimize(new InitialGuess(optimum.getPointRef()), objective, GoalType.MINIMIZE, bounds, sigma, popSize, maxEvaluations); if (debug) System.out.printf("CMAES Iter %d restart = %g (%d)\n", iteration, constrainedSolution.getValue(), opt.getEvaluations()); if (constrainedSolution.getValue() < optimum.getValue()) { optimum = constrainedSolution; } } catch (TooManyEvaluationsException e) { } catch (TooManyIterationsException e) { } finally { boundedEvaluations += maxEvaluations.getMaxEval(); } } return optimum; }
From source file:gdsc.smlm.ij.plugins.TraceDiffusion.java
/** * Fit the jump distance histogram using a cumulative sum as detailed in * <p>//from w w w.j a v a2 s .co m * Update the plot by adding the fit line. * * @param jumpDistances * @param jdHistogram * @param title * @param plot * @return */ private double[][] fitJumpDistance(StoredDataStatistics jumpDistances, double[][] jdHistogram, String title, Plot2 plot) { final double meanDistance = Math.sqrt(jumpDistances.getMean()) * 1e3; final double beta = meanDistance / precision; Utils.log( "Jump Distance analysis : N = %d, Time = %d frames (%s seconds). Mean Distance = %s nm, Precision = %s nm, Beta = %s", jumpDistances.getN(), settings.jumpDistance, Utils.rounded(settings.jumpDistance * exposureTime, 4), Utils.rounded(meanDistance, 4), Utils.rounded(precision, 4), Utils.rounded(beta, 4)); int n = 0; int N = 10; double[] SS = new double[N]; Arrays.fill(SS, -1); double[] ic = new double[N]; Arrays.fill(ic, Double.POSITIVE_INFINITY); double[][] coefficients = new double[N][]; double[][] fractions = new double[N][]; double[][] fitParams = new double[N][]; double bestIC = Double.POSITIVE_INFINITY; int best = -1; // Guess the D final double estimatedD = jumpDistances.getMean() / 4; Utils.log("Estimated D = %s um^2/s", Utils.rounded(estimatedD, 4)); // Fit using a single population model LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer(); try { final JumpDistanceFunction function = new JumpDistanceFunction(jdHistogram[0], jdHistogram[1], estimatedD); PointVectorValuePair lvmSolution = optimizer.optimize(new MaxIter(3000), new MaxEval(Integer.MAX_VALUE), new ModelFunctionJacobian(new MultivariateMatrixFunction() { public double[][] value(double[] point) throws IllegalArgumentException { return function.jacobian(point); } }), new ModelFunction(function), new Target(function.getY()), new Weight(function.getWeights()), new InitialGuess(function.guess())); fitParams[n] = lvmSolution.getPointRef(); SS[n] = calculateSumOfSquares(function.getY(), lvmSolution.getValueRef()); ic[n] = Maths.getInformationCriterion(SS[n], function.x.length, 1); coefficients[n] = fitParams[n]; fractions[n] = new double[] { 1 }; Utils.log("Fit Jump distance (N=%d) : D = %s um^2/s, SS = %f, IC = %s (%d evaluations)", n + 1, Utils.rounded(fitParams[n][0], 4), SS[n], Utils.rounded(ic[n], 4), optimizer.getEvaluations()); bestIC = ic[n]; best = 0; addToPlot(function, fitParams[n], jdHistogram, title, plot, Color.magenta); } catch (TooManyIterationsException e) { Utils.log("Failed to fit : Too many iterations (%d)", optimizer.getIterations()); } catch (ConvergenceException e) { Utils.log("Failed to fit : %s", e.getMessage()); } n++; // Fit using a mixed population model. // Vary n from 2 to N. Stop when the fit fails or the fit is worse. int bestMulti = -1; double bestMultiIC = Double.POSITIVE_INFINITY; while (n < N) { // Uses a weighted sum of n exponential functions, each function models a fraction of the particles. // An LVM fit cannot restrict the parameters so the fractions do not go below zero. // Use the CMEASOptimizer which supports bounded fitting. MixedJumpDistanceFunctionMultivariate mixedFunction = new MixedJumpDistanceFunctionMultivariate( jdHistogram[0], jdHistogram[1], estimatedD, n + 1); double[] lB = mixedFunction.getLowerBounds(); double[] uB = mixedFunction.getUpperBounds(); SimpleBounds bounds = new SimpleBounds(lB, uB); int maxIterations = 2000; double stopFitness = 0; //Double.NEGATIVE_INFINITY; boolean isActiveCMA = true; int diagonalOnly = 20; int checkFeasableCount = 1; RandomGenerator random = new Well19937c(); boolean generateStatistics = false; ConvergenceChecker<PointValuePair> checker = new SimpleValueChecker(1e-6, 1e-10); // The sigma determines the search range for the variables. It should be 1/3 of the initial search region. double[] s = new double[lB.length]; for (int i = 0; i < s.length; i++) s[i] = (uB[i] - lB[i]) / 3; OptimizationData sigma = new CMAESOptimizer.Sigma(s); OptimizationData popSize = new CMAESOptimizer.PopulationSize( (int) (4 + Math.floor(3 * Math.log(mixedFunction.x.length)))); // Iterate this for stability in the initial guess CMAESOptimizer opt = new CMAESOptimizer(maxIterations, stopFitness, isActiveCMA, diagonalOnly, checkFeasableCount, random, generateStatistics, checker); int evaluations = 0; PointValuePair constrainedSolution = null; for (int i = 0; i <= settings.fitRestarts; i++) { // Try from the initial guess try { PointValuePair solution = opt.optimize(new InitialGuess(mixedFunction.guess()), new ObjectiveFunction(mixedFunction), GoalType.MINIMIZE, bounds, sigma, popSize, new MaxIter(maxIterations), new MaxEval(maxIterations * 2)); if (constrainedSolution == null || solution.getValue() < constrainedSolution.getValue()) { evaluations = opt.getEvaluations(); constrainedSolution = solution; //Utils.log("[%da] Fit Jump distance (N=%d) : SS = %f (%d evaluations)", i, n + 1, // solution.getValue(), evaluations); } } catch (TooManyEvaluationsException e) { } if (constrainedSolution == null) continue; // Try from the current optimum try { PointValuePair solution = opt.optimize(new InitialGuess(constrainedSolution.getPointRef()), new ObjectiveFunction(mixedFunction), GoalType.MINIMIZE, bounds, sigma, popSize, new MaxIter(maxIterations), new MaxEval(maxIterations * 2)); if (constrainedSolution == null || solution.getValue() < constrainedSolution.getValue()) { evaluations = opt.getEvaluations(); constrainedSolution = solution; //Utils.log("[%db] Fit Jump distance (N=%d) : SS = %f (%d evaluations)", i, n + 1, // solution.getValue(), evaluations); } } catch (TooManyEvaluationsException e) { } } if (constrainedSolution == null) { Utils.log("Failed to fit N=%d", n + 1); break; } fitParams[n] = constrainedSolution.getPointRef(); SS[n] = constrainedSolution.getValue(); // TODO - Try a bounded BFGS optimiser // Try and improve using a LVM fit final MixedJumpDistanceFunctionGradient mixedFunctionGradient = new MixedJumpDistanceFunctionGradient( jdHistogram[0], jdHistogram[1], estimatedD, n + 1); PointVectorValuePair lvmSolution; try { lvmSolution = optimizer.optimize(new MaxIter(3000), new MaxEval(Integer.MAX_VALUE), new ModelFunctionJacobian(new MultivariateMatrixFunction() { public double[][] value(double[] point) throws IllegalArgumentException { return mixedFunctionGradient.jacobian(point); } }), new ModelFunction(mixedFunctionGradient), new Target(mixedFunctionGradient.getY()), new Weight(mixedFunctionGradient.getWeights()), new InitialGuess(fitParams[n])); double ss = calculateSumOfSquares(mixedFunctionGradient.getY(), lvmSolution.getValue()); // All fitted parameters must be above zero if (ss < SS[n] && Maths.min(lvmSolution.getPoint()) > 0) { //Utils.log(" Re-fitting improved the SS from %s to %s (-%s%%)", Utils.rounded(SS[n], 4), // Utils.rounded(ss, 4), Utils.rounded(100 * (SS[n] - ss) / SS[n], 4)); fitParams[n] = lvmSolution.getPoint(); SS[n] = ss; evaluations += optimizer.getEvaluations(); } } catch (TooManyIterationsException e) { //Utils.log("Failed to re-fit : Too many evaluations (%d)", optimizer.getEvaluations()); } catch (ConvergenceException e) { //Utils.log("Failed to re-fit : %s", e.getMessage()); } // Since the fractions must sum to one we subtract 1 degree of freedom from the number of parameters ic[n] = Maths.getInformationCriterion(SS[n], mixedFunction.x.length, fitParams[n].length - 1); double[] d = new double[n + 1]; double[] f = new double[n + 1]; double sum = 0; for (int i = 0; i < d.length; i++) { f[i] = fitParams[n][i * 2]; sum += f[i]; d[i] = fitParams[n][i * 2 + 1]; } for (int i = 0; i < f.length; i++) f[i] /= sum; // Sort by coefficient size sort(d, f); coefficients[n] = d; fractions[n] = f; Utils.log("Fit Jump distance (N=%d) : D = %s um^2/s (%s), SS = %f, IC = %s (%d evaluations)", n + 1, format(d), format(f), SS[n], Utils.rounded(ic[n], 4), evaluations); boolean valid = true; for (int i = 0; i < f.length; i++) { // Check the fit has fractions above the minimum fraction if (f[i] < minFraction) { Utils.log("Fraction is less than the minimum fraction: %s < %s", Utils.rounded(f[i]), Utils.rounded(minFraction)); valid = false; break; } // Check the coefficients are different if (i + 1 < f.length && d[i] / d[i + 1] < minDifference) { Utils.log("Coefficients are not different: %s / %s = %s < %s", Utils.rounded(d[i]), Utils.rounded(d[i + 1]), Utils.rounded(d[i] / d[i + 1]), Utils.rounded(minDifference)); valid = false; break; } } if (!valid) break; // Store the best model if (bestIC > ic[n]) { bestIC = ic[n]; best = n; } // Store the best multi model if (bestMultiIC < ic[n]) { break; } bestMultiIC = ic[n]; bestMulti = n; n++; } // Add the best fit to the plot and return the parameters. if (bestMulti > -1) { Function function = new MixedJumpDistanceFunctionMultivariate(jdHistogram[0], jdHistogram[1], 0, bestMulti + 1); addToPlot(function, fitParams[bestMulti], jdHistogram, title, plot, Color.yellow); } if (best > -1) { Utils.log("Best fit achieved using %d population%s: D = %s um^2/s, Fractions = %s", best + 1, (best == 0) ? "" : "s", format(coefficients[best]), format(fractions[best])); } return (best > -1) ? new double[][] { coefficients[best], fractions[best] } : null; }
From source file:gdsc.smlm.fitting.nonlinear.MaximumLikelihoodFitter.java
public FitStatus fit(int n, double[] y, double[] y_fit, double[] a, double[] a_dev, double[] error, double noise) { numberOfFittedPoints = n;/* w ww.j a v a 2 s. c o m*/ LikelihoodWrapper maximumLikelihoodFunction; // We can use different likelihood wrapper functions: switch (likelihoodFunction) { case POISSON_GAMMA_GAUSSIAN: // Poisson-Gamma-Gaussian - EM-CCD data if (alpha > 0 && sigma > 0) { maximumLikelihoodFunction = new PoissonGammaGaussianLikelihoodWrapper(f, a, y, n, alpha, sigma); break; } case POISSON_GAUSSIAN: // Poisson-Gaussian - CCD data if (sigma > 0) { maximumLikelihoodFunction = new PoissonGaussianLikelihoodWrapper(f, a, y, n, sigma); break; } case POISSON: default: // Poisson - most counting data maximumLikelihoodFunction = new PoissonLikelihoodWrapper(f, a, y, n); } // Check if the method requires the gradient but it cannot be computed if (searchMethod.usesGradient && !maximumLikelihoodFunction.canComputeGradient()) { maximumLikelihoodFunction = new PoissonLikelihoodWrapper(f, a, y, n); } try { double[] startPoint = getInitialSolution(a); PointValuePair optimum = null; if (searchMethod == SearchMethod.POWELL || searchMethod == SearchMethod.POWELL_BOUNDED) { // Non-differentiable version using Powell Optimiser // This is as per the method in Numerical Recipes 10.5 (Direction Set (Powell's) method) // I could extend the optimiser and implement bounds on the directions moved. However the mapping // adapter seems to work OK. final boolean basisConvergence = false; // Perhaps these thresholds should be tighter? // The default is to use the sqrt() of the overall tolerance //final double lineRel = FastMath.sqrt(relativeThreshold); //final double lineAbs = FastMath.sqrt(absoluteThreshold); //final double lineRel = relativeThreshold * 1e2; //final double lineAbs = absoluteThreshold * 1e2; // Since we are fitting only a small number of parameters then just use the same tolerance // for each search direction final double lineRel = relativeThreshold; final double lineAbs = absoluteThreshold; CustomPowellOptimizer o = new CustomPowellOptimizer(relativeThreshold, absoluteThreshold, lineRel, lineAbs, null, basisConvergence); OptimizationData maxIterationData = null; if (getMaxIterations() > 0) maxIterationData = new MaxIter(getMaxIterations()); if (searchMethod == SearchMethod.POWELL) { if (powellFunction == null) { // We must map all the parameters into the same range. This is done in the Mortensen MLE // Python code by using the sqrt of the number of photons and background. if (mapGaussian) { Gaussian2DFunction gf = (Gaussian2DFunction) f; // Re-map signal and background using the sqrt int[] indices = gf.gradientIndices(); int[] map = new int[indices.length]; int count = 0; // Background is always first if (indices[0] == Gaussian2DFunction.BACKGROUND) { map[count++] = 0; } // Look for the Signal in multiple peak 2D Gaussians for (int i = 1; i < indices.length; i++) if (indices[i] % 6 == Gaussian2DFunction.SIGNAL) { map[count++] = i; } if (count > 0) { powellFunction = new MappedMultivariateLikelihood(maximumLikelihoodFunction, Arrays.copyOf(map, count)); } } if (powellFunction == null) { powellFunction = new MultivariateLikelihood(maximumLikelihoodFunction); } } // Update the maximum likelihood function in the Powell function wrapper powellFunction.fun = maximumLikelihoodFunction; OptimizationData positionChecker = null; // new org.apache.commons.math3.optim.PositionChecker(relativeThreshold, absoluteThreshold); if (powellFunction.isMapped()) { MappedMultivariateLikelihood adapter = (MappedMultivariateLikelihood) powellFunction; optimum = o.optimize(maxIterationData, new MaxEval(getMaxEvaluations()), new ObjectiveFunction(powellFunction), GoalType.MINIMIZE, new InitialGuess(adapter.map(startPoint)), positionChecker); double[] solution = adapter.unmap(optimum.getPointRef()); optimum = new PointValuePair(solution, optimum.getValue()); } else { optimum = o.optimize(maxIterationData, new MaxEval(getMaxEvaluations()), new ObjectiveFunction(powellFunction), GoalType.MINIMIZE, new InitialGuess(startPoint), positionChecker); } } else { // Try using the mapping adapter for a bounded Powell search MultivariateFunctionMappingAdapter adapter = new MultivariateFunctionMappingAdapter( new MultivariateLikelihood(maximumLikelihoodFunction), lower, upper); optimum = o.optimize(maxIterationData, new MaxEval(getMaxEvaluations()), new ObjectiveFunction(adapter), GoalType.MINIMIZE, new InitialGuess(adapter.boundedToUnbounded(startPoint))); double[] solution = adapter.unboundedToBounded(optimum.getPointRef()); optimum = new PointValuePair(solution, optimum.getValue()); } iterations = o.getIterations(); evaluations = o.getEvaluations(); } else if (searchMethod == SearchMethod.BOBYQA) { // Differentiable approximation using Powell's BOBYQA algorithm. // This is slower than the Powell optimiser and requires a high number of evaluations. int numberOfInterpolationPoints = this.getNumberOfFittedParameters() + 2; BOBYQAOptimizer o = new BOBYQAOptimizer(numberOfInterpolationPoints); optimum = o.optimize(new MaxEval(getMaxEvaluations()), new ObjectiveFunction(new MultivariateLikelihood(maximumLikelihoodFunction)), GoalType.MINIMIZE, new InitialGuess(startPoint), new SimpleBounds(lower, upper)); iterations = o.getIterations(); evaluations = o.getEvaluations(); } else if (searchMethod == SearchMethod.CMAES) { // TODO - Understand why the CMAES optimiser does not fit very well on test data. It appears // to converge too early and the likelihood scores are not as low as the other optimisers. // CMAESOptimiser based on Matlab code: // https://www.lri.fr/~hansen/cmaes.m // Take the defaults from the Matlab documentation double stopFitness = 0; //Double.NEGATIVE_INFINITY; boolean isActiveCMA = true; int diagonalOnly = 0; int checkFeasableCount = 1; RandomGenerator random = new Well19937c(); boolean generateStatistics = false; // The sigma determines the search range for the variables. It should be 1/3 of the initial search region. double[] sigma = new double[lower.length]; for (int i = 0; i < sigma.length; i++) sigma[i] = (upper[i] - lower[i]) / 3; int popSize = (int) (4 + Math.floor(3 * Math.log(sigma.length))); // The CMAES optimiser is random and restarting can overcome problems with quick convergence. // The Apache commons documentations states that convergence should occur between 30N and 300N^2 // function evaluations final int n30 = FastMath.min(sigma.length * sigma.length * 30, getMaxEvaluations() / 2); evaluations = 0; OptimizationData[] data = new OptimizationData[] { new InitialGuess(startPoint), new CMAESOptimizer.PopulationSize(popSize), new MaxEval(getMaxEvaluations()), new CMAESOptimizer.Sigma(sigma), new ObjectiveFunction(new MultivariateLikelihood(maximumLikelihoodFunction)), GoalType.MINIMIZE, new SimpleBounds(lower, upper) }; // Iterate to prevent early convergence int repeat = 0; while (evaluations < n30) { if (repeat++ > 1) { // Update the start point and population size data[0] = new InitialGuess(optimum.getPointRef()); popSize *= 2; data[1] = new CMAESOptimizer.PopulationSize(popSize); } CMAESOptimizer o = new CMAESOptimizer(getMaxIterations(), stopFitness, isActiveCMA, diagonalOnly, checkFeasableCount, random, generateStatistics, new SimpleValueChecker(relativeThreshold, absoluteThreshold)); PointValuePair result = o.optimize(data); iterations += o.getIterations(); evaluations += o.getEvaluations(); //System.out.printf("CMAES [%d] i=%d [%d], e=%d [%d]\n", repeat, o.getIterations(), iterations, // o.getEvaluations(), totalEvaluations); if (optimum == null || result.getValue() < optimum.getValue()) { optimum = result; } } } else if (searchMethod == SearchMethod.BFGS) { // BFGS can use an approximate line search minimisation where as Powell and conjugate gradient // methods require a more accurate line minimisation. The BFGS search does not do a full // minimisation but takes appropriate steps in the direction of the current gradient. // Do not use the convergence checker on the value of the function. Use the convergence on the // point coordinate and gradient //BFGSOptimizer o = new BFGSOptimizer(new SimpleValueChecker(rel, abs)); BFGSOptimizer o = new BFGSOptimizer(); // Configure maximum step length for each dimension using the bounds double[] stepLength = new double[lower.length]; for (int i = 0; i < stepLength.length; i++) { stepLength[i] = (upper[i] - lower[i]) * 0.3333333; if (stepLength[i] <= 0) stepLength[i] = Double.POSITIVE_INFINITY; } // The GoalType is always minimise so no need to pass this in OptimizationData positionChecker = null; //new org.apache.commons.math3.optim.PositionChecker(relativeThreshold, absoluteThreshold); optimum = o.optimize(new MaxEval(getMaxEvaluations()), new ObjectiveFunctionGradient(new MultivariateVectorLikelihood(maximumLikelihoodFunction)), new ObjectiveFunction(new MultivariateLikelihood(maximumLikelihoodFunction)), new InitialGuess(startPoint), new SimpleBounds(lowerConstraint, upperConstraint), new BFGSOptimizer.GradientTolerance(relativeThreshold), positionChecker, new BFGSOptimizer.StepLength(stepLength)); iterations = o.getIterations(); evaluations = o.getEvaluations(); } else { // The line search algorithm often fails. This is due to searching into a region where the // function evaluates to a negative so has been clipped. This means the upper bound of the line // cannot be found. // Note that running it on an easy problem (200 photons with fixed fitting (no background)) the algorithm // does sometimes produces results better than the Powell algorithm but it is slower. BoundedNonLinearConjugateGradientOptimizer o = new BoundedNonLinearConjugateGradientOptimizer( (searchMethod == SearchMethod.CONJUGATE_GRADIENT_FR) ? Formula.FLETCHER_REEVES : Formula.POLAK_RIBIERE, new SimpleValueChecker(relativeThreshold, absoluteThreshold)); // Note: The gradients may become unstable at the edge of the bounds. Or they will not change // direction if the true solution is on the bounds since the gradient will always continue // towards the bounds. This is key to the conjugate gradient method. It searches along a vector // until the direction of the gradient is in the opposite direction (using dot products, i.e. // cosine of angle between them) // NR 10.7 states there is no advantage of the variable metric DFP or BFGS methods over // conjugate gradient methods. So I will try these first. // Try this: // Adapt the conjugate gradient optimiser to use the gradient to pick the search direction // and then for the line minimisation. However if the function is out of bounds then clip the // variables at the bounds and continue. // If the current point is at the bounds and the gradient is to continue out of bounds then // clip the gradient too. // Or: just use the gradient for the search direction then use the line minimisation/rest // as per the Powell optimiser. The bounds should limit the search. // I tried a Bounded conjugate gradient optimiser with clipped variables: // This sometimes works. However when the variables go a long way out of the expected range the gradients // can have vastly different magnitudes. This results in the algorithm stalling since the gradients // can be close to zero and the some of the parameters are no longer adjusted. // Perhaps this can be looked for and the algorithm then gives up and resorts to a Powell optimiser from // the current point. // Changed the bracketing step to very small (default is 1, changed to 0.001). This improves the // performance. The gradient direction is very sensitive to small changes in the coordinates so a // tighter bracketing of the line search helps. // Tried using a non-gradient method for the line search copied from the Powell optimiser: // This also works when the bracketing step is small but the number of iterations is higher. // 24.10.2014: I have tried to get conjugate gradient to work but the gradient function // must not behave suitably for the optimiser. In the current state both methods of using a // Bounded Conjugate Gradient Optimiser perform poorly relative to other optimisers: // Simulated : n=1000, signal=200, x=0.53, y=0.47 // LVM : n=1000, signal=171, x=0.537, y=0.471 (1.003s) // Powell : n=1000, signal=187, x=0.537, y=0.48 (1.238s) // Gradient based PR (constrained): n=858, signal=161, x=0.533, y=0.474 (2.54s) // Gradient based PR (bounded): n=948, signal=161, x=0.533, y=0.473 (2.67s) // Non-gradient based : n=1000, signal=151.47, x=0.535, y=0.474 (1.626s) // The conjugate optimisers are slower, under predict the signal by the most and in the case of // the gradient based optimiser, fail to converge on some problems. This is worse when constrained // fitting is used and not tightly bounded fitting. // I will leave the code in as an option but would not recommend using it. I may remove it in the // future. // Note: It is strange that the non-gradient based line minimisation is more successful. // It may be that the gradient function is not accurate (due to round off error) or that it is // simply wrong when far from the optimum. My JUnit tests only evaluate the function within the // expected range of the answer. // Note the default step size on the Powell optimiser is 1 but the initial directions are unit vectors. // So our bracketing step should be a minimum of 1 / average length of the first gradient vector to prevent // the first step being too large when bracketing. final double gradient[] = new double[startPoint.length]; maximumLikelihoodFunction.likelihood(startPoint, gradient); double l = 0; for (double d : gradient) l += d * d; final double bracketingStep = FastMath.min(0.001, ((l > 1) ? 1.0 / l : 1)); //System.out.printf("Bracketing step = %f (length=%f)\n", bracketingStep, l); o.setUseGradientLineSearch(gradientLineMinimisation); optimum = o.optimize(new MaxEval(getMaxEvaluations()), new ObjectiveFunctionGradient(new MultivariateVectorLikelihood(maximumLikelihoodFunction)), new ObjectiveFunction(new MultivariateLikelihood(maximumLikelihoodFunction)), GoalType.MINIMIZE, new InitialGuess(startPoint), new SimpleBounds(lowerConstraint, upperConstraint), new BoundedNonLinearConjugateGradientOptimizer.BracketingStep(bracketingStep)); iterations = o.getIterations(); evaluations = o.getEvaluations(); //maximumLikelihoodFunction.value(solution, gradient); //System.out.printf("Iter = %d, %g @ %s : %s\n", iterations, ll, Arrays.toString(solution), // Arrays.toString(gradient)); } final double[] solution = optimum.getPointRef(); setSolution(a, solution); //System.out.printf("Iter = %d, Eval = %d, %g @ %s\n", iterations, evaluations, optimum.getValue(), // java.util.Arrays.toString(solution)); // Compute residuals for the FunctionSolver interface if (y_fit == null || y_fit.length < n) y_fit = new double[n]; f.initialise(a); residualSumOfSquares = 0; for (int i = 0; i < n; i++) { y_fit[i] = f.eval(i); final double residual = y[i] - y_fit[i]; residualSumOfSquares += residual * residual; } if (a_dev != null) { // Assume the Maximum Likelihood estimator returns the optimum fit (achieves the Cramer Roa // lower bounds) and so the covariance can be obtained from the Fisher Information Matrix. final int[] gradientIndices = f.gradientIndices(); final int nparams = gradientIndices.length; GradientCalculator calculator = GradientCalculatorFactory.newCalculator(nparams); final double[] I = calculator.fisherInformationDiagonal(n, a, f); for (int i = 0; i < gradientIndices.length; i++) a_dev[gradientIndices[i]] = 1.0 / Math.sqrt(I[i]); } error[0] = NonLinearFit.getError(residualSumOfSquares, noise, n, f.gradientIndices().length); totalSumOfSquares = getSumOfSquares(n, y); } catch (TooManyIterationsException e) { //System.out.printf("Too many iterations = %d\n", e.getMax()); //e.printStackTrace(); return FitStatus.FAILED_TO_CONVERGE; } catch (TooManyEvaluationsException e) { //System.out.printf("Too many evaluations = %d\n", e.getMax()); //e.printStackTrace(); return FitStatus.FAILED_TO_CONVERGE; } catch (ConvergenceException e) { // Occurs when QR decomposition fails - mark as a singular non-linear model (no solution) //System.out.printf("Singular non linear model = %s\n", e.getMessage()); return FitStatus.SINGULAR_NON_LINEAR_MODEL; } catch (BFGSOptimizer.LineSearchRoundoffException e) { //System.out.println("BFGS error: " + e.getMessage()); //e.printStackTrace(); return FitStatus.FAILED_TO_CONVERGE; } catch (Exception e) { //System.out.printf("Unknown error = %s\n", e.getMessage()); e.printStackTrace(); return FitStatus.UNKNOWN; } return FitStatus.OK; }