
io.github.agentsoz.bdimatsim.MATSimModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bdi-matsim Show documentation
Show all versions of bdi-matsim Show documentation
Allows the use of MATsim (www.matsim.org) as the underlying ABM
The newest version!
package io.github.agentsoz.bdimatsim;
import io.github.agentsoz.bdiabm.ABMServerInterface;
import io.github.agentsoz.bdiabm.ModelInterface;
import io.github.agentsoz.bdiabm.QueryPerceptInterface;
import io.github.agentsoz.bdiabm.data.ActionContent;
import io.github.agentsoz.bdiabm.v2.AgentDataContainer;
import io.github.agentsoz.bdiabm.data.PerceptContent;
import io.github.agentsoz.dataInterface.DataClient;
import io.github.agentsoz.dataInterface.DataServer;
import io.github.agentsoz.nonmatsim.PAAgentManager;
import io.github.agentsoz.util.Location;
import io.github.agentsoz.util.ActionList;
import io.github.agentsoz.util.PerceptList;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.config.groups.PlanCalcScoreConfigGroup.ActivityParams;
import org.matsim.core.config.groups.PlanCalcScoreConfigGroup.ModeParams;
import org.matsim.core.config.groups.PlansConfigGroup.ActivityDurationInterpretation;
import org.matsim.core.config.groups.QSimConfigGroup.StarttimeInterpretation;
import org.matsim.core.controler.AbstractModule;
import org.matsim.core.controler.Controler;
import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting;
import org.matsim.core.events.handler.EventHandler;
import org.matsim.core.gbl.Gbl;
import org.matsim.core.mobsim.framework.MobsimAgent;
import org.matsim.core.mobsim.framework.PlayPauseSimulationControl;
import org.matsim.core.mobsim.framework.listeners.MobsimInitializedListener;
import org.matsim.core.mobsim.qsim.AbstractQSimModule;
import org.matsim.core.mobsim.qsim.QSim;
import org.matsim.core.mobsim.qsim.qnetsimengine.*;
import org.matsim.core.network.NetworkChangeEvent;
import org.matsim.core.network.NetworkUtils;
import org.matsim.core.population.routes.RouteUtils;
import org.matsim.core.router.util.LeastCostPathCalculator;
import org.matsim.core.scenario.ScenarioUtils;
import org.matsim.vehicles.Vehicle;
import org.matsim.withinday.mobsim.MobsimDataProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
/*
* #%L
* BDI-ABM Integration Package
* %%
* Copyright (C) 2014 - 2015 by its authors. See AUTHORS file.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
/**
* @author QingyuChen, KaiNagel, Dhi Singh
*/
public final class MATSimModel implements ABMServerInterface, ModelInterface, QueryPerceptInterface, DataClient {
private static final Logger log = LoggerFactory.getLogger(MATSimModel.class);
public static final String MATSIM_OUTPUT_DIRECTORY_CONFIG_INDICATOR = "--matsim-output-directory";
public static final String eGlobalStartHhMm = "startHHMM";
private static final String eConfigFile = "configXml";
private static final String eOutputDir = "outputDir";
private static final String eCongestionEvaluationInterval = "congestionEvaluationInterval";
private static final String eCongestionToleranceThreshold = "congestionToleranceThreshold";
private static final String eCongestionReactionProbability = "congestionReactionProbability";
// Defaults
private String optConfigFile = null;
private String optOutputDir = null;
private double optStartTimeInSeconds = 1.0;
private double optCongestionEvaluationInterval = 180;
private double optCongestionToleranceThreshold = 0.25;
private double optCongestionReactionProbability = 0.10;
private Config config = null;
private boolean configLoaded = false;
private Object sequenceLock;
private Scenario scenario = null;
private boolean scenarioLoaded = false ;
private boolean modelInitialised = false ;
/**
* A helper class essentially provided by the framework, used here. The only direct connection to matsim are the event
* monitors, which need to be registered, via the events monitor registry, as a matsim events handler.
*/
private PAAgentManager agentManager = null;
/**
* This is in fact a MATSim class that provides a view onto the QSim.
*/
@Inject private MobsimDataProvider mobsimDataProvider ;
@Inject private Replanner replanner;
// yy This is working because MATSimModel is bound somewhere.
private QSim qSim;
/**
* can be null (so non-final is ok)
*/
private List eventHandlers;
private PlayPauseSimulationControl playPause;
private final EventsMonitorRegistry eventsMonitors = new EventsMonitorRegistry() ;
private Thread matsimThread;
private DataServer dataServer;
private final Map dataListeners = createDataListeners();
private AgentDataContainer adc = new AgentDataContainer();
public enum RoutingMode {carFreespeed, carGlobalInformation}
private Controler controller;
public MATSimModel(Map opts, DataServer dataServer) {
this( new String [] {
opts.get( eConfigFile ) ,
MATSIM_OUTPUT_DIRECTORY_CONFIG_INDICATOR , opts.get( eOutputDir) ,
eGlobalStartHhMm , opts.get( eGlobalStartHhMm )
} ) ;
// yyyy this is so far NOT the same as what is was originally, see below, since the code below
// could pass "null" which the new code cannot. (However, the "null" was not really handled
// correctly in the receiving code so it needs to be repaired ...). kai, nov'18
// this(opts.get(eConfigFile), opts.get(eOutputDir), opts.get(eGlobalStartHhMm));
registerDataServer(dataServer);
if (opts == null) {
return;
}
for (String opt : opts.keySet()) {
log.info("Found option: {}={}", opt, opts.get(opt));
switch(opt) {
case eGlobalStartHhMm:
optStartTimeInSeconds = convertTimeToSeconds(opts.get(opt).replace(":", ""));
break;
case eConfigFile:
optConfigFile = opts.get(opt);
break;
case eOutputDir:
optOutputDir = opts.get(opt);
break;
case eCongestionEvaluationInterval:
optCongestionEvaluationInterval= Double.parseDouble(opts.get(opt));
break;
case eCongestionToleranceThreshold:
optCongestionToleranceThreshold= Double.parseDouble(opts.get(opt));
break;
case eCongestionReactionProbability:
optCongestionReactionProbability= Double.parseDouble(opts.get(opt));
break;
default:
log.warn("Ignoring option: " + opt + "=" + opts.get(opt));
}
}
}
// public MATSimModel(String matSimFile, String matsimOutputDirectory, String startHHMM) {
// // not the most elegant way of doing this ...
// // yy maybe just pass the whole string from above and take apart ourselves?
// this(
// matsimOutputDirectory==null ?
// new String[]{matSimFile} :
// new String[]{ matSimFile, MATSIM_OUTPUT_DIRECTORY_CONFIG_INDICATOR, matsimOutputDirectory, eGlobalStartHhMm, startHHMM }
// );
// }
public MATSimModel( String[] args) {
// Log level should be set in logback.xml, see
// https://github.com/agentsoz/ees/blob/a769eb9497c444beac7cf823bfae05764eb06356/src/main/resources/logback.xml#L39
//((ch.qos.logback.classic.Logger)log).setLevel( Level.DEBUG);
config = ConfigUtils.loadConfig( args[0] ) ;
Utils.parseAdditionalArguments(args, config);
config.network().setTimeVariantNetwork(true);
config.plans().setActivityDurationInterpretation(ActivityDurationInterpretation.tryEndTimeThenDuration);
config.qsim().setSimStarttimeInterpretation( StarttimeInterpretation.onlyUseStarttime );
config.controler().setWritePlansInterval(1);
config.controler().setOverwriteFileSetting( OverwriteFileSetting.deleteDirectoryIfExists );
config.planCalcScore().setWriteExperiencedPlans(true);
// we have to declare those routingModes where we want to use the network router:
{
Collection modes = config.plansCalcRoute().getNetworkModes();
Set newModes = new TreeSet<>( modes ) ;
for ( RoutingMode mode : RoutingMode.values() ) {
newModes.add( mode.name() ) ;
}
config.plansCalcRoute().setNetworkModes( newModes );
}
// the router also needs scoring parameters:
for ( RoutingMode mode : RoutingMode.values() ) {
ModeParams params = new ModeParams(mode.name());
config.planCalcScore().addModeParams(params);
}
// config.plansCalcRoute().setInsertingAccessEgressWalk(true);
// ConfigUtils.setVspDefaults(config);
// ---
scenario = ScenarioUtils.createScenario(config);
// (this is _created_ already here so that the scenario pointer can be final)
// ---
this.agentManager = new PAAgentManager(eventsMonitors) ;
}
public Config loadAndPrepareConfig() {
// currently already done in constructor. But I want to have this more
// expressive than model.getConfig(). kai, nov'17
configLoaded = true ;
return config ;
}
public Scenario loadAndPrepareScenario() {
if ( !configLoaded ) {
loadAndPrepareConfig() ;
}
ScenarioUtils.loadScenario(scenario) ;
scenarioLoaded=true ;
return scenario ;
}
@Override
public void init(Object[] args) {
List bdiAgentIDs = (List)args[0];
if ( !scenarioLoaded ) {
loadAndPrepareScenario() ;
}
// yy this could now be done in upstream code. But since upstream code is user code, maybe we don't want it in there? kai, nov'17
for(String agentId: bdiAgentIDs) {
agentManager.createAndAddBDIAgent(agentId);
// default action:
agentManager.getAgent(agentId).getActionHandler().registerBDIAction(
ActionList.DRIVETO, new DRIVETODefaultActionHandlerV2(this) );
agentManager.getAgent(agentId).getActionHandler().registerBDIAction(
ActionList.REPLAN_CURRENT_DRIVETO, new ReplanDriveToDefaultActionHandlerV2(this) );
}
{
ActivityParams params = new ActivityParams("driveTo");
params.setScoringThisActivityAtAll(false);
scenario.getConfig().planCalcScore().addActivityParams(params);
}
// ---
controller = new Controler( scenario );
controller.getEvents().addHandler(eventsMonitors);
// Register any supplied event handlers
if (eventHandlers != null) {
for (EventHandler handler : eventHandlers) {
controller.getEvents().addHandler(handler);
}
}
// infrastructure at QSim level (separating line not fully logical)
controller.addOverridingQSimModule( new AbstractQSimModule() {
@Override protected void configureQSim() {
this.bind(Replanner.class).in( Singleton.class ) ;
this.bind( MATSimModel.class ).toInstance( MATSimModel.this );
}
} );
// infrastructure at Controler level (separating line not fully logical)
controller.addOverridingModule(new AbstractModule(){
@Override public void install() {
this.bind( MobsimDataProvider.class ).in( Singleton.class ) ;
this.addMobsimListenerBinding().to( MobsimDataProvider.class ) ;
// (pulls mobsim from Listener Event. maybe not so good ...)
// analysis:
this.addControlerListenerBinding().to( OutputEvents2TravelDiaries.class );
this.addMobsimListenerBinding().toInstance((MobsimInitializedListener) e -> {
// memorize the qSim:
qSim = (QSim) e.getQueueSimulation() ;
// start the playPause functionality
playPause = new PlayPauseSimulationControl( qSim ) ;
playPause.pause();
// initialiseVisualisedAgents() ;
}) ;
// define the turn acceptance logic that reacts to blocked links:
{
ConfigurableQNetworkFactory qNetworkFactory = new ConfigurableQNetworkFactory( controller.getEvents(), scenario );
qNetworkFactory.setTurnAcceptanceLogic( new TurnAcceptanceLogic() {
TurnAcceptanceLogic delegate = new DefaultTurnAcceptanceLogic();
@Override
public AcceptTurn isAcceptingTurn( Link currentLink, QLaneI currentLane, Id nextLinkId, QVehicle veh, QNetwork qNetwork, double now ) {
AcceptTurn accept = delegate.isAcceptingTurn( currentLink, currentLane, nextLinkId, veh, qNetwork, now );
QLinkI nextQLink = qNetwork.getNetsimLink( nextLinkId );
double speed = nextQLink.getLink().getFreespeed( now );
if ( speed < 0.1 ) { // m/s
accept = AcceptTurn.WAIT;
Id driverId = veh.getDriver().getId();
Id currentLinkId = veh.getCurrentLink().getId();
Id vehicleId = veh.getId();
Id blockedLinkId = nextLinkId;
controller.getEvents().processEvent( new NextLinkBlockedEvent( now, vehicleId,
driverId, currentLinkId, blockedLinkId ) );
// yyyy this event is now generated both here and in the agent. In general,
// it should be triggered in the agent, giving the bdi time to compute. However, the
// blockage may happen between there and arriving at the node ... kai, dec'17
}
// log.debug( "time=" + MATSimModel.this.getTime() + ";\t fromLink=" + currentLink.getId() +
// ";\ttoLink=" + nextLinkId + ";\tanswer=" + accept.name() );
return accept;
}
} );
bind( QNetworkFactory.class ).toInstance( qNetworkFactory );
}
}
}) ;
modelInitialised = true;
}
@Override
public void start() {
if (!modelInitialised) {
log.warn("Model not initialised; cannot be run");
return;
}
// wrap the controller into a thread and start it:
this.matsimThread = new Thread( controller ) ;
this.matsimThread.setName("matsim");
matsimThread.start();
// wait until the thread has initialized before returning:
while( this.playPause==null ) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public final Replanner getReplanner() {
return this.replanner ;
}
@Override
public Object[] step(double time, Object[] args) {
if (args != null && args[0] != null && args[0] instanceof AgentDataContainer) {
AgentDataContainer inAdc = (AgentDataContainer) args[0];
return new Object[]{takeControl(time, inAdc)};
}
return null;
}
@Override public final AgentDataContainer takeControl(double time, AgentDataContainer agentDataContainer){
runUntilV2( (int)(playPause.getLocalTime() + 1), agentDataContainer ) ;
return adc;
}
public final void runUntilV2( long newTime , AgentDataContainer inAdc) {
log.trace("Received {} ", inAdc);
agentManager.updateActions(inAdc, adc);
playPause.doStep( (int) (newTime) );
}
@Override
public void setAgentDataContainer(AgentDataContainer adc) {
this.adc = adc;
}
@Override
public AgentDataContainer getAgentDataContainer() {
return adc;
}
public final boolean isFinished() {
return playPause.isFinished() ;
}
@Override
public void finish() {
playPause.play();
while( matsimThread.isAlive() ) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public final Scenario getScenario() {
// needed in the BDIActionHandlers!
return this.scenario ;
}
public void setEventHandlers(List eventHandlers) {
this.eventHandlers = eventHandlers;
}
private final void setFreeSpeedExample(){
// example how to set the freespeed of some link to zero:
final double now = this.qSim.getSimTimer().getTimeOfDay();
if ( now == 0.*3600. + 6.*60. ) {
NetworkChangeEvent event = new NetworkChangeEvent( now ) ;
event.setFreespeedChange(new NetworkChangeEvent.ChangeValue( NetworkChangeEvent.ChangeType.ABSOLUTE_IN_SI_UNITS, 0. ));
event.addLink( scenario.getNetwork().getLinks().get( Id.createLinkId( 51825 )));
NetworkUtils.addNetworkChangeEvent( scenario.getNetwork(),event);
for ( MobsimAgent agent : this.getMobsimDataProvider().getAgents().values() ) {
if ( !(agent instanceof MATSimStubAgent) ) {
this.getReplanner().reRouteCurrentLeg(agent, now);
}
}
}
}
public final void registerDataServer( DataServer server ) {
this.dataServer = server;
server.subscribe(this, PerceptList.TAKE_CONTROL_ABM);
}
@Override public void receiveData(double time, String dataType, Object data) {
// receiveData can be called prior to runUntil for a new time step,
// which can be at a granularity greater than 1 sec. In that case
// time != getTime() is excepted so am disabling this check. DS,Nov18
//
//if ( time+1 < getTime() || time-1 > getTime() ) {
// log.error( "given time in receiveData is {}, simulation time is {}. " +
// "Don't know what that means. Will use given time.",
// time, getTime() );
//}
double now = time; //getTime() ;
switch( dataType ) {
case PerceptList.TAKE_CONTROL_ABM:
dataListeners.get(dataType).receiveData(now, dataType, data);
break;
default:
throw new RuntimeException("Unknown data type received: " + dataType) ;
}
}
/**
* Creates a listener for each type of message we expect from the DataServer
* @return
*/
private Map createDataListeners() {
Map listeners = new HashMap<>();
listeners.put(PerceptList.TAKE_CONTROL_ABM, (DataClient) (time, dataType, data) -> {
synchronized (sequenceLock) {
adc.clear();
runUntilV2((long) time, data);
synchronized (this.getAgentManager().getAgentDataContainerV2()) {
copy(this.getAgentManager().getAgentDataContainerV2(), adc);
this.getAgentManager().getAgentDataContainerV2().clear();
}
dataServer.publish(PerceptList.AGENT_DATA_CONTAINER_FROM_ABM, adc);
}
});
return listeners;
}
private void copy(io.github.agentsoz.bdiabm.v2.AgentDataContainer from, io.github.agentsoz.bdiabm.v2.AgentDataContainer to) {
if (from != null) {
Iterator it = from.getAgentIdIterator();
while (it.hasNext()) {
String agentId = it.next();
// Copy percepts
Map percepts = from.getAllPerceptsCopy(agentId);
for (String perceptId : percepts.keySet()) {
PerceptContent content = percepts.get(perceptId);
to.putPercept(agentId, perceptId, content);
}
// Copy actions
Map actions = from.getAllActionsCopy(agentId);
for (String actionId : actions.keySet()) {
ActionContent content = actions.get(actionId);
to.putAction(agentId, actionId, content);
}
}
}
}
public void addNetworkChangeEvent(double speedInMpS, Link link, double startTime) {
NetworkChangeEvent changeEvent = new NetworkChangeEvent( startTime ) ;
changeEvent.setFreespeedChange(new NetworkChangeEvent.ChangeValue(
NetworkChangeEvent.ChangeType.ABSOLUTE_IN_SI_UNITS, speedInMpS
) ) ;
changeEvent.addLink( link ) ;
// (1) add to mobsim:
this.qSim.addNetworkChangeEvent(changeEvent);
// (2) add to replanner:
this.replanner.addNetworkChangeEvent(changeEvent);
// yyyy wanted to delay this until some agent has actually encountered it. kai, feb'18
}
public static double convertTimeToSeconds(String startHHMM) {
int hours = Integer.parseInt(startHHMM.substring(0, 2));
int minutes = Integer.parseInt(startHHMM.substring(2, 4));
double startTime = hours * 3600 + minutes * 60;
log.debug("orig={}, hours={}, min={}, sTime={}", startHHMM, hours, minutes, startTime);
return startTime;
}
public final double getTime() {
return this.qSim.getSimTimer().getTimeOfDay() ;
}
@Override public Object queryPercept(String agentID, String perceptID, Object args) {
log.debug("received query from agent {} for percept {} with args {}", agentID, perceptID, args);
switch(perceptID) {
case PerceptList.REQUEST_LOCATION:
final Link link = scenario.getNetwork().getLinks().get( this.getMobsimAgentFromIdString(agentID).getCurrentLinkId() );
Location[] coords = {
new Location(link.getFromNode().getId().toString(), link.getFromNode().getCoord().getX(), link.getFromNode().getCoord().getY()),
new Location(link.getToNode().getId().toString(), link.getToNode().getCoord().getX(), link.getToNode().getCoord().getY())
};
return coords;
case PerceptList.REQUEST_DRIVING_DISTANCE_TO :
if (args == null || !(args instanceof double[])) {
throw new RuntimeException("Query percept '"+perceptID+"' expecting double[] coordinates argument, but found: " + args);
}
double[] dest = (double[]) args;
Coord coord = new Coord( dest[0], dest[1] ) ;
final Link destLink = NetworkUtils.getNearestLink(getScenario().getNetwork(), coord );
Gbl.assertNotNull(destLink);
final Link currentLink = scenario.getNetwork().getLinks().get( this.getMobsimAgentFromIdString(agentID).getCurrentLinkId() );
final double now = getTime();
//final Person person = scenario.getPopulation().getPersons().get(agentID);
double res = 0.0;
synchronized (this.replanner) {
LeastCostPathCalculator.Path result = this.replanner.editRoutes(RoutingMode.carFreespeed).getPathCalculator().calcLeastCostPath(
currentLink.getFromNode(), destLink.getFromNode(), now, null, null
);
res = RouteUtils.calcDistance(result);
}
return res;
default:
throw new RuntimeException("Unknown query percept '"+perceptID+"' received from agent "+agentID+" with args " + args);
}
}
public PAAgentManager getAgentManager() {
return agentManager;
}
public MobsimDataProvider getMobsimDataProvider() {
return mobsimDataProvider;
}
public MobsimAgent getMobsimAgentFromIdString( String idString ) {
return this.getMobsimDataProvider().getAgent( Id.createPersonId(idString) ) ;
}
public EventsManager getEvents() {
return this.qSim.getEventsManager() ;
}
public Config getConfig() {
return config;
}
public double getOptCongestionEvaluationInterval() {
return optCongestionEvaluationInterval;
}
public double getOptCongestionToleranceThreshold() {
return optCongestionToleranceThreshold;
}
public double getOptCongestionReactionProbability() {
return optCongestionReactionProbability;
}
public void useSequenceLock(Object sequenceLock) {
this.sequenceLock = sequenceLock;
}
public Controler getControler() {
return controller;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy