5) Creating a Custom Controler

Note: All the following will probably work.  But it does not use the api, and so we make no promises regarding the future availability of the methods.  A new tutorial is under construction, but not there yet.  kai, apr'10

For simple scenarios, the configuration file offers enough flexibility to specify different simulation set¬ups. But as soon as you want to run more complex scenarios or want to better integrate your own code into MATSim, you will have to get known the important classes of MATSim so you can use them in your code, and better inject your code into MATSim. In the following sections, you will learn how to implement your own controler, which you will be able to extend as you like it. While creating your own controler, you will learn enough about MATSim classes and the “philosophy” behind MATSIm that you should be able to understand most of our code.

Reading network and plans

In a first step, we will just load some the plans and network and run them once through the mobility simulation. Start by creating a new class MyControler that contains a static main method.
At the moment, we will hard code required filenames into the code so it is easier to understand now, but switch later to using configuration files. Define some final String variables for both the network and the plans filename:

final String netFilename = "./examples/equil/network.xml";
final String plansFilename = "./examples/equil/plans100.xml";

The Scenario class contains all the important data containers that MATSim uses, like Network and Population. MATSim provides both data structures and corresponding readers and writers. We will first read the network with a specialized reader into the data structure provided by a Scenario:

Scenario scenario = new ScenarioImpl();
new MatsimNetworkReader(scenario.getNetwork()).readFile(netFilename);

For the population, the code looks similar. Note that the population reader uses the complete scenario object. This allows the reader to e.g. create references to links in the network:

new MatsimPopulationReader(scenario).readFile(plansFilename);

Setting up Events

The mobility simulation will create events, for which we have to provide a data structure as well.

Events events = new Events();

Events cannot be stored within this data structure because of the huge amount of events a large-scale simulation is likely to generate. Instead, the events are processed on-the-fly by so-called events han¬dlers. We will add such a handler that just writes all the events to a file:

EventWriterTXT eventWriter = new EventWriterTXT("./output/events.txt");
events.addHandler(eventWriter);

Running the Mobility Simulation

Finally, we can instantiate a mobility simulation and run it. In this example, we will use the Queue-Simulation:

QueueSimulation sim = new QueueSimulation(scenario, events); sim.run();

After the simulation was run, we have to close the event writer to ensure all data is flushed to disk and that the file is properly closed:

eventWriter.closeFile();

You should now be able to compile and run this example. You can also find the complete code for this example in src/MyControler1.java. You can compile the code with:

javac -cp MATSim_r4763.jar:src src/MyControler1.java (On Windows, replace : with ;) 

and can run it with:

java -cp MATSim_r4763.jar:src MyControler1           (On Windows, replace : with ;) 

Make sure the directory output exists; otherwise the events cannot be written.

Writing Visualizer Output

If you run the example above, all you can see is some information output on the console and the written events. To make it more interesting, we add now output for the visualizer. Before running the mobility simulation (sim.run();), open a writer for the visualizer:

sim.openNetStateWriter("./output/simout", netFilename, 10);

This command will advise the QueueSimulation to write the network state every 10 seconds to a file beginning with “simout”.
We can start the visualizer automatically after the simulation finished with:

String[] visargs = {"./output/simout"};
NetVis.main(visargs);

When you run this example (you can find the complete code for it in src/MyControler2.java), the visualizer should open automatically after the simulation finished.

Using Configuration Settings

Many aspects of MATSim can be configured. The actual configuration is stored in a config object. To create such an object,

use:Config config = Gbl.createConfig(null);

This configuration can then be used when creating a new scenario:

Scenario scenario = new ScenarioImpl(config);

We can set the start and end time of the simulation by adjusting the configuration settings:

config.simulation().setStartTime(Time.parseTime("05:55:00"));
config.simulation().setEndTime(Time.parseTime("08:00:00"));

Add these lines before the QueueSimulation is instantiated to have an effect on the simulation.
Instead of setting all configuration settings manually in the code, we can also use a configuration file. To read a configuration file, replace the call to Gbl.createConfig(null); with:

Config config = Gbl.createConfig(new String[] {"examples/tutorial/myConfig.xml"});

Now you can change settings like the start or end time of the simulation in the file configs/myConfig.xml. You find the example code in src/MyControler3.java.

Scoring Plans

For each plan, a score can be calculated that reflects how well the plan performed during the simula¬tion. If the agent traveled a long time, maybe was even stuck in a traffic jam, the score will be slower than when the agent could travel with free flow speed and had more time to perform activities.

