/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package cz.cvut.felk.cig.jcool.experiment;
import cz.cvut.felk.cig.jcool.core.Function;
import cz.cvut.felk.cig.jcool.core.OptimizationMethod;
import cz.cvut.felk.cig.jcool.core.Producer;
import cz.cvut.felk.cig.jcool.core.Telemetry;
import cz.cvut.felk.cig.jcool.experiment.ExperimentRun.ExperimentRunBuilder;
import cz.cvut.felk.cig.jcool.experiment.util.*;
import cz.cvut.felk.cig.jcool.solver.Solver;
import cz.cvut.felk.cig.jcool.solver.UserInterruptStopCondition;
import org.apache.log4j.Logger;
import org.ytoh.configurations.util.Annotations;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* @author ytoh
*/
public final class BasicExperimentRunner implements ExperimentRunner {
static Logger logger = Logger.getLogger(BasicExperimentRunner.class);
/** */
private volatile State state;
/** */
private final ReentrantLock lock = new ReentrantLock();
/** */
private final ExecutorService es;
/** */
private Future<ExperimentRunBuilder> runningExperiment;
/** */
private Experiment currentExperiment;
/** */
private PropertyChangeSupport support = new PropertyChangeSupport(this);
private List<TelemetryVisualization<? extends Telemetry>> visualizations;
/**
*
*/
public BasicExperimentRunner() {
this(Executors.newSingleThreadExecutor());
}
/**
*
* @param es
*/
public BasicExperimentRunner(ExecutorService es) {
this.es = es;
setState(State.NOT_READY);
newExperiment();
}
/**
*
*/
public void newExperiment() {
if (state == State.RUNNING) {
throw new IllegalStateException("Cannot start a new experiment, an experiment is running.");
}
lock.lock();
try {
this.currentExperiment = new Experiment(null, null, null);
this.visualizations = new ArrayList<TelemetryVisualization<? extends Telemetry>>();
} finally {
lock.unlock();
}
updateExperimentState();
}
/**
*
*/
public void resetExperiment() {
if (state == State.RUNNING) {
throw new IllegalStateException("Cannot reset experiment, experiment is running.");
}
newExperiment();
}
/**
*
*/
public void startExperiment() {
if (!canStartExperiment()) {
throw new IllegalStateException("Experiment not ready. (in state " + state + ")");
}
final ExperimentRunBuilder builder = ExperimentRun.newInstance();
final List<TelemetryVisualization<? extends Telemetry>> visualizationToUpdate;
lock.lock();
try {
visualizationToUpdate = visualizations;
builder.setFunction(currentExperiment.getFunction());
builder.setMethod(currentExperiment.getMethod());
builder.setSolver(currentExperiment.getSolver());
this.visualizations = new ArrayList<TelemetryVisualization<? extends Telemetry>>();
} finally {
lock.unlock();
}
final Experiment experiment = builder.getExperiment();
Aggregator<Telemetry> aggregator = Consumers.synchronizingAggregatorOf(Telemetry.class, experiment.getSolver().getClass());
// method => aggregator
Producer<? extends Telemetry> broadcast = Consumers.broadcast(experiment.getMethod());
broadcast.addConsumer(aggregator);
ValueTelemetryTransformer transformer = new ValueTelemetryTransformer();
// method => value transformer
broadcast.addConsumer(transformer);
transformer.addConsumer(aggregator);
// solver => aggregator
experiment.getSolver().addConsumer(aggregator);
// aggregator => builder
aggregator.addConsumer(builder);
for (TelemetryVisualization<? extends Telemetry> visualization : visualizationToUpdate) {
initializeVisualization((TelemetryVisualization<Telemetry>) visualization, aggregator, experiment.getSolver());
visualization.init(experiment.getFunction());
}
try {
experiment.getSolver().init(experiment.getFunction(), experiment.getMethod());
runningExperiment = es.submit(new Callable<ExperimentRunBuilder>() {
public ExperimentRunBuilder call() throws Exception {
String functionName = Annotations.getName(experiment.getSolver().getClass());
String methodName = Annotations.getName(experiment.getMethod().getClass());
String solverName = Annotations.getName(experiment.getSolver().getClass());
logger.info(String.format("Starting experiment - function: %s, method: %s, solver: %s ", functionName, methodName, solverName));
setState(State.RUNNING);
try{
experiment.getSolver().solve();
} finally {
setState(State.FINISHED);
// exception will be thrown in ExperimentRunner.getExperimentResults()
}
logger.info(String.format("Experiment done. %s", experiment.getSolver().getResults().getMetConditions()));
return builder;
}
});
} catch (Exception e){
setState(State.FINISHED);
logger.error("Error during solver init: " + e.getMessage());
throw new ExperimentException("Some error occurred during startExperiment action.", e);
}
}
/**
*
* @return
*/
public State getExperimentState() {
return state;
}
/**
* Convenience method.
*
* @return
*/
public boolean canStartExperiment() {
return (state == State.READY || state == State.FINISHED || state == State.CANCELLED);
}
/**
*
* @return
*/
public boolean canResetExperiment() {
return state == State.FINISHED | state == State.CANCELLED;
}
/**
*
* @return
*/
public boolean canCreateExperiment() {
return state != State.RUNNING;
}
/**
*
* @return
*/
public boolean canStopExperiment() {
return state == State.RUNNING;
}
/**
*
*/
public void stopExperiment() {
if (runningExperiment != null && !runningExperiment.isDone()) {
logger.info("Sending cancel request to solver.");
currentExperiment.getSolver().addSystemStopCondition(new UserInterruptStopCondition());
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.schedule(new Callable<Void>() {
public Void call() throws Exception {
if(!runningExperiment.isDone()) {
runningExperiment.cancel(true);
logger.info("Experiment forcefully interrupted.");
setState(State.CANCELLED);
}
return null;
}
}, 1, TimeUnit.SECONDS);
service.shutdown();
}
}
/**
*
* @return
*/
public Class getTelemetryType() {
if (currentExperiment.getMethod() != null) {
return currentExperiment.getMethod().getValue().getClass();
}
return Object.class;
}
/**
*
* @param <T>
* @param visualization
*/
private <T extends Telemetry> void initializeVisualization(final TelemetryVisualization<T> visualization, Aggregator<T> aggregator, final Solver solver) {
// filter out the desired value from aggregated ones
final Filter<Telemetry, T> filter = Producers.filtering(visualization.getAcceptableType(), Telemetry.class);
// wrapped the filtered telemetry in na Iteration wrapper
Wrapper<T, Iteration<T>> wrapper = Producers.wrap(filter, new Transformer<T, Iteration<T>>() {
private Solver s = solver;
public Iteration<T> transform(T input) {
return new Iteration<T>(input, s.getValue().getValue());
}
});
// add filter as one of aggregation consumers
aggregator.addConsumer(filter);
/// add the visualization as a consumer of the wrapped filtered telemetry
wrapper.addConsumer(visualization);
}
private void setState(State state) {
State old = this.state;
this.state = state;
support.firePropertyChange("state", old, this.state);
}
/**
*
*/
private void updateExperimentState() {
setState((currentExperiment.getFunction() != null && currentExperiment.getMethod() != null && currentExperiment.getSolver() != null) ? State.READY : State.NOT_READY);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
/**
*
* @param visualization
*/
public void addVisualization(final TelemetryVisualization<? extends Telemetry> visualization) {
if (visualization == null) {
throw new NullPointerException("Cannot set visualization, visualization is null.");
}
if (state == State.RUNNING) {
throw new IllegalStateException("Cannot set visualization, experiment is running.");
}
lock.lock();
try {
if(currentExperiment.getFunction() != null) {
visualization.init(currentExperiment.getFunction());
}
visualizations.add(visualization);
} finally {
lock.unlock();
}
}
/**
*
* @param function
*/
public void setFunction(Function function) {
if (function == null) {
throw new NullPointerException("Cannot set function, function is null.");
}
if (state == State.RUNNING) {
throw new IllegalStateException("Cannot set function, experiment is running.");
}
lock.lock();
try {
for (TelemetryVisualization<? extends Telemetry> telemetryVisualization : visualizations) {
telemetryVisualization.init(function);
}
currentExperiment = new Experiment(currentExperiment.getSolver(), function, currentExperiment.getMethod());
} finally {
lock.unlock();
}
updateExperimentState();
}
/**
*
* @param method
*/
public void setMethod(OptimizationMethod<? extends Telemetry> method) {
if (method == null) {
throw new NullPointerException("Cannot set method, method is null.");
}
if (state == State.RUNNING) {
throw new IllegalStateException("Cannot set method, experiment is running.");
}
lock.lock();
try {
currentExperiment = new Experiment(currentExperiment.getSolver(), currentExperiment.getFunction(), method);
} finally {
lock.unlock();
}
updateExperimentState();
}
/**
*
* @param solver
*/
public void setSolver(Solver solver) {
if (solver == null) {
throw new NullPointerException("Cannot set solver, solver is null.");
}
if (state == State.RUNNING) {
throw new IllegalStateException("Cannot set solver, experiment is running.");
}
lock.lock();
try {
currentExperiment = new Experiment(solver, currentExperiment.getFunction(), currentExperiment.getMethod());
} finally {
lock.unlock();
}
updateExperimentState();
}
/**
*
* @return
*/
public ExperimentRun getExperimentResults() {
if (state == State.CANCELLED) {
logger.info("Experiment was cancelled, no results available");
throw new IllegalStateException("Experiment was cancelled, no results available.");
}
try {
return runningExperiment.get().build();
} catch (InterruptedException ex) {
throw new ExperimentException("Experiment thread has been interrupted", ex);
} catch (ExecutionException ex) {
// unwrap to root cause
String message = null;
Throwable throwable = ex;
while (throwable.getCause() != null){
throwable = throwable.getCause();
}
message = throwable.getMessage();
logger.info("Error during experiment run: " + message);
throw new ExperimentException("Some error occurred during experiment run.", ex);
}
}
}