package org.geogebra.common.kernel.prover; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.TreeMap; import java.util.TreeSet; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.algos.AlgoDependentBoolean; import org.geogebra.common.kernel.algos.SymbolicParametersBotanaAlgo; import org.geogebra.common.kernel.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.prover.polynomial.PPolynomial; import org.geogebra.common.kernel.prover.polynomial.PTerm; import org.geogebra.common.kernel.prover.polynomial.PVariable; import org.geogebra.common.util.Prover; import org.geogebra.common.util.Prover.NDGCondition; import org.geogebra.common.util.debug.Log; /** * Detects polynomial NDG conditions and turns into human readable form * * @author Zoltan Kovacs <zoltan@geogebra.org> * */ public class NDGDetector { private HashMap<String, NDGCondition> lookupTable; private Prover prover; private HashMap<PVariable, BigInteger> substitutions; /** * Creates an NDGDetector instance. The NDG detector will try to detect * geometrical meanings of polynomials for the same prover and * substitutions. * * @param prover * The prover we are create this instance for. * @param substitutions2 * Fix substitutions. */ NDGDetector(Prover prover, HashMap<PVariable, BigInteger> substitutions2) { lookupTable = new HashMap<String, NDGCondition>(); this.prover = prover; this.substitutions = substitutions2; } /** * Returns the NDG condition (as a GeoGebra object) if the input polynomial * is detected as a recognizable geometrically meaningful condition * (collinearity, equality etc.). * * @param p * input polynomial * @return the NDG condition */ public NDGCondition detect(PPolynomial p) { GeoElement statement = prover.getStatement(); if (statement == null) { return null; } /* * Maybe this condition was already detected, or marked as unreadable. * By using the lookup table, we don't have to do heavy computations * twice. */ NDGCondition ndgc = null; String keyString = p.substitute(substitutions).toString(); ndgc = lookupTable.get(keyString); if (lookupTable.containsKey(keyString)) { ndgc = lookupTable.get(keyString); if (ndgc != null && ndgc.getReadability() == Double.POSITIVE_INFINITY) { return null; } return ndgc; } Log.debug("Trying to detect polynomial " + p); // CHECKING FORMULA WITH QUANTITIES if (statement.getParentAlgorithm() instanceof AlgoDependentBoolean) { // list of segments -> variables ArrayList<Entry<GeoElement, PVariable>> varSubstListOfSegs = ((AlgoDependentBoolean) statement .getParentAlgorithm()).getVarSubstListOfSegs(); // create list of variables -> segments HashMap<PVariable, GeoElement> geos = new HashMap<PVariable, GeoElement>(); if (varSubstListOfSegs != null) { for (int i = 0; i < varSubstListOfSegs.size(); ++i) { Entry<GeoElement, PVariable> e = varSubstListOfSegs.get(i); GeoElement g = e.getKey(); PVariable v = e.getValue(); geos.put(v, g); } /* contains only geometric quantities (now segments)? */ boolean qFormula = true; Kernel kernel = statement.getKernel(); TreeMap<PTerm, BigInteger> tm1 = p.getTerms(); ExpressionNode lhs = new ExpressionNode(kernel, 0); ExpressionNode rhs = new ExpressionNode(kernel, 0); /* are there any expressions on boths sides? */ boolean lt = false; boolean rt = false; outerloop: for (Entry<PTerm, BigInteger> entry : tm1 .entrySet()) { // e.g. 5*v1^3*v2 BigInteger coeff = entry.getValue(); // e.g. 5 /* always use the absolute value */ ExpressionNode c = new ExpressionNode(kernel, coeff.abs().longValue()); // FIXME TreeMap<PVariable, Integer> tm2 = entry.getKey().getTerm(); ExpressionNode en = new ExpressionNode(kernel, 1); /* e.g. v1->3, v2->1 */ TreeSet<GeoElement> geoSet = new TreeSet<GeoElement>(); HashMap<GeoElement, ExpressionNode> bases = new HashMap<GeoElement, ExpressionNode>(); for (Entry<PVariable, Integer> entry0 : tm2.entrySet()) { // e.g. // v1 PVariable t2 = entry0.getKey(); if (!geos.containsKey(t2)) { qFormula = false; break outerloop; } GeoElement g = geos.get(t2); ExpressionValue t = g.toValidExpression(); int exponent = entry0.getValue(); ExpressionNode base = new ExpressionNode(kernel, t); if (exponent > 1) { base = base.power(exponent); } geoSet.add(g); bases.put(g, base); } /* * This will sort the terms in order of creation of the * GeoElements, but this is mostly the same as alphabetical * order. Actually, most users want sorting in order of * creation. */ Iterator<GeoElement> it = geoSet.descendingIterator(); while (it.hasNext()) { GeoElement g = it.next(); en = en.multiply(bases.get(g)); } if (coeff.compareTo(BigInteger.ZERO) > 0) { lhs = lhs.plus(c.multiply(en)); lt = true; } else { rhs = rhs.plus(c.multiply(en)); rt = true; } } Equation eq = new Equation(kernel, lhs, rhs); if (qFormula) { if (!lt || !rt) { // This must be an uninteresting case, e.g. a+b+c=0 Log.debug(p + " means " + eq + ", uninteresting"); return null; } ndgc = new NDGCondition(); /* * TODO: Later eventually we want to use the equation, not * just an exported string. */ ndgc.setCondition( eq.toString(StringTemplate.defaultTemplate)); ndgc.setReadability(2); Log.debug(p + " means " + eq); return ndgc; } Log.debug(p + " cannot be described by quantities only"); } } List<GeoElement> freePoints = ProverBotanasMethod .getFreePoints(statement); HashSet<GeoElement> freePointsSet = new HashSet<GeoElement>(freePoints); // CHECKING COLLINEARITY Combinations triplets = new Combinations(freePointsSet, 3); while (triplets.hasNext()) { HashSet<Object> triplet = (HashSet<Object>) triplets.next(); Iterator<Object> it = triplet.iterator(); // GeoElement[] points = (GeoElement[]) triplet.toArray(); // This is not working directly, so we have to do it manually: int i = 0; GeoElement[] points = new GeoElement[triplet.size()]; while (it.hasNext()) { points[i] = (GeoElement) it.next(); i++; } PVariable[] fv1, fv2, fv3; try { fv1 = ((SymbolicParametersBotanaAlgo) points[0]) .getBotanaVars(points[0]); fv2 = ((SymbolicParametersBotanaAlgo) points[1]) .getBotanaVars(points[1]); fv3 = ((SymbolicParametersBotanaAlgo) points[2]) .getBotanaVars(points[2]); } catch (NoSymbolicParametersException e) { Log.debug("Cannot get Botana vars during NDG detection"); return null; } // Creating the polynomial for collinearity: PPolynomial coll = PPolynomial .collinear(fv1[0], fv1[1], fv2[0], fv2[1], fv3[0], fv3[1]) .substitute(substitutions); if (PPolynomial.areAssociates1(p, coll)) { Log.debug(p + " means collinearity for " + triplet); ndgc = new NDGCondition(); ndgc.setGeos(points); Arrays.sort(ndgc.getGeos()); ndgc.setCondition("AreCollinear"); lookupTable.put(keyString, ndgc); return ndgc; } } // CHECKING STRONG EQUALITY Combinations pairs = new Combinations(freePointsSet, 2); while (pairs.hasNext()) { HashSet<Object> pair = (HashSet<Object>) pairs.next(); Iterator<Object> it = pair.iterator(); // GeoElement[] points = (GeoElement[]) pair.toArray(); // This is not working directly, so we have to do it manually: int i = 0; GeoElement[] points = new GeoElement[pair.size()]; while (it.hasNext()) { points[i] = (GeoElement) it.next(); i++; } PVariable[] fv1, fv2; try { fv1 = ((SymbolicParametersBotanaAlgo) points[0]) .getBotanaVars(points[0]); fv2 = ((SymbolicParametersBotanaAlgo) points[1]) .getBotanaVars(points[1]); } catch (NoSymbolicParametersException e) { Log.debug("Cannot get Botana vars during NDG detection"); return null; } // Creating the polynomial for equality: PPolynomial eq = PPolynomial .sqrDistance(fv1[0], fv1[1], fv2[0], fv2[1]) .substitute(substitutions); if (PPolynomial.areAssociates1(p, eq)) { Log.debug(p + " means equality for " + pair); ndgc = new NDGCondition(); ndgc.setGeos(points); Arrays.sort(ndgc.getGeos()); ndgc.setCondition("AreEqual"); ndgc.setReadability(0.5); lookupTable.put(keyString, ndgc); return ndgc; } } HashSet<PVariable> freeXvars = new HashSet<PVariable>(); HashMap<PVariable, GeoElement> xvarGeo = new HashMap<PVariable, GeoElement>(); HashSet<PVariable> freeYvars = new HashSet<PVariable>(); HashMap<PVariable, GeoElement> yvarGeo = new HashMap<PVariable, GeoElement>(); Iterator<GeoElement> it = prover.getStatement().getAllPredecessors() .iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (geo.isGeoPoint() && (geo instanceof SymbolicParametersBotanaAlgo)) { PVariable x, y; try { x = ((SymbolicParametersBotanaAlgo) geo) .getBotanaVars(geo)[0]; if (x.isFree()) { freeXvars.add(x); xvarGeo.put(x, geo); } y = ((SymbolicParametersBotanaAlgo) geo) .getBotanaVars(geo)[1]; if (y.isFree()) { freeYvars.add(y); yvarGeo.put(y, geo); } } catch (NoSymbolicParametersException e) { Log.debug("Cannot get Botana vars during NDG detection"); return null; } } } // CHECKING EQUALITY (WHERE WE CAN GIVE SUFFICIENT CONDITIONS ONLY) pairs = new Combinations(freeXvars, 2); while (pairs.hasNext()) { HashSet<Object> pair = (HashSet<Object>) pairs.next(); Iterator<Object> itc = pair.iterator(); // GeoElement[] points = (GeoElement[]) pair.toArray(); // This is not working directly, so we have to do it manually: int i = 0; PVariable[] coords = new PVariable[pair.size()]; GeoElement[] points = new GeoElement[pair.size()]; while (itc.hasNext()) { coords[i] = (PVariable) itc.next(); points[i] = xvarGeo.get(coords[i]); i++; } PPolynomial xeq = (new PPolynomial(coords[0]) .subtract(new PPolynomial(coords[1]))) .substitute(substitutions); if (PPolynomial.areAssociates1(p, xeq)) { Log.debug(p + " means x-equality for " + pair); ndgc = new NDGCondition(); ndgc.setGeos(points); Arrays.sort(ndgc.getGeos()); ndgc.setCondition("xAreEqual"); ndgc.setReadability(Double.POSITIVE_INFINITY); // we don't want // this // condition lookupTable.put(keyString, ndgc); return ndgc; } } pairs = new Combinations(freeYvars, 2); while (pairs.hasNext()) { HashSet<Object> pair = (HashSet<Object>) pairs.next(); Iterator<Object> itc = pair.iterator(); // GeoElement[] points = (GeoElement[]) pair.toArray(); // This is not working directly, so we have to do it manually: int i = 0; PVariable[] coords = new PVariable[pair.size()]; GeoElement[] points = new GeoElement[pair.size()]; while (itc.hasNext()) { coords[i] = (PVariable) itc.next(); points[i] = yvarGeo.get(coords[i]); i++; } PPolynomial yeq = (new PPolynomial(coords[0]) .subtract(new PPolynomial(coords[1]))) .substitute(substitutions); if (PPolynomial.areAssociates1(p, yeq)) { Log.debug(p + " means y-equality for " + pair); ndgc = new NDGCondition(); ndgc.setGeos(points); Arrays.sort(ndgc.getGeos()); ndgc.setCondition("yAreEqual"); ndgc.setReadability(Double.POSITIVE_INFINITY); // we don't want // this // condition lookupTable.put(keyString, ndgc); return ndgc; } } // CHECKING PERPENDICULARITY AND PARALLELISM Combinations pairs1 = new Combinations(freePointsSet, 2); while (pairs1.hasNext()) { HashSet<Object> pair1 = (HashSet<Object>) pairs1.next(); Iterator<Object> it1 = pair1.iterator(); // GeoElement[] points = (GeoElement[]) pair.toArray(); // This is not working directly, so we have to do it manually: int i = 0; GeoElement[] points = new GeoElement[4]; while (it1.hasNext()) { points[i] = (GeoElement) it1.next(); i++; } Combinations pairs2 = new Combinations(freePointsSet, 2); while (pairs2.hasNext()) { HashSet<Object> pair2 = (HashSet<Object>) pairs2.next(); Iterator<Object> it2 = pair2.iterator(); // GeoElement[] points = (GeoElement[]) pair.toArray(); // This is not working directly, so we have to do it manually: i = 2; while (it2.hasNext()) { points[i] = (GeoElement) it2.next(); i++; } PVariable[] fv1, fv2, fv3, fv4; try { fv1 = ((SymbolicParametersBotanaAlgo) points[0]) .getBotanaVars(points[0]); fv2 = ((SymbolicParametersBotanaAlgo) points[1]) .getBotanaVars(points[1]); fv3 = ((SymbolicParametersBotanaAlgo) points[2]) .getBotanaVars(points[0]); fv4 = ((SymbolicParametersBotanaAlgo) points[3]) .getBotanaVars(points[1]); } catch (NoSymbolicParametersException e) { Log.debug("Cannot get Botana vars during NDG detection"); return null; } // Creating the polynomial for perpendicularity: PPolynomial eq = PPolynomial .perpendicular(fv1[0], fv1[1], fv2[0], fv2[1], fv3[0], fv3[1], fv4[0], fv4[1]) .substitute(substitutions); if (PPolynomial.areAssociates1(p, eq)) { Log.debug(p + " means perpendicularity for " + pair1 + " and " + pair2); ndgc = new NDGCondition(); ndgc.setGeos(points); ndgc.setCondition("ArePerpendicular"); ndgc.setReadability(0.75); lookupTable.put(keyString, ndgc); return ndgc; } // Creating the polynomial for parallelism: eq = PPolynomial .parallel(fv1[0], fv1[1], fv2[0], fv2[1], fv3[0], fv3[1], fv4[0], fv4[1]) .substitute(substitutions); if (PPolynomial.areAssociates1(p, eq)) { Log.debug(p + " means parallelism for " + pair1 + " and " + pair2); ndgc = new NDGCondition(); ndgc.setGeos(points); ndgc.setCondition("AreParallel"); ndgc.setReadability(0.75); lookupTable.put(keyString, ndgc); return ndgc; } } } // CHECKING ISOSCELES TRIANGLES pairs = new Combinations(freePointsSet, 2); while (pairs.hasNext()) { HashSet<Object> pair = (HashSet<Object>) pairs.next(); Iterator<Object> it1 = pair.iterator(); // GeoElement[] points = (GeoElement[]) pair.toArray(); // This is not working directly, so we have to do it manually: int i = 0; GeoElement[] points = new GeoElement[4]; while (it1.hasNext()) { points[i] = (GeoElement) it1.next(); i += 2; } it = freePointsSet.iterator(); while (it.hasNext()) { points[1] = points[3] = it.next(); PVariable[] fv1, fv2, fv3; try { fv1 = ((SymbolicParametersBotanaAlgo) points[0]) .getBotanaVars(points[0]); fv2 = ((SymbolicParametersBotanaAlgo) points[1]) .getBotanaVars(points[1]); fv3 = ((SymbolicParametersBotanaAlgo) points[2]) .getBotanaVars(points[2]); } catch (NoSymbolicParametersException e) { Log.debug("Cannot get Botana vars during NDG detection"); return null; } // Creating the polynomial for being isosceles: PPolynomial eq = PPolynomial.equidistant(fv1[0], fv1[1], fv2[0], fv2[1], fv3[0], fv3[1]).substitute(substitutions); if (PPolynomial.areAssociates1(p, eq)) { Log.debug(p + " means being isosceles triangle for base " + pair + " and opposite vertex " + points[1]); ndgc = new NDGCondition(); ndgc.setGeos(points); ndgc.setCondition("IsIsoscelesTriangle"); ndgc.setReadability(1.25); lookupTable.put(keyString, ndgc); return ndgc; } } } // Unsuccessful run: Log.debug("No human readable geometrical meaning found for " + p); lookupTable.put(keyString, null); return null; } }