package org.geogebra.common.kernel.statistics; /* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ import org.apache.commons.math3.linear.Array2DRowRealMatrix; import org.apache.commons.math3.linear.RealMatrix; import org.apache.commons.math3.linear.RealVector; import org.apache.commons.math3.linear.SingularValueDecomposition; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumberValue; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.implicit.GeoImplicit; import org.geogebra.common.plugin.GeoClass; /** * Fits implicit curve of given degree through points * */ public class AlgoFitImplicit extends AlgoElement { private GeoList pointlist; // input private GeoElement orderGeo = new GeoNumeric(cons, 2); private GeoImplicit fitfunction; // output // variables: private int datasize = 0; // rows in M and Y private RealMatrix M = null, V; /** * @param cons * construction * @param label * label * @param pointlist * list of input points * @param arg * order of implicit polynomial to fit */ public AlgoFitImplicit(Construction cons, String label, GeoList pointlist, GeoNumberValue arg) { super(cons); this.pointlist = pointlist; this.orderGeo = (GeoElement) arg; fitfunction = kernel.newImplicitPoly(cons); setInputOutput(); compute(); fitfunction.setLabel(label); } @Override public Commands getClassName() { return Commands.FitImplicit; } @Override protected void setInputOutput() { input = new GeoElement[2]; input[0] = pointlist; input[1] = orderGeo; setOnlyOutput(fitfunction); setDependencies(); } /** * @return resulting Fit function */ public GeoImplicit getFit() { return fitfunction; } @Override public final void compute() { int order = (int) orderGeo.evaluateDouble(); datasize = pointlist.size(); // rows in M and Y if (!pointlist.isDefined() || datasize < (order * (order + 3)) / 2) { fitfunction.setUndefined(); return; } if (!pointlist.getElementType().equals(GeoClass.POINT)) { fitfunction.setUndefined(); return; } // no additional degrees of freedom => use algo for ImplicitPoly[points] if (datasize == (order * (order + 3)) / 2) { fitfunction.throughPoints(pointlist); return; } try { // Get functions, x and y from lists if (!makeMatrixes()) { fitfunction.setUndefined(); return; } // Log.debug("datasize = " + datasize); // Log.debug("order = " + order); // Log.debug("M cols = "+M.getColumnDimension()); // Log.debug("M rows = "+M.getRowDimension()); // Log.debug("M = "+M.toString()); SingularValueDecomposition svd = new SingularValueDecomposition( M); V = svd.getV(); // Log.debug("V = "+V.toString()); // Log.debug("size of M = "+M.getColumnDimension()+" // "+M.getRowDimension()); // Log.debug("size of V = "+V.getColumnDimension()+" // "+V.getRowDimension()); makeFunction(); } catch (Throwable t) { fitfunction.setUndefined(); t.printStackTrace(); } }// compute() // Get info from lists into matrixes and functionarray private final boolean makeMatrixes() { GeoElement geo = null; GeoPoint point = null; double x, y; int order = (int) orderGeo.evaluateDouble(); // Make matrixes with the right values: M*P=Y M = new Array2DRowRealMatrix(datasize, (order + 1) * (order + 2) / 2); for (int r = 0; r < datasize; r++) { geo = pointlist.get(r); if (!geo.isGeoPoint()) { return false; } point = (GeoPoint) geo; x = point.getX() / point.getZ(); y = point.getY() / point.getZ(); int c = 0; // create powers eg x^2y^0, x^1y^1, x^0*y^2, x, y, 1 for (int i = 0; i <= order; i++) { for (int xpower = 0; xpower <= i; xpower++) { int ypower = i - xpower; double val = power(x, xpower) * power(y, ypower); // Log.debug(val + "x^"+xpower+" * y^"+ypower); M.setEntry(r, c++, val); } } } return true; } /** * @param x * base * @param power * exponent * @return base ^ exponent */ public static double power(double x, int power) { if (power == 0) { return 1; } if (power == 1) { return x; } double ret = x; int pow = power; while (pow > 1) { ret *= x; pow--; } return ret; } private final void makeFunction() { int order = (int) orderGeo.evaluateDouble(); double[][] coeffs = new double[order + 1][order + 1]; // Log.debug("row/cols = "+V.getRowDimension() + " "+ // V.getColumnDimension()+" "+(order * (order + 3) / 2 -1)); // Log.debug(V.toString()); RealVector coeffsRV = V.getColumnVector(V.getColumnDimension() - 1); int c = 0; // create powers eg x^2y^0, x^1y^1, x^0*y^2, x, y, 1 for (int i = 0; i <= order; i++) { for (int j = 0; j <= i; j++) { coeffs[j][i - j] = coeffsRV.getEntry(c++); } } fitfunction.setCoeff(coeffs); fitfunction.setDefined(); } }