/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ package org.geogebra.common.kernel.prover; import java.util.HashSet; import java.util.Iterator; import java.util.TreeSet; import org.geogebra.common.factories.UtilFactory; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.RelationNumerical; 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.GeoBoolean; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoLine; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoText; import org.geogebra.common.main.ProverSettings; 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; import org.geogebra.common.util.lang.Unicode; /** * Algo for the ProveDetails command. * * @author Zoltan Kovacs <zoltan@geogebra.org> */ public class AlgoProveDetails extends AlgoElement implements UsesCAS { private GeoElement root; // input private GeoList list; // output private boolean relTool = false; private String inputFingerprint; /** * Proves the given statement and gives some details in a list. * * @param cons * The construction * @param root * Input statement * @param relationTool * true if output should be given for Relation Tool (which is * more readable) */ public AlgoProveDetails(Construction cons, GeoElement root, boolean relationTool) { super(cons); cons.addCASAlgo(this); this.root = root; this.relTool = relationTool; list = new GeoList(cons); setInputOutput(); // for AlgoElement // compute value of dependent number initialCompute(); compute(); } /** * Proves the given statement and gives some details in a list. * * @param cons * The construction * @param label * Label for the output * @param root * Input statement */ public AlgoProveDetails(Construction cons, String label, GeoElement root) { this(cons, root); list.setLabel(label); } /** * Proves the given statement and gives some details in a list * * @param cons * The construction * @param root * Input statement */ public AlgoProveDetails(Construction cons, GeoElement root) { this(cons, root, false); } @Override public Commands getClassName() { return Commands.ProveDetails; } // for AlgoElement @Override protected void setInputOutput() { input = new GeoElement[1]; input[0] = root; super.setOutputLength(1); super.setOutput(0, list); setDependencies(); // done by AlgoElement inputFingerprint = fingerprint(root); } /** * Returns the output for the ProveDetails command * * @return A list: {true/false, {array of NDGConditions}} */ public GeoList getGeoList() { return list; } /** * Heavy computation of the proof. */ public final void initialCompute() { // Create and initialize the prover Prover p = UtilFactory.getPrototype().newProver(); ProverSettings proverSettings = ProverSettings.get(); if ("OpenGeoProver".equalsIgnoreCase(proverSettings.proverEngine)) { if ("Wu".equalsIgnoreCase(proverSettings.proverMethod)) { p.setProverEngine(ProverEngine.OPENGEOPROVER_WU); } else if ("Area".equalsIgnoreCase(proverSettings.proverMethod)) { p.setProverEngine(ProverEngine.OPENGEOPROVER_AREA); } } else if ("Botana".equalsIgnoreCase(proverSettings.proverEngine)) { p.setProverEngine(ProverEngine.BOTANAS_PROVER); } else if ("Recio".equalsIgnoreCase(proverSettings.proverEngine)) { p.setProverEngine(ProverEngine.RECIOS_PROVER); } else if ("PureSymbolic".equalsIgnoreCase(proverSettings.proverEngine)) { p.setProverEngine(ProverEngine.PURE_SYMBOLIC_PROVER); } else if ("Auto".equalsIgnoreCase(proverSettings.proverEngine)) { p.setProverEngine(ProverEngine.AUTO); } p.setTimeout(proverSettings.proverTimeout); p.setConstruction(cons); p.setStatement(root); // Compute extra NDG's: p.setReturnExtraNDGs(true); // Adding benchmarking: double startTime = cons.getApplication().getMillisecondTime(); p.compute(); // the computation of the proof 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"); ProofResult proofresult = p.getProofResult(); ExtendedBoolean result = p.getYesNoAnswer(); Log.debug("STATEMENT IS " + proofresult + " (yes/no: " + result + ")"); if (proofresult == ProofResult.PROCESSING) { list.setUndefined(); return; } list.setDefined(true); list.clear(); if (!ExtendedBoolean.UNKNOWN.equals(result)) { Boolean unreadable = null; if (proofresult == ProofResult.TRUE_NDG_UNREADABLE || proofresult == ProofResult.TRUE_COMPONENT) { unreadable = true; } if (proofresult == ProofResult.TRUE) { unreadable = false; } GeoBoolean answer = new GeoBoolean(cons); answer.setValue(result.boolVal()); list.add(answer); if (result.boolVal()) { HashSet<NDGCondition> ndgresult = p.getNDGConditions(); GeoList ndgConditionsList = new GeoList(cons); ndgConditionsList.clear(); ndgConditionsList.setDrawAsComboBox(true); Iterator<NDGCondition> it = ndgresult.iterator(); TreeSet<GeoText> sortedSet = new TreeSet<GeoText>( GeoText.getComparator()); // Collecting the set of NDG conditions. // The OGP data collector may left some unreadable conditions // so we make sure if the condition is readable. while (!unreadable && it.hasNext()) { GeoText ndgConditionText = new GeoText(cons); NDGCondition ndgc = it.next(); // Do not print unnecessary conditions: if (ndgc.getReadability() > 0) { ndgc.rewrite(cons); StringBuilder s = null; if (relTool) { String cond = ndgc.getCondition(); if ("AreParallel".equals(cond)) { // non-parallism in 2D means intersecting // FIXME: this is not true for 3D s = sb(RelationNumerical.intersectString( ndgc.getGeos()[0], ndgc.getGeos()[1], true, getLoc())); } else if ("AreCollinear".equals(cond)) { s = sb(RelationNumerical .triangleNonDegenerateString( (GeoPoint) ndgc.getGeos()[0], (GeoPoint) ndgc.getGeos()[1], (GeoPoint) ndgc.getGeos()[2], getLoc())); } else if ("AreEqual".equals(cond)) { s = sb(RelationNumerical.equalityString( ndgc.getGeos()[0], ndgc.getGeos()[1], false, getLoc())); } else if ("ArePerpendicular".equals(cond)) { s = sb(RelationNumerical.perpendicularString( (GeoLine) ndgc.getGeos()[0], (GeoLine) ndgc.getGeos()[1], false, getLoc())); } else if ("AreCongruent".equals(cond)) { s = sb(RelationNumerical.congruentSegmentString( ndgc.getGeos()[0], ndgc.getGeos()[1], false, getLoc())); } } if (s == null || !relTool) { GeoElement[] geos = ndgc.getGeos(); if (geos == null) { // formula with quantities s = sb(ndgc.getCondition()); } else { s = sb(getLoc() .getCommand(ndgc.getCondition())); s.append("["); for (int i = 0; i < ndgc .getGeos().length; ++i) { if (i > 0) { s.append(','); } /* * There can be a case when the underlying * prover sends such objects which cannot be * understood by GeoGebra. In this case we * use the "Objects" word. In this case we * normally return ProveResult.UNKNOWN to * not confuse the student, but for sure, we * still do the check here as well. */ GeoElement geo = ndgc.getGeos()[i]; if (geo != null) { s.append(ndgc.getGeos()[i] .getLabelSimple()); } else { s.append(Unicode.ellipsis); } } s.append("]"); if (relTool) { s.insert(0, getLoc().getPlain("not") + " "); } } } ndgConditionText.setTextString(s.toString()); ndgConditionText.setLabelVisible(false); ndgConditionText.setEuclidianVisible(false); sortedSet.add(ndgConditionText); } // For alphabetically ordering, we need a sorted set here: } // Copy the sorted list into the output: Iterator<GeoText> it2 = sortedSet.iterator(); while (it2.hasNext()) { ndgConditionsList.add(it2.next()); } if (unreadable) { GeoText ndgConditionText = new GeoText(cons); String cond = Unicode.ellipsis + ""; ndgConditionText.setTextString(cond); ndgConditionText.setLabelVisible(false); ndgConditionText.setEuclidianVisible(false); sortedSet.add(ndgConditionText); ndgConditionsList.add(ndgConditionText); } // Put this list to the final output (if non-empty): if (ndgConditionsList.size() > 0) { list.add(ndgConditionsList); } } } /* * Don't remove this. It is needed for testing the web platform. (String * match is assumed.) */ Log.debug("OUTPUT for ProveDetails: " + list); } private static StringBuilder sb(String content) { return content == null ? null : new StringBuilder(content); } @Override // Not sure how to do this hack normally. final public String getDefinitionName(StringTemplate tpl) { return "ProveDetails"; } @Override public void compute() { if (!kernel.getGeoGebraCAS().getCurrentCAS().isLoaded()) { inputFingerprint = null; return; } String inputFingerprintPrev = inputFingerprint; setInputOutput(); if (inputFingerprintPrev == null || !inputFingerprintPrev.equals(inputFingerprint)) { Log.trace(inputFingerprintPrev + " -> " + inputFingerprint); initialCompute(); } } /* * We use a very hacky way to avoid recomputing proof when the input is not * changed. To achieve that, we create a fingerprint of the current input. * 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 statement) { return Prover.getTextFormat(statement); } }