package org.geogebra.common.kernel.kernelND; import java.util.TreeMap; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.DistanceFunction; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.VarString; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.AlgoDependentFunction; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.Function; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.arithmetic.MyArbitraryConstant; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.geos.CasEvaluableFunction; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.ParametricCurve; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.debug.Log; /** * Abstract class for cartesian curves in any dimension * * @author Mathieu * */ public abstract class GeoCurveCartesianND extends GeoElement implements ParametricCurve, VarString, CasEvaluableFunction { /** * samples to find interval with closest parameter position to given point */ protected static final int CLOSEST_PARAMETER_SAMPLES = 100; /** coordinates functions */ protected final Function[] fun; /** coordinates with expanded function references */ protected final Function[] funExpanded; /** flag for each coordinate whether it depends on a function */ protected final boolean[] containsFunctions; /** derivative functions */ protected Function[] funD1; /** second derivative functions */ protected Function[] funD2; /** start parameter */ protected double startParam; /** end parameter */ protected double endParam; /** flag for isDefined() */ protected boolean isDefined = true; /** * distFun(t) evaluates distance this(t) to a distant point (attribute of * the distFun) */ protected DistanceFunction distFun; private ExpressionNode point; /** derivative */ protected GeoCurveCartesianND derivGeoFun; /** * common constructor * * @param c * construction * @param dimension * dimension 2 or 3 * @param point * defining expression as point */ public GeoCurveCartesianND(Construction c, int dimension, ExpressionNode point) { super(c); this.fun = new Function[dimension]; this.funExpanded = new Function[dimension]; this.containsFunctions = new boolean[dimension]; this.point = point; // moved from GeoElement's constructor // must be called from the subclass, see // http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/ setConstructionDefaults(); // init visual settings } /** * constructor with functions * * @param c * construction * @param fun * functions of parameter * @param point * defining expression as point */ public GeoCurveCartesianND(Construction c, Function[] fun, ExpressionNode point) { super(c); this.fun = fun; this.funExpanded = new Function[fun.length]; this.containsFunctions = new boolean[fun.length]; this.point = point; setConstructionDefaults(); } /** * set functions * * @param fun * functions */ public void setFun(Function[] fun) { for (int i = 0; i < fun.length; i++) { this.fun[i] = fun[i]; } } @Override public boolean isGeoCurveCartesian() { return true; } /** * Replaces geo and all its dependent geos in this function's expression by * copies of their values. * * @param geo * Element to be replaced */ @Override public void replaceChildrenByValues(GeoElement geo) { for (int i = 0; i < fun.length; i++) { if (fun[i] != null) { fun[i].replaceChildrenByValues(geo); } } } /** * Sets the start and end parameter value of this curve. * * @param startParam * start parameter * @param endParam * end parameter */ public void setInterval(double startParam, double endParam) { this.startParam = startParam; this.endParam = endParam; isDefined = startParam <= endParam; } /** * Returns the start parameter value for this path (may be * Double.NEGATIVE_INFINITY) * * @return start parameter */ @Override public double getMinParameter() { return startParam; } /** * Returns the largest possible parameter value for this path (may be * Double.POSITIVE_INFINITY) * * @return end parameter */ @Override public double getMaxParameter() { return endParam; } /** * returns all class-specific xml tags for getXML */ @Override protected void getXMLtags(StringBuilder sb) { super.getXMLtags(sb); // line thickness and type getLineStyleXML(sb); } @Override public boolean isPath() { return true; } @Override final public boolean isDefined() { return isDefined; } /** * @param defined * new value of defined flag */ public void setDefined(boolean defined) { isDefined = defined; } @Override public void setUndefined() { isDefined = false; } @Override public String toString(StringTemplate tpl) { StringBuilder sbToString = new StringBuilder(80); sbToString.setLength(0); if (isLabelSet()) { sbToString.append(label); // sbToString.append('('); // sbToString.append(funX.getVarString()); // sbToString.append(") = "); // changed to ':' to make LaTeX output better sbToString.append(':'); } sbToString.append(toValueString(tpl)); return sbToString.toString(); } @Override public String toValueString(StringTemplate tpl) { if (isDefined) { StringBuilder sbTemp = new StringBuilder(80); sbTemp.setLength(0); sbTemp.append('('); for (int i = 0; i < fun.length; i++) { // quick fix for NPE // https://play.google.com/apps/publish/?dev_acc=05873811091523087820#ErrorClusterDetailsPlace:p=org.geogebra.android.g3d&et=CRASH&sh=false&lr=LAST_7_DAYS&ecn=java.lang.NullPointerException&tf=SourceFile&tc=%2509at+org.geogebra.common.kernel.kernelND.GeoCurveCartesianND.toValueString(GeoCurveCartesianND.java:239)&tm=b&nid&an&c&s=new_status_desc&ed=1477717276985 if (fun[i] != null) { sbTemp.append(fun[i].toValueString(tpl)); if (i < fun.length - 1) { sbTemp.append(", "); } } } sbTemp.append(')'); return sbTemp.toString(); } return "?"; } /** * @param tpl * string template * @return symbolic string representation */ @Override public String toSymbolicString(StringTemplate tpl) { StringBuilder sbTemp = null; if (isDefined) { sbTemp = new StringBuilder(80); sbTemp.setLength(0); sbTemp.append('('); for (int i = 0; i < fun.length; i++) { sbTemp.append(fun[i].toString(tpl)); if (i < fun.length - 1) { sbTemp.append(", "); } } sbTemp.append(')'); return sbTemp.toString(); } return "?"; } /** * @param i * dimension index * @return i-th function */ @Override public Function getFun(int i) { if (i >= fun.length) { return new Function(new ExpressionNode(kernel, 0), fun[0].getFunctionVariable()); } return fun[i]; } @Override public final void update(boolean drag) { super.update(drag); for (int i = 0; i < this.funExpanded.length; i++) { this.funExpanded[i] = null; } } @Override public FunctionVariable[] getFunctionVariables() { return getFun(0).getFunctionVariables(); } @Override public String getVarString(StringTemplate tpl) { return getFun(0).getVarString(tpl); } /** * Set this curve by applying CAS command to f. */ @Override public void setUsingCasCommand(String ggbCasCmd, CasEvaluableFunction f, boolean symbolic, MyArbitraryConstant arbconst) { GeoCurveCartesianND c = (GeoCurveCartesianND) f; if (c.isDefined()) { // register the variable name to make sure parsing of CAS output // runs OK, see #3006 GeoNumeric geo = new GeoNumeric(this.cons); this.cons.addLocalVariable( getFun(0).getVarString(StringTemplate.defaultTemplate), geo); this.isDefined = true; for (int k = 0; k < getDimension(); k++) { setFun(k, (Function) c.getFunExpanded(k) .evalCasCommand(ggbCasCmd, symbolic, arbconst)); this.isDefined = this.isDefined && getFun(k) != null; } this.cons.removeLocalVariable( getFun(0).getVarString(StringTemplate.defaultTemplate)); if (this.isDefined) { setInterval(c.startParam, c.endParam); } } else { this.isDefined = false; } this.distFun = null; } @Override public void clearCasEvalMap(String key) { for (int k = 0; k < getDimension(); k++) { if (getFun(k) != null) { getFun(k).clearCasEvalMap(key); } } } @Override public void printCASEvalMapXML(StringBuilder sb) { // not supported for curves } @Override public void updateCASEvalMap(TreeMap<String, String> map) { // TODO } /** * @param i * dimension index * @param f * new function */ protected void setFun(int i, Function f) { this.fun[i] = f; this.funExpanded[i] = null; this.containsFunctions[i] = AlgoDependentFunction .containsFunctions(this.fun[i].getExpression()); } /** * @param i * dimension index * @return function with expanded function calls */ protected Function getFunExpanded(int i) { if (!this.containsFunctions[i]) { return getFun(i); } if (this.funExpanded[i] == null) { this.funExpanded[i] = new Function(getFun(i), this.kernel); ExpressionNode expr = AlgoDependentFunction .expandFunctionDerivativeNodes( getFun(i).getExpression().deepCopy(this.kernel), false) .wrap(); this.funExpanded[i].setExpression(expr); } return this.funExpanded[i]; } /** * Set this curve to the n-th derivative of c * * @param curve * curve whose derivative we want * * @param n * order of derivative */ public void setDerivative(GeoCurveCartesianND curve, int n) { if (curve.isDefined()) { this.isDefined = true; for (int k = 0; k < getDimension(); k++) { // changed from getFunExpanded() (didn't work) // now handled in ExpressionNode.derivative() case FUNCTION: setFun(k, curve.getFun(k).getDerivative(n, true)); this.isDefined = this.isDefined && getFun(k) != null; } if (this.isDefined) { setInterval(curve.startParam, curve.endParam); } } else { this.isDefined = false; } this.distFun = null; } /** * @return dimension */ public int getDimension() { return this.fun.length; } /** * * @param n * n * @return x, y, z, for n = 0, 1, 2 */ protected String getVariable(int n) { if (n < getDimension() && n >= 0) { switch (n) { case 0: return "x"; case 1: return "y"; case 2: return "z"; } } Log.debug("problem with variable number"); return ""; } /** * * @param p * point * @param minParameter * minimal parameter * @return path parameter */ public final double getClosestParameterForCurvature(GeoPointND p, double minParameter) { if (p.getDefinition() != null && p.getDefinition().getOperation() == Operation.VEC_FUNCTION && p.getDefinition().getLeft() == this) { return p.getDefinition().getRight().evaluateDouble(); } return getClosestParameter(p, minParameter); } /** * * @param p * point * @param minParameter * minimal parameter * @return path parameter */ public abstract double getClosestParameter(GeoPointND p, double minParameter); @Override public abstract double evaluateCurvature(double t); private boolean hideRangeInFormula; /** * @return whether range is hidden in algebra */ public boolean isHiddenRange() { return this.hideRangeInFormula; } /** * Hide range in formula -- needed when the curve is infinite and range is * used for drawing only (e.g. rotated functions) * * @param b * true to hide */ public void setHideRangeInFormula(boolean b) { this.hideRangeInFormula = b; } @Override public boolean isLaTeXDrawableGeo() { return true; } @Override final public String toLaTeXString(boolean symbolic, StringTemplate tpl) { if (this.isDefined) { StringBuilder sbTemp = new StringBuilder(80); String param = getVarString(tpl); if (!hideRangeInFormula && point == null) { sbTemp.append("\\left."); } if (point == null) { sbTemp.append("\\begin{array}{lll}"); for (int i = 0; i < getDimension(); i++) { if (i > 0) { sbTemp.append("\\\\ "); } sbTemp.append(getVariable(i)); sbTemp.append(" = "); sbTemp.append(getFun(i).toLaTeXString(symbolic, tpl)); } sbTemp.append(" \\end{array}"); } else { sbTemp.append(point.toLaTeXString(true, tpl)); } if (!hideRangeInFormula) { if (point == null) { sbTemp.append("\\right\\} \\; "); } else { sbTemp.append(", \\;\\;\\;\\; \\left("); } sbTemp.append(this.kernel.format(this.startParam, tpl)); sbTemp.append(" \\le "); sbTemp.append(param); sbTemp.append(" \\le "); sbTemp.append(this.kernel.format(this.endParam, tpl)); if (point == null) { // nothing to do here } else { sbTemp.append("\\right)"); } } return sbTemp.toString(); } return "?"; } /** * @param order * order of derivative * @return derivative as curve */ public GeoCurveCartesianND getGeoDerivative(int order) { if (this.derivGeoFun == null) { this.derivGeoFun = newGeoCurveCartesian(this.cons); } this.derivGeoFun.setDerivative(this, order); return this.derivGeoFun; } /** * @param cons1 * construction * @return curve in the same dimension */ protected abstract GeoCurveCartesianND newGeoCurveCartesian( Construction cons1); @Override public abstract ExpressionValue evaluateCurve(double double1); @Override public boolean isParametric() { return true; } /** * @return defining expression (null if defined per coords) */ public ExpressionNode getPointExpression() { return point; } /** * update distance function */ abstract public void updateDistanceFunction(); @Override public void evaluateCurve(double t, double[] f1eval) { // TODO Auto-generated method stub } /** * * @param points * list of vertices * @param repeatLast * true if we should add last-first edge */ public final void setFromPolyLine(GeoPointND[] points, boolean repeatLast) { double coef = 0; int dim = fun.length; if (points.length < 2) { setUndefined(); return; } ExpressionNode[] en = new ExpressionNode[dim]; for (int i = 0; i < dim; i++) { en[i] = new ExpressionNode(this.kernel, new MyDouble(this.kernel, pointToCoords(points[0]).get(i + 1))); } FunctionVariable fv = new FunctionVariable(this.kernel, "t"); double[] sum = new double[] { 0, 0, 0 }; double[] cumulative = new double[] { 0, 0, 0 }; int limit = repeatLast ? points.length + 1 : points.length; int nonzeroSegments = 0; for (int i = 1; i < limit; i++) { int pointIndex = i >= points.length ? 0 : i; ExpressionNode greater = new ExpressionNode(this.kernel, new ExpressionNode(this.kernel, fv, Operation.MINUS, new MyDouble(this.kernel, nonzeroSegments)), Operation.ABS, null); Coords c1 = pointToCoords(points[pointIndex]); Coords c2 = pointToCoords(points[i - 1]); if (c1.isEqual(c2)) { continue; } for (int j = 0; j < dim; j++) { coef = 0.5 * c1.get(j + 1) - 0.5 * c2.get(j + 1) - cumulative[j]; sum[j] += coef * nonzeroSegments; cumulative[j] += coef; en[j] = en[j].plus( greater.multiply(new MyDouble(this.kernel, coef))); } nonzeroSegments++; } for (int j = 0; j < dim; j++) { en[j] = en[j].plus( new ExpressionNode(this.kernel, fv, Operation.MULTIPLY, new MyDouble(this.kernel, cumulative[j]))); en[j] = en[j].plus(new MyDouble(this.kernel, -sum[j])); Function xFun = new Function(en[j], fv); this.setFun(j, xFun); } this.setInterval(0, nonzeroSegments); } /** * @param geoPointND * point * @return inhom coords in current dimension */ protected Coords pointToCoords(GeoPointND geoPointND) { return geoPointND.getInhomCoordsInD2(); } }