package org.geogebra.common.kernel.prover; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.geogebra.common.cas.GeoGebraCAS; import org.geogebra.common.cas.singularws.SingularWebService; import org.geogebra.common.factories.UtilFactory; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.advanced.AlgoDynamicCoordinates; import org.geogebra.common.kernel.algos.AlgoAngularBisectorPoints; import org.geogebra.common.kernel.algos.AlgoCirclePointRadius; import org.geogebra.common.kernel.algos.AlgoDependentBoolean; import org.geogebra.common.kernel.algos.AlgoDependentNumber; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.AlgoEllipseHyperbolaFociPoint; import org.geogebra.common.kernel.algos.AlgoIntersectConics; import org.geogebra.common.kernel.algos.AlgoIntersectLineConic; import org.geogebra.common.kernel.algos.AlgoPointOnPath; import org.geogebra.common.kernel.algos.SymbolicParametersBotanaAlgo; import org.geogebra.common.kernel.algos.SymbolicParametersBotanaAlgoAre; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.MyList; import org.geogebra.common.kernel.arithmetic.ValidExpression; import org.geogebra.common.kernel.geos.GeoAngle; import org.geogebra.common.kernel.geos.GeoBoolean; import org.geogebra.common.kernel.geos.GeoConic; import org.geogebra.common.kernel.geos.GeoConicPart; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoLine; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoSegment; import org.geogebra.common.kernel.prover.polynomial.PPolynomial; import org.geogebra.common.kernel.prover.polynomial.PVariable; import org.geogebra.common.main.ProverSettings; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.ExtendedBoolean; import org.geogebra.common.util.Prover; import org.geogebra.common.util.Prover.NDGCondition; import org.geogebra.common.util.Prover.ProofResult; import org.geogebra.common.util.Prover.ProverEngine; import org.geogebra.common.util.debug.Log; /** * A prover which uses Francisco Botana's method to prove geometric theorems. * * @author Zoltan Kovacs * @author Csilla Solyom-Gecse * */ public class ProverBotanasMethod { private static HashMap<List<PVariable>, GeoElement> botanaVarsInv; /** * Inverse mapping of botanaVars for a given statement. * * @param statement * the input statement * @throws NoSymbolicParametersException * if implementation is missing */ static void updateBotanaVarsInv(GeoElement statement) throws NoSymbolicParametersException { if (botanaVarsInv == null) { botanaVarsInv = new HashMap<List<PVariable>, GeoElement>(); } for (GeoElement geo : statement.getAllPredecessors()) { if (!(geo instanceof GeoNumeric)) { PVariable[] vars = ((SymbolicParametersBotanaAlgo) geo) .getBotanaVars(geo); if (vars != null) { List<PVariable> varsList = Arrays.asList(vars); botanaVarsInv.put(varsList, geo); } } } } /** * Compute free points in a statement. * * @param statement * the input statement * @return list of free points */ public static List<GeoElement> getFreePoints(GeoElement statement) { List<GeoElement> freePoints = new ArrayList<GeoElement>(); for (GeoElement geo : statement.getAllPredecessors()) { if (geo.isGeoPoint() && geo.getParentAlgorithm() == null) { /* this is a free point */ freePoints.add(geo); } } return freePoints; } /** * Compute predecessor free points of an element, used for locus/envelope. * Works similarly like getFreePoints(), but geos whose parent is * AlgoDynamicCoordinates will be used as free points and their predecessors * can be ignored. * * @param geo * input geo * @return list of free points */ private static HashSet<GeoElement> getLocusFreePoints(GeoElement geo) { HashSet<GeoElement> freePoints = new HashSet<GeoElement>(); AlgoElement algo = geo.getParentAlgorithm(); if (algo != null) { for (GeoElement g : algo.getInput()) { AlgoElement a = g.getParentAlgorithm(); if (g.isGeoPoint() && a == null) { /* this is a free point */ freePoints.add(g); } else if (g.isGeoPoint() && a instanceof AlgoDynamicCoordinates) { /* this will be considered as a free point */ freePoints.add(g); } else { /* find recursively the parents */ freePoints.addAll(getLocusFreePoints(g)); } } } return freePoints; } /** * Creates those polynomials which describe that none of 3 free points can * lie on the same line. * * @param prover * the underlying prover * * @return the NDG polynomials (in denial form) * @throws NoSymbolicParametersException * if implementation is missing */ static PPolynomial[] create3FreePointsNeverCollinearNDG( Prover prover) throws NoSymbolicParametersException { /* Creating the set of free points first: */ List<GeoElement> freePoints = getFreePoints(prover.getStatement()); int setSize = freePoints.size(); /* Creating NDGs: */ NDGCondition ndgc = new NDGCondition(); if (setSize > 3) { ndgc.setCondition("DegeneratePolygon"); } else { ndgc.setCondition("AreCollinear"); } GeoElement[] geos = new GeoElement[setSize]; int i = 0; for (GeoElement freePoint : freePoints) { geos[i++] = freePoint; } ndgc.setGeos(geos); Arrays.sort(ndgc.getGeos()); prover.addNDGcondition(ndgc); /* The output will contain $\binom{n}{3}$ elements: */ PPolynomial[] ret = new PPolynomial[setSize * (setSize - 1) * (setSize - 2) / 6]; i = 0; /* Creating the set of triplets: */ HashSet<HashSet<GeoElement>> triplets = new HashSet<HashSet<GeoElement>>(); for (GeoElement geo1 : freePoints) { for (GeoElement geo2 : freePoints) { if (!geo1.isEqual(geo2)) { for (GeoElement geo3 : freePoints) { if (!geo1.isEqual(geo3) && !geo2.isEqual(geo3)) { HashSet<GeoElement> triplet = new HashSet<GeoElement>(); triplet.add(geo1); triplet.add(geo2); triplet.add(geo3); /* * Only the significantly new triplets will be * processed: */ if (!triplets.contains(triplet)) { triplets.add(triplet); PVariable[] fv1 = ((SymbolicParametersBotanaAlgo) geo1) .getBotanaVars(geo1); PVariable[] fv2 = ((SymbolicParametersBotanaAlgo) geo2) .getBotanaVars(geo2); PVariable[] fv3 = ((SymbolicParametersBotanaAlgo) geo3) .getBotanaVars(geo3); /* Creating the polynomial for collinearity: */ PPolynomial p = PPolynomial.collinear(fv1[0], fv1[1], fv2[0], fv2[1], fv3[0], fv3[1]); Log.info("Forcing non-collinearity for points " + geo1.getLabelSimple() + ", " + geo2.getLabelSimple() + " and " + geo3.getLabelSimple()); /* * Rabinowitsch trick for prohibiting * collinearity: */ ret[i] = p .multiply( new PPolynomial(new PVariable( geo1.getKernel()))) .subtract( new PPolynomial(BigInteger.ONE)); /* * FIXME: this always introduces an extra * variable, shouldn't do */ i++; } } } } } } return ret; } /** * Uses a minimal heuristics to fix the first four variables to certain * "easy" numbers. The first two variables (usually the coordinates of the * first point) are set to 0, and the second two variables (usually the * coordinates of the second point) are set to 0 and 1. * * @param prover * the input prover * @param coords * number of fixed coordinates * @return a HashMap, containing the substitutions * @throws NoSymbolicParametersException */ private static HashMap<PVariable, BigInteger> fixValues(Prover prover, int coords) throws NoSymbolicParametersException { BigInteger[] fixCoords = { BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ONE }; GeoElement statement = prover.getStatement(); List<GeoElement> freePoints = getFreePoints(statement); List<GeoElement> fixedPoints = new ArrayList<GeoElement>(); /* Adding free points: */ for (GeoElement ge : freePoints) { fixedPoints.add(ge); } HashMap<PVariable, BigInteger> ret = new HashMap<PVariable, BigInteger>(); Iterator<GeoElement> it = fixedPoints.iterator(); GeoElement[] geos = new GeoElement[2]; int i = 0, j = 0; while (it.hasNext() && i < 2 && j < coords) { GeoElement geo = it.next(); PVariable[] fv = ((SymbolicParametersBotanaAlgo) geo) .getBotanaVars(geo); geos[i] = geo; ret.put(fv[0], fixCoords[j]); ++j; if (j < coords) { ret.put(fv[1], fixCoords[j]); ++i; ++j; } } /* We implicitly assumed that the first two points are different: */ if (i == 2 && prover.isReturnExtraNDGs()) { NDGCondition ndgc = new NDGCondition(); ndgc.setCondition("AreEqual"); ndgc.setGeos(geos); Arrays.sort(ndgc.getGeos()); prover.addNDGcondition(ndgc); } return ret; } /** * Translation of a geometric statement into an algebraic one. We use * polynomials and integer coefficients. The computations assume that a * complex algebraic geometry approach will be used based on the Groebner * basis method (or Wu's characteristic method, but that's not yet * implemented). */ public static class AlgebraicStatement { /** * The statement in geometric form, e.g. AreCollinear[D,E,F]. */ public GeoElement geoStatement; /** * The prover which uses this class. */ Prover geoProver; /** * The set of polynomials which are the translations of the geometric * hypotheses and the thesis. The thesis is stored reductio ad absurdum. */ private Set<PPolynomial> polynomials; /** * Should the "false" result be interpreted as undefined? */ boolean interpretFalseAsUndefined = false; /** * Should the "true" result be interpreted as undefined? */ boolean interpretTrueAsUndefined = false; private boolean disallowFixSecondPoint = false; private String polys, elimVars, freeVars; private PPolynomial[] thesisFactors; /** * Return the polynomials of the algebraic structure as a String. Use * computeStrings() before using this method. * * @return polynomials in String format */ public String getPolys() { return polys; } private HashMap<GeoElement, PPolynomial[]> geoPolys = new HashMap<GeoElement, PPolynomial[]>(); /** * Retrieve polynomial belonging to a given GeoElement. * * @param geo * input geometric object * @return algebraic representation of the geometric object */ public PPolynomial[] getGeoPolys(GeoElement geo) { return geoPolys.get(geo); } /** * Add algebraic representation of a geometric object to the polynomial * system. It may contain one or more polynomials. * * @param geo * geometric object * @param ps * algebraic representation of the geometric object */ public void addGeoPolys(GeoElement geo, PPolynomial[] ps) { geoPolys.put(geo, ps); for (PPolynomial p : ps) { addPolynomial(p); } } /** * Remove all polynomials from the system which describe constraints of * the given input geometric object * * @param geo * geometric object */ public void removeGeoPolys(GeoElement geo) { PPolynomial[] ps = geoPolys.get(geo); for (PPolynomial p : ps) { removePolynomial(p); } geoPolys.remove(geo); } /** * Return the elimination variables of the algebraic structure as a * String. Use computeStrings() before using this method. * * @return elimination variables in String format */ public String getElimVars() { return elimVars; } /** * Return the free variables of the algebraic structure as a String. Use * computeStrings() before using this method. * * @return free variables in String format */ public String getFreeVars() { return freeVars; } /** * @return the polynomials */ public Set<PPolynomial> getPolynomials() { return polynomials; } /** * Create the negated thesis. Note that this can be called only once and * cannot be reverted. */ public void negateThesis() { removeGeoPolys(geoStatement); addGeoPolys(geoStatement, thesisFactors); } /** * Add a polynomial to the system manually. * * @param p * the polynomial to be added */ public void addPolynomial(PPolynomial p) { polynomials.add(p); } /** * Remove a polynomial from the system manually. * * @param p * the polynomial to be removed */ public void removePolynomial(PPolynomial p) { polynomials.remove(p); } /** * Number of maximal fix coordinates. -1 if no limit. Sometimes we need * to limit the maximum if the construction contains constrained point * on a path. */ int maxFixcoords = -1; /** * A map of substitutions, used only in locus equations and envelopes. */ public HashMap<PVariable, BigInteger> substitutions; /** * The variables for x and y, used only in locus equations and * envelopes. */ public PVariable[] curveVars = new PVariable[2]; /** * The result of the proof (even if no computation was done). Sometimes * it can be predicted without any further computations. */ ProofResult result = null; /** * The result of the computation. * * @return result */ public ProofResult getResult() { return result; } /** * Create an algebraic equation system of the statement given in the * construction, by using the underlying prover settings. * * @param statement * the statement to be proven * @param movingPoint * use numerical approximation for this element instead of * computing it symbolically. This can be useful if an * element cannot be precisely described symbolically: only * its generalized formula can be described symbolically. * @param prover * the underlying prover */ public AlgebraicStatement(GeoElement statement, GeoElement movingPoint, Prover prover) { if (statement.kernel.getGeoGebraCAS().getCurrentCAS().isLoaded()) { algebraicTranslation(statement, movingPoint, prover); } else { result = ProofResult.PROCESSING; } } /** * Convert Java datatypes into String datatypes. */ public void computeStrings() { TreeSet<PVariable> dependentVariables = new TreeSet<PVariable>(); /* * We don't use this at the moment, but later it could be useful for * the prover. */ TreeSet<PVariable> freeVariables = new TreeSet<PVariable>(); PPolynomial[] eqSystem = this.getPolynomials() .toArray(new PPolynomial[this.getPolynomials().size()]); TreeSet<PVariable> variables = new TreeSet<PVariable>( PPolynomial.getVars(eqSystem)); Iterator<PVariable> variablesIterator = variables.iterator(); while (variablesIterator.hasNext()) { PVariable variable = variablesIterator.next(); if (!variable.isFree()) { dependentVariables.add(variable); } else { if (substitutions == null || !substitutions.containsKey(variable)) { freeVariables.add(variable); } } } PPolynomial[] eqSystemSubstituted; if (substitutions != null) { eqSystemSubstituted = new PPolynomial[eqSystem.length]; for (int i = 0; i < eqSystem.length; i++) { eqSystemSubstituted[i] = eqSystem[i] .substitute(substitutions); } variables.removeAll(substitutions.keySet()); } else { eqSystemSubstituted = eqSystem; } Log.debug( "Eliminating system in " + variables.size() + " variables (" + dependentVariables.size() + " dependent)"); this.polys = PPolynomial .getPolysAsCommaSeparatedString(eqSystemSubstituted); this.elimVars = PPolynomial.getVarsAsCommaSeparatedString( eqSystemSubstituted, null, false); this.freeVars = PPolynomial.getVarsAsCommaSeparatedString( eqSystemSubstituted, null, true); Log.trace("gbt polys = " + polys); Log.trace("gbt vars = " + elimVars + "," + freeVars); } private void setHypotheses(GeoElement movingPoint) { polynomials = new HashSet<PPolynomial>(); int nHypotheses = 0; TreeSet<GeoElement> predecessors = new TreeSet<GeoElement>(); TreeSet<GeoElement> allPredecessors = geoStatement .getAllPredecessors(); if (geoProver.getProverEngine() == ProverEngine.LOCUS_EXPLICIT) { allPredecessors.add(geoStatement); } Iterator<GeoElement> it; /* * The algo of the moving point will be computed numerically after * the end of the symbolic computations. This is not ideal since * some symbolic algos e.g. AlgoIntersectLineConic want to use * existing equations and those will be symbolic in all cases, that * is, it would be better to compute all numerical equations first * or (even better) during the symbolic process. */ GeoElement numerical = null; AlgoElement numAlgo; if (movingPoint != null && (numAlgo = movingPoint.getParentAlgorithm()) != null) { numerical = (GeoElement) numAlgo.getInput(0); /* * Normally we don't want to use the numerical formula for * linear objects. Now we still compute the numerical formula * for most of the cases. */ if (numerical instanceof GeoSegment || numerical instanceof GeoConicPart) { // we don't want the equation of the length numerical = null; } } /* * Remove geos directly related with AlgoDependentNumber algos since * we don't want to add them twice (they will be invoked during * their occurrence on a higher level in their geos). Hopefully this * is OK in general, that is, we never need using * AlgoDependentNumber's polynomials directly. If this is still the * case, our idea here must be redesigned. Also remove geos which * should be computed numerically. */ it = allPredecessors.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (!(geo instanceof GeoNumeric && geo .getParentAlgorithm() instanceof AlgoDependentNumber)) { predecessors.add(geo); } } ProverSettings proverSettings = ProverSettings.get(); it = predecessors.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (geo instanceof SymbolicParametersBotanaAlgo) { try { if (geo instanceof GeoLine && ((GeoLine) geo).hasFixedSlope() && !(geoProver .getProverEngine() == ProverEngine.LOCUS_EXPLICIT || geoProver .getProverEngine() == ProverEngine.LOCUS_IMPLICIT)) { Log.info( "Statements containing axes or fixed slope lines are unsupported"); result = ProofResult.UNKNOWN; return; } if (proverSettings.captionAlgebra) { geo.setCaption(null); } String command = geo .getDefinition(StringTemplate.noLocalDefault); if (!("".equals(command))) { Log.debug(geo.getLabelSimple() + " = " + geo.getDefinition( StringTemplate.noLocalDefault) + " /* " + geo.getDefinitionDescription( StringTemplate.noLocalDefault) + " */"); } else { String description = geo .getAlgebraDescriptionDefault(); if ((geo instanceof GeoLine && ((GeoLine) geo).hasFixedSlope()) || (geo instanceof GeoNumeric)) { Log.debug(description); } else if (!description.startsWith("xOyPlane")) { /* * handling GeoGebra3D's definition for xy-plane */ Log.debug(description + " /* free point */"); PVariable[] v; v = ((SymbolicParametersBotanaAlgo) geo) .getBotanaVars(geo); if (proverSettings.captionAlgebra) { geo.setCaptionBotanaVars("(" + v[0].toTeX() + "," + v[1].toTeX() + ")"); } if (v != null) { Log.debug("// Free point " + geo.getLabelSimple() + "(" + v[0] + "," + v[1] + ")"); } } } PPolynomial[] geoPolynomials = ((SymbolicParametersBotanaAlgo) geo) .getBotanaPolynomials(geo); /* * We used to check if the construction step could be * reliably translated to an algebraic representation. * This was the case for linear constructions (parallel, * perpendicular etc.) but not for quadratic ones * (intersection of conics etc.). In the latter case the * equation system might have been solvable even if * geometrically, "seemingly" the statement was true. To * avoid such confusing cases, it was better to report * undefined instead of false. * * Now we check the statement's negation as well and it * is enough to handle those cases. * * TODO: This piece of code can be removed on a cleanup. */ AlgoElement algo = geo.getParentAlgorithm(); if (algo instanceof AlgoAngularBisectorPoints || algo instanceof AlgoEllipseHyperbolaFociPoint || (algo instanceof AlgoIntersectConics && ((AlgoIntersectConics) algo) .existingIntersections() != 1) || (algo instanceof AlgoIntersectLineConic && ((AlgoIntersectLineConic) algo) .existingIntersections() != 1)) { // interpretFalseAsUndefined = true; Log.info(algo + " is not 1-1 algebraic mapping, but FALSE will not be interpreted as UNKNOWN"); } /* * End of reliability check. */ if (algo instanceof AlgoCirclePointRadius) { disallowFixSecondPoint = true; } /* * Consider the following case: Let AB a segment and C a * point on it. Move C to A. Now let's check if * Prove[A==C] returns false. Since C is on a line and * normally A=(0,0) and B=(0,1), thus x(C)=0 follows. * But we set x(C) to be a free variable in the * AlgoPointOnPath equation and y(C) to be dependent * which is a bad idea for Cox's method: this scenario * cannot be constructed (the converse scenario: x(C) is * dependent and y(C) is free would be fine), so Cox's * method will return true (because a non-constructible * setting is always contradictory)---even if the * statement is false. So we avoid setting B=(0,1) for * Cox's method when there is a point on a path, * otherwise we will get true for a false statement! See * Example 52 in Zoltan's diss on page 176---here we * need to generalize B to avoid getting true. This will * slow down some things, but that's the price for the * correct behavior. Note that non-linear paths are not * affected. */ if (algo instanceof AlgoPointOnPath && algo.input[0] instanceof GeoLine && proverSettings.transcext) { maxFixcoords = 2; } if (geoPolynomials != null) { if (geo instanceof GeoPoint) { PVariable[] v; v = ((SymbolicParametersBotanaAlgo) geo) .getBotanaVars(geo); Log.debug("// Constrained point " + geo.getLabelSimple() + "(" + v[0] + "," + v[1] + ")"); if (proverSettings.captionAlgebra) { geo.setCaptionBotanaVars("(" + v[0].toTeX() + "," + v[1].toTeX() + ")"); } } boolean useThisPoly = true; if (algo != null && algo instanceof AlgoPointOnPath && geoProver .getProverEngine() == ProverEngine.LOCUS_EXPLICIT) { /* * Is this an Envelope command with geo on the * virtual path? In this case we should not * change to the numerical approach. */ if (!algo.equals( geoStatement.getParentAlgorithm())) { /* * Skip this object for now: it is a point * on a path. Its coordinates will be used * directly (with substitution) or---for the * moving point---the numerical poly will be * used. */ useThisPoly = false; } } if (geo.equals(numerical)) { // don't create the symbolic equation for a // numerically used object useThisPoly = false; } if (numerical == null) { // there is no numerical object, // so we still use this poly useThisPoly = true; } if (useThisPoly) { Log.debug("Hypotheses:"); addGeoPolys(geo, geoPolynomials); for (PPolynomial p : geoPolynomials) { nHypotheses++; Log.debug((nHypotheses) + ". " + p); if (proverSettings.captionAlgebra) { geo.addCaptionBotanaPolynomial( p.toTeX()); } } } else { Log.debug( "This object will be computed numerically"); } } } catch (NoSymbolicParametersException e) { Log.info(geo.getParentAlgorithm() + " is not fully implemented"); result = ProofResult.UNKNOWN; return; } } else { Log.info(geo.getParentAlgorithm() + " unimplemented"); result = ProofResult.UNKNOWN; return; } } /* * Processing numerical object. The equation computed by GeoGebra * will be used. */ Log.debug("Processing numerical object"); if (numerical != null) { try { PVariable[] vars = ((SymbolicParametersBotanaAlgo) movingPoint) .getBotanaVars(movingPoint); Kernel kernel = geoStatement.kernel; // int decimals = kernel.getPrintDecimals(); // kernel.setPrintDecimals(decimals * 2); String strForGiac = getFormulaString(numerical); // kernel.setPrintDecimals(decimals); GeoGebraCAS cas = (GeoGebraCAS) kernel.getGeoGebraCAS(); String giacOutput4 = ""; try { String giacOutput = cas.getCurrentCAS() .evaluateRaw(strForGiac); // create a poly instead of equation String strForGiac2 = "lhs(" + giacOutput + ")-rhs(" + giacOutput + ")"; String giacOutput2 = cas.getCurrentCAS() .evaluateRaw(strForGiac2); // create a poly with integer coeffs: lcm of // denominators String strForGiac3 = "lcm(denom(coeff(" + giacOutput2 + ")))"; String giacOutput3 = cas.getCurrentCAS() .evaluateRaw(strForGiac3); // multiply with the lcm String strForGiac4 = "expand((" + giacOutput2 + ") * " + giacOutput3 + ")"; giacOutput4 = cas.getCurrentCAS() .evaluateRaw(strForGiac4); } catch (Throwable t) { Log.debug("Unhandled case (Giac)"); } String outputSubst = giacOutput4 .replaceAll("x", vars[0].toString()) .replaceAll("y", vars[1].toString()); /* * Now we have the equation in terms of the Botana * variables. Next, we have to convert the equation to a * Botana polynomial. This piece of code is borrowed from * AlgoDependentNumber. */ ValidExpression resultVE = cas.getCASparser() .parseGeoGebraCASInputAndResolveDummyVars( outputSubst, kernel, null); PolynomialNode polyNode = new PolynomialNode(); ExpressionNode en = new ExpressionNode(kernel, resultVE); AlgoDependentNumber adn = new AlgoDependentNumber( geoStatement.getConstruction(), en, false, null, false); adn.setBotanaVars(vars); adn.buildPolynomialTree(en, polyNode); adn.expressionNodeToPolynomial(en, polyNode); while (polyNode.getPoly() == null) { adn.expressionNodeToPolynomial(en, polyNode); } /* Finally we obtain the Botana polynomial. */ PPolynomial botanaPolynomial = polyNode.getPoly(); /* Don't use this algo any longer. */ movingPoint.getConstruction().removeFromAlgorithmList(adn); movingPoint.getConstruction() .removeFromConstructionList(adn); Log.debug("Hypothesis:"); PPolynomial[] botanaPolynomials = new PPolynomial[1]; botanaPolynomials[0] = botanaPolynomial; addGeoPolys(movingPoint, botanaPolynomials); nHypotheses++; Log.debug((nHypotheses) + ". " + botanaPolynomial); if (proverSettings.captionAlgebra) { numerical.addCaptionBotanaPolynomial( botanaPolynomial.toTeX()); } } catch (NoSymbolicParametersException e) { Log.info("Unhandled case on processing numerical objects"); result = ProofResult.UNKNOWN; return; } } Log.debug("Hypotheses have been processed."); } /** * @param numerical * numerical object * @return equation as string */ String getFormulaString(GeoElement numerical) { return numerical.getFormulaString( StringTemplate.giacTemplateInternal, true); } private void setThesis() { try { interpretTrueAsUndefined = false; /* * The sets of statement polynomials. The last equation of each * set will be negated. */ PPolynomial[][] statements; AlgoElement ae = geoStatement.getParentAlgorithm(); if (ae != null) { statements = ((SymbolicParametersBotanaAlgoAre) ae) .getBotanaPolynomials(); } else { statements = new PPolynomial[1][1]; if (geoStatement instanceof GeoBoolean) { if (((GeoBoolean) geoStatement).getBoolean()) { statements[0][0] = new PPolynomial(0); } else { statements[0][0] = new PPolynomial(BigInteger.ONE); } } else { Log.debug( "Unhandled case, statement is UNKNOWN at the moment"); result = ProofResult.UNKNOWN; return; } } /* case input was an expression */ if (statements == null) { /* * Disallow fixing the second point. This is crucial, * otherwise false theorems like Segment[A,B]==1 will be * proven. */ maxFixcoords = 2; AlgoElement algo = geoStatement.getParentAlgorithm(); /* get expression string for giac */ String strForGiac = ((AlgoDependentBoolean) algo) .getStrForGiac(); String userStrForGiac = ((AlgoDependentBoolean) algo) .getUserGiacString(); GeoGebraCAS cas = (GeoGebraCAS) geoStatement.getKernel() .getGeoGebraCAS(); try { /* K: extended polynomial */ String output = cas.getCurrentCAS() .evaluateRaw(strForGiac); /* F: user's polynomial formula */ String userOutput = cas.getCurrentCAS() .evaluateRaw(userStrForGiac); /* * T = K/F: the factor between user's formula and the * extended one */ String casResult = cas.getCurrentCAS().evaluateRaw( "simplify(" + output + "/" + userOutput + ")"); /* unhandled input expression */ if (output.contains("?") || userOutput.contains("?") || casResult.contains("?")) { this.result = ProofResult.UNKNOWN; return; } /* T is not empty */ /* * Put possible extended factors into the NDG list. Here * we simply parse the Giac output. This code is ugly, * TODO: use a more elegant way. */ if (geoProver .getProverEngine() != ProverEngine.LOCUS_IMPLICIT && !("{}".equals(casResult))) { // skip { and } casResult = casResult.substring(1, casResult.length() - 1); // factorization of the result String factResult = cas.getCurrentCAS() .evaluateRaw("factor(" + casResult + ")"); // removing leading - from a product (if any) if (factResult.length() > 1 && factResult .substring(0, 2).equals("-(")) { factResult = factResult.substring(1); } // split regarding to )*( String[] factors = factResult.split("\\)\\*\\("); // if there are more factors, the first and last // still contain ( and ), trim them if (factors.length > 1) { factors[0] = factors[0].substring(1); factors[factors.length - 1] = factors[factors.length - 1] .substring(0, factors[factors.length - 1].length() - 1); } boolean polyIsConst = false; if (factors.length == 1 && factors[0] .matches("[-+]?\\d*\\.?\\d+")) { polyIsConst = true; // poly is a number } // list of polynomial factors ArrayList<PPolynomial> polyListOfFactors = new ArrayList<PPolynomial>(); if (!polyIsConst) { for (String factor : factors) { // parse factors into expression ValidExpression resultVE = (geoStatement .getKernel().getGeoGebraCAS()) .getCASparser() .parseGeoGebraCASInputAndResolveDummyVars( factor, geoStatement .getKernel(), null); PolynomialNode polyRoot = new PolynomialNode(); // build polynomial to parsed expression ((AlgoDependentBoolean) algo) .buildPolynomialTree( (ExpressionNode) resultVE, polyRoot); ((AlgoDependentBoolean) algo) .expressionNodeToPolynomial( (ExpressionNode) resultVE, polyRoot); while (polyRoot.getPoly() == null) { ((AlgoDependentBoolean) algo) .expressionNodeToPolynomial( (ExpressionNode) resultVE, polyRoot); } // add polynomial to list of polys PPolynomial poly = polyRoot.getPoly(); if (poly != null) { polyListOfFactors.add(poly); } } } for (PPolynomial p : polyListOfFactors) { NDGCondition ndgc = new NDGDetector(geoProver, null).detect(p); if (ndgc != null) { geoProver.addNDGcondition(ndgc); } } /* * Put possible extended factors into the NDG list, * end. */ } /* giac output is not empty */ if (!("{}".equals(output))) { ValidExpression validExpression = (geoStatement .getKernel().getGeoGebraCAS()) .getCASparser() .parseGeoGebraCASInputAndResolveDummyVars( output, geoStatement.getKernel(), null); PolynomialNode polyRoot = new PolynomialNode(); ExpressionNode expNode = new ExpressionNode( geoStatement.getKernel(), ((ExpressionNode) validExpression) .getLeft()); MyList list = new MyList(geoStatement.getKernel()); ExpressionNode root = null; if (expNode.getLeft() instanceof MyList) { list = ((MyList) expNode.getLeft()).getMyList(); } if (list.getListElement(0).isExpressionNode()) { root = (ExpressionNode) list.getListElement(0); } ((AlgoDependentBoolean) algo) .buildPolynomialTree(root, polyRoot); ((AlgoDependentBoolean) algo) .expressionNodeToPolynomial(root, polyRoot); while (polyRoot.getPoly() == null) { ((AlgoDependentBoolean) algo) .expressionNodeToPolynomial(root, polyRoot); } /* get distance polynomials */ ArrayList<PPolynomial> extraPolys = ((AlgoDependentBoolean) algo) .getExtraPolys(); statements = new PPolynomial[1][extraPolys.size() + 1]; int index = 0; for (PPolynomial p : extraPolys) { statements[0][index] = p; index++; } /* add input polynomial */ statements[0][index] = polyRoot.getPoly(); } /* case giac result was empty */ else { statements = new PPolynomial[1][1]; statements[0][0] = new PPolynomial(0); } } catch (Throwable e) { Log.debug( "Unsuccessful run on evaluating the expression, statement is UNKNOWN at the moment"); result = ProofResult.UNKNOWN; return; } } if (disallowFixSecondPoint) { maxFixcoords = 2; } AlgoElement algo = geoStatement.getParentAlgorithm(); if (algo instanceof AlgoAreCongruent) { if (algo.input[0] instanceof GeoAngle && algo.input[1] instanceof GeoAngle) { interpretTrueAsUndefined = true; } } if (algo instanceof AlgoDependentBoolean) { Operation operation = ((AlgoDependentBoolean) algo) .getOperation(); if (operation == Operation.IS_ELEMENT_OF) { if (algo.input[0] instanceof GeoConic && (((GeoConic) algo.input[0]).isEllipse() || ((GeoConic) algo.input[0]) .isHyperbola())) { interpretTrueAsUndefined = true; } else if (algo.input[1] instanceof GeoConic && (((GeoConic) algo.input[1]).isEllipse() || ((GeoConic) algo.input[1]) .isHyperbola())) { interpretTrueAsUndefined = true; } } else if (operation == Operation.EQUAL_BOOLEAN) { if ((algo.input[0] instanceof GeoAngle && algo.input[1] instanceof GeoAngle)) { interpretTrueAsUndefined = true; } } } int k = polynomials.size(); int minus = 1; if (geoProver .getProverEngine() == ProverEngine.LOCUS_IMPLICIT) { minus = 0; } ProverSettings proverSettings = ProverSettings.get(); Log.debug("Thesis equations (non-denied ones):"); for (PPolynomial[] statement : statements) { for (int j = 0; j < statement.length - minus; ++j) { /* Note: the geo is not stored */ addPolynomial(statement[j]); Log.debug((k + 1) + ". " + statement[j]); if (proverSettings.captionAlgebra) { geoStatement.addCaptionBotanaPolynomial( statement[j].toTeX()); } k++; } } if (geoProver .getProverEngine() == ProverEngine.LOCUS_IMPLICIT) { Log.debug("Not using refutation"); return; } /* * Rabinowitsch trick for the last polynomials of the theses of * the statement. Here we use that NOT (A and B and C) == (NOT * A) or (NOT b) or (NOT c), and disjunctions can be algebraized * by using products. */ Log.debug( "Thesis reductio ad absurdum (denied statement), product of factors:"); PPolynomial spoly = new PPolynomial(BigInteger.ONE); PVariable z = new PVariable(geoStatement.getKernel()); /* * It is OK to use the same variable for each factor since it is * enough to find one counterexample only for one of the theses. * See * http://link.springer.com/article/10.1007%2Fs10817-009-9133-x * Appendix, Proposition 6 and Corollary 2 to read more on this. * FIXME: this always introduces an extra variable, shouldn't * do. */ thesisFactors = new PPolynomial[statements.length]; int i = 0; for (PPolynomial[] statement : statements) { PPolynomial factor = (statement[statement.length - 1]); thesisFactors[i] = factor; Log.debug("(" + factor + ")*" + z + "-1"); factor = factor.multiply(new PPolynomial(z)) .subtract(new PPolynomial(BigInteger.ONE)); spoly = spoly.multiply(factor); i++; } /* * We store the geoStatement -> product mapping. Later we should * be able to remove this and use the last polys for checking * the negated statement. */ PPolynomial[] spolys = new PPolynomial[1]; spolys[0] = spoly; addGeoPolys(geoStatement, spolys); Log.debug("that is,"); Log.debug((k + 1) + ". " + spoly); if (proverSettings.captionAlgebra) { geoStatement.addCaptionBotanaPolynomial(spoly.toTeX()); } } catch (NoSymbolicParametersException e) { Log.debug( "Unsuccessful run, statement is UNKNOWN at the moment"); result = ProofResult.UNKNOWN; } } private void algebraicTranslation(GeoElement statement, GeoElement numerical, Prover prover) { ProverSettings proverSettings = ProverSettings.get(); geoStatement = statement; geoProver = prover; /* * Make sure that the prover has the same statement. FIXME: this is * redundant, it would be enough to set the prover here. */ prover.setStatement(statement); setHypotheses(numerical); if (result != null) { return; } try { updateBotanaVarsInv(statement); } catch (NoSymbolicParametersException e) { Log.debug("Botana vars cannot be inverted"); result = ProofResult.UNKNOWN; return; } if (prover.getProverEngine() == ProverEngine.LOCUS_EXPLICIT) { return; } setThesis(); if (result != null) { return; } /* * Only for the Prove command makes sense to set up extra NDG * conditions */ if (prover.getProverEngine() != ProverEngine.RECIOS_PROVER && proverSettings.freePointsNeverCollinear != null && proverSettings.freePointsNeverCollinear && !(prover.isReturnExtraNDGs())) { try { Collections.addAll(polynomials, create3FreePointsNeverCollinearNDG(prover)); } catch (NoSymbolicParametersException e) { Log.debug("Extra NDG conditions cannot be added"); result = ProofResult.UNKNOWN; } } } } /** * Proves the statement by using Botana's method * * @param prover * the prover input object * @return if the statement is true */ public ProofResult prove(Prover prover) { GeoElement statement = prover.getStatement(); ProverSettings proverSettings = ProverSettings.get(); /* * Decide quickly if proving this kind of statement is already * implemented at all: */ if (!(statement .getParentAlgorithm() instanceof SymbolicParametersBotanaAlgoAre)) { Log.info(statement.getParentAlgorithm() + " unimplemented"); return ProofResult.UNKNOWN; /* * If not, let's not spend any time here, but give up immediately. */ } /* If Singular is not available, let's try Giac (mainly on web) */ SingularWebService singularWS = prover.getConstruction() .getApplication().getSingularWS(); if (singularWS == null || (!singularWS.isAvailable())) { proverSettings.transcext = false; } /* The NDG conditions (automatically created): */ if (proverSettings.freePointsNeverCollinear == null) { proverSettings.freePointsNeverCollinear = !prover.getConstruction() .getApplication().singularWSisAvailable(); } AlgebraicStatement as = new AlgebraicStatement(statement, null, prover); /* * It's possible that we already know the answer without computing * anything on the polynomials. If yes, we quit here and return the * known result. */ if (as.result != null) { return as.result; } /* Set substitutions. */ HashMap<PVariable, BigInteger> substitutions = null; int fixcoords; if (prover.isReturnExtraNDGs()) { fixcoords = proverSettings.useFixCoordinatesProveDetails; } else { fixcoords = proverSettings.useFixCoordinatesProve; } if (as.maxFixcoords >= 0 && as.maxFixcoords < fixcoords) { fixcoords = as.maxFixcoords; } if (fixcoords > 0) { try { substitutions = fixValues(prover, fixcoords); } catch (NoSymbolicParametersException e) { as.result = ProofResult.UNKNOWN; Log.debug("Cannot add fix values"); return as.result; } Log.debug("substitutions: " + substitutions); } if (prover.isReturnExtraNDGs()) { /* START OF PROVEDETAILS. */ Set<Set<PPolynomial>> eliminationIdeal; NDGDetector ndgd = new NDGDetector(prover, substitutions); boolean found = false; int permutation = 0; int MAX_PERMUTATIONS = 1; /* * Giac cannot permute the variables at * the moment. */ if (prover.getConstruction().getApplication() .singularWSisAvailable()) { /* * TODO: Limit MAX_PERMUTATIONS to (#freevars-#substitutes)! to * prevent unneeded computations: */ MAX_PERMUTATIONS = 8; /* * intuitively set, see Polynomial.java * for more on info (Pappus6 will work * with 7, too) */ /* Pappus6 is at http://www.tube.geogebra.org/student/m57255 */ } while (!found && permutation < MAX_PERMUTATIONS) { Kernel k = statement.getKernel(); eliminationIdeal = PPolynomial.eliminate( as.getPolynomials() .toArray(new PPolynomial[as.getPolynomials() .size()]), substitutions, k, permutation++, true, false); if (eliminationIdeal == null) { return ProofResult.UNKNOWN; } Iterator<Set<PPolynomial>> ndgSet = eliminationIdeal.iterator(); List<HashSet<GeoPoint>> xEqualSet = new ArrayList<HashSet<GeoPoint>>(); xEqualSet.add(new HashSet<GeoPoint>()); List<HashSet<GeoPoint>> yEqualSet = new ArrayList<HashSet<GeoPoint>>(); yEqualSet.add(new HashSet<GeoPoint>()); boolean xyRewrite = (eliminationIdeal.size() == 2); List<NDGCondition> bestNdgSet = new ArrayList<NDGCondition>(); double bestScore = Double.POSITIVE_INFINITY; int ndgI = 0; while (ndgSet.hasNext()) { ndgI++; Log.debug("Considering NDG " + ndgI + "..."); List<NDGCondition> ndgcl = new ArrayList<NDGCondition>(); double score = 0.0; /* * All NDGs must be translatable into human readable form. */ boolean readable = true; Set<PPolynomial> thisNdgSet = ndgSet.next(); Iterator<PPolynomial> ndg = thisNdgSet.iterator(); while (ndg.hasNext() && readable) { PPolynomial poly = ndg.next(); if (poly.isZero()) { /* * Here we know that the statement is reported to be * not generally true. */ Log.debug("Statement is NOT GENERALLY TRUE"); /* * It is possible that the statement is not * generally false, either. * */ as.negateThesis(); eliminationIdeal = PPolynomial.eliminate( as.getPolynomials() .toArray(new PPolynomial[as .getPolynomials().size()]), substitutions, k, permutation++, true, false); ndgSet = eliminationIdeal.iterator(); while (ndgSet.hasNext()) { thisNdgSet = ndgSet.next(); ndg = thisNdgSet.iterator(); while (ndg.hasNext()) { poly = ndg.next(); if (poly.isZero()) { /* * Here we know that the statement is * reported to be not generally false. */ Log.debug( "Statement is NOT GENERALLY FALSE"); return ProofResult.TRUE_COMPONENT; } } } /* * End of checking if the statement is not generally * false. */ if (as.interpretFalseAsUndefined) { Log.debug("Interpreting FALSE as UNKNOWN"); return ProofResult.UNKNOWN; } return ProofResult.FALSE; } /* * Here we know that the statement is reported to be * generally true with some NDGs. */ if (!poly.isConstant()) { if (as.interpretTrueAsUndefined) { Log.debug("Interpreting TRUE as UNKNOWN"); return ProofResult.UNKNOWN; } NDGCondition ndgc = ndgd.detect(poly); if (ndgc == null) { readable = false; } else { /* * Check if this elimination ideal equals to * {xM-xN,yM-yN}: */ xyRewrite = (xyRewrite && thisNdgSet.size() == 1); /* * Note that in some cases the CAS may return * (xM-xN)*(-1) which consists of two factors, * so thisNdgSet.size() == 1 will fail. Until * now there is no experience of such behavior * for such simple ideals, so maybe this check * is OK. */ if (xyRewrite) { if (ndgc.getCondition() .equals("xAreEqual")) { HashSet<GeoPoint> points = new HashSet<GeoPoint>(); points.add( (GeoPoint) ndgc.getGeos()[0]); points.add( (GeoPoint) ndgc.getGeos()[1]); xEqualSet.add(points); } if (ndgc.getCondition() .equals("yAreEqual")) { HashSet<GeoPoint> points = new HashSet<GeoPoint>(); points.add( (GeoPoint) ndgc.getGeos()[0]); points.add( (GeoPoint) ndgc.getGeos()[1]); yEqualSet.add(points); } if (xEqualSet.size() == 1 && xEqualSet.equals(yEqualSet)) { /* * If yes, set the condition to * AreEqual(M,N) and readable enough: */ ndgc.setCondition("AreEqual"); ndgc.setReadability(0.5); } } ndgcl.add(ndgc); score += ndgc.getReadability(); } } } /* * Now we take the set if the conditions are readable and * the set is the current best. TODO: Here we should * simplify the NDGs, i.e. if one of them is a logical * consequence of others, then it should be eliminated. */ if (readable && score < bestScore) { Log.debug("Found a better NDG score (" + score + ") than " + bestScore); bestScore = score; bestNdgSet = ndgcl; found = true; } else { if (readable) { Log.debug("Not better than previous NDG score (" + bestScore + "), this is " + score); } else { Log.debug("...unreadable"); } } } if (found) { for (NDGCondition aBestNdgSet : bestNdgSet) { prover.addNDGcondition(aBestNdgSet); } } } /* * No readable NDGs was found, search for another prover to make a * better job: */ if (!found) { Log.debug("Statement is TRUE but NDGs are UNREADABLE"); return ProofResult.TRUE_NDG_UNREADABLE; } /* END OF PROVEDETAILS. */ /* START OF PROVE. */ } else { ExtendedBoolean solvable = PPolynomial.solvable( as.getPolynomials() .toArray(new PPolynomial[as.getPolynomials() .size()]), substitutions, statement.getKernel(), proverSettings.transcext); if (ExtendedBoolean.UNKNOWN.equals(solvable)) { /* * Prover returned with no success, search for another prover: */ Log.debug( "Unsuccessful run, statement is UNKNOWN at the moment"); return ProofResult.UNKNOWN; } if (solvable.boolVal()) { if (!proverSettings.transcext) { /* * We cannot reliably tell if the statement is really false: */ Log.debug( "No transcext support, system is solvable, statement is UNKNOWN"); return ProofResult.UNKNOWN; } /* Here we know that the statement is not generally true. */ Log.debug("Statement is NOT GENERALLY TRUE"); /* * It is possible that the statement is not generally false, * either. */ as.negateThesis(); solvable = PPolynomial .solvable( as.getPolynomials() .toArray(new PPolynomial[as .getPolynomials().size()]), substitutions, statement.getKernel(), proverSettings.transcext); if (ExtendedBoolean.UNKNOWN.equals(solvable)) { /* * Prover returned with no success, search for another * prover: */ Log.debug( "Unsuccessful run on negated statement, statement is UNKNOWN at the moment"); return ProofResult.UNKNOWN; } if (solvable.boolVal()) { /* * Here we know that the statement is not generally false. */ Log.debug("Statement is NOT GENERALLY FALSE"); return ProofResult.TRUE_COMPONENT; } /* End of checking if the statement is not generally false. */ if (as.interpretFalseAsUndefined && !as.interpretTrueAsUndefined) { Log.debug("Interpreting FALSE as UNKNOWN"); return ProofResult.UNKNOWN; } return ProofResult.FALSE; } } if (as.interpretTrueAsUndefined) { Log.debug("Interpreting TRUE as UNKNOWN"); return ProofResult.UNKNOWN; } Log.debug("Statement is GENERALLY TRUE"); return ProofResult.TRUE; } /** * Create algebraic equations of the construction to prepare computing a * locus or envelope equation. * * @param tracer * the locus point * @param mover * the moving point * @param implicit * if the locus equation is implicit * @param callerAlgo * the caller Algo * @return the object which describes the construction algebraically */ public static AlgebraicStatement translateConstructionAlgebraically( GeoElement tracer, GeoElement mover, boolean implicit, AlgoElement callerAlgo) { Prover p = UtilFactory.getPrototype().newProver(); p.setProverEngine(implicit ? ProverEngine.LOCUS_IMPLICIT : ProverEngine.LOCUS_EXPLICIT); AlgebraicStatement as = new AlgebraicStatement(tracer, mover, p); ProofResult proofresult = as.getResult(); if (proofresult == ProofResult.PROCESSING || proofresult == ProofResult.UNKNOWN) { /* * Don't do further computations until CAS is ready or there were * unimplemented algos or some other issues: */ Log.debug("Cannot compute implicit curve: " + proofresult); return null; } as.substitutions = new HashMap<PVariable, BigInteger>(); HashSet<GeoElement> freePoints = ProverBotanasMethod .getLocusFreePoints(tracer); if (!implicit) { freePoints.add(tracer); } if (!freePoints.contains(mover)) { freePoints.add(mover); } /* axis and fixed slope line support */ Kernel k = mover.getKernel(); for (GeoElement geo : (tracer).getAllPredecessors()) { if (geo instanceof GeoLine && ((GeoLine) geo).hasFixedSlope()) { PVariable[] vars; try { vars = ((SymbolicParametersBotanaAlgo) geo) .getBotanaVars(geo); } catch (NoSymbolicParametersException e) { Log.debug("Cannot get Botana variables for " + geo); return null; } GeoLine l = (GeoLine) geo; /* * a0/a1*x+b0/b1*y+c0/c1=0, that is: * a0*b1*c1*x+a1*b0*c1*y+a1*b1*c0=0 */ Coords P = l.getCoords(); long[] a = k.doubleToRational(P.get(1)); long[] b = k.doubleToRational(P.get(2)); long[] c = k.doubleToRational(P.get(3)); // Setting up two equations for the two points: PPolynomial a0 = new PPolynomial((int) a[0]); PPolynomial a1 = new PPolynomial((int) a[1]); PPolynomial b0 = new PPolynomial((int) b[0]); PPolynomial b1 = new PPolynomial((int) b[1]); PPolynomial c0 = new PPolynomial((int) c[0]); PPolynomial c1 = new PPolynomial((int) c[1]); PPolynomial xp = new PPolynomial(vars[0]); PPolynomial yp = new PPolynomial(vars[1]); PPolynomial xq = new PPolynomial(vars[2]); PPolynomial yq = new PPolynomial(vars[3]); PPolynomial ph = a0.multiply(b1).multiply(c1).multiply(xp) .add(a1.multiply(b0).multiply(c1).multiply(yp)) .add(a1.multiply(b1).multiply(c0)); as.addPolynomial(ph); Log.debug("Extra poly 1 for " + l.getLabelSimple() + ": " + ph); ph = a0.multiply(b1).multiply(c1).multiply(xq) .add(a1.multiply(b0).multiply(c1).multiply(yq)) .add(a1.multiply(b1).multiply(c0)); as.addPolynomial(ph); Log.debug("Extra poly 2 for " + l.getLabelSimple() + ": " + ph); if (a[0] != 0) { /* * This equation is not horizontal, so y can be arbitrarily * chosen. Let's choose y=0 and y=1 for the 2 points. */ ph = yp; as.addPolynomial(ph); Log.debug("Extra poly 3 for " + l.getLabelSimple() + ": " + ph); ph = yq.subtract(new PPolynomial(BigInteger.ONE)); Log.debug("Extra poly 4 for " + l.getLabelSimple() + ": " + ph); as.addPolynomial(ph); } else { /* * This equation is horizontal, so x can be arbitrarily * chosen. Let's choose x=0 and x=1 for the 2 points. */ ph = xp; as.addPolynomial(ph); Log.debug("Extra poly 3 for " + l.getLabelSimple() + ": " + ph); ph = xq.subtract(new PPolynomial(BigInteger.ONE)); as.addPolynomial(ph); Log.debug("Extra poly 4 for " + l.getLabelSimple() + ": " + ph); } // These coordinates are no longer free. for (int i = 0; i < 4; i++) { vars[i].setFree(false); } } AlgoElement algo = geo.getParentAlgorithm(); boolean condition; condition = implicit || geo != tracer; if (condition && algo instanceof AlgoPointOnPath) { /* * We need to add handle all points which are on a path like * free points (that is, substitution of their coordinates will * be performed later), unless this point is the locus point. */ if (!freePoints.contains(geo)) { freePoints.add(geo); } } } /* free point support */ /* * Note that sometimes free points can be on a path, but they are * considered free if they are not changed while the mover moves. */ for (GeoElement freePoint : freePoints) { freePoint.addToUpdateSetOnly(callerAlgo); PVariable[] vars; try { vars = ((SymbolicParametersBotanaAlgo) freePoint) .getBotanaVars(freePoint); } catch (NoSymbolicParametersException e1) { Log.debug("Cannot get Botana variables for " + freePoint); return null; } boolean condition = !mover.equals(freePoint); if (!implicit) { condition &= !tracer.equals(freePoint); } if (condition) { boolean createX = true; boolean createY = true; AlgoElement ae = freePoint.getParentAlgorithm(); /* * If this "free" point is on a path, then its path may be * important to be kept as a symbolic object for consistency. * Let's do that if the path is linear. */ if (ae instanceof AlgoPointOnPath && ae.input[0] instanceof GeoLine) { PPolynomial[] symPolys; try { symPolys = ((SymbolicParametersBotanaAlgo) freePoint) .getBotanaPolynomials(freePoint); } catch (NoSymbolicParametersException e) { Log.debug( "An error occured during obtaining symbolic parameters"); return null; } int i = 1; for (PPolynomial symPoly : symPolys) { as.addPolynomial(symPoly); Log.debug("Extra symbolic poly " + i + " for " + freePoint.getLabelSimple() + ": " + symPoly); } double[] dir = new double[2]; ((GeoLine) ae.input[0]).getDirection(dir); if (dir[0] == 0.0) { /* vertical */ vars[0].setFree(false); vars[1].setFree(true); createX = false; } else { /* horizontal */ vars[0].setFree(true); vars[1].setFree(false); createY = false; } } if (createX && createY) { /* * Remove any other polynomials which define other * constraints for this point. This is necessary because we * can obtain contradiction if the other (symbolic) * constraints are slightly different than this inaccurate * numerical one. */ if (as.getGeoPolys(freePoint) != null) { Log.debug("Removing other constraints for " + freePoint.getLabelSimple()); as.removeGeoPolys(freePoint); } } long[] q = new long[2]; // P and Q for P/Q if (createX) { double x = ((GeoPoint) freePoint).getInhomX(); /* * Use the fraction P/Q according to the current kernel * setting. We use the P/Q=x <=> P-Q*x=0 equation. */ if ((x % 1) == 0) { // integer q[0] = (long) x; q[1] = 1L; } else { // fractional q = k.doubleToRational(x); } vars[0].setFree(false); PPolynomial ph = new PPolynomial((int) q[0]) .subtract(new PPolynomial(vars[0]) .multiply(new PPolynomial((int) q[1]))); as.addPolynomial(ph); Log.debug("Extra poly for x of " + freePoint.getLabelSimple() + ": " + ph); } if (createY) { double y = ((GeoPoint) freePoint).getInhomY(); /* * Use the fraction P/Q according to the current kernel * setting. We use the P/Q=x <=> P-Q*x=0 equation. */ if ((y % 1) == 0) { // integer q[0] = (long) y; q[1] = 1L; } else { // fractional q = k.doubleToRational(y); } vars[1].setFree(false); PPolynomial ph = new PPolynomial((int) q[0]) .subtract(new PPolynomial(vars[1]) .multiply(new PPolynomial((int) q[1]))); as.addPolynomial(ph); Log.debug("Extra poly for y of " + freePoint.getLabelSimple() + ": " + ph); } } else { condition = true; if (!implicit) { condition = tracer.equals(freePoint); } if (condition) { vars[0].setFree(true); vars[1].setFree(true); as.curveVars = vars; } else { vars[0].setFree(false); vars[1].setFree(false); } } } as.computeStrings(); return as; } }