package bsearch.fx;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.FutureTask;
import bsearch.algorithms.SearchParameterException;
import bsearch.app.BehaviorSearch;
import bsearch.app.SearchProtocol;
import bsearch.evaluation.ResultListener;
import bsearch.evaluation.SearchManager;
import bsearch.nlogolink.ModelRunResult;
import bsearch.nlogolink.ModelRunner.ModelRunnerException;
import bsearch.representations.Chromosome;
import bsearch.space.SearchSpace;
import bsearch.util.GeneralUtils;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class ProgressController {
@FXML
LineChart<Number, Number> progressLineChart;
@FXML
ProgressBar progressBar;
@FXML
Button cancelAndDoneButton;
@FXML
Label labelTimeRemaining;
@FXML
Label labelMessage;
@FXML
WebView infoTextWebView;
private FutureTask<Void> task;
private long taskStartTime;
private boolean done = false;
public void initialize() {
progressLineChart.setTitle("Search Progress");
progressLineChart.getXAxis().setLabel("# of model runs");
progressLineChart.getYAxis().setLabel("Fitness");
progressLineChart.setCreateSymbols(false);
progressLineChart.getYAxis().setAutoRanging(true);
progressLineChart.setAnimated(false);
progressLineChart.getXAxis().setAnimated(true);
}
public void startSearchTask(SearchProtocol protocol, BehaviorSearch.RunOptions runOptions) {
labelMessage.setText("Search 0 of " + runOptions.numSearches);
taskStartTime = System.currentTimeMillis();
TaskWorker insideTask = new TaskWorker(protocol, runOptions);
task = new FutureTask<Void>(insideTask, null);
new Thread(new Runnable() {
@Override
public void run() {
try {
task.run();
task.get();
// check fatal exception field and re-throw it, and handle
// that below
if (insideTask.fatalException != null)
{
throw insideTask.fatalException;
}
} catch (CancellationException e){
Platform.runLater(new Runnable() {
public void run() {
Alert alert = new Alert(AlertType.WARNING);
alert.setTitle("Cancelled");
alert.setContentText("You canceled the search. \nPartial results may have been saved to output files.");
alert.showAndWait();
}
});
}
catch (ModelRunnerException e) {
e.printStackTrace();
MainController.handleError("Error running the model:\n" +e.getMessage(), e);
} catch (SearchParameterException e) {
e.printStackTrace();
MainController.handleError("Error setting search method parameters:\n" +e.getMessage(), e);
}
catch (IOException e) {
MainController.handleError("Error reading or writing files:\n" +e.getMessage(), e);
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
MainController.handleError(e.getMessage() , e);
} catch (Throwable e) {
e.printStackTrace();
MainController.handleError("Serious Error: " + e.getMessage(), e);
}
}
}).start();
}
public void doCancel(ActionEvent event) {
if (done) {
Node source = (Node) event.getSource();
Stage thisStage = (Stage) source.getScene().getWindow();
thisStage.close();
} else {
task.cancel(true);
cancelAndDoneButton.setText("Done");
done = true;
}
}
class TaskWorker implements Runnable, ResultListener {
private SearchProtocol protocol;
private BehaviorSearch.RunOptions runOptions;
protected Throwable fatalException = null;
private double evaluationLimit;
public TaskWorker(SearchProtocol protocol, BehaviorSearch.RunOptions runOptions) {
this.protocol = protocol;
this.runOptions = runOptions;
this.evaluationLimit = (double) protocol.evaluationLimit;
}
@Override
public void initListener(SearchSpace space) {
}
@Override
public void modelRunOccurred(SearchManager manager, Chromosome point, ModelRunResult result) {
long currentTime = System.currentTimeMillis();
long elapsed = currentTime - taskStartTime;
String elapsedStr = GeneralUtils.formatTimeNicely(elapsed);
int searchesCompleted = (manager.getSearchIDNumber() - runOptions.firstSearchNumber);
int totalSearches = runOptions.numSearches;
double runsCompleted = manager.getEvaluationCount() + manager.getAuxilliaryEvaluationCount();
double totalRuns = protocol.evaluationLimit + manager.getAuxilliaryEvaluationCount();
double searchProgress = (searchesCompleted + runsCompleted / totalRuns) / totalSearches;
long remaining = (long) (elapsed / searchProgress - elapsed); // in
// milliseconds
String remainingStr = GeneralUtils.formatTimeNicely(remaining);
Platform.runLater(new Runnable() {
public void run() {
labelTimeRemaining.setText(" (" + elapsedStr + " elapsed - " + remainingStr + " remaining)");
}
});
}
@Override
public void fitnessComputed(SearchManager manager, Chromosome point, double fitness) {
final int searchNumber = manager.getSearchIDNumber();
final double bestFitnessSoFar = protocol.useBestChecking() ? manager.getCurrentBestFitnessCheckedEstimate()
: manager.getCurrentBestFitness();
final int evaluationCount = manager.getEvaluationCount();
Platform.runLater(new Runnable() {
@SuppressWarnings("unchecked")
public void run() {
progressBar.setProgress(evaluationCount / evaluationLimit);
if (searchNumber - runOptions.firstSearchNumber + 1 > progressLineChart.getData().size()) {
XYChart.Series series = new XYChart.Series();
series.setName("Search " + searchNumber);
progressLineChart.getData().addAll(series);
}
XYChart.Series series = progressLineChart.getData()
.get(searchNumber - runOptions.firstSearchNumber);
int itemCount = series.getData().size();
if (itemCount == 0) {
series.getData().add(new Data<Number, Number>(evaluationCount, bestFitnessSoFar));
} else {
Data<Number, Number> lastPoint = (Data<Number, Number>) series.getData().get(itemCount - 1);
if (evaluationCount > lastPoint.getXValue().intValue()
|| (bestFitnessSoFar != lastPoint.getYValue().doubleValue())) {
// This is a preventive measure to not bog down
// real-time graphing in FXChart:
// if the fitness is on a plateau, we don't need a
// million XY data points to show a flat line
// we can use just 2 points (one at the beginning of
// the plateau, and one at the end)
if (itemCount > 1 && lastPoint.getYValue().doubleValue() == bestFitnessSoFar
&& ((Data<Number, Number>) series.getData().get(itemCount - 2)).getYValue()
.doubleValue() == bestFitnessSoFar) {
series.getData().remove(itemCount - 1);
}
series.getData().add(new Data<Number, Number>(evaluationCount, bestFitnessSoFar));
}
}
}
});
}
@Override
public void newBestFound(SearchManager manager) {
updateInfoText("In Search #" + manager.getSearchIDNumber() + ":",
manager.getCurrentBest(), manager.getCurrentBestFitness(),
manager.getCurrentBestFitnessCheckedEstimate());
}
private void updateGUIForNextSearch(final int searchNum) {
Platform.runLater(new Runnable() {
public void run() {
labelMessage.setText("Performing search " + (searchNum - runOptions.firstSearchNumber + 1) + " of " + runOptions.numSearches + ": ");
}
});
}
@Override
public void searchStarting(SearchManager manager) {
updateGUIForNextSearch(manager.getSearchIDNumber());
}
Chromosome overallBest = null;
double overallBestFitness;
double overallBestFitnessChecked;
public void searchFinished(SearchManager manager) {
double bestFitness = manager.getCurrentBestFitness();
if (overallBest == null || manager.fitnessStrictlyBetter(bestFitness, overallBestFitness)) {
overallBest = manager.getCurrentBest();
overallBestFitness = bestFitness;
if (protocol.useBestChecking()) {
overallBestFitnessChecked = manager.getCurrentBestFitnessCheckedEstimate();
}
}
}
@Override
public void allSearchesFinished() {
updateInfoText("From all searches:", overallBest,
overallBestFitness, overallBestFitnessChecked);
}
@Override
public void searchesAborted() {
// do nothing
}
@Override
public void run() {
try {
List<ResultListener> listeners = new ArrayList<ResultListener>();
listeners.add(this);
BehaviorSearch.runWithOptions(runOptions, protocol, listeners);
updateGUIWhenFinished();
} catch (Throwable e) {
fatalException = e; // handle it later, back in the regular
// Swing event thread
}
}
public void updateGUIWhenFinished() {
Platform.runLater(new Runnable() {
public void run() {
labelMessage.setText(
"Finished search " + runOptions.numSearches + " of " + runOptions.numSearches + ": ");
progressBar.setProgress(1.0);
cancelAndDoneButton.setText("Done");
done = true;
}
});
}
private void updateInfoText(String title, Chromosome c, double fitness, double checkedFitness) {
String bestText = GeneralUtils.getParamSettingsTextHTML(c.getParamSettings());
StringBuilder sb = new StringBuilder();
sb.append("<p>");
sb.append("<B><I>" + title + "</I></B>");
sb.append("<BR><BR><B>Best found so far:</B><BR>");
sb.append(bestText);
sb.append("<BR><B>Fitness</B>=");
sb.append(String.format("%10.6g", fitness));
sb.append("<BR>");
if (protocol.useBestChecking()) {
sb.append("<B>(re-checked)</B>=");
sb.append(String.format("%10.6g", checkedFitness));
sb.append("<BR>");
}
sb.append("</p>");
String text = sb.toString();
Platform.runLater(new Runnable() {
public void run() {
infoTextWebView.getEngine().loadContent(text);
}
});
}
}
}