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.Path;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.algos.AlgoPointOnPath;
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 Zoltan Kovacs <zoltan@geogebra.org> The Singular computations are
* provided by Francisco Botana and the grobcov library by Antonio
* Montes & al. Based on Sergio's LocusEquation. Works out the equation
* for a given envelope.
*/
public class AlgoEnvelope extends AlgoElement implements UsesCAS {
private GeoPoint movingPoint;
private GeoElement path;
/**
* class name
*/
public static final String CLASS_NAME = "AlgoEnvelope";
private GeoImplicit geoPoly;
private GeoElement[] efficientInput, standardInput;
private String efficientInputFingerprint;
/**
* Constructor.
*
* @param cons
* construction
* @param label
* label
* @param path
* path
* @param movingPoint
* moving point
*/
public AlgoEnvelope(Construction cons, String label, GeoElement path,
GeoPoint movingPoint) {
this(cons, path, movingPoint);
this.geoPoly.setLabel(label);
}
/**
* Constructor.
*
* @param cons
* construction
* @param path
* path
* @param movingPoint
* moving point
*/
public AlgoEnvelope(Construction cons, GeoElement path,
GeoPoint movingPoint) {
super(cons);
this.movingPoint = movingPoint;
this.path = path;
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>();
inSet.add(this.movingPoint.getPath().toGeoElement());
// we need all independent parents of Q PLUS
// all parents of Q that are points on a path
Iterator<GeoElement> it = this.path.getAllPredecessors().iterator();
while (it.hasNext()) {
GeoElement geo = it.next();
if (geo.isIndependent() || geo.isPointOnPath()) {
inSet.add(geo);
}
}
// remove P from input set!
inSet.remove(movingPoint);
/*
* We need to create a new object from "linear". E.g., if it is a
* circle, we have to define an equation for the circle by putting x and
* y the free variables.
*/
efficientInput = new GeoElement[inSet.size()];
efficientInput = inSet.toArray(efficientInput);
standardInput = new GeoElement[2];
standardInput[0] = this.path;
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);
}
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;
return;
}
String efficientInputFingerprintPrev = efficientInputFingerprint;
setInputOutput();
if (efficientInputFingerprintPrev == null
|| !efficientInputFingerprintPrev
.equals(efficientInputFingerprint)) {
Log.trace(efficientInputFingerprintPrev + " -> "
+ efficientInputFingerprint);
initialCompute();
}
}
private void initialCompute() {
computeEnvelope();
}
/**
* Compute the locus equation curve and put into geoPoly.
*
*/
public void computeEnvelope() {
double startTime = cons.getApplication().getMillisecondTime();
String result = null;
try {
result = getImplicitPoly();
} 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");
}
/**
* Set up dependencies for the input and output objects.
*/
protected void setInputOutputEnvelope() {
TreeSet<GeoElement> inSet = new TreeSet<GeoElement>();
inSet.add(this.movingPoint);
Iterator<GeoElement> it = this.path.getAllPredecessors().iterator();
while (it.hasNext()) {
GeoElement geo = it.next();
if (geo.isIndependent() || geo.isPointOnPath()) {
inSet.add(geo);
}
}
inSet.remove(movingPoint);
efficientInput = new GeoElement[inSet.size()];
efficientInput = inSet.toArray(efficientInput);
standardInput = new GeoElement[2];
standardInput[0] = this.path;
standardInput[1] = this.movingPoint;
setOutputLength(1);
setOutput(0, this.geoPoly.toGeoElement());
setEfficientDependencies(standardInput, efficientInput);
efficientInputFingerprint = fingerprint(efficientInput);
}
private String getImplicitPoly() throws Throwable {
/*
* First we create a virtual locus point on the path object. This is
* done with AlgoPointOnPath. Then we retrieve the corresponding
* equation to this virtual locus point.
*/
GeoPoint locusPoint = new GeoPoint(cons);
AlgoPointOnPath apop = new AlgoPointOnPath(cons, (Path) path, 1, 1);
locusPoint.setParentAlgorithm(apop);
/*
* Now we collect all the restriction equations except for the linear
* itself. This is exactly the same as in AlgoLocusEquation.
*/
AlgebraicStatement as = ProverBotanasMethod
.translateConstructionAlgebraically(locusPoint,
movingPoint, false, this);
// It is safe to remove the virtual locus point here.
locusPoint.remove();
if (as == null) {
Log.debug("Cannot compute envelope equation (yet?)");
return null;
}
return envelopeEqu(as);
}
@Override
public Commands getClassName() {
return Commands.Envelope;
}
/**
* 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 envelopeEqu(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. */
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.
*/
sb.append("proc mylocusdgto(list L) {" + "poly p=1;"
+ "int i; int j; int k;"
+ "for(i=1;i<=size(L);i++) { if(L[i][3]<>\"Degenerate\")"
+ " { if(size(L[i][1])>1) {p=p*((L[i][1][1])^2+(L[i][1][2])^2);}"
+ "else {p=p*L[i][1][1];}" + "} } return(p); }");
sb.append("proc myenvelopeto (list GG) {" + "list GGG;"
+ "if (GG[1][2][1]<>1) { GGG=delete(GG,1); }"
+ "else { GGG=GG; };" + "string SLo=locusto(locus(GGG));"
+ "if (find(SLo,\"Normal\") == 0 and find(SLo,\"Accumulation\") == 0 and find(SLo,\"Special\") == 0)"
+ "{ return(1); }"
+ "else { return(mylocusdgto(locus(GGG))); } }");
sb.append("LIB \"" + locusLib + ".lib\";ring r=(0," + vars + "),("
+ elimVars).append("),dp;short=0;ideal m=");
sb.append(polys);
sb.append(";poly D=det(jacob(m));ideal S=" + polys
+ ",D;list e=myenvelopeto(grobcov(S));");
/*
* This trick is required to push the result polynomial to the new
* ring world:
*/
sb.append("string ex=\"poly p=\" + string(e[1]);");
sb.append("ring rr=0,(" + vars + "),dp;");
sb.append("execute(ex);");
/*
* Now we obtain the coefficients (see exactly the same code for
* locus equation):
*/
sb.append("string out=sprintf(\"%s,%s,%s\",size(coeffs(p," + varx
+ ")),size(coeffs(p," + vary + ")),")
.append("coeffs(coeffs(p," + varx + ")," + vary + "));");
/*
* Temporary workaround by creating dummy factor, because the output
* is not factorized (that is, it may not produce nice plots in some
* cases:
*/
sb.append("sprintf(\"{{%s},{1,%s}}\",out,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.ENVELOPE_EQU).append("([").append(polys)
.append("],[").append(elimVars).append("],").append(PRECISION)
.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;
}
}
}