package org.geogebra.common.kernel.prover;
import java.math.BigInteger;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeSet;
import org.geogebra.common.factories.UtilFactory;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.SymbolicParameters;
import org.geogebra.common.kernel.algos.SymbolicParametersAlgo;
import org.geogebra.common.kernel.algos.SymbolicParametersBotanaAlgo;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.prover.ProverBotanasMethod.AlgebraicStatement;
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.util.ExtendedBoolean;
import org.geogebra.common.util.Prover;
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 Tomas Recios method to prove geometric theorems.
*
* @author Simon Weitzhofer
*
*/
public abstract class AbstractProverReciosMethod {
private static GeoElement[] fixedPoints;
/**
* The prover which tries to prove the statement with the help of Tomas
* Recios method.
*
* @param prover
* the prover input object
* @return The result of the prove.
*/
public ProofResult prove(Prover prover) {
SymbolicParameters s = null;
boolean B = false; // use Botana's method or not
if ("groebner".equals(ProverSettings.get().proverMethod)) {
B = true;
}
AlgebraicStatement as = null;
GeoElement statement = prover.getStatement();
Prover p;
if (statement instanceof SymbolicParametersAlgo) {
s = (((SymbolicParametersAlgo) statement).getSymbolicParameters());
} else if (statement
.getParentAlgorithm() instanceof SymbolicParametersAlgo) {
s = (((SymbolicParametersAlgo) statement.getParentAlgorithm())
.getSymbolicParameters());
} else {
return ProofResult.UNKNOWN;
}
if (B) {
// use Botana's method if there is no native support
p = UtilFactory.getPrototype().newProver();
p.setProverEngine(ProverEngine.RECIOS_PROVER);
as = new AlgebraicStatement(statement, null, p);
if (as.getResult() == ProofResult.PROCESSING) {
// Don't do further computations until CAS is ready:
return ProofResult.PROCESSING;
}
}
HashSet<PVariable> variables = new HashSet<PVariable>();
if (!B) {
try {
variables = s.getFreeVariables();
} catch (NoSymbolicParametersException e) {
return ProofResult.UNKNOWN;
}
} else {
List<GeoElement> freePoints = ProverBotanasMethod
.getFreePoints(statement);
Iterator<GeoElement> it = freePoints.iterator();
while (it.hasNext()) {
GeoElement geo = it.next();
PVariable[] vars;
try {
vars = ((SymbolicParametersBotanaAlgo) geo)
.getBotanaVars(geo);
} catch (NoSymbolicParametersException e) {
Log.debug("Cannot get Botana variables for " + geo);
return ProofResult.UNKNOWN;
}
variables.add(vars[0]);
variables.add(vars[1]);
/*
* This is not automatically set in Botana's prover. Consider do
* this setting there instead of here. TODO.
*/
vars[0].setTwin(vars[1]);
vars[1].setTwin(vars[0]);
vars[0].setParent(geo);
vars[1].setParent(geo);
/*
* Consider using the same way also in Botana's prover for the
* other method. TODO.
*/
}
}
// setting two points fixed (the first to (0,0) and the second to (0,1))
// all other variables are stored in freeVariables
Iterator<PVariable> it = variables.iterator();
HashMap<PVariable, BigInteger> values = new HashMap<PVariable, BigInteger>();
TreeSet<PVariable> fixedVariables = new TreeSet<PVariable>(
new Comparator<PVariable>() {
@Override
public int compare(PVariable v1, PVariable v2) {
String nameV1, nameV2;
if (v1.getParent() == null
|| (nameV1 = v1.getParent().getLabel(
StringTemplate.defaultTemplate)) == null) {
if (v2.getParent() == null
|| v1.getParent().getLabel(
StringTemplate.defaultTemplate) == null) {
return v1.compareTo(v2);
}
return -1;
}
if (v2.getParent() == null
|| (nameV2 = v2.getParent().getLabel(
StringTemplate.defaultTemplate)) == null) {
return 1;
}
int compareNames = nameV1.compareTo(nameV2);
if (compareNames == 0) {
return v1.compareTo(v2);
}
return compareNames;
}
});
HashSet<PVariable> freeVariables = new HashSet<PVariable>();
while (it.hasNext()) {
PVariable fv = it.next();
if (fv.getTwin() == null || !variables.contains(fv.getTwin())) {
freeVariables.add(fv);
continue;
}
fixedVariables.add(fv);
}
it = fixedVariables.iterator();
int nrOfFixedPoints = 0;
GeoElement fixedElement1 = null, fixedElement2 = null;
while (it.hasNext()) {
PVariable var;
if (nrOfFixedPoints == 0) {
var = it.next();
values.put(var, BigInteger.ZERO);
values.put(it.next(), BigInteger.ZERO);
fixedElement1 = var.getParent();
nrOfFixedPoints = 1;
} else if (nrOfFixedPoints == 1) {
var = it.next();
values.put(var, BigInteger.ZERO);
values.put(it.next(), BigInteger.ONE);
fixedElement2 = var.getParent();
nrOfFixedPoints = 2;
} else {
freeVariables.add(it.next());
}
}
if (nrOfFixedPoints == 1) {
fixedPoints = new GeoElement[1];
fixedPoints[0] = fixedElement1;
} else if (nrOfFixedPoints == 2) {
fixedPoints = new GeoElement[2];
fixedPoints[0] = fixedElement1;
fixedPoints[1] = fixedElement2;
}
int nrFreeVariables = freeVariables.size();
if (nrFreeVariables > 5) {
// It would take too much time, it's better to find another method.
// TODO: This is not a problem in the method, it is in the
// implementation.
// FIXME: Make the implementation faster.
Log.debug(
"Recio's method is currently disabled when # of free variables > 5");
return ProofResult.UNKNOWN;
}
int[] degs;
try {
degs = s.getDegrees();
} catch (NoSymbolicParametersException e) {
return ProofResult.UNKNOWN;
}
int deg = 0;
for (int i : degs) {
deg = Math.max(deg, i);
}
switch (nrFreeVariables) {
case 0:
return compute0d(values, s, as);
case 1:
return compute1d(freeVariables, values, deg, s, as);
case 2:
return compute2d(freeVariables, values, deg, s, as);
default:
return computeNd(freeVariables, values, deg, s, as);
}
}
private static ProofResult compute0d(HashMap<PVariable, BigInteger> values,
SymbolicParameters s, AlgebraicStatement as) {
if (as != null) {
// use Botana's method
HashMap<PVariable, BigInteger> substitutions = new HashMap<PVariable, BigInteger>();
for (Entry<PVariable, BigInteger> entry : values.entrySet()) {
PVariable v = entry.getKey();
// FIXME: Change Long in Variable to BigInteger
substitutions.put(v, entry.getValue());
}
ProverSettings proverSettings = ProverSettings.get();
ExtendedBoolean solvable = PPolynomial.solvable(
as.getPolynomials().toArray(
new PPolynomial[as.getPolynomials().size()]),
substitutions, as.geoStatement.getKernel(),
proverSettings.transcext);
Log.debug("Recio meets Botana:" + substitutions);
if (solvable.boolVal()) {
return ProofResult.FALSE;
}
} else {
try {
BigInteger[] exactCoordinates = s.getExactCoordinates(values);
for (BigInteger result : exactCoordinates) {
if (!result.equals(BigInteger.ZERO)) {
return ProofResult.FALSE;
}
}
} catch (NoSymbolicParametersException e) {
return ProofResult.UNKNOWN;
}
}
return ProofResult.TRUE;
}
private static ProofResult compute1d(final HashSet<PVariable> freeVariables,
final HashMap<PVariable, BigInteger> values, final int deg,
final SymbolicParameters s, AlgebraicStatement as) {
PVariable variable = freeVariables.iterator().next();
for (int i = 1; i <= deg + 2; i++) {
values.put(variable, BigInteger.valueOf(i));
if (as != null) {
// use Botana's method
HashMap<PVariable, BigInteger> substitutions = new HashMap<PVariable, BigInteger>();
for (Entry<PVariable, BigInteger> entry : values.entrySet()) {
PVariable v = entry.getKey();
// FIXME: Change Long in Variable to BigInteger
substitutions.put(v, entry.getValue());
}
ProverSettings proverSettings = ProverSettings.get();
ExtendedBoolean solvable = PPolynomial.solvable(
as.getPolynomials().toArray(
new PPolynomial[as.getPolynomials().size()]),
substitutions, as.geoStatement.getKernel(),
proverSettings.transcext);
Log.debug("Recio meets Botana: #" + i + " " + substitutions);
if (solvable.boolVal()) {
return ProofResult.FALSE;
}
} else {
try {
BigInteger[] exactCoordinates = s
.getExactCoordinates(values);
for (BigInteger result : exactCoordinates) {
if (!result.equals(BigInteger.ZERO)) {
return ProofResult.FALSE;
}
}
} catch (NoSymbolicParametersException e) {
return ProofResult.UNKNOWN;
}
}
}
return ProofResult.TRUE;
}
private static ProofResult compute2d(final HashSet<PVariable> freeVariables,
final HashMap<PVariable, BigInteger> values, final int deg,
final SymbolicParameters s, AlgebraicStatement as) {
PVariable[] variables = new PVariable[freeVariables.size()];
Iterator<PVariable> it = freeVariables.iterator();
for (int i = 0; i < variables.length; i++) {
variables[i] = it.next();
}
int nrOfTests = ((deg + 2) * (deg + 1)) / 2;
Log.debug("nr of tests: " + nrOfTests);
int caseno = 0;
for (int i = 1; i < /* = */deg + 2; i++) {
for (int j = 1; j <= i; j++) {
caseno++;
values.put(variables[0],
BigInteger.valueOf((deg + 2 - i) * (deg + 2 - j)));
values.put(variables[1], BigInteger.valueOf(i * j));
if (as != null) {
// use Botana's method
HashMap<PVariable, BigInteger> substitutions = new HashMap<PVariable, BigInteger>();
for (Entry<PVariable, BigInteger> entry : values
.entrySet()) {
PVariable v = entry.getKey();
// FIXME: Change Long in Variable to BigInteger
substitutions.put(v, entry.getValue());
}
ExtendedBoolean solvable = PPolynomial.solvable(
as.getPolynomials()
.toArray(new PPolynomial[as.getPolynomials()
.size()]),
substitutions, as.geoStatement.getKernel(),
ProverSettings.get().transcext);
Log.debug("Recio meets Botana: #" + caseno + " "
+ substitutions);
if (solvable.boolVal()) {
return ProofResult.FALSE;
}
} else {
try {
BigInteger[] exactCoordinates = s
.getExactCoordinates(values);
for (BigInteger result : exactCoordinates) {
if (!result.equals(BigInteger.ZERO)) {
return ProofResult.FALSE;
}
}
} catch (NoSymbolicParametersException e) {
return ProofResult.UNKNOWN;
}
}
}
}
return ProofResult.TRUE;
}
/**
* More complicated calculations are done by multiple threads in desktop
*
* @param freeVariables
* The free variables ruling the construction
* @param values
* The values for the fixed variables (If e.g. one point gets
* fixed coordinates (0,0) and another (0,1)
* @param deg
* The bound for the degree of the statement
* @param s
* The Symbolic parameters class that is used to test the
* statement for a fixed point
* @param as
* The algebraic translation of the statement, if null, use
* native computations (by Weitzhofer), otherwise use the Botana
* equations (by Kovacs/Solyom-Gecse)
* @return the result of the proof
*/
protected abstract ProofResult computeNd(
final HashSet<PVariable> freeVariables,
final HashMap<PVariable, BigInteger> values, final int deg,
final SymbolicParameters s, AlgebraicStatement as);
/**
* Returns the elements which are fixed by Recio's method prover
*
* @return the fixed elements
*/
public static GeoElement[] getFixedPoints() {
return fixedPoints;
}
}