Files
BH2023-Minesweeper/info/Minesweeper/MineSweeperSolver/src/minesweeper/solver/BruteForce.java
2023-09-28 20:23:18 +08:00

465 lines
15 KiB
Java

package minesweeper.solver;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import Monitor.AsynchMonitor;
import minesweeper.solver.constructs.CandidateLocation;
import minesweeper.solver.constructs.Square;
import minesweeper.solver.constructs.Witness;
import minesweeper.solver.iterator.WitnessWebIterator;
import minesweeper.solver.utility.Logger.Level;
import minesweeper.structure.Location;
public class BruteForce {
//private final static BigDecimal ZERO_THRESHOLD = new BigDecimal("0.25");
private final WitnessWeb web;
private final Solver solver;
private final BoardState boardState;
private final int mines;
private final BigInteger maxIterations;
private final int bfMaxSolutions;
private CrunchResult crunchResult;
private boolean hasRun = false;
private boolean certainClear = false;
//private final List<SuperLocation> zeroLocations = new ArrayList<>();
private final List<CandidateLocation> results = new ArrayList<>();
private final String scope;
private BigInteger iterations;
private BruteForceAnalysisModel bruteForceAnalysis;
public BruteForce(Solver solver, BoardState boardState, WitnessWeb web, int mines, BigInteger maxIterations, int bfMaxSolutions, String scope) {
this.solver = solver;
this.boardState = boardState;
this.maxIterations = maxIterations;
this.bfMaxSolutions = bfMaxSolutions;
this.scope = scope;
this.web = web;
this.mines = mines;
}
public void process() {
solver.logger.log(Level.INFO, "Brute force on %d Squares with %d mines", web.getSquares().size(), mines);
// if we have no mines to place then everything must be a clear
if (mines == 0 ) {
solver.logger.log(Level.INFO, "brute force but already found all the mines - clear all the remaining squares");
for (Square squ: web.getSquares()) {
results.add(new CandidateLocation(squ.x, squ.y, BigDecimal.ONE, boardState.countAdjacentUnrevealed(squ), boardState.countAdjacentConfirmedFlags(squ)));
}
iterations = BigInteger.ONE;
hasRun = true;
return;
}
// now doing this logic 'just in time' rather than always at witness web generation
web.generateIndependentWitnesses();
// and crunch the result if we have something to check against
if (web.getPrunedWitnesses().size() >= 0) {
iterations = web.getIterations(mines);
if (iterations.compareTo(maxIterations) <= 0) {
//display("Brute Force about to process " + iterations + " iterations");
WitnessWebIterator[] iterators = buildParallelIterators(mines, iterations);
this.bruteForceAnalysis = new BruteForceAnalysis(solver, iterators[0].getLocations(), bfMaxSolutions, scope, solver.bfdaStartLocations());
crunchResult = crunchParallel(web.getSquares(), web.getPrunedWitnesses(), true, iterators);
// if there are too many to process then don't bother
if (this.bruteForceAnalysis != null && this.bruteForceAnalysis.tooMany()) {
this.bruteForceAnalysis = null;
}
int actIterations = 0;
for (WitnessWebIterator i: iterators) {
actIterations = actIterations + i.getIterations();
}
solver.logger.log(Level.DEBUG, "Expected iterations %d Actual iterations %d", iterations, actIterations);
solver.logger.log(Level.INFO, "Found %d candidate solutions in the %s", crunchResult.bigGoodCandidates, scope);
certainClear = findCertainClear(crunchResult);
if (certainClear) {
this.bruteForceAnalysis = null;
}
hasRun = true;
//TODO zero additional mines calculater - do we want it?
/*
if (crunchResult.bigGoodCandidates.signum() != 0) {
BigInteger hwm = BigInteger.ZERO;
int best = -1;
for (int i=0; i < crunchResult.getSquare().size(); i++) {
Location loc = crunchResult.getSquare().get(i);
int adjacentMines = boardState.countAdjacentConfirmedFlags(loc);
BigDecimal prob = new BigDecimal(crunchResult.bigDistribution[i][adjacentMines]).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP);
if (prob.compareTo(ZERO_THRESHOLD) >= 0) {
SuperLocation zl = new SuperLocation(loc.x, loc.y, boardState.countAdjacentUnrevealed(loc), adjacentMines);
zl.setProbability(prob);
zeroLocations.add(zl);
}
if (crunchResult.bigDistribution[i][adjacentMines].compareTo(hwm) > 0) {
hwm = crunchResult.bigDistribution[i][adjacentMines];
best = i;
}
}
if (best != -1) {
BigDecimal prob = new BigDecimal(hwm).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP);
boardState.display("Location " + crunchResult.getSquare().get(best).display() + " is a 'zero additional mines' with probability " + prob);
}
}
*/
} else {
if (maxIterations.signum() != 0) {
solver.logger.log(Level.INFO, "Brute Force too large with %d iterations", iterations);
}
}
} else {
solver.logger.log(Level.INFO, "Brute Force not performed since there are no witnesses");
}
}
// break a witness web search into a number of non-overlapping iterators
private WitnessWebIterator[] buildParallelIterators(int mines, BigInteger totalIterations) {
solver.logger.log(Level.DEBUG, "Building parallel iterators");
//WitnessWebIterator[] result1 = new WitnessWebIterator[1];
//result1[0] = new WitnessWebIterator(web, mines);
//return result1;
solver.logger.log(Level.DEBUG, "Non independent iterations %d", web.getNonIndependentIterations(mines));
// if there is only one cog then we can't lock it,so send back a single iterator
if (web.getIndependentWitnesses().size() == 1 && web.getIndependentMines() >= mines || totalIterations.compareTo(Solver.PARALLEL_MINIMUM) < 0
|| web.getPrunedWitnesses().size() == 0 || solver.preferences.isSingleThread()) {
solver.logger.log(Level.DEBUG, "Only a single iterator will be used");
WitnessWebIterator[] result = new WitnessWebIterator[1];
result[0] = new WitnessWebIterator(web, mines);
return result;
}
int witMines = web.getIndependentWitnesses().get(0).getMines();
int squares = web.getIndependentWitnesses().get(0).getSquares().size();
BigInteger bigIterations = Solver.combination(witMines, squares);
int iter = bigIterations.intValue();
solver.logger.log(Level.DEBUG, "The first cog has %d iterations, so parallel processing is possible", iter);
WitnessWebIterator[] result = new WitnessWebIterator[iter];
for (int i=0; i < iter; i++) {
result[i] = new WitnessWebIterator(web, mines, i); // create a iterator with a lock first got at position i
}
return result;
}
// process the iterators in parallel
private CrunchResult crunchParallel(List<Square> square, List<Witness> witness, boolean calculateDistribution, WitnessWebIterator... iterator) {
solver.logger.log(Level.DEBUG, "At parallel iterator processing");
Cruncher[] crunchers = new Cruncher[iterator.length];
for (int i=0; i < iterator.length; i++) {
crunchers[i] = new Cruncher(boardState, iterator[i].getLocations(), witness, iterator[i], false, bruteForceAnalysis);
}
AsynchMonitor monitor = new AsynchMonitor(crunchers);
monitor.setMaxThreads(Solver.CORES);
try {
monitor.startAndWait();
} catch (Exception ex) {
solver.logger.log(Level.ERROR, "Parallel processing caused an error: %s", ex.getMessage());
ex.printStackTrace();
}
CrunchResult[] results = new CrunchResult[crunchers.length];
for (int i=0; i < crunchers.length; i++) {
results[i] = crunchers[i].getResult();
}
CrunchResult result = CrunchResult.bigMerge(results);
return result;
}
private boolean findCertainClear(CrunchResult output) {
// if there were no good candidates then there is nothing to check
if (output.bigGoodCandidates.signum() == 0) {
return false;
}
// check the tally information to see if we have a square where a
// mine is never present
for (int i=0; i < output.bigTally.length; i++) {
if (output.bigTally[i].signum() == 0) {
return true;
}
}
return false;
}
/*
// do the tally check using the BigInteger values
private List<CandidateLocation> checkBigTally(CrunchResult output) {
List<CandidateLocation> result = new ArrayList<>();
// if there were no good candidates then there is nothing to check
if (output.bigGoodCandidates.compareTo(BigInteger.ZERO) == 0) {
return result;
}
// check the tally information to see if we have a square where a
// mine is always present or never present
for (int i=0; i < output.bigTally.length; i++) {
// if there is always a mine here then odds of clear is zero
if (output.bigTally[i].compareTo(output.bigGoodCandidates) == 0) {
int x = output.getSquare().get(i).x;
int y = output.getSquare().get(i).y;
results.add(new CandidateLocation(x, y, BigDecimal.ZERO, boardState.countAdjacentUnrevealed(x, y), boardState.countAdjacentConfirmedFlags(x, y)));
// if never a mine then odds of clear is one
} else if (output.bigTally[i].compareTo(BigInteger.ZERO) == 0) {
int x = output.getSquare().get(i).x;
int y = output.getSquare().get(i).y;
results.add(new CandidateLocation(x, y, BigDecimal.ONE, boardState.countAdjacentUnrevealed(x, y), boardState.countAdjacentConfirmedFlags(x, y)));
certainClear = true;
}
}
return result;
}
*/
/*
public List<CandidateLocation> getBestSolutions(BigDecimal freshhold) {
if (crunchResult == null) {
return results;
}
if (!results.isEmpty()) {
return results;
}
List<CandidateLocation> candidates = new ArrayList<>();
boolean ignoreBad = true;
if (crunchResult.getMaxCount() <= 1) {
ignoreBad = false;
solver.display("No candidates provide additional information");
}
// Calculate the probability of a mine being in the square and store in a list
for (int i=0; i < crunchResult.bigTally.length; i++) {
BigDecimal mine = new BigDecimal(crunchResult.bigTally[i]).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP);
BigDecimal notMine = BigDecimal.ONE.subtract(mine);
Location l = crunchResult.getSquare().get(i);
// ignore candidates that yield no info, unless none do or they are certainties
if (crunchResult.getBigCount()[i] > 1 || !ignoreBad || notMine.compareTo(BigDecimal.ZERO) == 0 || notMine.compareTo(BigDecimal.ONE) == 0) {
candidates.add(new CandidateLocation(l.x, l.y, notMine, boardState.countAdjacentUnrevealed(l), boardState.countAdjacentConfirmedFlags(l), crunchResult.getBigCount()[i]));
} else {
solver.display(l.display() + " clear probability " + notMine + " discarded because it reveals no further information");
}
//candidates.add(new CandidateLocation(l.x, l.y, notMine, boardState.countAdjacentUnrevealed(l), boardState.countAdjacentConfirmedFlags(l)));
}
// sort the candidates into descending order by probability
Collections.sort(candidates, CandidateLocation.SORT_BY_PROB_FLAG_FREE);
BigDecimal hwm = candidates.get(0).getProbability();
BigDecimal tolerence;
if (hwm.compareTo(BigDecimal.ONE) == 0) {
tolerence = hwm;
} else {
tolerence = hwm.multiply(freshhold);
}
for (CandidateLocation cl: candidates) {
if (cl.getProbability().compareTo(tolerence) >= 0) {
results.add(cl);
} else {
break;
}
}
boardState.display("Best Guess: " + candidates.size() + " candidates, " + results.size() + " passed tolerence at " + tolerence);
return results;
}
*/
public boolean hasRun() {
return this.hasRun;
}
public boolean hasCertainClear() {
return this.certainClear;
}
public CrunchResult getCrunchResult() {
return this.crunchResult;
}
public BigInteger getSolutionCount() {
if (crunchResult == null) {
return BigInteger.ONE;
}
return crunchResult.bigGoodCandidates;
}
public BigInteger getIterations() {
return this.iterations;
}
public int getTileCount() {
return web.getSquares().size();
}
public BruteForceAnalysisModel getBruteForceAnalysis() {
return bruteForceAnalysis;
}
/**
* Set the probability for the probabilityLocation being satisfied
* @param list
*/
/*
public <T extends ProbabilityLocation> List<T> setProbabilities(List<T> list) {
if (!hasRun) {
return list;
}
List<T> output = new ArrayList<>();
for (ProbabilityLocation pl: list) {
for (int i=0; i < crunchResult.getSquare().size(); i++) {
if (crunchResult.getSquare().get(i).equals(pl)) {
// get the values which are good for this location
int[] adjFlagsRequired = pl.getAdjacentFlagsRequired();
// count the number of solutions which have those values
BigInteger count = BigInteger.ZERO;
for (int j = 0; j < adjFlagsRequired.length; j++) {
count = count.add(crunchResult.bigDistribution[i][adjFlagsRequired[j]]);
}
// work out the % chance of it happening or if zero chance discard the location
if (count.signum() != 0) {
BigDecimal prob = new BigDecimal(count).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP);
pl.setProbability(prob);
boardState.display(pl.display() + " has probability " + prob);
output.add((T) pl);
} else {
boardState.display(pl.display() + " has probability zero and is being discarded");
}
break;
}
}
}
return output;
}
*/
/**
* Returns the probability that this square is not a mine
*/
public BigDecimal getProbability(int x, int y) {
Location l = this.boardState.getLocation(x,y);
if (crunchResult == null) { // this can happen if there are no mines left to find, so everything is a clear
for (Location loc: web.getSquares()) { // if the mouse is hovering over one of the brute forced squares
if (loc.equals(l)) {
return BigDecimal.ONE;
}
}
return BigDecimal.ZERO;
}
for (int i=0; i < crunchResult.getSquare().size(); i++) {
if (crunchResult.getSquare().get(i).equals(l)) {
BigDecimal prob = new BigDecimal(crunchResult.bigTally[i]).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP);
return BigDecimal.ONE.subtract(prob);
}
}
return BigDecimal.ZERO;
}
}