/** * */ package org.geogebra.common.kernel.prover; import java.util.Iterator; import java.util.TreeSet; import org.geogebra.common.cas.GeoGebraCAS; import org.geogebra.common.cas.giac.CASgiac.CustomFunctions; import org.geogebra.common.cas.singularws.SingularWebService; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.cas.UsesCAS; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.implicit.GeoImplicit; import org.geogebra.common.kernel.prover.ProverBotanasMethod.AlgebraicStatement; import org.geogebra.common.util.debug.Log; /** * @author sergio Works out the equation for a given locus. */ public class AlgoLocusEquation extends AlgoElement implements UsesCAS { private GeoPoint movingPoint, locusPoint; private GeoImplicit geoPoly; private GeoElement[] efficientInput, standardInput; private String efficientInputFingerprint; private GeoElement implicitLocus = null; private long myPrecision = 0; /** * @param cons * construction * @param locusPoint * dependent point * @param movingPoint * moving point */ public AlgoLocusEquation(Construction cons, GeoPoint locusPoint, GeoPoint movingPoint) { super(cons); this.movingPoint = movingPoint; this.locusPoint = locusPoint; this.implicitLocus = null; this.geoPoly = kernel.newImplicitPoly(cons); setInputOutput(); initialCompute(); } /** * @param cons * construction * @param implicitLocus * boolean describing the locus * @param movingPoint * moving point */ public AlgoLocusEquation(Construction cons, GeoElement implicitLocus, GeoPoint movingPoint) { super(cons); this.implicitLocus = implicitLocus; this.movingPoint = movingPoint; this.geoPoly = kernel.newImplicitPoly(cons); setInputOutput(); initialCompute(); } /* * (non-Javadoc) * * @see geogebra.common.kernel.algos.AlgoElement#setInputOutput() */ @Override protected void setInputOutput() { // it is inefficient to have Q and P as input // let's take all independent parents of Q // and the path as input TreeSet<GeoElement> inSet = new TreeSet<GeoElement>(); Iterator<GeoElement> it; standardInput = new GeoElement[2]; if (implicitLocus != null) { inSet.add(this.movingPoint); it = this.implicitLocus.getAllPredecessors().iterator(); standardInput[0] = this.implicitLocus; } else { inSet.add(this.movingPoint.getPath().toGeoElement()); it = this.locusPoint.getAllPredecessors().iterator(); standardInput[0] = this.locusPoint; } // we need all independent parents of Q PLUS // all parents of Q that are points on a path while (it.hasNext()) { GeoElement geo = it.next(); if (geo.isIndependent() || geo.isPointOnPath()) { inSet.add(geo); } } // remove P from input set! inSet.remove(movingPoint); efficientInput = new GeoElement[inSet.size()]; efficientInput = inSet.toArray(efficientInput); standardInput[1] = this.movingPoint; setOutputLength(1); setOutput(0, this.geoPoly.toGeoElement()); setEfficientDependencies(standardInput, efficientInput); // Removing extra algos manually: Construction c = movingPoint.getConstruction(); do { c.removeFromAlgorithmList(this); } while (c.getAlgoList().contains(this)); // Adding this again: c.addToAlgorithmList(this); // TODO: consider moving setInputOutput() out from compute() efficientInputFingerprint = fingerprint(efficientInput); myPrecision = kernel.precision(); } /** * Reset fingerprint to force recomputing the locus equation if the * precision has dramatically changed. * * @param k * kernel * @param force * reset the fingerprint even if the precision has not changed * * @return true if the fingerprint was reset */ public boolean resetFingerprint(Kernel k, boolean force) { long kernelPrecision = k.precision(); double precisionRatio = (double) myPrecision / kernelPrecision; if (precisionRatio > 5 || precisionRatio < 0.2 || force) { Log.debug("myPrecision=" + myPrecision + " kernelPrecision=" + kernelPrecision + " precisionRatio=" + precisionRatio); efficientInputFingerprint = null; myPrecision = kernelPrecision; return true; } return false; } /* * We use a very hacky way to avoid drawing locus equation when the curve is * not changed. To achieve that, we create a fingerprint of the current * coordinates or other important parameters of the efficient input. (It * contains only those inputs which are relevant in computing the curve, * hence if they are not changed, the curve will not be recomputed.) The * fingerprint function should eventually be improved. Here we assume that * the input objects are always in the same order (that seems sensible) and * the obtained algebraic description changes iff the object does. This may * not be the case if rounding/precision is not as presumed. */ private static String fingerprint(GeoElement[] input) { StringBuilder ret = new StringBuilder(); int size = input.length; for (int i = 0; i < size; ++i) { ret.append(input[i] .getAlgebraDescription(StringTemplate.defaultTemplate)); ret.append(","); } return ret.toString(); } /** * @return the result. */ public GeoImplicit getPoly() { return this.geoPoly; } /* * (non-Javadoc) * * @see geogebra.common.kernel.algos.AlgoElement#compute() */ @Override public void compute() { if (!kernel.getGeoGebraCAS().getCurrentCAS().isLoaded()) { efficientInputFingerprint = null; myPrecision = 0; return; } String efficientInputFingerprintPrev = efficientInputFingerprint; setInputOutput(); if (efficientInputFingerprintPrev == null || !efficientInputFingerprintPrev .equals(efficientInputFingerprint)) { Log.trace(efficientInputFingerprintPrev + " -> " + efficientInputFingerprint); initialCompute(); } } private void initialCompute() { computeExplicitImplicit(implicitLocus != null); } /* * (non-Javadoc) * * @see geogebra.common.kernel.algos.AlgoElement#getClassName() */ @Override public Commands getClassName() { return Commands.LocusEquation; } private String getImplicitPoly(boolean implicit) throws Throwable { AlgebraicStatement as = ProverBotanasMethod .translateConstructionAlgebraically( implicit ? implicitLocus : locusPoint, movingPoint, implicit, this); if (as == null) { Log.debug("Cannot compute locus equation (yet?)"); return null; } return locusEqu(as); } /** * Compute the locus equation curve and put into geoPoly. * * @param implicit * if the computation will be done for an implicit locus */ public void computeExplicitImplicit(boolean implicit) { double startTime = cons.getApplication().getMillisecondTime(); String result = null; try { result = getImplicitPoly(implicit); } catch (Throwable ex) { ex.printStackTrace(); Log.debug("Cannot compute implicit curve (yet?)"); } if (result != null) { try { GeoGebraCAS cas = (GeoGebraCAS) kernel.getGeoGebraCAS(); this.geoPoly.setCoeff(cas.getCurrentCAS() .getBivarPolyCoefficientsAll(result)); this.geoPoly.setDefined(); // Timeout => set undefined } catch (Exception e) { this.geoPoly.setUndefined(); } } else { this.geoPoly.setUndefined(); } int elapsedTime = (int) (cons.getApplication().getMillisecondTime() - startTime); /* * Don't remove this. It is needed for automated testing. (String match * is assumed.) */ Log.debug("Benchmarking: " + elapsedTime + " ms"); } /** * Compute the coefficients of the implicit curve for the envelope equation. * * @param as * the algebraic statement structure * @return the implicit curve as a string */ public String locusEqu(AlgebraicStatement as) { StringBuilder sb = new StringBuilder(); String polys = as.getPolys(); String elimVars = as.getElimVars(); String varx = as.curveVars[0].toString(); String vary = as.curveVars[1].toString(); String vars = varx + "," + vary; String PRECISION = Long.toString(kernel.precision()); Log.debug("PRECISION = " + PRECISION); /* Use Singular if it is enabled. TODO: Implement this. */ SingularWebService singularWS = kernel.getApplication().getSingularWS(); if (singularWS != null && singularWS.isAvailable()) { String locusLib = singularWS.getLocusLib(); /* * Constructing the Singular script. This code contains a modified * version of Francisco Botana's locusdgto() and envelopeto() * procedures in the grobcov library. I.e. we no longer use these * two commands, but locusto(), locus() and locusdg() only. We use * one single Singular call instead of two (as above for Giac). * Computation of the Jacobian is maybe slower here. * * At the moment this code is here for backward compatibility only. * It is not used in the web version and can be invoked only by * forcing SingularWS on startup. TODO: Consider implementing * Singular's grobcov library in Giac---it may produce better * envelopes. */ /* * Convert v1-a,v2-b type ideals to (v1-a)^2+(v2-b)^2. This works * also in general, not only for linear polys. */ sb.append( "proc point_to_0circle(ideal l) { if (size(l)==1) {return(l[1]);} if (size(l)==2) {return((l[1])^2+(l[2])^2));} return 1; }; "); sb.append("LIB \"").append(locusLib).append(".lib\";ring r=(0,") .append(vars) .append("),(") .append(elimVars) .append("),dp;") .append("short=0;ideal I=") .append(polys) .append(";def Gp=grobcov(I);list l=") .append(singularWS.getLocusCommand()); sb.append("(Gp);"); /* * If Gp is an empty list, then there is no locus, so that we return * 0=-1. */ sb.append("if(size(l)==0){print(\"{{1,1,1},{1,1,1,1}}\");exit;}") .append("poly pp=1; ideal ii; int i; int j=1; poly c; for (i=1; i<=size(l); i++)") .append("{ if ((string(l[i][3])==\"Normal\") || (string(l[i][3])==\"Accumulation\")) { c=point_to_0circle(l[i][1]); pp=pp*c; ii[j]=c; j++; } }") .append("string s=string(pp);string si=\"ideal iii=\"+string(ii); int sl=size(s);if(sl==1){print(\"{{1,1,1},{1,1,1,1}}\");exit;}string pg=\"poly p=\"+s[2,sl-2];") .append("ring rr=0,(").append(vars) .append("),dp;execute(pg);execute(si);") .append("string out=sprintf(\"{{%s,%s,%s},{\",size(coeffs(p,") .append(varx).append(")),size(coeffs(p,").append(vary) .append(")),").append("coeffs(coeffs(p,").append(varx) .append("),").append(vary).append("));"); sb.append( "int iiis=size(iii);out=out+sprintf(\"%s,\",iiis);for (i=1; i<=iiis; i++)") .append("{out=out+sprintf(\"%s,%s,%s\",size(coeffs(iii[i],") .append(varx).append(")),size(coeffs(iii[i],").append(vary) .append(")),coeffs(coeffs(iii[i],").append(varx) .append("),").append(vary).append("));"); sb.append("if (i<iiis) {out=out+\",\";} } sprintf(\"%s}}\", out);"); Log.trace("Input to singular: " + sb); String result; try { result = kernel.getApplication().getSingularWS() .directCommand(sb.toString()); } catch (Throwable e) { Log.error("Error on running Singular code"); return null; } Log.trace("Output from singular: " + result); return result; } /* Otherwise use Giac. */ sb.append(CustomFunctions.LOCUS_EQU).append("([").append(polys) .append("],[").append(elimVars).append("],").append(PRECISION) .append(",").append(",").append(as.curveVars[0]).append(",") .append(as.curveVars[1]).append(")"); GeoGebraCAS cas = (GeoGebraCAS) kernel.getGeoGebraCAS(); try { String result = cas.getCurrentCAS().evaluateRaw(sb.toString()); Log.trace("Output from giac: " + result); return result; } catch (Throwable ex) { Log.error("Error on running Giac code"); return null; } } }