package org.geogebra.desktop.kernel.prover; import java.math.BigInteger; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map.Entry; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import org.geogebra.common.kernel.algos.SymbolicParameters; import org.geogebra.common.kernel.prover.AbstractProverReciosMethod; import org.geogebra.common.kernel.prover.NoSymbolicParametersException; 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.ProofResult; import org.geogebra.common.util.debug.Log; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * This class can prove a statement by a bounded number of checks. In this * desktop version this is done by multiple threads, if the CPU has multiple * threads. * * @author Simon * */ public class ProverReciosMethodD extends AbstractProverReciosMethod { private enum TestPointResult { /** * The statement is true in the point */ PASSED, /** * The statement is false in the point */ FALSE, /** * An error occurred */ ERROR } private PointTester[] pointTesters; /** * The queue which contains the coordinates of the points to test */ final LinkedBlockingQueue<BigInteger[]> coordinatesQueue = new LinkedBlockingQueue<BigInteger[]>(); private AtomicInteger verifiedPoints; private boolean stop; private boolean errorOccured; private Thread[] threads; // stops all working threads private void interruptThreads() { for (Thread t : threads) { t.interrupt(); } } /** * Takes the result back from the threads. * * @param result * the result of the test point. */ @SuppressFBWarnings({ "SF_SWITCH_FALLTHROUGH", "missing break is deliberate" }) protected void writeResult(TestPointResult result) { switch (result) { case PASSED: verifiedPoints.incrementAndGet(); break; case ERROR: errorOccured = true; // fall through case FALSE: stop = true; coordinatesQueue.clear(); } } private boolean getErrorOccured() { return errorOccured; } @Override protected final ProofResult computeNd(HashSet<PVariable> freeVariables, HashMap<PVariable, BigInteger> values, int deg, SymbolicParameters s, AlgebraicStatement as) { int n = freeVariables.size(); PVariable[] variables = new PVariable[n]; Iterator<PVariable> it = freeVariables.iterator(); for (int i = 0; i < n; i++) { variables[i] = it.next(); } coordinatesQueue.clear(); verifiedPoints = new AtomicInteger(0); stop = false; errorOccured = false; int[] indices = new int[n]; for (int i = 0; i < n; i++) { indices[i] = n - i; } boolean indicesChanged; int nrOfTests = 0, changedIndex = n - 1; BigInteger[][] cache = new BigInteger[n][n]; BigInteger[] coordinates = new BigInteger[n]; Runtime runtime = Runtime.getRuntime(); int useProcessors = runtime.availableProcessors() - 1; useProcessors = 0; // do not use threads until #3399 is fixed pointTesters = new PointTester[useProcessors]; threads = new Thread[useProcessors]; for (int i = 0; i < useProcessors; i++) { pointTesters[i] = new PointTester(this, values, variables, s); threads[i] = new Thread(pointTesters[i], "ProverReciosMethod_TestPoints" + i); threads[i].start(); } do { if (Thread.interrupted()) { interruptThreads(); return ProofResult.UNKNOWN; } // calculation of the coordinates for (int i = 0; i < n; i++) { BigInteger result; if (changedIndex == n - 1) { result = BigInteger.ONE; } else { result = cache[i][changedIndex + 1]; } for (int j = changedIndex; j >= 0; j--) { result = result.multiply((BigInteger.valueOf(n) .multiply(BigInteger.valueOf(indices[j]))) .subtract(BigInteger.valueOf(i))); cache[i][j] = result; } coordinates[i] = result; } nrOfTests++; try { coordinatesQueue.put(coordinates); } catch (InterruptedException e) { return ProofResult.UNKNOWN; } // the following is the loop header // the created indices sequence is: // [n n-1 n-2 ... 1] // [n+1 n-1 n-2 ... 1] // ... // [n+d n-1 n-2 ... 1] // [n+1 n n-2 ... 1] // [n+2 n n-2 ... 1] // ... // [n+d n+d-1 ... d] indicesChanged = false; for (int i = 0; i < n; i++) { if (indices[i] < (deg - i + n)) { indices[i]++; for (int j = 0; j < i; j++) { indices[j] = indices[i] + i - j; } changedIndex = i; indicesChanged = true; break; } } } while (indicesChanged && !stop); if (stop) { interruptThreads(); if (getErrorOccured()) { return ProofResult.UNKNOWN; } return ProofResult.FALSE; } int nrOfChecks = 0; boolean wrong = false; // if the tests are not finished by the threads // we help the threads testing the points. while (!stop && verifiedPoints.get() < nrOfTests) { if (Thread.interrupted()) { interruptThreads(); return ProofResult.UNKNOWN; } coordinates = coordinatesQueue.poll(); if (coordinates == null) { continue; } for (int i = 0; i < coordinates.length; i++) { values.put(variables[i], coordinates[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()); } ExtendedBoolean solvable = PPolynomial.solvable( as.getPolynomials() .toArray(new PPolynomial[as.getPolynomials().size()]), substitutions, as.geoStatement.getKernel(), ProverSettings.get().transcext); Log.debug("Recio meets Botana (threaded): " + substitutions); if (solvable.boolVal()) { wrong = true; break; } } else { try { BigInteger[] exactCoordinates = s .getExactCoordinates(values); wrong = false; for (BigInteger result : exactCoordinates) { nrOfChecks++; if (!result.equals(BigInteger.ZERO)) { wrong = true; break; } } } catch (NoSymbolicParametersException e) { writeResult(TestPointResult.ERROR); continue; } } if (wrong) { writeResult(TestPointResult.FALSE); } else { writeResult(TestPointResult.PASSED); } } if (stop) { // the theorem could not be verified in one point if (getErrorOccured()) { return ProofResult.UNKNOWN; } return ProofResult.FALSE; } // all points are tested now interruptThreads(); for (int i = 0; i < pointTesters.length; i++) { Log.debug(pointTesters[i].nrOfTests + " tests done by thread " + i); } Log.debug(nrOfChecks + " tests done by main thread"); return ProofResult.TRUE; } private final static class PointTester implements Runnable { HashMap<PVariable, BigInteger> values; PVariable[] variables; ProverReciosMethodD prover; SymbolicParameters s; public int nrOfTests; public PointTester(final ProverReciosMethodD prover, final HashMap<PVariable, BigInteger> values, final PVariable[] variables, final SymbolicParameters s) { this.prover = prover; this.variables = variables; this.values = (HashMap<PVariable, BigInteger>) values.clone(); this.s = s; } @Override public void run() { BigInteger[] coordinates; boolean wrong; nrOfTests = 0; while (!Thread.interrupted()) { try { coordinates = prover.coordinatesQueue.take(); } catch (InterruptedException e) { return; } for (int i = 0; i < coordinates.length; i++) { this.values.put(variables[i], coordinates[i]); } try { BigInteger[] exactCoordinates = s .getExactCoordinates(values); nrOfTests++; wrong = false; for (BigInteger result : exactCoordinates) { if (!result.equals(BigInteger.ZERO)) { wrong = true; break; } } } catch (NoSymbolicParametersException e) { prover.writeResult(TestPointResult.ERROR); continue; } if (wrong) { prover.writeResult(TestPointResult.FALSE); } else { prover.writeResult(TestPointResult.PASSED); } } } } }