All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rinde.sim.pdptw.experiment.Experiment Maven / Gradle / Ivy

The newest version!
/**
 * 
 */

package rinde.sim.pdptw.experiment;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;

import javax.annotation.Nullable;

import org.apache.commons.math3.random.MersenneTwister;
import org.apache.commons.math3.random.RandomGenerator;

import rinde.sim.core.model.Model;
import rinde.sim.pdptw.common.AddDepotEvent;
import rinde.sim.pdptw.common.AddParcelEvent;
import rinde.sim.pdptw.common.AddVehicleEvent;
import rinde.sim.pdptw.common.DynamicPDPTWProblem;
import rinde.sim.pdptw.common.DynamicPDPTWScenario;
import rinde.sim.pdptw.common.ObjectiveFunction;
import rinde.sim.pdptw.common.RouteRenderer;
import rinde.sim.pdptw.common.ScenarioParser;
import rinde.sim.pdptw.common.StatisticsDTO;
import rinde.sim.scenario.ScenarioController.UICreator;
import rinde.sim.util.SupplierRng;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

/**
 * Utility for defining and performing experiments. An experiment is composed of
 * a set of {@link DynamicPDPTWScenario}s and a set of {@link MASConfiguration}
 * s. For each combination of these a user configurable number of
 * simulations is performed. The number of used threads in the experiment can be
 * set via {@link Builder#withThreads(int)}.
 * 

* Example Consider an experiment with three scenarios and two * configurations, and each simulation needs to be repeated twice. The code * required for this setup: * *

 * {@code
 * Experiment.experiment(objFunc)
 *    .addConfiguration(config1)
 *    .addConfiguration(config2)
 *    .addScenario(scen1)
 *    .addScenarios(asList(scen2,scen3))
 *    .repeat(2)
 *    .perform();
 *    }
 * 
* * The following simulations will be run: *
    *
  1. config1, scen1, seed1
  2. *
  3. config1, scen1, seed2
  4. *
  5. config1, scen2, seed1
  6. *
  7. config1, scen2, seed2
  8. *
  9. config1, scen3, seed1
  10. *
  11. config1, scen3, seed2
  12. *
  13. config2, scen1, seed1
  14. *
  15. config2, scen1, seed2
  16. *
  17. config2, scen2, seed1
  18. *
  19. config2, scen2, seed2
  20. *
  21. config2, scen3, seed1
  22. *
  23. config2, scen3, seed2
  24. *
* For each simulation a {@link SimulationResult} returned. * * @author Rinde van Lon */ public final class Experiment { // TODO add strict mode which checks whether there are not too many // vehicles/parcels/depots? private Experiment() {} /** * Create an experiment with the specified {@link ObjectiveFunction}. * @param objectiveFunction The objective function which is used to evaluate * all simulation runs. * @return An {@link Builder} instance as per the builder pattern. */ public static Builder build(ObjectiveFunction objectiveFunction) { return new Builder(objectiveFunction); } /** * Can be used to run a single simulation run. * @param scenario The scenario to run on. * @param configuration The configuration to use. * @param seed The seed of the run. * @param objFunc The {@link ObjectiveFunction} to use. * @param showGui If true enables the gui. * @param postProcessor The post processor to use for this run. * @param uic The UICreator to use. * @return The {@link SimulationResult} generated in the run. */ public static SimulationResult singleRun(DynamicPDPTWScenario scenario, MASConfiguration configuration, long seed, ObjectiveFunction objFunc, boolean showGui, @Nullable PostProcessor postProcessor, @Nullable UICreator uic) { final ExperimentRunner er = new ExperimentRunner(scenario, configuration, seed, objFunc, showGui, postProcessor, uic); final SimulationResult res = er.call(); checkState(res != null); return res; } /** * Initialize a {@link DynamicPDPTWProblem} instance. * @param scenario The scenario to use. * @param config The configuration to use. * @param showGui Whether to show the gui. * @return The {@link DynamicPDPTWProblem} instance. */ @VisibleForTesting static DynamicPDPTWProblem init(DynamicPDPTWScenario scenario, MASConfiguration config, long seed, boolean showGui, @Nullable UICreator uic) { final RandomGenerator rng = new MersenneTwister(seed); final long simSeed = rng.nextLong(); final ImmutableList>> modelSuppliers = config .getModels(); final Model[] models = new Model[modelSuppliers.size()]; for (int i = 0; i < modelSuppliers.size(); i++) { models[i] = modelSuppliers.get(i).get(rng.nextLong()); } final DynamicPDPTWProblem problem = new DynamicPDPTWProblem(scenario, simSeed, models); problem.addCreator(AddVehicleEvent.class, config.getVehicleCreator()); if (config.getDepotCreator().isPresent()) { problem.addCreator(AddDepotEvent.class, config.getDepotCreator().get()); } if (config.getParcelCreator().isPresent()) { problem.addCreator(AddParcelEvent.class, config.getParcelCreator().get()); } if (showGui) { if (uic == null) { problem.addRendererToUI(new RouteRenderer()); problem.enableUI(); } else { problem.enableUI(uic); } } return problem; } /** * Builder for configuring experiments. * @author Rinde van Lon */ public static final class Builder { final ObjectiveFunction objectiveFunction; final ImmutableList.Builder configurationsBuilder; final ImmutableList.Builder scenariosBuilder; @Nullable UICreator uiCreator; @Nullable PostProcessor postProc; boolean showGui; int repetitions; long masterSeed; private int numThreads; Builder(ObjectiveFunction objectiveFunction) { this.objectiveFunction = objectiveFunction; configurationsBuilder = ImmutableList.builder(); scenariosBuilder = ImmutableList.builder(); showGui = false; repetitions = 1; masterSeed = 0L; numThreads = 1; } /** * Set the number of repetitions for each simulation. * @param times The number of repetitions. * @return This, as per the builder pattern. */ public Builder repeat(int times) { checkArgument(times > 0); repetitions = times; return this; } /** * Enable the GUI for each simulation. When a large number of simulations is * performed this may slow down the experiment significantly. The GUI can * not be shown when more than one thread is used. * @return This, as per the builder pattern. */ public Builder showGui() { showGui = true; return this; } /** * Enable the GUI using the specified creator for each simulation. When a * large number of simulations is performed this may slow down the * experiment significantly. The GUI can not be shown when more than one * thread is used. * @param uic The {@link UICreator} to use for creating the GUI. * @return This, as per the builder pattern. */ public Builder showGui(UICreator uic) { uiCreator = uic; return showGui(); } /** * Add a configuration to the experiment. For each simulation * {@link SupplierRng#get(long)} is called and the resulting * {@link MASConfiguration} is used for a single simulation. * @param config The configuration to add. * @return This, as per the builder pattern. */ public Builder addConfiguration(MASConfiguration config) { configurationsBuilder.add(config); return this; } /** * Adds all configurations to the experiment. For each simulation * {@link SupplierRng#get(long)} is called and the resulting * {@link MASConfiguration} is used for a single simulation. * @param configs The configurations to add. * @return This, as per the builder pattern. */ public Builder addConfigurations(List configs) { configurationsBuilder.addAll(configs); return this; } /** * Add a scenario to the set of scenarios. * @param scenario The scenario to add. * @return This, as per the builder pattern. */ public Builder addScenario(DynamicPDPTWScenario scenario) { scenariosBuilder.add(scenario); return this; } /** * Add all scenarios to the set of scenarios. * @param scenarios The scenarios to add. * @return This, as per the builder pattern. */ public Builder addScenarios(List scenarios) { scenariosBuilder.addAll(scenarios); return this; } /** * Parse all scenarios with the given file names and parse them using the * given parser. * @param parser The parser to use for parsing. * @param files The files to parse. * @return This, as per the builder pattern. */ public Builder addScenarios( ScenarioParser parser, List files) { for (final String file : files) { scenariosBuilder.add(parser.parse(file)); } return this; } /** * Specify the number of threads to use for computing the experiments, the * default is 1. * @param threads The number of threads to use. * @return This, as per the builder pattern. */ public Builder withThreads(int threads) { checkArgument(threads > 0, "Only a positive number of threads is allowed, was %s.", threads); numThreads = threads; return this; } /** * Set the master random seed for the experiments. * @param seed The seed to use. * @return This, as per the builder pattern. */ public Builder withRandomSeed(long seed) { masterSeed = seed; return this; } /** * Specify a {@link PostProcessor} which is used to gather additional * results from a simulation. The data gathered by the post-processor ends * up in {@link SimulationResult#simulationData}. * @param postProcessor The post-processor to use, by default there is no * post-processor. * @return This, as per the builder pattern. */ public Builder usePostProcessor(PostProcessor postProcessor) { postProc = postProcessor; return this; } /** * Perform the experiment. For every scenario every configuration is used * n times. Where n is the number of repetitions * as specified. * @return An {@link ExperimentResults} instance which contains all * experiment parameters and the corresponding results. */ public ExperimentResults perform() { checkArgument(numThreads == 1 || !showGui, "The GUI can not be shown when using more than one thread."); final List seeds = generateSeeds(); // run Forrest run! final ImmutableList runners = gatherAllRunners(seeds); return runAllRunners(runners); } private ImmutableList generateSeeds() { if (repetitions > 1) { final RandomGenerator rng = new MersenneTwister(masterSeed); return ExperimentUtil .generateDistinct(rng, repetitions); } else { return ImmutableList.of(masterSeed); } } private ImmutableList gatherAllRunners(List seeds) { final ImmutableList scen = scenariosBuilder.build(); final ImmutableList conf = configurationsBuilder .build(); checkArgument(!scen.isEmpty(), "At least one scenario is required."); checkArgument(!conf.isEmpty(), "At least one configuration is required."); final ImmutableList.Builder runnerBuilder = ImmutableList .builder(); for (final MASConfiguration configuration : conf) { for (final DynamicPDPTWScenario scenario : scen) { for (int i = 0; i < repetitions; i++) { final long seed = seeds.get(i); runnerBuilder.add(new ExperimentRunner(scenario, configuration, seed, objectiveFunction, showGui, postProc, uiCreator)); } } } return runnerBuilder.build(); } private ExperimentResults runAllRunners( ImmutableList runners) { final int threads = Math.min(numThreads, runners.size()); final ListeningExecutorService executor; if (threads > 1) { executor = MoreExecutors .listeningDecorator(Executors.newFixedThreadPool(threads)); } else { executor = MoreExecutors.sameThreadExecutor(); } final List results; try { // safe cast according to javadoc @SuppressWarnings({ "unchecked", "rawtypes" }) final List> futures = (List) executor .invokeAll(runners); results = Futures.allAsList(futures).get(); } catch (final InterruptedException e) { throw new IllegalStateException(e); } catch (final ExecutionException e) { // FIXME need some way to gracefully handle this error. All data // should be saved to reproduce this simulation. throw new IllegalStateException(e); } executor.shutdown(); return new ExperimentResults(this, ImmutableList.copyOf(results)); } } /** * The result of a single simulation. It contains both the resulting * statistics as well as the inputs used to obtain this result. * @author Rinde van Lon */ public static final class SimulationResult { /** * The simulation statistics. */ public final StatisticsDTO stats; /** * The scenario on which the simulation was run. */ public final DynamicPDPTWScenario scenario; /** * The configuration which was used to configure the MAS. */ public final MASConfiguration masConfiguration; /** * The seed that was supplied to {@link SupplierRng#get(long)}. */ public final long seed; /** * Additional simulation data as gathered by a {@link PostProcessor}, or if * no post-processor was used this object defaults to null. */ @Nullable public Object simulationData; SimulationResult(StatisticsDTO stats, DynamicPDPTWScenario scenario, MASConfiguration masConfiguration, long seed, @Nullable Object simData) { this.stats = stats; this.scenario = scenario; this.masConfiguration = masConfiguration; this.seed = seed; simulationData = simData; } @Override public boolean equals(@Nullable Object obj) { if (obj == null) { return false; } if (obj.getClass() != getClass()) { return false; } final SimulationResult other = (SimulationResult) obj; return Objects.equal(stats, other.stats) && Objects.equal(scenario, other.scenario) && Objects.equal(masConfiguration, other.masConfiguration) && Objects.equal(seed, other.seed) && Objects.equal(simulationData, other.simulationData); } @Override public int hashCode() { return Objects.hashCode(stats, scenario, masConfiguration, seed, simulationData); } @Override public String toString() { return Objects.toStringHelper(this) .add("stats", stats) .add("scenario", scenario) .add("masConfiguration", masConfiguration) .add("seed", seed) .add("simulationData", simulationData) .toString(); } } /** * Value object containing all the results of a single experiment as performed * by {@link Builder#perform()}. * @author Rinde van Lon */ public static final class ExperimentResults { /** * The {@link ObjectiveFunction} that was used for this experiment. */ public final ObjectiveFunction objectiveFunction; /** * The configurations that were used in this experiment. */ public final ImmutableList configurations; /** * The scenarios that were used in this experiment. */ public final ImmutableList scenarios; /** * Indicates whether the experiment was executed with or without the * graphical user interface. */ public final boolean showGui; /** * The number of repetitions for each run (with a different seed). */ public final int repetitions; /** * The seed of the master random generator. */ public final long masterSeed; /** * The list of individual simulation results. */ public final ImmutableList results; ExperimentResults(Builder exp, ImmutableList res) { objectiveFunction = exp.objectiveFunction; configurations = exp.configurationsBuilder.build(); scenarios = exp.scenariosBuilder.build(); showGui = exp.showGui; repetitions = exp.repetitions; masterSeed = exp.masterSeed; results = res; } @Override public int hashCode() { return Objects.hashCode(objectiveFunction, configurations, scenarios, showGui, repetitions, masterSeed, results); } @Override public boolean equals(@Nullable Object other) { if (other == null) { return false; } if (other.getClass() != getClass()) { return false; } final ExperimentResults er = (ExperimentResults) other; return Objects.equal(objectiveFunction, er.objectiveFunction) && Objects.equal(configurations, er.configurations) && Objects.equal(scenarios, er.scenarios) && Objects.equal(showGui, er.showGui) && Objects.equal(repetitions, er.repetitions) && Objects.equal(masterSeed, er.masterSeed) && Objects.equal(results, er.results); } @Override public String toString() { return Objects.toStringHelper(this) .add("objectiveFunction", objectiveFunction) .add("configurations", configurations) .add("scenarios", scenarios) .add("showGui", showGui) .add("repetitions", repetitions) .add("masterSeed", masterSeed) .add("results", results) .toString(); } } private static class ExperimentRunner implements Callable { private final DynamicPDPTWScenario scenario; private final MASConfiguration configuration; private final long seed; private final ObjectiveFunction objectiveFunction; private final boolean showGui; @Nullable private final UICreator uiCreator; @Nullable private final PostProcessor postProcessor; ExperimentRunner(DynamicPDPTWScenario scenario, MASConfiguration configuration, long seed, ObjectiveFunction objectiveFunction, boolean showGui, @Nullable PostProcessor postProc, @Nullable UICreator uic) { this.scenario = scenario; this.configuration = configuration; this.seed = seed; this.objectiveFunction = objectiveFunction; this.showGui = showGui; postProcessor = postProc; uiCreator = uic; } @Override public SimulationResult call() { try { final DynamicPDPTWProblem prob = init(scenario, configuration, seed, showGui, uiCreator); final StatisticsDTO stats = prob.simulate(); @Nullable Object data = null; if (postProcessor != null) { data = postProcessor.collectResults(prob.getSimulator()); } checkState(objectiveFunction.isValidResult(stats), "The simulation did not result in a valid result: %s.", stats); final SimulationResult result = new SimulationResult(stats, scenario, configuration, seed, data); // FIXME this should be changed into a more decent progress indicator System.out.print("."); return result; } catch (final RuntimeException e) { final StringBuilder sb = new StringBuilder().append("[Scenario= ") .append(scenario).append(",").append(scenario.getProblemClass()) .append(",").append(scenario.getProblemInstanceId()).append("]") .append(",seed=").append(seed).append(",config=") .append(configuration); throw new RuntimeException(sb.toString(), e); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy