/* This file is part of Eternity II Editor.
*
* Eternity II Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Eternity II Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Eternity II Editor. If not, see <http://www.gnu.org/licenses/>.
*
* Eternity II Editor project is hosted on SourceForge:
* http://sourceforge.net/projects/eternityii/
* and maintained by Yannick Kirschhoffer <alcibiade@alcibiade.org>
*/
package org.alcibiade.eternity.editor.solver;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.alcibiade.eternity.editor.log.SolverLog;
import org.alcibiade.eternity.editor.model.GridModel;
import org.alcibiade.eternity.editor.stats.ScoreStats;
public class ClusterManager implements Serializable {
private static final long serialVersionUID = 1L;
private static final String PARAM_BLOCK_SIZE = "stats.block";
private ThreadLocal<Long> timeStart;
private SolverLog log;
private GridModel bestSolution;
private int bestScore;
private String resultFileName;
private boolean solutionFound;
private ScoreStats stats;
private Set<ClusterListener> clusterListeners;
public ClusterManager(SolverLog log) {
this.log = log;
this.timeStart = new ThreadLocal<Long>();
this.bestSolution = new GridModel();
this.bestScore = 0;
this.solutionFound = false;
String blockString = System.getProperties().getProperty(PARAM_BLOCK_SIZE);
if (blockString == null) {
stats = null;
} else {
stats = new ScoreStats(log, Integer.parseInt(blockString));
}
clusterListeners = new HashSet<ClusterListener>();
}
public ClusterManager(SolverLog log, String resultFileName) {
this(log);
this.resultFileName = resultFileName;
}
public int getBestScore() {
return bestScore;
}
public GridModel getBestSolution() {
return bestSolution;
}
public synchronized void showStartMessage() {
logMessage("Solver starting");
timeStart.set(System.currentTimeMillis());
}
public SolverLog getSolverLog() {
return log;
}
public synchronized void showStats(long iterations) {
long duration = getElapsedTime();
if (duration > 150) {
logMessage("Duration: %s", durationToString(duration));
logMessage("Iterations: %d = %dit/s", iterations, iterations * 1000 / duration);
}
}
public long getElapsedTime() {
long elapsed = 0;
Long timeStart = this.timeStart.get();
if (timeStart != null) {
long time_now = System.currentTimeMillis();
elapsed = time_now - timeStart;
}
return elapsed;
}
private static long[] splitUnits(long value, long... bases) {
long[] units = new long[bases.length + 1];
long remaining = value;
for (int bIndex = bases.length - 1; bIndex >= 0; bIndex--) {
long base = bases[bIndex];
units[bIndex + 1] = remaining % base;
remaining /= base;
}
units[0] = remaining;
return units;
}
private static String durationToString(long millis) {
long[] timeUnits = splitUnits(millis, 24, 60, 60, 1000);
return String.format("%d days, %d hours, %d minutes, %d seconds", timeUnits[0],
timeUnits[1], timeUnits[2], timeUnits[3]);
}
public boolean isSolutionFound() {
return solutionFound;
}
public boolean submitSolution(GridModel grid) {
int gridScore = grid.countPairs();
int targetScore = grid.countConnections();
synchronized (this) {
// Computes stats periodically
if (stats != null) {
stats.recordScore(gridScore);
}
// Register solution if better than the current best
if (bestScore < gridScore) {
grid.copyTo(bestSolution);
bestScore = gridScore;
logMessage(String.format("Best score: %3d/%d", bestScore, targetScore));
writeResultToFile(grid, targetScore);
notifyListeners(bestScore);
}
}
if (!solutionFound && gridScore == targetScore) {
solutionFound = true;
logMessage("Solution found !");
}
return solutionFound;
}
private void writeResultToFile(GridModel grid, int targetScore) {
if (resultFileName != null) {
try {
SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
PrintWriter writer = new PrintWriter(new FileWriter(resultFileName));
writer.println("#");
writer.println(String.format("# Grid format: %dx%dx%d", grid.getSize(), grid
.getSize(), grid.getPatternStats().getPatterns().size()));
writer.println(String.format("# Grid score: %d/%d", bestScore, targetScore));
writer.println("# Time: " + df.format(new Date()));
writer.println("# Elapsed: " + durationToString(getElapsedTime()));
writer.println("# Host: " + InetAddress.getLocalHost().getHostName());
writer.println("# Thread: " + Thread.currentThread().getName());
writer.println("#");
writer.println();
writer.println(bestSolution.toQuadString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void logMessage(String message, Object... args) {
log.logMessage(String.format(message, args));
}
protected void notifyListeners(int bestScore) {
for (ClusterListener listener : clusterListeners) {
listener.bestSolutionUpdated(bestScore);
}
}
public void addClusterListener(ClusterListener listener) {
clusterListeners.add(listener);
}
public void removeClusterListener(ClusterListener listener) {
clusterListeners.remove(listener);
}
}