From 98920bba0053179c5fcc371cac1e53b30bc93bff Mon Sep 17 00:00:00 2001
From: oblonski <4sschroeder@gmail.com>
Date: Mon, 14 Apr 2014 12:39:45 +0200
Subject: [PATCH] refined, simplified, accelerated and documented
analysis.toolbox.ComputationalLaboratory.java
---
.../toolbox/ComputationalLaboratory.java | 392 ++++++++++++------
1 file changed, 264 insertions(+), 128 deletions(-)
diff --git a/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/ComputationalLaboratory.java b/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/ComputationalLaboratory.java
index 36034422..3c283e8e 100644
--- a/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/ComputationalLaboratory.java
+++ b/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/ComputationalLaboratory.java
@@ -2,83 +2,198 @@ package jsprit.analysis.toolbox;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import jsprit.core.algorithm.VehicleRoutingAlgorithm;
import jsprit.core.algorithm.VehicleRoutingAlgorithmFactory;
-import jsprit.core.algorithm.listener.VehicleRoutingAlgorithmListeners.Priority;
import jsprit.core.problem.VehicleRoutingProblem;
import jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import jsprit.core.util.BenchmarkInstance;
-import jsprit.core.util.Solutions;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class ComputationalLaboratory {
- public abstract static interface Result {
+ /**
+ * Listener-interface to listen to calculation.
+ *
+ *
Note that calculations are run concurrently, i.e. a unique task that is distributed to an available thread is
+ * {algorithm, instance, run}.
+ *
+ * @author schroeder
+ *
+ */
+ public static interface CalculationListener {
- public abstract String getIndicatorName();
+ public void calculationStarts(final BenchmarkInstance p, final String algorithmName, final VehicleRoutingAlgorithm algorithm, final int run);
- public abstract double getResult();
- }
-
- public abstract static interface Indicator {
-
- public abstract void calculationStarts();
-
- public abstract void runStarts(VehicleRoutingAlgorithm vra);
-
- public abstract void runEnds(VehicleRoutingProblemSolution vrs);
-
- public abstract Collection getResults();
+ public void calculationEnds(final BenchmarkInstance p, final String algorithmName, final VehicleRoutingAlgorithm algorithm, final int run, final Collection solutions);
}
- public static class ResultContainer {
+ /**
+ * Collects whatever indicators you require by algorithmName, instanceName, run and indicator.
+ *
+ * @author schroeder
+ *
+ */
+ public static class DataCollector {
- private List results = new ArrayList();
+ public static class Key {
+ private String instanceName;
+ private String algorithmName;
+ private int run;
+ private String indicatorName;
+
+ public Key(String instanceName, String algorithmName, int run,String indicatorName) {
+ super();
+ this.instanceName = instanceName;
+ this.algorithmName = algorithmName;
+ this.run = run;
+ this.indicatorName = indicatorName;
+ }
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime
+ * result
+ + ((algorithmName == null) ? 0 : algorithmName
+ .hashCode());
+ result = prime
+ * result
+ + ((indicatorName == null) ? 0 : indicatorName
+ .hashCode());
+ result = prime
+ * result
+ + ((instanceName == null) ? 0 : instanceName.hashCode());
+ result = prime * result + run;
+ return result;
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Key other = (Key) obj;
+ if (algorithmName == null) {
+ if (other.algorithmName != null)
+ return false;
+ } else if (!algorithmName.equals(other.algorithmName))
+ return false;
+ if (indicatorName == null) {
+ if (other.indicatorName != null)
+ return false;
+ } else if (!indicatorName.equals(other.indicatorName))
+ return false;
+ if (instanceName == null) {
+ if (other.instanceName != null)
+ return false;
+ } else if (!instanceName.equals(other.instanceName))
+ return false;
+ if (run != other.run)
+ return false;
+ return true;
+ }
+ public String getInstanceName() {
+ return instanceName;
+ }
+ public String getAlgorithmName() {
+ return algorithmName;
+ }
+ public int getRun() {
+ return run;
+ }
+ public String getIndicatorName() {
+ return indicatorName;
+ }
+
+ @Override
+ public String toString() {
+ return "[algorithm="+algorithmName+"][instance="+instanceName+"][run="+run+"][indicator="+indicatorName+"]";
+ }
+
+ }
- private String instanceName;
+ private ConcurrentHashMap data = new ConcurrentHashMap();
- private String algorithmName;
+ /**
+ * Adds a single date by instanceName, algorithmName, run and indicatorName.
+ * If there is already an entry for this instance, algorithm, run and indicatorName, it is overwritten.
+ *
+ * @param instanceName
+ * @param algorithmName
+ * @param run
+ * @param indicatorName
+ * @param value
+ */
+ public void addDate(String instanceName, String algorithmName, int run, String indicatorName, double value){
+ Key key = new Key(instanceName,algorithmName,run,indicatorName);
+ data.put(key, value);
+ }
- public ResultContainer(String instanceName, String algorithmName) {
- this.instanceName = instanceName;
- this.algorithmName = algorithmName;
+ /**
+ * Returns a collections of indicator values representing the calculated values of individual runs.
+ *
+ * @param instanceName
+ * @param algorithmName
+ * @param indicator
+ * @return
+ */
+ public Collection getData(String instanceName, String algorithmName, String indicator){
+ List values = new ArrayList();
+ for(Key key : data.keySet()){
+ if(key.getAlgorithmName().equals(algorithmName) && key.getInstanceName().equals(instanceName) && key.getIndicatorName().equals(indicator)){
+ values.add(data.get(key));
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Returns indicator value.
+ *
+ * @param instanceName
+ * @param algorithmName
+ * @param run
+ * @param indicator
+ * @return
+ */
+ public Double getDate(String instanceName, String algorithmName, int run, String indicator){
+ return data.get(new Key(instanceName,algorithmName,run,indicator));
}
- public void addResult(Result result){
- results.add(result);
+ /**
+ * Returns all keys that have been created. A key is a unique combination of algorithmName, instanceName, run and indicator.
+ *
+ * @return
+ */
+ public Set keySet(){
+ return data.keySet();
}
-
- public List getResults() {
- return results;
- }
-
- public String getInstanceName() { return instanceName; }
- public String getAlgorithmName() { return algorithmName; }
-
- public void addAllResults(Collection results) {
- this.results.addAll(results);
+ /**
+ * Returns date associated to specified key.
+ *
+ * @param key
+ * @return
+ */
+ public Double getData(Key key){
+ return data.get(key);
}
}
- public abstract static interface IndicatorFactory {
- public abstract Indicator createIndicator(VehicleRoutingProblem vrp);
- }
-
- public static interface ResultWriter {
- public void writeResults(Collection results);
- }
private static class Algorithm {
@@ -98,46 +213,90 @@ public class ComputationalLaboratory {
private int runs = 1;
- private Collection writers = new ArrayList();
-
- private Collection results = new ArrayList();
-
- private Collection indicatorFactories = new ArrayList();
+ private Collection listeners = new ArrayList();
private List algorithms = new ArrayList();
+ private Set algorithmNames = new HashSet();
+
+ private Set instanceNames = new HashSet();
+
private int threads = Runtime.getRuntime().availableProcessors()+1;
public ComputationalLaboratory() {
Logger.getRootLogger().setLevel(Level.ERROR);
}
- public void addResultWriter(ResultWriter writer){
- writers.add(writer);
- }
-
+ /**
+ * Adds algorithmFactory by name.
+ *
+ * @param name
+ * @param factory
+ * @throws IllegalStateException if there is already an algorithmFactory with the same name
+ */
public void addAlgorithmFactory(String name, VehicleRoutingAlgorithmFactory factory){
+ if(algorithmNames.contains(name)) throw new IllegalStateException("there is already a algorithmFactory with the same name (algorithmName="+name+"). unique names are required.");
algorithms.add(new Algorithm(name,factory));
- }
-
- public void addIndicatorFactory(IndicatorFactory indicatorFactory){
- indicatorFactories.add(indicatorFactory);
+ algorithmNames.add(name);
}
+ /**
+ * Adds instance by name.
+ *
+ * @param name
+ * @param problem
+ * @throws IllegalStateException if there is already an instance with the same name.
+ */
public void addInstance(String name, VehicleRoutingProblem problem){
+ if(benchmarkInstances.contains(name)) throw new IllegalStateException("there is already an instance with the same name (instanceName="+name+"). unique names are required.");
benchmarkInstances.add(new BenchmarkInstance(name,problem,null,null));
+ instanceNames.add(name);
}
+ /**
+ * Adds instance.
+ *
+ * @param name
+ * @param problem
+ * @throws IllegalStateException if there is already an instance with the same name.
+ */
public void addInstance(BenchmarkInstance instance){
+ if(benchmarkInstances.contains(instance.name)) throw new IllegalStateException("there is already an instance with the same name (instanceName="+instance.name+"). unique names are required.");
benchmarkInstances.add(instance);
+ instanceNames.add(instance.name);
}
+ /**
+ * Adds collection of instances.
+ *
+ * @param name
+ * @param problem
+ * @throws IllegalStateException if there is already an instance with the same name.
+ */
public void addAllInstances(Collection instances){
- benchmarkInstances.addAll(instances);
+ for(BenchmarkInstance i : instances){
+ addInstance(i);
+ }
}
+ /**
+ * Adds instance by name, and with best known results.
+ *
+ * @param name
+ * @param problem
+ * @throws IllegalStateException if there is already an instance with the same name.
+ */
public void addInstance(String name, VehicleRoutingProblem problem, Double bestKnownResult, Double bestKnownVehicles){
- benchmarkInstances.add(new BenchmarkInstance(name,problem,bestKnownResult,bestKnownVehicles));
+ addInstance(new BenchmarkInstance(name,problem,bestKnownResult,bestKnownVehicles));
+ }
+
+ /**
+ * Adds listener to listen computational experiments.
+ *
+ * @param listener
+ */
+ public void addListener(CalculationListener listener){
+ listeners.add(listener);
}
/**
@@ -150,6 +309,19 @@ public class ComputationalLaboratory {
this.runs = runs;
}
+ /**
+ * Runs experiments.
+ *
+ * If nuThreads > 1 it runs them concurrently, i.e. individual runs are distributed to available threads. Therefore
+ * a unique task is defined by its algorithmName, instanceName and its runNumber.
+ *
If you have one algorithm called "myAlgorithm" and one instance called "myInstance", and you need to run "myAlgorithm" on "myInstance" three times
+ * with three threads then "myAlgorithm","myInstance",run1 runs on the first thread, "myAlgorithm", "myInstance", run2 on the second etc.
+ *
You can register whatever analysisTool you require by implementing and registering CalculationListener. Then your tool is informed just
+ * before a calculation starts as well as just after a calculation has been finished.
+ *
+ * @see CalculationListener
+ * @throws IllegalStateException if either no algorithm or no instance has been specified
+ */
public void run(){
if(algorithms.isEmpty()){
throw new IllegalStateException("no algorithm specified. at least one algorithm needs to be specified.");
@@ -157,96 +329,60 @@ public class ComputationalLaboratory {
if(benchmarkInstances.isEmpty()){
throw new IllegalStateException("no instance specified. at least one instance needs to be specified.");
}
- if(indicatorFactories.isEmpty()){
- throw new IllegalStateException("no indicator specified. at least one indicator needs to be specified.");
- }
System.out.println("start benchmarking [nuAlgorithms="+algorithms.size()+"][nuInstances=" + benchmarkInstances.size() + "][runsPerInstance=" + runs + "]");
double startTime = System.currentTimeMillis();
ExecutorService executor = Executors.newFixedThreadPool(threads);
- List> futures = new ArrayList>();
for(final Algorithm algorithm : algorithms){
for(final BenchmarkInstance p : benchmarkInstances){
+ for(int run=0;run futureResult = executor.submit(new Callable(){
-
- @Override
- public ResultContainer call() throws Exception {
- return runAlgoAndGetResult(p, algorithm);
- }
-
- });
- futures.add(futureResult);
+ @Override
+ public void run() {
+ runAlgorithm(p, algorithm, r+1);
+ }
+
+ });
+ }
}
}
try {
- for(Future f : futures){
- results.add(f.get());
- }
+ executor.shutdown();
+ executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);
+
} catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (ExecutionException e) {
- // TODO Auto-generated catch block
e.printStackTrace();
}
- executor.shutdown();
- write(results);
- System.out.println("done [time="+(System.currentTimeMillis()-startTime)/1000 + "sec]");
+ System.out.println("benchmarking done [time="+(System.currentTimeMillis()-startTime)/1000 + "sec]");
}
+ /**
+ * Sets number of threads.
+ * By default: nuThreads = Runtime.getRuntime().availableProcessors()+1
+ *
+ * @param threads
+ */
public void setThreads(int threads) {
this.threads = threads;
}
- private ResultContainer runAlgoAndGetResult(BenchmarkInstance p, Algorithm algorithm) {
- System.out.println("run " + algorithm.name + " on " + p.name);
- List indicators = createIndicators(p.vrp);
- informCalculationStarts(indicators);
- for(int run=0;run solutions = vra.searchSolutions();
+ System.out.println("[algorithm=" + algorithm.name + "][instance="+p.name+"][run="+run+"][status=finished]");
+ informCalculationsEnds(p, algorithm.name, vra, run, solutions);
}
- private ResultContainer getResultContainer(List indicators, String instanceName, String algorithmName) {
- ResultContainer rc = new ResultContainer(instanceName, algorithmName);
- for(final Indicator i : indicators) {
- rc.addAllResults(i.getResults());
- }
- return rc;
+ private void informCalculationStarts(BenchmarkInstance p, String name, VehicleRoutingAlgorithm vra, int run) {
+ for(CalculationListener l : listeners) l.calculationStarts(p, name, vra, run);
}
- private void informRunEnds(List indicators,VehicleRoutingProblemSolution best) {
- for(Indicator i : indicators) i.runEnds(best);
- }
-
- private void informRunStarts(List indicators,VehicleRoutingAlgorithm vra) {
- for(Indicator i : indicators) i.runStarts(vra);
- }
-
- private void informCalculationStarts(List indicators) {
- for(Indicator i : indicators) i.calculationStarts();
- }
-
- private List createIndicators(VehicleRoutingProblem vrp) {
- List indicators = new ArrayList();
- for(IndicatorFactory iFactory : indicatorFactories) indicators.add(iFactory.createIndicator(vrp));
- return indicators;
- }
-
- private void write(Collection results) {
- for(ResultWriter writer : writers){
- writer.writeResults(results);
- }
+ private void informCalculationsEnds(BenchmarkInstance p, String name, VehicleRoutingAlgorithm vra, int run,
+ Collection solutions) {
+ for(CalculationListener l : listeners) l.calculationEnds(p, name, vra, run, solutions);
}
}