This section describes setting up a model from scratch. If you like to develop your model using Repast Simphony, refer to RS Models. Of course, simulations need to be triggered somehow. Depending on
Implement createAgents() to create your agents and call addAgent(agent) for every created agent.
Fill the method getAgentIterable() that returns an Iterable over your agents.
Building your own realisation of LaraModel may be a suitable alternative when your model course deviates from the standard one.
There are two different ways of implementing agent classes for LARA:
NOTE: It is important to assign a distinct ID to each agent since the ordering of behavioural options for instance depends on agents' hash code which - by default - is based on its ID. Furthermore, agents' equals() method is based on its ID.
The extending class should implement onEvent() and react to certain events:
@Override public <T extends LaraEvent> void onEvent(T event) { if (event instanceof LAgentPerceptionEvent) { perceive(((LAgentPerceptionEvent) event).getDecisionConfiguration()); } else if (event instanceof LAgentPreprocessEvent) { preProcess(((LAgentPreprocessEvent) event) .getDecisionConfiguration()); } else if (event instanceof LAgentDecideEvent) { decide(((LAgentDecideEvent) event).getDecisionConfiguration()); } else if (event instanceof LAgentPostprocessEvent) { postProcess(((LAgentPostprocessEvent) event) .getDecisionConfiguration()); } else if (event instanceof LAgentExecutionEvent) { execute(((LAgentExecutionEvent) event).getDecisionConfiguration()); } }
Post_processing: Do evaluations of the decision or execution of behaviour in case of synchronised agent triggering here.
Of course, the agent instance needs to register itself at the event bus for the events it handles:
// subscribe to the events the agent should react on eventBus.subscribe(this, LAgentPerceptionEvent.class); eventBus.subscribe(this, LAgentPreprocessEvent.class); eventBus.subscribe(this, LAgentDecideEvent.class); eventBus.subscribe(this, LAgentPostprocessEvent.class); eventBus.subscribe(this, LAgentExecutionEvent.class);
It is important to assign a distinct ID to each agent since the ordering of behavioural options for instance depends on agents' hash code which - by default - is based on its ID. Furthermore, agents' equals() method is based on its ID.
While many properties have a natural retention time, i.e. they become forgotten in case they are not mentioned for a certain time span, others are kind of carved in memory. To model such everlasting memory items one might either...
There are three ways to influence the order of pre-processor components:
These options differ in their flexibility and harshness. Whereas (1) requires only changes in the mode selector component, it is less binding since other components or events might define constraints that counteract the chosen order. Constrains on the level of components (3) are binding but require possibly many new implementations of other components and events to adapt to the new situation. Constrains on the level of events (2) are binding but require possibly many new implementations of other events and components to adapt to the new situation.
Even if the preprocessor is set to its default by LDefaultAgentComp in case it has not been set when it is required the first time, it's a good idea to think about the correct pre-processor configuration. There is also a reason regarding performance: Since the default procedure initialises a separate configuration for every agent it is recommended to use a global configuration before instead:
The pre-processing is configured using an instance of LPreprocessorConfigurator which then provides a LaraPreprocessor. See also How_to_Design_Your_Decision.
LARA provides means to associate each agent with its environment (or a hierarchy of environments). LARA does not directly depend on an agent's environment. Thus it is ok to assign null. However, in many cases the context plays a crucial role in assessing BOs or updating their preferences. Therefore, it is a good idea to keep the features in mind:
When implementing the environment the first decision to make is whether one environment is enough or several environments (possibly as a hierarchy) are required.
In this case, your implementation just needs to extend LaraEnvironment. Of course, the default implementation (LEnvironment)does so but has additional functionality you may not need.
In this case, the LaraSuperEnvironment interface defines the methods one requires to register a bunch of LaraEnvironments at the base environment (the one that is known to the LaraAgentComponent). Since LEnvironment implements LaraSuperEnvironment one may use just this class as base environment (default) or let custom implementations extend it.
The actual selection of a BO is performed by an instance of LaraDecider which is selected in the pre-processor. Current decision mechanisms are:
See the JavaDoc of particular LaraDeciders for their behaviour and opportunities of configuration.
Each Decider is build by a LaraDeciderFactory (LDeliberativeDeciderFactory, LHabitDeciderFactory). The LTreeDeciderFactory requires a LDecionTree to initialise a LTreeDecider (see below).
See Adding Deciders to introduce new deciders.
LARA currently provides these decision modes which are associated with an implementation of LaraDecider:
See Adding Decision Modes to introduce new decision modes.
There are several ways to adapt decision mode selection. Version A) is recommended when the decision mode strongly depends on agent properties and/or varies across agent types. Version B) is required if a custom LaraDecider shall be applied. Version B) might also be a good idea to speed up decision mode selection if it is actually very simple.
Agents may implement the LaraDecisionModeProvidingAgent: the method getDecisionModeSuggestion() returns one of LaraDecisionModes (which is an enumeration). However, whether the return mode is considered depends on the applied LaraDecisionModeSelector. Furthermore, is may be required for the mode selector to choose another mode because the given one is not able to be applied.
The default implementation of LaraDecisionModeSelector is LDefaultDecisionModeSelector. Of course, it is possible to extend this class in order to adapt decision mode selection, for instance for use of LTreeDecider. As LDefaultDecisionModeSelector provides protected methods to perform habit, deliberative, or explorative decision making it is pretty easy to implement the class. For most purposes, only onInternalEvent() needs to be overridden. The class de.cesr.lara.houseplatn.decision.DecisionModeSelector within the Houseplant model gives an example.
Depending on the selected decision mode the pre-processor needs to perform certain steps like collecting BOs and updating preferences. The LaraDecisionModeSelector is responsible to fire according event, e.g.:
protected void doDeliberative() { LaraDeciderFactory<A, BO> factory = LDeliberativeDeciderFactory.getFactory(agent.getClass()); agent.getLaraComp().getDecisionData(dConfig).setDeciderFactory(factory); eBus.publish(new LPpBoCollectorEvent(agent, dConfig)); eBus.publish(new LPpBoPreselectorEvent(agent, dConfig)); eBus.publish(new LPpBoUtilityUpdaterEvent(agent, dConfig)); eBus.publish(new LPpPreferenceUpdaterEvent(agent, dConfig)); }
Finally, the LPreprocessor needs to be configured for the newly implemented decision mode selector:
LaraPreprocessorConfigurator<AgentType, BehaviouralOption> ppConfigurator = LPreprocessorConfigurator.<AgentType, BehaviouralOption> getNewPreprocessorConfigurator(); ppConfigurator.setDecisionModeSelector(new YourDecisionModeSelector<AgentType, BehaviouralOption>(), yourDecisionConfiguration); preprocessor = ppConfigurator.getPreprocessor();
The preprocessor then needs to be assigned to each agent's LARA component, of course:
getLaraComp().setPreprocessor(preprocessor);
Post-Processing is intended to follow up the basic decision making before a selected BO is executed. This enables the agent to revise its selected decision, e.g. if the BO's performance is worse than desired. Furthermore, it is responsible to store the selected BO in the agent's general memory (this is the only action that LDefaultPostProcessorComp performs).
To implement custom post-processing, extend LDefaultPostProcessorComp and implement it's postProcess(A agent, LaraDecisionConfiguration dConfig) method. NOTE: Models that use the LDefaultDecisionModeSelector or a similar decision mode selector that enables habit decision mode need to call the super method to store selected BOs!
A LaraPostProcessor is assigned to the LaraAgentComponent for all decision. This is due to performance issues and practicable because special post-processing apart from the default storage of selected BO is rare. However, it is possible to implement a meat post-processor that references post-processors for certain decisions.
Components are connected to the Eventbus following a publisher/subscriber model.
Event | Publisher (typically) | Subscriber (typically) | Purpose | State before | State after |
LModelInstantiatedEvent | Your model controller | Your models base class (AbstractLaraModel) | Is fired on instantiation of the model, triggers initialization of your models base class. | You just created a new instance of your model. | Your models base class (AbstractLaraModel) initializes itself. |
LInternalModelInitializedEvent | LARA internal (AbstractLaraModel) | Your model | Is fired when the abstract model is initialized. | The abstract model is initialized. Everythink is setup, so you can now initialize your model. | Your model is initialized and ready to simulate the timesteps. |
LModelStepEvent | Your model controller | Your model and your models base class (AbstractLaraModel) | Is fired when a new timestep begins. Your model should trigger the model cycle (perceive » preprocess » decide » post » execute) now. | The model has just been initialized and the model cycle begins OR the previous timestep just ended. | A timestep has been simulated. |
LUpdateEnvironmentEvent | Your model | Your environment | Triggers the environment to update itself. Typically called when a new timestep begins. | Your environment might be out-of-date. | Your environment is now up-to-date. Environment is set up for the timestep. |
LAgentPerceptionEvent | Your model | Your agents | Triggers agents to perceive (every timestep). | A new timestep begins, so there are potential changes in the environment. | The agent has an up-to-date view of the current state of the environment. |
LAgentPreprocessEvent | Your model | Your agent | Triggers agents to preprocess. | Agents perceived their environment. | Agents are ready to decide about their actions. |
LAgentDecideEvent | Your model | Your agent | Triggers agents to decide (every timestep) | Agents have perceived the environment for the current timestep, but did not make their decisions yet. | Decisions are made, ready for action. |
LAgentPostprocessEvent | Your model | Your agent | Triggers agents to postprocess. | Decisons were made by the agents. | The agents actions are ready for execution. |
LAgentExcecutionEvent | Your model | Your agent | Triggers agents to execute (every timestep) - execute an action in LARA context typically means update the environment. | The agents decisions where made. | Agents changed the environment as a result of their actions. Next timestep can begin. |
LPostExcecutionEvent | Your model | Your agent | Used for internal components. You can hook in. | Agent did execute. | Timestep ends. |
LModelFinishEvent | Your model controller | Your model | Is fired when a simulation run is finished. | The simulation has run and has now finished. | File handlers are closed, all result data is stored etc. The program can now exit. |
Subscribers must implement the interface LaraEventSubscriber. This requires the implementation of the method onEvent(T event). This method is called by the eventbus whenever an event of a type your components subscribes to is published. The code for the onEvent(T event) method in your component could look something like the following:
@Override public void onEvent(T event) { if (event instanceof MyCustomEvent) { // do some work here } }
To make subscribers listen to specific classes one needs to apply if-statements in the onEvent method and trigger warnings/error messages if other events occur. Finally, subscriptions works like this:
LaraEventbus eventbus = LModel.getLModel(id).getEventbus(); eventbus.subscribe(this, MyCustomEvent.class); // your component will now be notified of events of type MyCustomEvent.class
Assuming you would like to have your class MyCustomPublisher publish an event of type MyCustomEvent.class you would put something similar to the following code in the code of your component.
LaraEventbus eventbus = LaraEventbus.getInstance(); // create an instance of an event an publish it even tbus.publish(new MyCustomEvent()); // all subscribers of events of type MyCustomEvent.class will now be notified
Even that depend on the former execution of another event should implement LaraRequiresPrecedingEvent. The eventbus then checks whether the event returned by getRequiredPrecedingEventClass() has been fired. Otherwise, it issues a WARN message and does not trigger the event.
Events are marked having consecutive events by implementing the interface LaraHasConsecutiveEvent and the method getConsecutiveEvent(). This means, after the event is performed the eventbus fires the consecutive evnt automatically.
There is an option to force LEventbus to treat event sequentially no matter how they are defined:
PmParameterManager.setParameter(de.cesr.lara.components.param.LBasicPa.EVENTBUS_FORCE_SEQUENTIAL, true);
NOTE: Make sure to set the parameter before the LEventbus is initialised!
Whenever possible, event classes should be implemented as singletons (only private constructor + getInstance())
There are different classes of events to realise that cause a different execution behaviour:
Event class | Execution |
LaraSynchronousEvent | Synchronous (Parallel execution, Eventbus waits until all subscribers finish) |
LaraAsynchronousEvent | Asynchronous (Parallel execution, Eventbus does not wait) |
LaraSequentialEvent | Sequential (sequential execution) |
Decision trees are suitable to implement rule-based heuristics for decision making. In this case, the result objects are BOs. Furhermore, they can be used to determine the decision mode. Result objects are then likely to be of type LaraDecisionModes.
LARA currently provides abstract classes for binary trees. Each inner part of the tree has an evaluate() method that decides which subtree to follow. Leaves have a getSelectedBos() method.
Desicion trees always return a set of result objects instead of single result object to be useable also as preselector in the pre-processor step. It is convenient to use LSet to easily initialize the required set.
LaraDeciderFactory<Agent, AbstractIrrigationBehaviouralOption<Agent>> factory = new LTreeDeciderFactory<Agent, AbstractIrrigationBehaviouralOption<Agent>>(new DecisionTree()); agent.getLaraComp().getDecisionData(dConfig).setDeciderFactory(factory);
An example for the use of a decision tree is in the houseplant model, classes DecisionModeSelector and DecisionTree. Another compact example can be found in the test class LTreeDeciderTest.
When you do several runs in line, do not forget to clear the agent collection. Especially in Repast Simphony, the class that implements ContextBuilder stays alive, and agents in collections that are properties of this class may survive as well. Since the LAbstractAgent implements equals() and hashCode() according to the agent ID, this might cause a lot of trouble.
Furthermore, you need to reset LARA:
LModel.getModel().resetLara()
To simplify accessing agent methods we use java generics. However, there is an obstacle using generics in combination with the Singleton Design Pattern that ensures a limited amount of instances existing. To preserve efficiency, consider the javadoc of LDeliberativeDeciderFactory, for instance.
LARA uses Log4j (API) for logging all kind of information. It provides extensive support to get the messages you want in a way you need.
Configuring Log4j is quite comfortable by using name/value-pairs in configuration files. These file are located in the "config/log4j" folder. The files mostly contain several options to choose from. Symbols for the pattern layout are found here. To apply a configuration file, add
-Dlog4j.configuration=confFilename
as VM argument in the Run Configuration.
To analyze logs in eclipse, Ganymed is a valuable tool. Get it by adding
http://ganymede.sf.net/update
as software site in eclipse. It requires the SocketAppender to receive logs from Log4j.
Another famous standalone-tools for analyzing log event is Chainsaw, that should work well with log files produced by Log4j's XMLLayout Appender.
Basically, the decision mode is represented by the applied decider and can be accessed as follows:
agent.getLaraComp().getDecisionData(decisionConfig).getDecider().getDecisionMode()
Note that the decider is no longer available when an LAgentPostExecutionEvent was triggered that removes the according decision data.