diff --git a/json/players/gameSpecific/TicTacToe.json b/json/players/gameSpecific/TicTacToe.json index a62ed7813..f534f498e 100644 --- a/json/players/gameSpecific/TicTacToe.json +++ b/json/players/gameSpecific/TicTacToe.json @@ -2,11 +2,11 @@ "budgetType": "BUDGET_TIME", "rolloutLength": 30, "opponentTreePolicy": "OneTree", - "MASTGamma": 0, + "MASTGamma": 0.0, "heuristic": { "class": "players.heuristics.WinOnlyHeuristic" }, - "K": 1, + "K": 1.0, "exploreEpsilon": 0.1, "treePolicy": "UCB", "MAST": "Both", diff --git a/src/main/java/core/Game.java b/src/main/java/core/Game.java index 9a73226fe..cff66ba8a 100644 --- a/src/main/java/core/Game.java +++ b/src/main/java/core/Game.java @@ -571,7 +571,6 @@ public AbstractGameState runInstance(LinkedList players, int see } if (debug) System.out.println("Exiting synchronized block in Game"); } - System.out.println("Done playing: " + players); if (firstEnd) { if (gameState.coreGameParameters.verbose) { System.out.println("Ended"); diff --git a/src/main/java/evaluation/RunArg.java b/src/main/java/evaluation/RunArg.java index 63fbc1648..8461566aa 100644 --- a/src/main/java/evaluation/RunArg.java +++ b/src/main/java/evaluation/RunArg.java @@ -123,7 +123,7 @@ public enum RunArg { new Usage[]{Usage.ParameterSearch, Usage.RunGames}), nThreads("The number of threads that can be spawned in order to evaluate games.", 1, - new Usage[]{Usage.RunGames}), + new Usage[]{Usage.ParameterSearch, Usage.RunGames}), neighbourhood("The size of neighbourhood to look at in NTBEA. Default is min(50, |searchSpace|/100) ", 50, new Usage[]{Usage.ParameterSearch}), diff --git a/src/main/java/evaluation/optimisation/MultiNTBEA.java b/src/main/java/evaluation/optimisation/MultiNTBEA.java index d15a8d9ee..8620d2c33 100644 --- a/src/main/java/evaluation/optimisation/MultiNTBEA.java +++ b/src/main/java/evaluation/optimisation/MultiNTBEA.java @@ -132,4 +132,8 @@ private static int manhattan(int[] x, int[] y) { return retValue; } + @Override + public NTBEA copy() { + return new MultiNTBEA(params, game, nPlayers); + } } diff --git a/src/main/java/evaluation/optimisation/NTBEA.java b/src/main/java/evaluation/optimisation/NTBEA.java index 951c29c11..56e386d0c 100644 --- a/src/main/java/evaluation/optimisation/NTBEA.java +++ b/src/main/java/evaluation/optimisation/NTBEA.java @@ -1,20 +1,16 @@ package evaluation.optimisation; import core.AbstractGameState; -import core.AbstractParameters; import core.AbstractPlayer; import core.interfaces.IGameHeuristic; import core.interfaces.IStateHeuristic; import evaluation.RunArg; import evaluation.listeners.IGameListener; -import evaluation.tournaments.AbstractTournament; import evaluation.tournaments.RoundRobinTournament; -import org.apache.commons.math3.util.CombinatoricsUtils; import games.GameType; import ntbea.NTupleBanditEA; import ntbea.NTupleSystem; import org.json.simple.JSONObject; -import players.IAnyTimePlayer; import players.PlayerFactory; import players.heuristics.OrdinalPosition; import players.heuristics.PureScoreHeuristic; @@ -28,6 +24,9 @@ import java.io.FileWriter; import java.io.IOException; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.function.IntToDoubleFunction; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -136,15 +135,53 @@ public void writeAgentJSON(int[] settings, String fileName) { * @return */ public Pair run() { + ExecutorService executor = params.nThreads > 1 ? Executors.newFixedThreadPool(params.nThreads) : null; + // if we're multithreading, we don't want to have different threads interfering with eachother + List clones = new ArrayList<>(); + if (executor == null) { + // no multithreading, so list of clones consists only of the current object + clones.add(this); + } + // Only this loop is parallelized, since the rest is just analysis, or initializing a tournament (which is + // on its own already parallelized) for (currentIteration = 0; currentIteration < params.repeats; currentIteration++) { - runIteration(); - writeAgentJSON(winnerSettings.get(winnerSettings.size() - 1), - params.destDir + File.separator + "Recommended_" + currentIteration + ".json"); + NTBEA clone = executor == null ? this : this.copy(); + if (executor != null) { + clone.currentIteration = currentIteration; // for correct reporting + // run in parallel if allowed + executor.submit(clone::runIteration); + clones.add(clone); + } else { + clone.runIteration(); + } + } + + if (executor != null) { + executor.shutdown(); + try { + // Wait for all tasks to complete; no timeout (infty hours) because this normally also has no timeout + if (!executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS)) { + executor.shutdownNow(); // Force shutdown if tasks are hanging + } + } catch (InterruptedException e) { + executor.shutdownNow(); // Restore interrupted status and shutdown + Thread.currentThread().interrupt(); + } + } + + // After all runs are complete, do some cleaning up and logging + for (NTBEA clone : clones) { + writeAgentJSON(clone.winnerSettings.get(clone.winnerSettings.size() - 1), + params.destDir + File.separator + "Recommended_" + clone.currentIteration + ".json"); + } + if (clones.size() > 1 || clones.get(0) != this) { + // aggregate all data from all clones + collectCloneResults(clones); } - // After all runs are complete, if tournamentGames are specified, then we allow all the - // winners from each iteration to play in a tournament and pick the winner of this tournament + // If tournamentGames are specified, then we allow all the winners from each iteration + // to play in a tournament and pick the winner of this tournament if (params.tournamentGames > 0 && winnersPerRun.get(0) instanceof AbstractPlayer) { activateTournament(); } @@ -158,6 +195,22 @@ public Pair run() { return new Pair<>(params.searchSpace.getAgent(bestResult.b), bestResult.b); } + /** + * Gathers all data from all parallel threads, which have their data stored in a clone of this object + * This ensures all metrics from these clones are incorporated into this object's data + * @param clones the list of clones that have run in separate threads + */ + protected void collectCloneResults(List clones) { + for (NTBEA clone : clones) { + this.elites.addAll(clone.elites); + this.winnersPerRun.addAll(clone.winnersPerRun); + this.winnerSettings.addAll(clone.winnerSettings); + if (clone.bestResult.a.a > this.bestResult.a.a) { + bestResult = clone.bestResult; + } + } + } + protected void activateTournament() { if (!elites.isEmpty()) { // first of all we add the elites into winnerSettings, and winnersPerRun @@ -195,6 +248,7 @@ protected void activateTournament() { config.put(RunArg.budget, params.budget); config.put(RunArg.verbose, false); config.put(RunArg.destDir, params.destDir); + config.put(RunArg.nThreads, params.nThreads); RoundRobinTournament tournament = new RoundRobinTournament(players, game, nPlayers, params.gameParams, config); createListeners().forEach(tournament::addListener); tournament.run(); @@ -384,6 +438,10 @@ private static void logSummary(Pair, int[]> data, NTBEAPara } } + public NTBEA copy() { + return new NTBEA(params, game, nPlayers); + } + private static String valueToString(int paramIndex, int valueIndex, ITPSearchSpace ss) { Object value = ss.value(paramIndex, valueIndex); String valueString = value.toString(); @@ -394,5 +452,4 @@ private static String valueToString(int paramIndex, int valueIndex, ITPSearchSpa } return valueString; } - } diff --git a/src/main/java/evaluation/optimisation/NTBEAParameters.java b/src/main/java/evaluation/optimisation/NTBEAParameters.java index 5bfec6c3d..45d219d90 100644 --- a/src/main/java/evaluation/optimisation/NTBEAParameters.java +++ b/src/main/java/evaluation/optimisation/NTBEAParameters.java @@ -43,6 +43,7 @@ public enum Mode { public ITPSearchSpace searchSpace; public AbstractParameters gameParams; public boolean byTeam; + public int nThreads; public NTBEAParameters(Map args) { this(args, Function.identity()); @@ -67,6 +68,7 @@ public NTBEAParameters(Map args, Function prepro GameType game = GameType.valueOf(args.get(RunArg.game).toString()); gameParams = args.get(RunArg.gameParams).equals("") ? null : AbstractParameters.createFromFile(game, (String) args.get(RunArg.gameParams)); + nThreads = (int) args.get(RunArg.nThreads); mode = Mode.valueOf((String) args.get(RunArg.NTBEAMode)); logFile = "NTBEA.log"; diff --git a/src/main/java/evaluation/tournaments/RoundRobinTournament.java b/src/main/java/evaluation/tournaments/RoundRobinTournament.java index e0a98466f..1d86889a9 100644 --- a/src/main/java/evaluation/tournaments/RoundRobinTournament.java +++ b/src/main/java/evaluation/tournaments/RoundRobinTournament.java @@ -232,7 +232,6 @@ public AbstractPlayer getWinner() { */ public void createAndRunMatchUp(List matchUp) { ExecutorService executor = nThreads > 1 ? Executors.newFixedThreadPool(nThreads) : null; - int nTeams = byTeam ? game.getGameState().getNTeams() : nPlayers; switch (tournamentMode) { case RANDOM: @@ -304,6 +303,7 @@ public void createAndRunMatchUp(List matchUp) { break; case EXHAUSTIVE: case EXHAUSTIVE_SELF_PLAY: + // TODO: Make iterative instead of recursive, to parallelize // in this case we are in exhaustive mode, so we recursively construct all possible combinations of players if (matchUp.size() == nTeams) { evaluateMatchUp(matchUp, gamesPerMatchup, gameSeeds);