package cad.gcs; import gnu.trove.list.TDoubleList; import gnu.trove.list.array.TDoubleArrayList; import org.apache.commons.math3.exception.ConvergenceException; import org.apache.commons.math3.exception.DimensionMismatchException; import org.apache.commons.math3.exception.MathInternalError; import org.apache.commons.math3.exception.util.LocalizedFormats; import org.apache.commons.math3.linear.Array2DRowRealMatrix; import org.apache.commons.math3.linear.ArrayRealVector; import org.apache.commons.math3.linear.BlockRealMatrix; import org.apache.commons.math3.linear.DecompositionSolver; import org.apache.commons.math3.linear.LUDecomposition; import org.apache.commons.math3.linear.QRDecomposition; import org.apache.commons.math3.linear.RealMatrix; import org.apache.commons.math3.linear.SingularMatrixException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class Solver { public static final boolean useLU = true; private static final double DBL_EPSILON = Double.MIN_VALUE; enum SolveStatus { Success, // Found a solution zeroing the error function Converged, // Found a solution minimizing the error function Failed // Failed to find any solution } static int MaxIterations = 100; //Note that the total number of iterations allowed is MaxIterations *xLength public static SolveStatus solve_DL(SubSystem subsys) { double tolg = 1e-80, tolx = 1e-80, tolf = 1e-10; int xsize = subsys.pSize(); int csize = subsys.cSize(); if (xsize == 0) return SolveStatus.Success; RealMatrix x = mtrx(xsize), x_new = mtrx(xsize); RealMatrix fx = mtrx(csize), fx_new = mtrx(csize); RealMatrix Jx = mtrx(csize, xsize), Jx_new = mtrx(csize, xsize); RealMatrix g = mtrx(xsize), h_sd = mtrx(xsize), h_gn = mtrx(xsize), h_dl = mtrx(xsize); // subsys.redirectParams(); double err; subsys.fillParams(x); err = subsys.calcResidual(fx); subsys.calcJacobi(Jx); g = Jx.transpose().multiply(fx.scalarMultiply(-1)); // get the infinity norm fx_inf and g_inf double g_inf = infinityNorm(g); double fx_inf = infinityNorm(fx); int maxIterNumber = MaxIterations * xsize; double divergingLim = 1e6 * err + 1e12; double delta = 0.1; double alpha = 0.; double nu = 2.; int iter = 0, stop = 0, reduce = 0; double mu = 1e-8; while (stop == 0) { // check if finished if (fx_inf <= tolf) // Success stop = 1; else if (g_inf <= tolg) stop = 2; else if (delta <= tolx * (tolx + x.getFrobeniusNorm())) stop = 2; else if (iter >= maxIterNumber) stop = 4; else if (err > divergingLim || err != err) { // check for diverging and NaN stop = 6; } else { // get the steepest descent direction alpha = squaredNorm(g) / squaredNorm((Jx.multiply(g))); h_sd = g.scalarMultiply(alpha); RealMatrix A = Jx.transpose().multiply(Jx); RealMatrix gg = Jx.transpose().multiply(fx.scalarMultiply(-1)); double[] diag_A = diagonal(A); double mu_increase_factor_ = 10.0; do { for (int i = 0; i < xsize; ++i) { A.addToEntry(i, i, mu); } boolean success = true; for (int _ = 0; _ < 1000; _++) { try { h_gn = new LUDecomposition(A).getSolver().solve(gg); } catch (Exception ssse) { mu *= 1./3.; for (int i = 0; i < xsize; ++i) { A.setEntry(i, i, diag_A[i] * mu); } if (_ == 999) { return SolveStatus.Success; } continue; } break; } if (success) { break; } mu *= mu_increase_factor_; } while (mu < 1.0); // get the gauss-newton step // h_gn = new LUDecomposition(new Array2DRowRealMatrix(makeSquare(Jx.getData()))).getSolver().solve(fx.scalarMultiply(-1)); for (int i = 0; i < xsize; ++i) // restore diagonal J^T J entries { A.setEntry(i, i, diag_A[i]); } double rel_error = (Jx.multiply(h_gn).add(fx)).getFrobeniusNorm() / fx.getFrobeniusNorm(); if (rel_error > 1e15) break; // compute the dogleg step if (h_gn.getFrobeniusNorm() < delta) { h_dl = h_gn; if (h_dl.getFrobeniusNorm() <= tolx * (tolx + x.getFrobeniusNorm())) { stop = 5; break; } } else if (alpha * g.getFrobeniusNorm() >= delta) { h_dl = h_sd.scalarMultiply(delta / (alpha * g.getFrobeniusNorm())); } else { //compute beta double beta = 0; RealMatrix b = h_gn.subtract(h_sd); double bb = (b.transpose().multiply(b)).getFrobeniusNorm(); double gb = (h_sd.transpose().multiply(b)).getFrobeniusNorm(); double c = (delta + h_sd.getFrobeniusNorm()) * (delta - h_sd.getFrobeniusNorm()); if (gb > 0) beta = c / (gb + Math.sqrt(gb * gb + c * bb)); else beta = (Math.sqrt(gb * gb + c * bb) - gb) / bb; // and update h_dl and dL with beta h_dl = h_sd.add(b.scalarMultiply(beta)); } } // see if we are already finished if (stop != 0) break; // it didn't work in some tests // // restrict h_dl according to maxStep // double scale = subsys->maxStep(h_dl); // if (scale < 1.) // h_dl *= scale; // get the new values double err_new; x_new = x.add(h_dl); subsys.setParams(x_new); err_new = subsys.calcResidual(fx_new); subsys.calcJacobi(Jx_new); // calculate the linear model and the update ratio double dL = err - 0.5 * squaredNorm((fx.add(Jx.multiply(h_dl)))); double dF = err - err_new; double rho = dL / dF; if (dF > 0 && dL > 0) { x = x_new.copy(); Jx = Jx_new.copy(); fx = fx_new.copy(); err = err_new; g = Jx.transpose().multiply(fx.scalarMultiply(-1)); // get infinity norms g_inf = infinityNorm(g); fx_inf = infinityNorm(fx); } else rho = -1; // update delta if (Math.abs(rho - 1.) < 0.2 && h_dl.getFrobeniusNorm() > delta / 3. && reduce <= 0) { delta = 3 * delta; nu = 2; reduce = 0; } else if (rho < 0.25) { delta = delta / nu; nu = 2 * nu; reduce = 2; } else reduce--; // count this iteration and start again iter++; } // subsys.revertParams(); return (stop == 1) ? SolveStatus.Success : SolveStatus.Failed; } private static RealMatrix lu(RealMatrix jx, RealMatrix fx) { return new org.apache.commons.math3.linear.LUDecomposition(new Array2DRowRealMatrix(makeSquare(jx.getData()))).getSolver().solve(fx); // DoubleMatrix2D solve = new LUDecomposition(new DenseDoubleMatrix2D(jx.getData())) // .solve(new DenseDoubleMatrix2D(fx.getData())); } public static double[][] makeSquare(double[][] m) { if (m.length > m[0].length) { for (int r = 0; r < m.length; r++) { double[] row = m[r]; m[r] = new double[m.length]; java.lang.System.arraycopy(row, 0, m[r], 0, row.length); } } else { double[][] _m = new double[m[0].length][]; for (int r = 0; r < m.length; r++) { _m[r] = m[r]; } for (int r = m.length; r < _m.length; r++) { _m[r] = new double[m[0].length]; } m = _m; } return m; } public static void optimize(SubSystem subSystem) { final int cSize = subSystem.cSize(); final double[] currentPoint = subSystem.getParams().toArray(); final int pSize = currentPoint.length; // iterate until convergence is reached double[] current = null; int iter = 0; for (boolean converged = false; !converged; ) { ++iter; // evaluate the objective function and its jacobian // Value of the objective function at "currentPoint". final double[] currentResiduals = subSystem.calcResidual().toArray(); final RealMatrix jacobian = new Array2DRowRealMatrix(makeSquare(subSystem.makeJacobi().getData())); // build the linear problem final double[] b = new double[pSize]; final double[][] a = new double[pSize][pSize]; for (int i = 0; i < cSize; ++i) { final double[] grad = jacobian.getRow(i); final double weight = 1; final double residual = currentResiduals[i]; // compute the normal equation final double wr = weight * residual; for (int j = 0; j < pSize; ++j) { b[j] += wr * grad[j]; } // build the contribution matrix for measurement i for (int k = 0; k < pSize; ++k) { double[] ak = a[k]; double wgk = weight * grad[k]; for (int l = 0; l < pSize; ++l) { ak[l] += wgk * grad[l]; } } } try { // solve the linearized least squares problem RealMatrix mA = new BlockRealMatrix(a); DecompositionSolver solver = useLU ? new org.apache.commons.math3.linear.LUDecomposition(jacobian).getSolver() : new QRDecomposition(jacobian).getSolver(); final double[] dX = solver.solve(new ArrayRealVector(currentPoint).mapMultiply(-1)).toArray(); // update the estimated parameters for (int i = 0; i < pSize; ++i) { currentPoint[i] += dX[i]; } subSystem.setParams(currentPoint); } catch (SingularMatrixException e) { throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM); } // Check convergence. if (iter != 0) { // converged = checker.converged(iter, previous, current); // if (converged) { // setCost(computeCost(currentResiduals)); // return current; // } if (subSystem.valueSquared() < 0.0001) { return; } } } // Must never happen. throw new MathInternalError(); } public RealMatrix solve(RealMatrix b, int[] pivot, double[][] lu) { final int m = pivot.length; if (b.getRowDimension() != m) { throw new DimensionMismatchException(b.getRowDimension(), m); } final int nColB = b.getColumnDimension(); // Apply permutations to b final double[][] bp = new double[m][nColB]; for (int row = 0; row < m; row++) { final double[] bpRow = bp[row]; final int pRow = pivot[row]; for (int col = 0; col < nColB; col++) { bpRow[col] = b.getEntry(pRow, col); } } // Solve LY = b for (int col = 0; col < m; col++) { final double[] bpCol = bp[col]; for (int i = col + 1; i < m; i++) { final double[] bpI = bp[i]; final double luICol = lu[i][col]; for (int j = 0; j < nColB; j++) { bpI[j] -= bpCol[j] * luICol; } } } // Solve UX = Y for (int col = m - 1; col >= 0; col--) { final double[] bpCol = bp[col]; final double luDiag = lu[col][col]; for (int j = 0; j < nColB; j++) { bpCol[j] /= luDiag; } for (int i = 0; i < col; i++) { final double[] bpI = bp[i]; final double luICol = lu[i][col]; for (int j = 0; j < nColB; j++) { bpI[j] -= bpCol[j] * luICol; } } } return new Array2DRowRealMatrix(bp, false); } public static SolveStatus solve_LM(SubSystem subsys) { int xsize = subsys.pSize(); int csize = subsys.cSize(); if (xsize == 0) { return SolveStatus.Success; } RealMatrix e = mtrx(csize), e_new = mtrx(csize); // vector of all function errors (every constraint is one function) RealMatrix J = mtrx(csize, xsize); // Jacobi of the subsystem RealMatrix A = mtrx(xsize, xsize); RealMatrix x = mtrx(xsize), h = mtrx(xsize), x_new = mtrx(xsize), g = mtrx(xsize); double[] diag_A; // subsys.redirectParams(); subsys.fillParams(x); subsys.calcResidual(e); e = e.scalarMultiply(-1); int maxIterNumber = MaxIterations * xsize; double divergingLim = 1e6 * squaredNorm(e) + 1e12; double eps = 1e-10, eps1 = 1e-80; double tau = 1e-3; double nu = 2, mu = 0; int iter = 0, stop = 0; for (iter = 0; iter < maxIterNumber && stop == 0; ++iter) { // check error double err = squaredNorm(e); if (err <= eps) { // error is small, Success stop = 1; break; } else if (err > divergingLim || err != err) { // check for diverging and NaN stop = 6; break; } // J^T J, J^T e subsys.calcJacobi(J); ; A = J.transpose().multiply(J); g = J.transpose().multiply(e); // Compute ||J^T e||_inf double g_inf = infinityNorm(g); diag_A = diagonal(A); // save diagonal entries so that augmentation can be later canceled // check for convergence if (g_inf <= eps1) { stop = 2; break; } // compute initial damping factor if (iter == 0) { mu = tau * new ArrayRealVector(diag_A).getLInfNorm() ; } // determine increment using adaptive damping int k = 0; while (k < 50) { // augment normal equations A = A+uI for (int i = 0; i < xsize; ++i) { A.addToEntry(i, i, mu); } //solve augmented functions A*h=-g for (int _ = 0; _ < 1000; _++) { try { h = new LUDecomposition(A).getSolver().solve(g); } catch (Exception ssse) { mu *= 1./3.; for (int i = 0; i < xsize; ++i) { A.setEntry(i, i, diag_A[i] * mu); } if (_ == 999) { return SolveStatus.Success; } continue; } break; } double rel_error = (A.multiply(h).subtract(g)).getFrobeniusNorm() / g.getFrobeniusNorm(); // check if solving works if (rel_error < 1e-5) { // restrict h according to maxStep // double scale = subsys.maxStep(h); // if (scale < 1.) { // h = h.scalarMultiply(scale); // } // compute par's new estimate and ||d_par||^2 x_new = x.add(h); double h_norm = squaredNorm(h); if (h_norm <= eps1 * eps1 * x.getFrobeniusNorm()) { // relative change in p is small, stop stop = 3; break; } else if (h_norm >= (x.getFrobeniusNorm() + eps1) / (DBL_EPSILON * DBL_EPSILON)) { // almost singular stop = 4; break; } subsys.setParams(x_new); subsys.calcResidual(e_new); e_new = e_new.scalarMultiply(-1); double dF = squaredNorm(e) - squaredNorm(e_new); double dL = dot(h, (h.scalarMultiply(mu).add(g))); if (dF > 0. && dL > 0.) { // reduction in error, increment is accepted double tmp = 2 * dF / dL - 1.; mu *= Math.max(1. / 3., 1. - tmp * tmp * tmp); nu = 2; // update par's estimate x = x_new.copy(); e = e_new.copy(); break; } } // if this point is reached, either the linear system could not be solved or // the error did not reduce; in any case, the increment must be rejected mu *= nu; nu *= 2.0; for (int i = 0; i < xsize; ++i) // restore diagonal J^T J entries { A.setEntry(i, i, diag_A[i]); } k++; } if (k > 50) { stop = 7; break; } } if (iter >= maxIterNumber) { stop = 5; } // subsys.revertParams(); return (stop == 1) ? SolveStatus.Success : SolveStatus.Failed; } private static void identity(RealMatrix m) { for (int i = 0; i < m.getColumnDimension() && i < m.getRowDimension(); i++) { m.setEntry(i, i, 1.0); } } static double lineSearch(SubSystem subsys, RealMatrix xdir) { double f1, f2, f3, alpha1, alpha2, alpha3, alphaStar; double alphaMax = 1;//subsys.maxStep(xdir); int pSize = subsys.pSize(); RealMatrix x0 = mtrx(pSize), x = mtrx(pSize); //Save initial values subsys.fillParams(x0); //Start at the initial position alpha1 = 0 alpha1 = 0.; f1 = subsys.errorSquared(); //Take a step of alpha2 = 1 alpha2 = 1.; x = x0.add(xdir.scalarMultiply(alpha2)); subsys.setParams(x); f2 = subsys.errorSquared(); //Take a step of alpha3 = 2*alpha2 alpha3 = alpha2 * 2; x = x0.add(xdir.scalarMultiply(alpha3)); subsys .setParams(x); f3 = subsys .errorSquared(); //Now reduce or lengthen alpha2 and alpha3 until the minimum is //Bracketed by the triplet f1>f2 f1 || f2 > f3) { if (f2 > f1) { //If f2 is greater than f1 then we shorten alpha2 and alpha3 closer to f1 //Effectively both are shortened by a factor of two. alpha3 = alpha2; f3 = f2; alpha2 = alpha2 / 2; x = x0.add( xdir.scalarMultiply(alpha2 )); subsys . setParams(x); f2 = subsys .errorSquared(); } else if (f2 > f3) { if (alpha3 >= alphaMax) { break; } //If f2 is greater than f3 then we increase alpha2 and alpha3 away from f1 //Effectively both are lengthened by a factor of two. alpha2 = alpha3; f2 = f3; alpha3 = alpha3 * 2; x = x0.add( xdir.scalarMultiply(alpha3)); subsys . setParams(x); f3 = subsys .errorSquared(); } } //Get the alpha for the minimum f of the quadratic approximation alphaStar = alpha2 + ((alpha2 - alpha1) * (f1 - f3)) / (3 * (f1 - 2 * f2 + f3)); //Guarantee that the new alphaStar is within the bracket if (alphaStar >= alpha3 || alphaStar <= alpha1) { alphaStar = alpha2; } if (alphaStar > alphaMax) { alphaStar = alphaMax; } if (alphaStar != alphaStar) { alphaStar = 0.; } //Take a final step to alphaStar x = x0 .add( xdir.scalarMultiply( alphaStar ) ); subsys . setParams(x); return alphaStar; } public static SolveStatus solve_BFGS(SubSystem subsys, boolean isFine) { int xsize = subsys.pSize(); if (xsize == 0) { return SolveStatus.Success; } RealMatrix D = new Array2DRowRealMatrix(xsize, xsize); identity(D); // RealMatrix x = new Array2DRowRealMatrix(xsize, 1); RealMatrix xdir = new Array2DRowRealMatrix(xsize, 1); RealMatrix grad = new Array2DRowRealMatrix(xsize, 1); RealMatrix h = new Array2DRowRealMatrix(xsize, 1); RealMatrix y = new Array2DRowRealMatrix(xsize, 1); RealMatrix Dy = new Array2DRowRealMatrix(xsize, 1); // Initial unknowns vector and initial gradient vector subsys.fillParams(x); subsys.calcGrad(grad); // Initial search direction oposed to gradient (steepest-descent) xdir = grad.scalarMultiply(-1); lineSearch(subsys, xdir); double err = subsys.errorSquared(); h = x.copy(); subsys.fillParams(x); h = x.subtract(h); // = x - xold double convergence = isFine ? XconvergenceFine : XconvergenceRough; int maxIterNumber = MaxIterations * xsize; double divergingLim = 1e6 * err + 1e12; for (int iter = 1; iter < maxIterNumber; iter++) { if (h.getFrobeniusNorm() <= convergence || err <= smallF) { break; } if (err > divergingLim || err != err) // check for diverging and NaN { break; } y = grad.copy(); subsys.calcGrad(grad); y = grad.subtract(y); // = grad - gradold double hty = dotProduct(h, y); //make sure that hty is never 0 if (hty == 0) { hty = .0000000001; } Dy = D.multiply(y); double ytDy = dotProduct(y, Dy); //Now calculate the BFGS update on D D = D.add(h.scalarMultiply((1. + ytDy / hty) / hty).multiply(h.transpose())); D = D.subtract(( h.multiply(Dy.transpose()) .add(Dy.multiply(h.transpose())) ).scalarMultiply(1. / hty) ); xdir = D.scalarMultiply(-1).multiply(grad); lineSearch(subsys, xdir); err = subsys.errorSquared(); h = x.copy(); subsys.fillParams(x); h = x.subtract(h); // = x - xold } // subsys.revertParams(); if (err <= smallF) { return SolveStatus.Success; } if (h.getFrobeniusNorm() <= convergence) { return SolveStatus.Converged; } return SolveStatus.Failed; } private static double[] diagonal(RealMatrix a) { int s = Math.min(a.getColumnDimension(), a.getRowDimension()); double[] d = new double[s]; for (int i = 0; i < s; i++) { d[i] = a.getEntry(i, i); } return d; } private static double dot(RealMatrix m1, RealMatrix m2) { return new ArrayRealVector(m1.getData()[0]).dotProduct(new ArrayRealVector(m2.getData()[0])); } private static double infinityNorm(RealMatrix g) { return new ArrayRealVector(g.getData()[0]).getLInfNorm(); } private static double squaredNorm(RealMatrix matrix) { double norm = matrix.getFrobeniusNorm(); return norm * norm; } private static RealMatrix mtrx(int size) { return new Array2DRowRealMatrix(size, 1); } private static RealMatrix mtrx(int rsize, int csize) { return new Array2DRowRealMatrix(rsize, csize); } static class ParamInfo { int id; Set constraints = new LinkedHashSet<>(); ParamInfo(int id) { this.id = id; } } public static class SubSystem { public final List constraints; private final LinkedHashMap params = new LinkedHashMap<>(); public SubSystem(List constraints) { this.constraints = new ArrayList<>(constraints); for (Constraint c : constraints) { for (Param p : c.getParams()) { ParamInfo paramInfo = params.get(p); if (paramInfo == null) { paramInfo = new ParamInfo(params.size()); params.put(p, paramInfo); } paramInfo.constraints.add(c); } } } public int pSize() { return params.size(); } public int cSize() { return constraints.size(); } public void fillParams(RealMatrix x) { x.setColumn(0, getParams().toArray()); } public TDoubleList getParams() { TDoubleList params_ = new TDoubleArrayList(); for (Param p : params.keySet()) { params_.add(p.get()); } return params_; } public TDoubleList getValues() { TDoubleList values = new TDoubleArrayList(); for (Constraint c : constraints) { values.add(c.error()); } return values; } public double calcResidual(RealMatrix r) { double err = 0.; int i = 0; for (Constraint c : constraints) { double v = c.error(); r.setEntry(i++, 0, v); err += v * v; } err *= 0.5; return err; } public TDoubleList calcResidual() { TDoubleList r = new TDoubleArrayList(); double err = 0.; int i = 0; for (Constraint c : constraints) { double v = c.error(); r.add(v); err += v * v; } err *= 0.5; return r; } public double valueSquared() { double err = 0.; for (Constraint c : constraints) { double v = c.error(); err += v * v; } err *= 0.5; return err; } public double value() { double err = 0.; for (Constraint c : constraints) { err += Math.abs(c.error()); } return err; } public void calcJacobi(RealMatrix jacobi) { // jacobi.setZero(csize, params.size()); for (int j=0; j < pSize(); j++) { for (int i=0; i < constraints.size(); i++) { jacobi.setEntry(i, j, 0); } } for (int i=0; i < constraints.size(); i++) { Constraint c = constraints.get(i); Param[] cParams = c.getParams(); double[] grad = new double[cParams.length]; c.gradient(grad); for (int p = 0; p < cParams.length; p++) { Param param = cParams[p]; ParamInfo pi = params.get(param); if (pi == null) continue; int j = pi.id; jacobi.setEntry(i,j, param.isLocked() ? 0 : grad[p]); } } } public RealMatrix makeJacobi() { RealMatrix jacobi = new Array2DRowRealMatrix(cSize(), pSize()); calcJacobi(jacobi); return jacobi; } public void setParams(RealMatrix params) { setParams(params.getColumn(0)); } public void setParams(double[] arr) { Iterator pit = params.keySet().iterator(); for (double v : arr) { pit.next().set(v); } } public double errorSquared() { return valueSquared(); } public double[] calcGrad() { double[] grad = new double[params.size()]; for (Constraint c : constraints) { double error = c.error(); double[] localGrad = new double[c.pSize()]; c.gradient(localGrad); Param[] localParams = c.getParams(); for (int i = 0; i < localParams.length; i++) { ParamInfo pi = params.get(localParams[i]); if (pi == null) continue; grad[pi.id] += error * localGrad[i]; } } return grad; } public void calcGrad(RealMatrix out) { double[] grad = calcGrad(); for (int i = 0; i < grad.length; i++) { double v = calcGrad()[i]; out.setEntry(i, 0, v); } } public List splitUp () { List subSystems = new ArrayList<>(); for (Constraint constraint : constraints) { Set params = new HashSet<>(Arrays.asList(constraint.getParams())); SubSystem subSystem = new SubSystem(constraints); subSystems.add(subSystem); Iterator it = subSystem.params.keySet().iterator(); while (it.hasNext()) { Param param = it.next(); if (!params.contains(param)) { it.remove(); } } int i = 0; for (ParamInfo pi : subSystem.params.values()) { pi.id = i ++; } Iterator cit = subSystem.constraints.iterator(); while (cit.hasNext()) { Constraint c = cit.next(); boolean remove = true; for (ParamInfo pi : subSystem.params.values()) { if (pi.constraints.contains(c)) { remove = false; break; } } if (remove) { cit.remove(); } } } return subSystems; } } private static double dotProduct(RealMatrix m1, RealMatrix m2) { return new ArrayRealVector(m1.getData()[0]).dotProduct(new ArrayRealVector(m2.getData()[0])); } static double XconvergenceRough = 1e-8; static double XconvergenceFine = 1e-10; static double smallF = 1e-20; }