EMAworkbench
EMAworkbench copied to clipboard
Connector for Repast Symphony
On the ema homepage it is stated
Future plans include support for Netlogo and Repast http://simulation.tbm.tudelft.nl/ema-workbench/contents.html
While there is already some connector for Netlogo...
- https://github.com/quaquel/EMAworkbench/tree/master/ema_workbench/connectors
- https://github.com/quaquel/pyNetLogo
- http://jasss.soc.surrey.ac.uk/21/2/4.html
... I could not find information about a connector for Repast.
=>Is somebody already working on that? (Tried and failed for some reason?)
Some Links about controlling Repast:
- https://sourceforge.net/p/repast/mailman/message/30465311/
- https://sourceforge.net/p/repast/mailman/message/19672480/
- https://repast.github.io/docs/api/repast_simphony/index.html
I had hoped to do it quickly, but other stuff keeps me busy. I know it probably is an hour of work, because the remote api of repast is quite straight forward. Through jpype, it should be quite simple.
Happy to help if you have time. All I need is some simple pieces of python code:
- loading a repast model
- setting variables
- "pressing run"
- getting variables
with this, a proof is easy to put together
If you would get that done in an hour I would be very impressed. :) I currently try to get jpype running, which needs C++ build tools to be installed. I'll play around with jpype and repast and will let you know how far I get.
if you have conda installing jpype is easy: https://jpype.readthedocs.io/en/latest/install.html
For reference, I built the first prototype of pyNetLogo on the train in 2 hours without much prior jpype knowledge and no netlogo knowledge. It helps that I learned programming in Java.
Repast Symphony comes with a view examples. I used the JZombies_Demo from https://github.com/Repast/repast.simphony.models/tree/master/JZombies_Demo
The model consists mainly of the *.class files that are located in the bin folder after compiling the Demo:
Human.class JZombiesBuilder.class Zombie.class
and configuration files that are located in the folder JZombies_Demo.rs, e.g. parameters.xml
In order to run the model, the Repast Symphony jar files are required, which are located in the lib folder, e.g. repast.simphony.runtime.jar
From the Windows command line the Repast example model can be batch executed with
D:/EclipseJava/app/jdk/bin/java.exe" -cp ".;./lib/*;./bin/." repast.simphony.batch.BatchMain -params ./batch/batch_params.xml -interactive ./JZombies_Demo.rs
Some equivalent jpype code is:
import jpype
import os
repastPath = '.'
libPath = repastPath + '/lib'
libJarPaths = str.join(';', [libPath + '/' + name for name in os.listdir(libPath)])
classPath = repastPath + '/.;' + libJarPaths + ';' + repastPath + '/bin/.'
batch_params_xml_file_path = repastPath + '/batch/batch_params.xml'
repast_config_path = repastPath + '/JZombies_Demo.rs'
jvmPath = 'D:/EclipseJava/App/jdk/bin/server/jvm.dll' #jpype.getDefaultJVMPath()
jpype.startJVM(jvmPath, '-ea', '-Djava.class.path=' + classPath )
link = jpype.JClass('repast.simphony.batch.BatchMain')
link.main(['-params', batch_params_xml_file_path, '-interactive', repast_config_path])
jpype.shutdownJVM()
Still need to find out how to set variable values using the API instead of loading them from .xml files and also how to get output using the API: https://github.com/Repast/repast.simphony/issues/18
you are using the batch functionality here. In many ways, the workbench is a replacement for this with more flexibility, so I would probably not look at doing at that way. Do you have access to the source code that is used by the batch run system?
Fully agree. Just wanted to document what I got so far. (In case that single/subsequent execution through the repast API should be too slow, above strategy might be a fallback.) For now the InstanceRunner
class seems to be the way to go and I'll have a closer look: https://github.com/Repast/repast.simphony/issues/18
@quaquel I managed to create the first draft of some Java Code to remotely control a Repast model. I needed to adapt the repast code / create some new Java classes. Could we put that additional Java source code in the repository of the ema workbench? That would allow the jpype part in python to be more simple.
If you find some time (?), please feel free to improve my first draft, so that the connector can be used not only for TimeSeriesOutput but also for ArrayOutcome and Constraint ect.
https://github.com/Repast/repast.simphony/issues/18
Here is a draft for the Repast connector that works for me. Please feel free to adapt it to your needs and include it in the official release if you want.
RepastRunner.java
package repast.simphony.batch;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import repast.simphony.scenario.ScenarioLoadException;
public class RepastRunner {
public static void main(String[] args) throws IOException {
var projectPath = ".";
var instanceId = "myInstanceId";
var inputs = new HashMap<String, Object>();
inputs.put("zombie_count", 10);
inputs.put("human_count", 20);
var outcomes = Arrays.asList( //
"Agent Counts.run1", //
"Agent Counts.tick", //
"Agent Counts.Human Count", //
"Agent Counts.Zombie Count" //
);
var result = run(projectPath, instanceId, inputs, outcomes);
System.out.println(result);
}
public static Map<String, List<Double>> run(String repastHomePath, String instanceId, Map<String, Object> inputs,
List<String> outcomes) throws IOException {
var parameterFilePath = repastHomePath + "/batch/batch_params.xml";
var scenarioFolderPath = determineScenarioFolderPath(repastHomePath);
System.out.println("Scenario folder path: '" + scenarioFolderPath + "'");
var parameterString = "1\t" //
+ inputs.entrySet() //
.stream() //
.map(entry -> entry.getKey() + "\t" + entry.getValue()) //
.collect(Collectors.joining(","));
// System.out.println("Parameter string: '" + parameterString + "'");
var instanceRunner = new EmaInstanceRunner();
try {
instanceRunner.configure(new String[] { //
"-pxml=" + parameterFilePath, //
"-scenario=" + scenarioFolderPath, //
"-id=" + instanceId, //
parameterString });
} catch (ScenarioLoadException exception) {
var message = "Could not configure InstanceRunner with scenario folder '" + scenarioFolderPath + "' and"
+ "parameter string '" + parameterString + "'.";
throw new IllegalStateException(message, exception);
}
Map<String, List<Double>> result = null;
try {
result = instanceRunner.run(outcomes);
} catch (ScenarioLoadException exception) {
var message = "Could not run InstanceRunner with scenario folder '" + scenarioFolderPath + "' and"
+ "parameter string '" + parameterString + "'.";
throw new IllegalStateException(message, exception);
}
return result;
}
public static void killWorkspace() {
}
private static String determineScenarioFolderPath(String repastHomePath) {
var directory = new File(repastHomePath);
var scenarioFolderPathList = directory.list(new FilenameFilter() {
@Override
public boolean accept(File current, String name) {
var currentElement = new File(current, name);
if (!currentElement.isDirectory()) {
return false;
}
;
return name.endsWith(".rs");
}
});
return repastHomePath + "/"+ scenarioFolderPathList[0];
}
}
EmaInstanceRunner.java
package repast.simphony.batch;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import repast.simphony.batch.BatchConstants;
import repast.simphony.batch.RunningStatus;
import repast.simphony.batch.parameter.ParameterLineParser;
import repast.simphony.data2.DataConstants;
import repast.simphony.data2.DataSetRegistry;
import repast.simphony.data2.DataSink;
import repast.simphony.engine.environment.ControllerAction;
import repast.simphony.engine.environment.RunState;
import repast.simphony.parameter.Parameters;
import repast.simphony.scenario.ScenarioLoadException;
import simphony.util.messages.MessageCenter;
import simphony.util.messages.MessageEvent;
import simphony.util.messages.MessageEventListener;
/**
* Runs a single instance of a simphony model in a batch run. This expects to be
* passed the following arguments:
* <ol>
* <li>-pxml <parameter xml file>
* <li>-scenario <scenario directory>
* <li>-id <instance id>
* <li>optional -pinput <input file> if the parameter input is in a file.
* </ul>
* if no -pinput then last arg is expected to be a string in unrolled parameter
* format. A parameter line hasthe format R\tP1\tV1,P2\tV2,P3\tV3,... R is the
* run number followed by a tab. P* and V* is a parameter name and value pair
* which are separated from each other by a tab and from other PV pairs by a
* comma delimeter.
* <p>
*
* The InstanceRunner will feed each line from the file / command line to the
* model and run the model for that parameter combination. When all the lines
* have been processed the batch run is finished. If there are warnings or
* errors produced during the run then those will be written to a WARN or
* FAILURE file in the working directory. If there is an error, no more lines
* will be read and the InstanceRunner will stop.
*
* original author of InstanceRunner: Nick Collier
* modified by Stefan Eidelloth
*/
public class EmaInstanceRunner {
private static MessageCenter msg = MessageCenter.getMessageCenter(EmaInstanceRunner.class);
private static final String PXML = "pxml";
private static final String ID = "id";
private static final String SCENARIO = "scenario";
private static final String PINPUT = "pinput";
private String input;
private boolean isFileInput = false;
private ParameterLineParser lineParser;
private EmaOneRunBatchRunner runner;
private String id;
private RunningStatus status = RunningStatus.OK;
private Options options;
private Map<String, MemoryDataSink> memoryDataSinks = new HashMap<>();
public EmaInstanceRunner() throws IOException {
Properties props = new Properties();
File in = new File("../MessageCenter.log4j.properties");
props.load(new FileInputStream(in));
PropertyConfigurator.configure(props);
MessageCenter.addMessageListener(new MessageEventListener() {
public void messageReceived(MessageEvent evt) {
Level level = evt.getLevel();
if (level == Level.ERROR || level == Level.WARN || level == Level.FATAL) {
if (level == Level.WARN && status == RunningStatus.OK)
status = RunningStatus.WARN;
else if (level != Level.WARN)
status = RunningStatus.FAILURE;
writeMessage(evt);
}
}
});
initOptions();
}
public Map<String, List<Double>> run(List<String> outcomeKeys) throws ScenarioLoadException {
this.createMemoryDataSinks(outcomeKeys);
runner.batchInit();
String line = null;
try (BufferedReader reader = new BufferedReader(
isFileInput ? new FileReader(input) : new StringReader(input))) {
while ((line = reader.readLine()) != null) {
if (line.trim().length() > 0) {
Parameters params;
try {
params = lineParser.parse(line);
} catch (NumberFormatException exception) {
var message = "Could not parse Parameter line '" + line + "'. Please check if the parameters have the right format, e.g. Integer instead of Float.";
throw new IllegalStateException(message, exception);
}
int runNum = (Integer) params.getValue(BatchConstants.BATCH_RUN_PARAM_NAME);
runner.run(runNum, params);
if (status == RunningStatus.FAILURE)
break;
}
}
} catch (IOException ex) {
throw new ScenarioLoadException("Error while reading parameter input", ex);
}
var resultMap = collectDataFromMemoryDataSinks(outcomeKeys);
runner.batchCleanup();
return resultMap;
}
private void createMemoryDataSinks(List<String> outcomeKeys) {
var controller = runner.getController();
var controllerRegistry = controller.getControllerRegistry();
var contextId = controllerRegistry.getMasterContextId();
var actionTree = controllerRegistry.getActionTree(contextId);
var self = this;
actionTree.addNode(actionTree.getRoot(), new ControllerAction() {
@Override
public void batchInitialize(RunState runState, Object contextId) {
var dataSetRegistry = (DataSetRegistry) runState.getFromRegistry(DataConstants.REGISTRY_KEY);
var dataSetManager = dataSetRegistry.getDataSetManager(contextId);
for(var outcomeKey: outcomeKeys) {
var parts = outcomeKey.split("\\.");
var dataSetKey = parts[0];
if(!self.memoryDataSinks.containsKey(dataSetKey)){
var dataSetBuilder = dataSetManager.getDataSetBuilder(dataSetKey);
if(dataSetBuilder!=null) {
var dataSink = new MemoryDataSink();
self.memoryDataSinks.put(dataSetKey, dataSink);
dataSetBuilder.addDataSink(dataSink);
}
}
}
}
@Override
public void runInitialize(RunState runState, Object contextId, Parameters runParams) {
}
@Override
public void runCleanup(RunState runState, Object contextId) {
}
@Override
public void batchCleanup(RunState runState, Object contextId) {
}
});
}
private Map<String, List<Double>> collectDataFromMemoryDataSinks(List<String> outcomeKeys){
var resultMap = new HashMap<String, List<Double>>();
for(var outcomeKey: outcomeKeys) {
var parts = outcomeKey.split("\\.");
var dataSetId = parts[0];
var dataSourceId = parts[1];
var dataSink = this.memoryDataSinks.get(dataSetId);
if(dataSink!=null) {
//var sourceIds = dataSink.getSourceIds();
var sourceValues = dataSink.getSourceValues(dataSourceId);
if(sourceValues!=null) {
resultMap.put(outcomeKey, sourceValues);
} else {
var message = "Could not find DataSource '" + outcomeKey + "'. Please create it in the Repast model or correct the outcome identifier.";
msg.warn(message);
resultMap.put(outcomeKey, new ArrayList<>());
}
} else {
var message = "Could not find DataSet '" + dataSetId + "'. Please create it in the Repast model or correct the outcome identifier.";
msg.warn(message);
resultMap.put(outcomeKey, new ArrayList<>());
}
}
return resultMap;
}
@SuppressWarnings("static-access")
private void initOptions() {
options = new Options();
Option help = new Option("help", "print this message");
options.addOption(help);
Option pxml = OptionBuilder.withArgName("file").hasArg().withDescription("use given parameter xml file")
.create(PXML);
options.addOption(pxml);
Option scenario = OptionBuilder.withArgName("directory").hasArg().withDescription("use given scenario")
.create(SCENARIO);
options.addOption(scenario);
Option id = OptionBuilder.withArgName("value").hasArg().withDescription("use given value as instance id")
.create(ID);
options.addOption(id);
Option pinput = OptionBuilder.withArgName("file").hasArg()
.withDescription("use given file as run parameter input").create(PINPUT);
options.addOption(pinput);
}
private void writeMessage(MessageEvent evt) {
String fname = status.toString();
File file = new File(fname + "_" + id);
boolean append = file.exists();
PrintWriter writer = null;
try {
writer = new PrintWriter(new FileWriter(file, append));
writer.append(evt.getMessage().toString());
writer.append("\n");
if (evt.getThrowable() != null) {
evt.getThrowable().printStackTrace(writer);
}
} catch (IOException ex) {
if (evt.getThrowable() != null) {
evt.getThrowable().printStackTrace();
}
ex.printStackTrace();
} finally {
if (writer != null)
writer.close();
}
}
public void configure(String[] args) throws IOException, ScenarioLoadException {
CommandLineParser parser = new GnuParser();
try {
CommandLine line = parser.parse(options, args);
if (line.hasOption(PXML)) {
String paramFile = line.getOptionValue(PXML);
File params = new File(paramFile);
lineParser = new ParameterLineParser(params.toURI());
} else {
throw new ScenarioLoadException("Command line is missing required -pxml option");
}
if (line.hasOption(SCENARIO)) {
File scenario = new File(line.getOptionValue(SCENARIO));
runner = new EmaOneRunBatchRunner(scenario);
} else {
throw new ScenarioLoadException("Command line is missing required -scenario option");
}
if (line.hasOption(ID)) {
id = line.getOptionValue(ID);
} else {
throw new ScenarioLoadException("Command line is missing required -id option");
}
if (line.hasOption(PINPUT)) {
input = line.getOptionValue(PINPUT);
isFileInput = true;
} else {
String[] otherArgs = line.getArgs();
input = otherArgs[otherArgs.length - 1];
}
} catch (ParseException ex) {
throw new ScenarioLoadException("Error while parsing command line args", ex);
}
}
class MemoryDataSink implements DataSink {
private Map<String, List<Double>> sourceIdToSourceValuesMap = new HashMap<>();
@Override
public void append(String key, Object value) {
//System.out.println("append");
if(!sourceIdToSourceValuesMap.containsKey(key)) {
this.sourceIdToSourceValuesMap.put(key, new ArrayList<Double>());
}
var doubleValue = Double.parseDouble(value.toString());
this.sourceIdToSourceValuesMap.get(key).add(doubleValue);
}
@Override
public void open(List<String> sourceIds) {
// System.out.println("open");
}
@Override
public void rowStarted() {
// System.out.println("rowStarted");
}
@Override
public void rowEnded() {
// System.out.println("rowEnded");
}
@Override
public void recordEnded() {
// System.out.println("recordEnded");
}
@Override
public void flush() {
// System.out.println("flush");
}
@Override
public void close() {
// System.out.println("close");
}
public List<Double> getSourceValues(String sourceId){
return this.sourceIdToSourceValuesMap.get(sourceId);
}
public Set<String> getSourceIds(){
return this.sourceIdToSourceValuesMap.keySet();
}
}
}
EmaOneRunBatchRunner.java
package repast.simphony.batch;
import java.io.File;
import repast.simphony.batch.BatchScenarioLoader;
import repast.simphony.batch.BatchScheduleRunner;
import repast.simphony.batch.OneRunBatchRunner;
import repast.simphony.engine.controller.DefaultController;
import repast.simphony.engine.environment.AbstractRunner;
import repast.simphony.engine.environment.ControllerRegistry;
import repast.simphony.engine.environment.DefaultRunEnvironmentBuilder;
import repast.simphony.engine.environment.RunEnvironmentBuilder;
import repast.simphony.engine.environment.RunListener;
import repast.simphony.parameter.ParameterConstants;
import repast.simphony.parameter.ParameterSetter;
import repast.simphony.parameter.Parameters;
import repast.simphony.parameter.ParametersCreator;
import repast.simphony.parameter.SweeperProducer;
import repast.simphony.scenario.ScenarioLoadException;
import repast.simphony.scenario.ScenarioLoader;
import simphony.util.messages.MessageCenter;
/**
* original author of OneRunBatchRunner: Nick Collier
* modified by Stefan Eidelloth
*/
public class EmaOneRunBatchRunner implements RunListener {
private static MessageCenter msgCenter = MessageCenter.getMessageCenter(OneRunBatchRunner.class);
private RunEnvironmentBuilder runEnvironmentBuilder;
protected ORBController controller;
protected boolean pause = false;
protected Object monitor = new Object();
protected SweeperProducer producer;
public EmaOneRunBatchRunner(File scenarioDir) throws ScenarioLoadException {
AbstractRunner scheduleRunner = new BatchScheduleRunner();
scheduleRunner.addRunListener(this);
runEnvironmentBuilder = new DefaultRunEnvironmentBuilder(scheduleRunner, true);
controller = new ORBController(runEnvironmentBuilder);
controller.setScheduleRunner(scheduleRunner);
init(scenarioDir);
}
private void init(File scenarioDir) throws ScenarioLoadException {
if (scenarioDir.exists()) {
ScenarioLoader loader = createScenarioLoader(scenarioDir);
ControllerRegistry registry = loader.load(runEnvironmentBuilder);
controller.setControllerRegistry(registry);
} else {
msgCenter.error("Scenario not found", new IllegalArgumentException("Invalid scenario "
+ scenarioDir.getAbsolutePath()));
}
}
//edit: adapted this function to avoid issues with non-existing resource file
private ScenarioLoader createScenarioLoader(File scenarioDir) {
return new BatchScenarioLoader(scenarioDir);
}
public void batchInit() {
controller.batchInitialize();
}
public void batchCleanup() {
controller.batchCleanup();
}
public void run(int runNum, Parameters params) {
pause = true;
params = setupSweep(params);
controller.runParameterSetters(params);
controller.setRunNumber(runNum);
controller.runInitialize(params);
controller.execute();
waitForRun();
controller.runCleanup();
}
protected boolean keepRunning() {
for (ParameterSetter setter : controller.getControllerRegistry().getParameterSetters()) {
if (!setter.atEnd()) {
return true;
}
}
return false;
}
private Parameters setupSweep(Parameters params) {
if (!params.getSchema().contains(ParameterConstants.DEFAULT_RANDOM_SEED_USAGE_NAME)) {
ParametersCreator creator = new ParametersCreator();
creator.addParameters(params);
creator.addParameter(ParameterConstants.DEFAULT_RANDOM_SEED_USAGE_NAME, Integer.class,
(int) System.currentTimeMillis(), false);
params = creator.createParameters();
}
return params;
}
protected void waitForRun() {
// msgCenter.info("Waiting");
synchronized (monitor) {
while (pause) {
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
// msgCenter.info("Done Waiting");
}
protected void notifyMonitor() {
synchronized (monitor) {
monitor.notify();
}
}
/**
* Invoked when the current run has been paused.
*/
public void paused() {
}
/**
* Invoked when the current run has been restarted after a pause.
*/
public void restarted() {
}
/**
* Invoked when the current run has been started.
*/
public void started() {
}
/**
* Invoked when the current run has been stopped. This will stop this thread
* from waiting for the current run to finish.
*/
public void stopped() {
pause = false;
notifyMonitor();
// msgCenter.info("Stopped Called");
}
//edit: added to allow access to controller
public DefaultController getController() {
return controller;
}
private static class ORBController extends DefaultController {
private int runNumber;
/**
* @param runEnvironmentBuilder
*/
public ORBController(RunEnvironmentBuilder runEnvironmentBuilder) {
super(runEnvironmentBuilder);
}
public void setRunNumber(int runNumber) {
this.runNumber = runNumber;
}
/*
* (non-Javadoc)
*
* @see repast.simphony.engine.controller.DefaultController#prepare()
*/
@Override
protected boolean prepare() {
boolean retVal = super.prepare();
this.getCurrentRunState().getRunInfo().setRunNumber(runNumber);
return retVal;
}
/*
* (non-Javadoc)
*
* @see
* repast.simphony.engine.controller.DefaultController#prepareForNextRun()
*/
@Override
protected void prepareForNextRun() {
super.prepareForNextRun();
this.getCurrentRunState().getRunInfo().setRunNumber(runNumber);
}
}
}
EMA Connector repast.py
'''
This module specifies a generic ModelStructureInterface for controlling
Repast Symphony models.
'''
from __future__ import (absolute_import, print_function, division, unicode_literals)
from ema_workbench.em_framework.model import Replicator, SingleReplication
from ema_workbench.em_framework.model import WorkingDirectoryModel
from ema_workbench.util.ema_logging import method_logger
from ema_workbench.util import debug
import os
import sys
import jpype
__all__ = ['RepastModel']
def find_jars(path):
"""Find all jar files in directory and return as list
Parameters
----------
path : str
Path in which to find jar files
Returns
-------
str
List of jar files
"""
jars = []
for root, _, files in os.walk(path):
for file in files: # @ReservedAssignment
if file == 'NetLogo.jar':
jars.insert(0, os.path.join(root, file))
elif file.endswith(".jar"):
jars.append(os.path.join(root, file))
return jars
#Basic project exception
class RepastException(Exception):
pass
class RepastLink(object):
"""Create a link with Repast Simphony. Underneath, the NetLogo JVM
is started through Jpype.
If `jvm_home` is not provided, the link will try to identify the correct parameters automatically.
Parameters
----------
repast_home : str, optional
Home directory of repast project. That home directory must
* contain a scenario sub folder whose name ends with ".rs", e.g. JZombies_Demo.rs
* contains batch sub folder that contains a batch parameter file batch_params.xml
* contain a lib folder with all required repast *.jar files
* contain a bin folder with the compiled model files
jvm_home : str, optional
Java home directory for Jpype
jvmargs : list of str, optional
additional arguments that should be used when starting
the jvm
"""
def __init__(self, repast_home, jvm_path=None, jvmargs=[]):
self.repast_home = repast_home
if not jvm_path:
jvm_path = jpype.getDefaultJVMPath()
self.jvm_path = jvm_path
if sys.platform == 'win32':
path_sep = ';'
else:
path_sep = ':'
if not jpype.isJVMStarted():
libPath = repast_home + '/lib'
paths = find_jars(libPath)
paths.append(os.path.join(repast_home, '.'))
paths.append(os.path.join(repast_home, 'bin'))
joined_paths = path_sep.join(paths)
classPathArgument = '-Djava.class.path={}'.format(joined_paths)
jvm_args = [classPathArgument, ] + jvmargs
try:
jpype.startJVM(jvm_path, *jvm_args)
except RuntimeError as e:
raise e
self.link = jpype.JClass('repast.simphony.batch.RepastRunner')
def run(self, name, experiment, outputVariables):
jExperiment = jpype.java.util.HashMap();
for key, value in experiment.items():
jExperiment.put(key,value)
jOutputs = jpype.java.util.ArrayList()
for outputVariable in outputVariables:
jOutputs.add(outputVariable)
jResults = self.link.run(self.repast_home, name, jExperiment, jOutputs)
''' #This conversion does not seem to be required because ema seem to
#iterate and convert the results by iteself
results = {}
for entry in jResults.entrySet():
key = entry.getKey()
valueList = entry.getValue()
result = [float(value) for value in valueList]
results[key] = np.asarray(result)
'''
return jResults
def kill_workspace(self):
self.link.killWorkspace()
class BaseRepastModel(WorkingDirectoryModel):
'''Base class for interfacing with repast models. This class
extends :class:`em_framework.ModelStructureInterface`.
'''
def __init__(self, name, repast_home, jvm_path=None):
"""
init of class
Parameters
----------
wd : str
working directory for the model.
name : str
name of the modelInterface. The name should contain only
alpha-numerical characters.
repast_home : str, optional
Path to the Repast project
jvm_home : str, optional
Java home directory for Jpype
Raises
------
EMAError if name contains non alpha-numerical characters
Note
----
Anything that is relative to `self.working_directory`should be
specified in `model_init` and not in `src`. Otherwise, the code
will not work when running it in parallel. The reason for this is that
the working directory is being updated by parallelEMA to the worker's
separate working directory prior to calling `model_init`.
"""
super(BaseRepastModel, self).__init__(name, wd=repast_home)
self.run_length = None
self.jvm_path = jvm_path
@method_logger
def model_init(self, policy):
'''
Method called to initialize the model.
Parameters
----------
policy : dict
policy to be run
'''
super(BaseRepastModel, self).model_init(policy)
if not hasattr(self,'repast'):
debug("trying to start Repast")
self.repast = RepastLink(repast_home=self.working_directory, jvm_path=self.jvm_path)
debug("repast started")
@method_logger
def run_experiment(self, experiment):
"""
Method for running an instantiated model structure.
Parameters
----------
experiment : dict like
Raises
------
jpype.JavaException if there is any exception thrown by the repast model
"""
return self.repast.run(self.name, experiment, self.output_variables)
def retrieve_output(self):
"""
Method for retrieving output after a model run.
Returns
-------
dict with the results of a model run.
"""
return self.output
def cleanup(self):
'''
This method is called after finishing all the experiments, but
just prior to returning the results. This method gives a hook for
doing any cleanup, such as closing applications.
In case of running in parallel, this method is called during
the cleanup of the pool, just prior to removing the temporary
directories.
'''
if hasattr(self,'repast'):
self.repast.kill_workspace()
jpype.shutdownJVM()
class RepastModel(Replicator, BaseRepastModel):
pass
class SingleReplicationRepastModel(SingleReplication, BaseRepastModel):
pass
repastDemo.py
from ema_workbench import IntegerParameter
from ema_workbench import Constant
from ema_workbench import TimeSeriesOutcome
from ema_workbench import ema_logging
from ema_workbench import perform_experiments
from ema.connector.repast import SingleReplicationRepastModel
from ema_workbench.analysis.plotting import envelopes
from ema_workbench.analysis.plotting_util import KDE
from matplotlib import pyplot as plt
def main():
#define input
uncertainties = [IntegerParameter('human_count', 10, 20)]
#levers = [IntegerParameter('zombie_count',5,10)]
constants = [Constant('zombie_count',10)]
#define output
outcomes = [
TimeSeriesOutcome('Agent Counts.Human Count'),
TimeSeriesOutcome('Agent Counts.Zombie Count')
]
#instantiate the repast model
repast_home = r'D:\EclipsePython\workspace\ExploratoryModelling\repast'
jvm_path = r'D:\EclipsePython\App\jdk\bin\server\jvm.dll'
vensimModel = SingleReplicationRepastModel("simpleModel", repast_home, jvm_path)
vensimModel.uncertainties = uncertainties
vensimModel.outcomes = outcomes
#vensimModel.levers = levers
vensimModel.constants = constants
#configure logging (e.g. progress messages)
ema_logging.LOG_FORMAT = '[%(name)s/%(levelname)s/%(processName)s] %(message)s'
ema_logging.log_to_stderr(ema_logging.INFO)
results = perform_experiments(vensimModel, scenarios=10)
experiments, outcomes = results
print(experiments.shape)
print(list(outcomes.keys()))
# the plotting functions return the figure and a dict of axes
envelopes(results, group_by='policy', density=KDE, fill=True)
plt.show()
if __name__ == '__main__':
main()
This looks pretty comprehensive and gives me a perfect basis for getting this done. I hope I can work on at the end of April, unless you need it sooner (in which case, I will try to get to it earlier).
I also need the Repast connector... I hope it can be included in the official release soon. Thank you guys.
hope to finally work on this after the summer break. It is quite high on my nice to have list. Problem is that I am not a repast user myself, nor do I have anyone in my direct vicinity that uses it. However, the proof of principle developed here gives me an excellent starting point to finally make this connector.