package aima.gui.fx.applications.search.local;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.function.Function;
import aima.core.agent.Action;
import aima.core.agent.impl.DynamicAction;
import aima.core.search.framework.problem.Problem;
import aima.core.search.local.Scheduler;
import aima.core.search.local.SimulatedAnnealingSearch;
import aima.gui.fx.framework.IntegrableApplication;
import aima.gui.fx.framework.Parameter;
import aima.gui.fx.framework.SimulationPaneBuilder;
import aima.gui.fx.framework.SimulationPaneCtrl;
import aima.gui.fx.views.FunctionPlotterCtrl;
import javafx.application.Platform;
import javafx.scene.canvas.Canvas;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
/**
* Demonstrates, how simulated annealing can be used, to find maximums in
* mathematical functions. Different parameter settings can be tried out and
* progress shown for each iteration.
*
* @author Ruediger Lunde
*/
public class SimulatedAnnealingMaximumFinderApp extends IntegrableApplication {
public static void main(String[] args) {
launch(args);
}
public final static String PARAM_FUNC_SELECT = "funcSelect";
public final static String PARAM_K = "k";
public final static String PARAM_LAMBDA = "lambda";
public final static String PARAM_MAX_ITER = "maxIter";
protected FunctionPlotterCtrl funcPlotterCtrl;
private SimulationPaneCtrl simPaneCtrl;
private Random random = new Random();
private SimulatedAnnealingSearch search;
@Override
public String getTitle() {
return "Simulated Annealing Maximum Finder App";
}
/**
* Defines state view, parameters, and call-back functions and calls the
* simulation pane builder to create layout and controller objects.
*/
@Override
public Pane createRootPane() {
BorderPane root = new BorderPane();
Canvas canvas = new Canvas();
funcPlotterCtrl = new FunctionPlotterCtrl(canvas);
funcPlotterCtrl.setLimits(Functions.minX, Functions.maxX, Functions.minY, Functions.maxY);
List<Parameter> params = createParameters();
SimulationPaneBuilder builder = new SimulationPaneBuilder();
builder.defineParameters(params);
builder.defineStateView(canvas);
builder.defineInitMethod(this::initialize);
builder.defineSimMethod(this::simulate);
simPaneCtrl = builder.getResultFor(root);
simPaneCtrl.setParam(SimulationPaneCtrl.PARAM_SIM_SPEED, 1);
return root;
}
protected List<Parameter> createParameters() {
Parameter p1 = new Parameter(PARAM_FUNC_SELECT);
p1.setValues(Functions.f1, Functions.f2, Functions.f3);
p1.setValueNames("f1", "f2", "f3");
Parameter p2 = new Parameter(PARAM_K, 1, 20, 100);
p2.setDefaultValueIndex(1);
Parameter p3 = new Parameter(PARAM_LAMBDA, 0.01, 0.05, 0.1, 0.5);
p3.setDefaultValueIndex(1);
Parameter p4 = new Parameter(PARAM_MAX_ITER, 100, 500, 1000);
p4.setDefaultValueIndex(1);
return Arrays.asList(p1, p2, p3, p4);
}
/** Displays the selected function on the state view. */
@SuppressWarnings("unchecked")
@Override
public void initialize() {
funcPlotterCtrl.setFunction((Function<Double, Double>) simPaneCtrl.getParamValue(PARAM_FUNC_SELECT));
}
@Override
public void cleanup() {
simPaneCtrl.cancelSimulation();
}
/** Starts the experiment. */
@SuppressWarnings("unchecked")
public void simulate() {
Set<Action> actions = new HashSet<>();
actions.add(new DynamicAction("Move"));
Problem problem = new Problem(getRandomState(), s -> actions, (s, a) -> getSuccessor(s), s -> false);
Function<Double, Double> func = (Function<Double, Double>) simPaneCtrl.getParamValue(PARAM_FUNC_SELECT);
Scheduler scheduler = new Scheduler(simPaneCtrl.getParamAsInt(PARAM_K),
simPaneCtrl.getParamAsDouble(PARAM_LAMBDA), simPaneCtrl.getParamAsInt(PARAM_MAX_ITER));
search = new SimulatedAnnealingSearch(s -> 1 - func.apply((Double) s), scheduler);
search.addNodeListener(n -> updateStateView(n.getState()));
search.findActions(problem);
updateStateView(search.getLastSearchState());
}
/** Creates a random initial state for the maximum search problem. */
private Double getRandomState() {
return random.nextDouble() * (Functions.maxX - Functions.minX) + Functions.minX;
}
/**
* Successor function for maximum search problem. Just one successor is
* presented which corresponds to one mutation step in the genetic algorithm
* for numbers.
*/
protected Double getSuccessor(Object state) {
double result = (Double) state;
double r = random.nextDouble() - 0.5;
result += r * r * r * (Functions.maxX - Functions.minX) / 2;
if (result < Functions.minX)
result = Functions.minX;
else if (result > Functions.maxX)
result = Functions.maxX;
// result += (random.nextInt(3) - 1) * funcPlotter.getDeltaX();
return result;
}
/**
* Caution: While the background thread should be slowed down, updates of
* the GUI have to be done in the GUI thread!
*/
private void updateStateView(Object state) {
Platform.runLater(() -> updateStateViewLater(state));
simPaneCtrl.waitAfterStep();
}
/**
* Must be called by the GUI thread!
*/
private void updateStateViewLater(Object state) {
funcPlotterCtrl.update();
if (state instanceof Double) {
double temp = search.getMetrics().getDouble(SimulatedAnnealingSearch.METRIC_TEMPERATURE);
Paint fill = Color.RED;
if (temp < 1)
fill = Color.rgb((int) (255 * temp), 0, (int) (255 * (1 - temp)));
funcPlotterCtrl.setMarker((Double) state, Optional.of(fill));
simPaneCtrl.setStatus(search.getMetrics().toString());
} else {
simPaneCtrl.setStatus("");
}
}
}