first concurrent solution

This commit is contained in:
Luca Conte 2025-06-09 17:01:56 +02:00
parent 502458d373
commit 7c146ce8ad
1 changed files with 131 additions and 46 deletions

View File

@ -26,10 +26,10 @@ final public class Solver extends JPanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
// The default size of the labyrinth (i.e. unless program is invoked with size arguments): // The default size of the labyrinth (i.e. unless program is invoked with size arguments):
private static final int DEFAULT_WIDTH_IN_CELLS = 100; private static final int DEFAULT_WIDTH_IN_CELLS = 1000;
private static final int DEFAULT_HEIGHT_IN_CELLS = 100; private static final int DEFAULT_HEIGHT_IN_CELLS = 1000;
private static final int N_RUNS_HALF = 5; // #runs will be 2*N_RUNS_HALF + 1 private static final int N_RUNS_HALF = 10; // #runs will be 2*N_RUNS_HALF + 1
// The grid defining the structure of the labyrinth // The grid defining the structure of the labyrinth
private final Labyrinth labyrinth; private final Labyrinth labyrinth;
@ -38,7 +38,8 @@ final public class Solver extends JPanel {
private boolean[][] visited; // initialized in solve() private boolean[][] visited; // initialized in solve()
private AtomicBoolean[][] visitedAtomic; private AtomicBoolean[][] visitedAtomic;
private static final int DISTANCE_PER_TASK = 20; // determined by trial and error
private static final int DISTANCE_PER_TASK = DEFAULT_WIDTH_IN_CELLS * DEFAULT_HEIGHT_IN_CELLS / 128;
private Point[] solution = null; // set to solution path once that has been computed private Point[] solution = null; // set to solution path once that has been computed
@ -124,16 +125,19 @@ final public class Solver extends JPanel {
public Point[] solveConcurrently() { public Point[] solveConcurrently() {
// dummy origin direction for start // dummy origin direction for start
PointAndDirection start = new PointAndDirection(labyrinth.getStart(), Direction.N); PointAndDirection start = new PointAndDirection(labyrinth.getStart(), null);
visitedAtomic = new AtomicBoolean[labyrinth.getWidth()][labyrinth.getHeight()]; visitedAtomic = new AtomicBoolean[labyrinth.getWidth()][labyrinth.getHeight()];
for (int i = 0; i < visitedAtomic.length; i++) {
ArrayDeque<PointAndDirection> backtrackStack = new ArrayDeque<PointAndDirection>(); for (int j = 0; j < visitedAtomic[i].length; j++) {
visitedAtomic[i][j] = new AtomicBoolean(false);
}
}
ForkJoinPool pool = ForkJoinPool.commonPool(); ForkJoinPool pool = ForkJoinPool.commonPool();
ArrayDeque<Point> pathSoFar = new ArrayDeque<>();
ConcurrentSolverTask t = new ConcurrentSolverTask(backtrackStack, start); ConcurrentSolverTask t = new ConcurrentSolverTask(start, pathSoFar, pool);
Point[] result = pool.invoke(t); Point[] result = pool.invoke(t);
return result; return result;
@ -142,55 +146,136 @@ final public class Solver extends JPanel {
private class ConcurrentSolverTask extends RecursiveTask<Point[]> { private class ConcurrentSolverTask extends RecursiveTask<Point[]> {
private ArrayDeque<PointAndDirection> backtrackStack; private ArrayDeque<PointAndDirection> backtrackStack;
private PointAndDirection start; private PointAndDirection start;
private int initialStackSize;
private HashSet<Point> visitedThisTask; private HashSet<Point> visitedThisTask;
public ConcurrentSolverTask(ArrayDeque<PointAndDirection> backtrackStack, PointAndDirection start) { private ArrayDeque<Point> pathSoFar;
this.backtrackStack = backtrackStack; private ForkJoinPool pool;
private ArrayList<ConcurrentSolverTask> subtasks;
public ConcurrentSolverTask(PointAndDirection start, ArrayDeque<Point> pathSoFar, ForkJoinPool pool) {
this.start = start; this.start = start;
this.initialStackSize = backtrackStack.size(); this.pathSoFar = pathSoFar;
this.visitedThisTask = new HashSet<>(); this.pool = pool;
this.backtrackStack = new ArrayDeque<>();
this.subtasks = new ArrayList<>();
} }
public Point[] compute() { public Point[] compute() {
PointAndDirection current = this.start; int currentLength = 0;
Point current = this.start.getPoint();
if (labyrinth.isDestination(current.getPoint())) { Point next;
return backtrackStackToPath(backtrackStack); Direction[] dirs = Direction.values();
while (!labyrinth.isDestination(current) && !Thread.interrupted()) {
// System.out.println("Visiting " + current);
next = null;
for (Direction dir : dirs) {
// don't check direction of origin
if (
current.equals(this.start.getPoint())
&& dir.equals(start.getDirectionToBranchingPoint())
) {
continue;
} }
if (!current.getDirectionToBranchingPoint().equals(Direction.N)) { // check if labyrinth has passage in this direction
if () { if (!labyrinth.hasPassage(current, dir)) {
continue;
}
Point neighbour = current.getNeighbor(dir);
// avoid blind alleys
if (
labyrinth.isBlindAlley(neighbour, dir.opposite)
&& !labyrinth.isDestination(neighbour)
) {
continue;
}
// check if that cell has been visited
// set visitedAtomic to true if it has not been visited
if (!visitedAtomic[neighbour.x][neighbour.y].compareAndSet(false, true)) {
continue;
}
// System.out.println("Found unvisited neighbour: " + neighbour);
if (currentLength >= DISTANCE_PER_TASK) {
// if we have reached the distance limit, create a subtask
ArrayDeque<Point> subPath = this.pathSoFar.clone();
subPath.addLast(current);
ConcurrentSolverTask subtask = new ConcurrentSolverTask(
new PointAndDirection(neighbour, dir.opposite),
subPath,
this.pool
);
subtask.fork();
subtasks.add(subtask);
} else {
if (next == null) {
// visit first unvisited neighbour next
next = neighbour;
// System.out.println("Visiting next.");
} else {
// push all other unvisited neighbours on backtrack stack to be checked later
this.backtrackStack.push(new PointAndDirection(neighbour, dir.opposite));
// System.out.println("Pushing to stack.");
}
} }
} }
// at least one unvisited neighbour found
if (next != null) {
pathSoFar.addLast(current);
current = next;
currentLength++;
}
if (next == null) {
// no unvisited neighbour found
// backtrack if possible
if (this.backtrackStack.isEmpty()) {
// no solution found from this point
// check for solutions in subtasks
// System.out.println("End of task reached - waiting for " + subtasks.size() + " subtasks");
for (ConcurrentSolverTask subtask : subtasks) {
Point[] subtaskResult = subtask.join();
if (subtaskResult != null) {
// found a solution in a subtask
return subtaskResult;
}
}
// no solution found
return null; return null;
} }
private boolean visit(PointAndDirection p) { PointAndDirection pd = this.backtrackStack.pop();
if (visitedThisTask.contains(p.getPoint())) { current = pd.getPoint();
return false;
}
Point lastPoint = backtrackStack.getLast().getPoint();
if (Math.abs(lastPoint.y - p.getPoint().y) + Math.abs(lastPoint.x - p.getPoint().x) > 1) {
System.err.println("Cannot visit point " + p.getPoint() + " because it is not adjacened to " + lastPoint);
return false;
}
if (!visitedAtomic[p.getPoint().x][p.getPoint().y].compareAndSet(false, true)) return false;
backtrackStack.add(p); // Remove the dead end from the top of pathSoFar, i.e. all cells after branchingPoint:
visitedThisTask.add(p.getPoint()); Point branchingPoint = current.getNeighbor(pd.getDirectionToBranchingPoint());
while (!pathSoFar.isEmpty() && !pathSoFar.peekLast().equals(branchingPoint)) {
return true; pathSoFar.removeLast();
} currentLength--;
} }
private Point[] backtrackStackToPath(ArrayDeque<PointAndDirection> backtrackStack) {
Point[] result = new Point[backtrackStack.size()];
for (int i = 0; i < result.length; i++) {
result[backtrackStack.size() - 1] = backtrackStack.remove().getPoint();
} }
return result; }
// parent thread has found solution or other interrupt
if (Thread.interrupted()) {
return null;
}
pathSoFar.addLast(current);
// solution found, interrupt subtasks
for (ConcurrentSolverTask subtask : subtasks) {
subtask.cancel(true);
}
return pathSoFar.toArray(new Point[0]);
}
} }
@Override @Override
@ -321,7 +406,7 @@ private static void displayLabyrinth(Solver solver) {
} }
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
solver.solution = solver.solve(); solver.solution = solver.solveConcurrently();
long endTime = System.currentTimeMillis(); long endTime = System.currentTimeMillis();
if (solver.solution == null) if (solver.solution == null)