Add the following lines to your code before the simulation is run:

CharyparNagelScoringFunctionFactory factory = new CharyparNagelScoringFunctionFactory(config.charyparNagelScoring());
EventsToScore scoring = new EventsToScore(scenario.getPopulation(), factory);
events.addHandler(scoring);

and the following line after the simulation is run:

scoring.finish();

EventsToScore collects events and uses them to calculate how long the agent was traveling and how long the agent was performing activities. The scoring function calculates the actual scores for traveling or performing an activity. We provide a default scoring function, CharyparNagelScoringFunction, to calculate the effective scores. This two-tier approach makes it possible to only replace the calculation of the score with a more specialized function without having to re-implement all the event-handling functionality. Because the scoring object cannot know when the last event for an agent is handled and thus when the final score for an agent can be calculated, a call to scoring.finish() is necessary. In this call, the final scores for all agents are calculated and assigned to the executed plans.
To calculate the scores, the scorer needs additional settings, which can be done in the configuration file. Make sure that your code loads the configuration file configs/myConfigScoring.xml (instead of examples/tutorial/myConfig.xml).
You can calculate the average score of all plans with:

PlanAverageScore average = new PlanAverageScore();
average.run(scenario.getPopulation());
System.out.println("The average score is " + average.getAverage());

You find the complete example in src/MyControler4.java.

Running Multiple Iterations

If you want to run multiple iterations, you will need to encapsulate the call to the QueueSimulation in a loop:

for (int iteration = 0; iteration <= 10; iteration++) {
QueueSimulation sim = new QueueSimulation(scenario, events);
  sim.openNetStateWriter("./output/simout", netFilename, 10);
  sim.run();
}

But this is not yet very interesting, as in every iteration the exact same plans are executed. We need to define some re-planning that modifies the plans between runs of the mobility simulation.

Adding Re-Planning

Re-planning is a central concept in MATSim, as it is in all agent-based simulation. Without re-planning (sometimes also called agent-learning) the agents would perform the same plans in every iteration. Re-planning is done by applying so-called strategies to the plans. The javadoc for the package org.matsim.demandmodeling.replanning is a good starting point to get accustomed to the terms used to describe re-planning. A strategy can consist of zero or more strategy modules that modify the plans. A strategy manager coordinates the re-planning process. Start by creating a new StrategyManager and adding two strategies to it:

StrategyManager strategyManager = new StrategyManager();

PlanStrategy strategy1 = new PlanStrategy(new BestPlanSelector());

PlanStrategy strategy2 = new PlanStrategy(new RandomPlanSelector());

strategyManager.addStrategy(strategy1, 0.9);

strategyManager.addStrategy(strategy2, 0.1);

As an agent can have more than one plan, we have to tell the strategy which plan should be modified. The first strategy will select the plan with the best score from the agent, while the second strategy will select a random plan from the agent. When adding the strategy to the strategyManager, a weight has to be specified that defines how likely an agent will be modified with the added strategy.
Now we can add a strategy module to one of the strategies:

TravelTimeCalculatorArray ttimeCalc = new TravelTimeCalculator(scenario.getNetwork());

TravelTimeDistanceCostCalculator costCalc = new TravelTimeDistanceCostCalculator(ttimeCalc,config.charyparNagelScoring());

strategy2.addStrategyModule(new ReRouteDijkstra(scenario.getNetwork(), costCalc, ttimeCalc));

events.addHandler(ttimeCalc);

The actual strategy module added is the ReRoute-module. This module calculates the fastest route through the network from one location to another. Because the travel time on the network can vary depending on how many other agents are on the road (maybe causing traffic jams), the ReRoute-module needs some way to figure out the actual travel time on a link at a specified time. That’s what the TravelTimeCalculator is for: It analyzes the events from the mobility simulation and calculates the actual travel time on the links. This is why it has to be added to the events as a handler. The ReRoute-module will then query the TravelTimeCalculator when it has to find the fastest route. Additionally, the routing process needs link costs to find the cheapest route. The TravelTimeDistanceCostCalculator calculates link costs based on the actual travel times and the distance.
The first strategy remains without an actual StrategyModule. It will just select the plan with the best score of an agent for the next execution of the mobility simulation.
In the iteration loop, we can now call the strategyManager to re-plan the agents after the mobility simulation ran and the score is updated:

strategyManager.run(scenario.getPopulation());

If you run this example (you can find it in src/tutorial/MyControler5.java), you should see a similar output than in one of the previous, config-file only, examples.