package bsearch.app;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.nlogo.util.MersenneTwisterFast;
import org.xml.sax.SAXException;
import bsearch.algorithms.SearchMethod;
import bsearch.algorithms.SearchMethodLoader;
import bsearch.algorithms.SearchParameterException;
import bsearch.evaluation.DerivativeFitnessFunction;
import bsearch.evaluation.StandardFitnessFunction;
import bsearch.evaluation.FitnessFunction;
import bsearch.evaluation.ResultListener;
import bsearch.evaluation.SearchManager;
import bsearch.nlogolink.BatchRunner;
import bsearch.nlogolink.ModelRunner;
import bsearch.nlogolink.NetLogoLinkException;
import bsearch.nlogolink.Utils;
import bsearch.representations.ChromosomeFactory;
import bsearch.representations.ChromosomeTypeLoader;
import bsearch.space.ParameterSpec;
import bsearch.space.SearchSpace;
import bsearch.util.GeneralUtils;
/**
* NOTES:
* TODO: Eventually handle setting world-dimensions? and random-seed? (prob. not needed)
*
*/
public strictfp class BehaviorSearch {
public static void runMultipleSearches(SearchProtocol protocol, int numSearches, int firstSearchNumber, String fnameStem, List<ResultListener> listeners, int numThreads, int firstRandomSeed) throws BehaviorSearchException, InterruptedException, SearchParameterException
{
SearchSpace space = new SearchSpace(protocol.paramSpecStrings);
for (ResultListener listener : listeners) {
listener.initListener(space);
}
for (int searchNumber = firstSearchNumber; searchNumber < numSearches + firstSearchNumber; searchNumber++)
{
MersenneTwisterFast rng = new MersenneTwisterFast(firstRandomSeed + (searchNumber - firstSearchNumber));
BehaviorSearch.runProtocol(protocol, space, searchNumber, numThreads, rng, listeners) ;
}
for (ResultListener listener : listeners) {
listener.allSearchesFinished();
}
}
public static SearchManager runProtocol(SearchProtocol protocol, SearchSpace space, int searchIDNumber, int numEvaluationThreads, MersenneTwisterFast rng, List<ResultListener> listeners) throws SearchParameterException, BehaviorSearchException, InterruptedException
{
boolean measureEveryTick = !protocol.fitnessCollecting.equals(SearchProtocol.FITNESS_COLLECTING.AT_FINAL_STEP);
ModelRunner.Factory mrunnerFactory = new ModelRunner.Factory(
GeneralUtils.attemptResolvePathFromProtocolFolder(protocol.modelFile), measureEveryTick,
protocol.modelStepLimit, protocol.modelSetupCommands, protocol.modelStepCommands,
protocol.modelStopCondition, protocol.modelMetricReporter, protocol.modelMeasureIf);
BatchRunner batchRunner = new BatchRunner(numEvaluationThreads, mrunnerFactory);
SearchMethod searcher = SearchMethodLoader.createFromName(protocol.searchMethodType);
//if the search method in the protocol is missing some parameters, fill them in with defaults
HashMap<String, String> defaultParams = searcher.getSearchParams();
for (String key : defaultParams.keySet())
{
if (!protocol.searchMethodParams.containsKey(key))
{
protocol.searchMethodParams.put(key, defaultParams.get(key));
}
}
searcher.setSearchParams(protocol.searchMethodParams);
FitnessFunction ffun;
if (protocol.fitnessSamplingReplications == 0 && !searcher.supportsAdaptiveSampling())
{
throw new BehaviorSearchException("Error: " + searcher.getName() + " does not support adaptive fitness sampling!");
}
else
{
if (protocol.fitnessDerivativeParameter.length() > 0)
{
ffun = new DerivativeFitnessFunction(protocol,rng);
}
else {
ffun = new StandardFitnessFunction(protocol) ;
}
}
SearchManager archive = new SearchManager(searchIDNumber, batchRunner, protocol, ffun, false, 0.0);
for (ResultListener listener: listeners)
{
archive.addResultsListener(listener);
}
ChromosomeFactory cFactory = ChromosomeTypeLoader.createFromName(protocol.chromosomeType);
try {
for (ResultListener listener : listeners) {
listener.searchStarting(archive);
}
searcher.search( space , cFactory, protocol, archive, rng );
for (ResultListener listener : listeners) {
listener.searchFinished(archive);
}
}
catch (NetLogoLinkException ex)
{
System.err.println("***" + ex.getMessage() + "***");
throw new BehaviorSearchException(ex.getMessage());
}
finally {
try {
batchRunner.dispose();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return archive;
}
public static class RunOptions implements Cloneable {
@Option(name="-p",aliases={"--protocol"},required=true,usage="file (.bsearch) from which to load the search experiment protocol.")
public String protocolFilename;
@Option(name="-o",aliases={"--output"},required=true,usage="output filename STEM: will create files named STEM.xxxx.csv")
public String outputStem;
@Option(name="-t",aliases={"--threads"},usage="number of simultaneous threads to run the search with (defaults to the number of processors available).")
public int numThreads = Runtime.getRuntime().availableProcessors();
@Option(name="-n",aliases={"--numsearches"},usage="number of times to repeat the search (default 1)")
public int numSearches = 1;
@Option(name="-f",aliases={"--firstsearchnum"},usage="searches will be numbered starting at this index (only affects search # column in the output data)")
public int firstSearchNumber = 1;
@Option(name="-r",aliases={"--randomseed"},usage="random seed to start the first search (additional searches will be seeded with following consecutive numbers)")
// if none specified, choose a random integer to start with.
public Integer randomSeed = (Integer) new MersenneTwisterFast().nextInt();
@Option(name="-q",aliases={"--quiet"},usage="suppress printing progress to stdout")
boolean quiet = false;
//TODO: Decide if --shorten option is even worthwhile... sort of doubtful, since in the most general case, each
// fitness evaluation may take an arbitrary number of model runs, meaning that whatever shorten factor you use,
// you can't guarantee it'll work out nicely to get every Nth one - you might just get some of the Nth ones.
// ~Forrest (12/23/2009)
@Option(name="--shorten",usage="only print information to the .objectiveFunctionHistory.csv after every Nth model run.")
int shortenOutputFactor = 1;
@Option(name="-b", aliases={"--brief-output"},usage="shorthand flag for suppressing model-run-history and objective-function-history output, since these are the largest output files.")
public boolean briefOutput = false;
@Option(name="--suppress-model-run-history",usage="don't create the .modelRunHistory.csv file")
boolean suppressModelRunHistory = false;
@Option(name="--suppress-objective-function-history",usage="don't create the .objectiveFunctionHistory.csv file")
boolean suppressObjectiveFunctionHistory = false;
@Option(name="--suppress-best-history",usage="don't create the .bestHistory.csv file")
boolean suppressBestHistory = false;
@Option(name="-v", aliases={"--version"},usage="print version number and exit")
boolean printVersion = false;
@Option(name="--override-parameter-spec",usage="override the parameter spec/range given in the .bsearch file, using usual syntax (e.g. [\"population\" 100 200 400] (NOTE: you may need to \\ escape the quotes in your shell)", multiValued=true)
List<String> overrideParameters = new java.util.LinkedList<String>();
//@Argument(usage="any number of arguments...")
//List<String> arguments;
@Override
public RunOptions clone()
{
try {
return (RunOptions) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // won't happen, since class implements Cloneable
}
}
}
public static void main(String[] args) {
RunOptions runOptions = new RunOptions();
CmdLineParser parser = new CmdLineParser(runOptions);
try {
parser.parseArgument(args);
} catch( CmdLineException e ) {
if (runOptions.printVersion)
{
System.out.println("BehaviorSearch v" + GeneralUtils.getVersionString());
System.exit(0);
}
if (args.length == 0)
{
System.err.println("BehaviorSearch v" + GeneralUtils.getVersionString() + "\n");
}
else {
System.err.println(e.getMessage());
}
String scriptName = "behaviorsearch.sh";
if (GeneralUtils.isOSWindows())
{
scriptName = "behaviorsearch.bat";
}
System.err.println(scriptName + " [options...] arguments...");
parser.printUsage(System.err);
System.err.println();
System.err.println(" Example: " + scriptName + " -p myexperiment.bsearch -o myoutput --threads 1 -n 5");
System.exit(1);
}
try {
runWithOptions(runOptions);
}
catch(Exception ex) {
ex.printStackTrace();
System.exit(1);
}
finally {
try {
Utils.fullyShutDownNetLogoLink();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void runWithOptions(RunOptions runOptions) throws IOException, SAXException, BehaviorSearchException, InterruptedException, SearchParameterException
{
runWithOptions(runOptions,null,null);
}
/**
* Runs BehaviorSearch with the specified options (command line options, or could be specified through the GUI)
* @param runOptions - Options for running BehaviorSearch
* @param protocol - SearchProtocol to use for the search. If null, then load the protocol from runOptions.protocolFilename.
* @param additionalListeners - a list of additional listeners that should receive events relating to the search progress.
* @throws IOException
* @throws SAXException
* @throws BehaviorSearchException
* @throws InterruptedException
* @throws SearchParameterException
*/
public static void runWithOptions(RunOptions runOptions, SearchProtocol protocol, List<ResultListener> additionalListeners) throws IOException, SAXException, BehaviorSearchException, InterruptedException, SearchParameterException
{
runOptions = (RunOptions) runOptions.clone(); // so we don't cause any side-effects to the parameter
if (protocol == null)
{
runOptions.protocolFilename = GeneralUtils.attemptResolvePathFromStartupFolder(runOptions.protocolFilename);
runOptions.outputStem = GeneralUtils.attemptResolvePathFromStartupFolder(runOptions.outputStem);
protocol = SearchProtocol.loadFile(runOptions.protocolFilename);
}
GeneralUtils.updateProtocolFolder(runOptions.protocolFilename);
// allow users to override parameter spec ranges from the command line...
for (String override : runOptions.overrideParameters)
{
ParameterSpec newSpec = ParameterSpec.fromString(override);
boolean foundReplacement = false;
for (int i = 0; i < protocol.paramSpecStrings.size(); i++) {
ParameterSpec spec = ParameterSpec.fromString(protocol.paramSpecStrings.get(i));
if (spec.getParameterName().equals(newSpec.getParameterName())) {
protocol.paramSpecStrings.remove(i);
protocol.paramSpecStrings.add(i, override);
foundReplacement= true;
}
}
if (!foundReplacement) {
protocol.paramSpecStrings.add(override);
}
}
if (runOptions.briefOutput)
{
runOptions.suppressModelRunHistory = true;
runOptions.suppressObjectiveFunctionHistory = true;
}
ArrayList<ResultListener> listeners = new ArrayList<ResultListener>();
listeners.add(new CSVLoggerListener(protocol, runOptions.outputStem, !runOptions.suppressModelRunHistory, !runOptions.suppressObjectiveFunctionHistory, !runOptions.suppressBestHistory, true, runOptions.shortenOutputFactor));
if (!runOptions.quiet)
{
listeners.add(new ConsoleProgressListener(protocol.evaluationLimit, System.out));
}
if (additionalListeners != null)
{
listeners.addAll(additionalListeners);
}
BehaviorSearch.runMultipleSearches(protocol, runOptions.numSearches, runOptions.firstSearchNumber,
runOptions.outputStem,
listeners, runOptions.numThreads, runOptions.randomSeed );
}
}