Java tutorial
/* *********************************************************************** * * project: org.matsim.* * QueueLink.java * * * *********************************************************************** * * * * copyright : (C) 2007 by the members listed in the COPYING, * * LICENSE and WARRANTY file. * * email : info at matsim dot org * * * * *********************************************************************** * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * See also COPYING, LICENSE and WARRANTY file * * * * *********************************************************************** */ package playground.sergioo.ptsim2013.qnetsimengine; import org.apache.commons.math.MathException; import org.apache.commons.math.distribution.NormalDistributionImpl; import org.apache.log4j.Logger; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.events.*; 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.gbl.Gbl; import org.matsim.core.gbl.MatsimRandom; import org.matsim.core.mobsim.framework.MobsimAgent; import org.matsim.core.mobsim.framework.MobsimAgent.State; import org.matsim.core.mobsim.framework.MobsimDriverAgent; import org.matsim.core.mobsim.framework.PassengerAgent; import org.matsim.core.mobsim.qsim.interfaces.MobsimVehicle; import org.matsim.core.mobsim.qsim.pt.TransitDriverAgent; import org.matsim.core.mobsim.qsim.qnetsimengine.FIFOVehicleQ; import org.matsim.core.mobsim.qsim.qnetsimengine.NetsimLink; import org.matsim.core.mobsim.qsim.qnetsimengine.QVehicle; import org.matsim.core.mobsim.qsim.qnetsimengine.VehicleQ; import org.matsim.core.network.LinkImpl; import org.matsim.core.network.NetworkImpl; import org.matsim.core.utils.misc.Time; import org.matsim.pt.transitSchedule.api.TransitStopFacility; import org.matsim.signals.mobsim.DefaultSignalizeableItem; import org.matsim.core.mobsim.qsim.qnetsimengine.SignalGroupState; import org.matsim.vehicles.Vehicle; import org.matsim.vis.snapshotwriters.VisData; import playground.sergioo.ptsim2013.TransitSheduleToNetwork; import playground.sergioo.singapore2012.transitRouterVariable.stopStopTimes.StopStopTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Please read the docu of QBufferItem, QLane, QLinkInternalI (arguably to be renamed * into something like AbstractQLink) and QLinkImpl jointly. kai, nov'11 * * @author dstrippgen * @author dgrether * @author mrieser */ public class PTQLink implements NetsimLink { // static variables (no problem with memory) final private static Logger log = Logger.getLogger(PTQLink.class); private static int spaceCapWarningCount = 0; static boolean HOLES = false; // can be set from elsewhere in package, but not from outside. kai, nov'10 private static int congDensWarnCnt = 0; private static int congDensWarnCnt2 = 0; /** * The remaining integer part of the flow capacity available in one time step to move vehicles into the * buffer. This value is updated each time step by a call to * {@link #updateBufferCapacity(double)}. */ double remainingflowCap = 0.0; /** * Stores the accumulated fractional parts of the flow capacity. See also * flowCapFraction. */ double flowcap_accumulate = 1.0; /** * true, i.e. green, if the link is not signalized */ boolean thisTimeStepGreen = true; double inverseFlowCapacityPerTimeStep; double flowCapacityPerTimeStepFractionalPart; /** * The number of vehicles able to leave the buffer in one time step (usually 1s). */ double flowCapacityPerTimeStep; int bufferStorageCapacity; double usedBufferStorageCapacity = 0.0; // instance variables (problem with memory) private final Queue<Hole> holes = new LinkedList<Hole>(); private static final Comparator<QVehicle> VEHICLE_EXIT_COMPARATOR = new QVehicleEarliestLinkExitTimeComparator(); final Link link; final QNetwork network; // joint implementation for Customizable private Map<String, Object> customAttributes = new HashMap<String, Object>(); private final Map<Id<Vehicle>, QVehicle> parkedVehicles = new LinkedHashMap<Id<Vehicle>, QVehicle>(10); private final Map<Id<Person>, MobsimAgent> additionalAgentsOnLink = new LinkedHashMap<Id<Person>, MobsimAgent>(); private final Map<Id<Vehicle>, Queue<MobsimDriverAgent>> driversWaitingForCars = new LinkedHashMap<Id<Vehicle>, Queue<MobsimDriverAgent>>(); private final Map<Id<Person>, MobsimDriverAgent> driversWaitingForPassengers = new LinkedHashMap<Id<Person>, MobsimDriverAgent>(); // vehicleId private final Map<Id<Vehicle>, Set<MobsimAgent>> passengersWaitingForCars = new LinkedHashMap<Id<Vehicle>, Set<MobsimAgent>>(); /** * A list containing all transit vehicles that are at a stop but not * blocking other traffic on the lane. */ /*package*/ final Queue<QVehicle> transitVehicleStopQueue = new PriorityQueue<QVehicle>(5, VEHICLE_EXIT_COMPARATOR); /** * All vehicles from parkingList move to the waitingList as soon as their time * has come. They are then filled into the vehQueue, depending on free space * in the vehQueue */ /*package*/ final Queue<QVehicle> waitingList = new LinkedList<QVehicle>(); /*package*/ NetElementActivator netElementActivator; /*package*/ final boolean insertingWaitingVehiclesBeforeDrivingVehicles; /** * Reference to the QueueNode which is at the end of each QueueLink instance */ private final QNode toQueueNode; private boolean active = false; private final double length; private double freespeedTravelTime = Double.NaN; /** the last timestep the front-most vehicle in the buffer was moved. Used for detecting dead-locks. */ private double bufferLastMovedTime = Time.UNDEFINED_TIME; /** * The list of vehicles that have not yet reached the end of the link * according to the free travel speed of the link */ private final VehicleQ<QVehicle> vehQueue; /** * This needs to be a ConcurrentHashMap because it can be accessed concurrently from * two different threads via addFromIntersection(...) and popFirstVehicle(). */ private final Map<QVehicle, Double> linkEnterTimeMap = new ConcurrentHashMap<QVehicle, Double>(); private double storageCapacity; private double usedStorageCapacity; /** * Holds all vehicles that are ready to cross the outgoing intersection */ private final Queue<QVehicle> buffer = new LinkedList<QVehicle>(); /** * null if the link is not signalized */ private DefaultSignalizeableItem qSignalizedItem = null; private double congestedDensity_veh_m; private int nHolesMax; /** * Initializes a QueueLink with one QueueLane. * @param link2 * @param queueNetwork * @param toNode */ public PTQLink(final Link link2, QNetwork network, final QNode toNode, StopStopTime stopStopTime) { this(link2, network, toNode, new FIFOVehicleQ(), stopStopTime); } /** * This constructor allows inserting a custom vehicle queue proper, e.g. to implement passing. * */ public PTQLink(final Link link2, QNetwork network, final QNode toNode, final VehicleQ<QVehicle> vehicleQueue, StopStopTime stopStopTime) { this.link = link2; this.network = network; this.netElementActivator = network.simEngine; this.insertingWaitingVehiclesBeforeDrivingVehicles = network.simEngine.getMobsim().getScenario().getConfig() .qsim().isInsertingWaitingVehiclesBeforeDrivingVehicles(); this.toQueueNode = toNode; this.vehQueue = vehicleQueue; this.length = this.getLink().getLength(); this.freespeedTravelTime = this.length / this.getLink().getFreespeed(); this.stopStopTime = stopStopTime; if (Double.isNaN(this.freespeedTravelTime)) { throw new IllegalStateException( "Double.NaN is not a valid freespeed travel time for a link. Please check the attributes length and freespeed!"); } this.calculateCapacities(); if (HOLES) { for (int ii = 0; ii < this.storageCapacity; ii++) { Hole hole = new Hole(); hole.setEarliestLinkExitTime(Double.NEGATIVE_INFINITY); this.holes.add(hole); } // this does, once more, not work with variable vehicle sizes. kai, may'13 } } /* * There are two "active" functionalities (see isActive()). It probably still works, but it does not look like * it is intended this way. kai, nov'11 */ void activateLink() { if (!this.active) { netElementActivator.activateLink(this); this.active = true; } } void clearVehicles() { double now = this.network.simEngine.getMobsim().getSimTimer().getTimeOfDay(); /* * Some agents might be present in multiple lists/maps. * Ensure that only one stuck event per agent is created. */ Set<Id<Person>> stuckAgents = new HashSet<Id<Person>>(); for (QVehicle veh : this.parkedVehicles.values()) { if (veh.getDriver() != null) { // skip transit driver which perform an activity while their vehicle is parked if (veh.getDriver().getState() != State.LEG) continue; if (stuckAgents.contains(veh.getDriver().getId())) continue; else stuckAgents.add(veh.getDriver().getId()); this.network.simEngine.getMobsim().getEventsManager().processEvent(new PersonStuckEvent(now, veh.getDriver().getId(), veh.getCurrentLink().getId(), veh.getDriver().getMode())); this.network.simEngine.getMobsim().getAgentCounter().incLost(); this.network.simEngine.getMobsim().getAgentCounter().decLiving(); } for (PassengerAgent passenger : veh.getPassengers()) { if (stuckAgents.contains(passenger.getId())) continue; else stuckAgents.add(passenger.getId()); MobsimAgent mobsimAgent = (MobsimAgent) passenger; this.network.simEngine.getMobsim().getEventsManager().processEvent(new PersonStuckEvent(now, mobsimAgent.getId(), veh.getCurrentLink().getId(), mobsimAgent.getMode())); this.network.simEngine.getMobsim().getAgentCounter().incLost(); this.network.simEngine.getMobsim().getAgentCounter().decLiving(); } } this.parkedVehicles.clear(); for (MobsimAgent driver : driversWaitingForPassengers.values()) { if (stuckAgents.contains(driver.getId())) continue; else stuckAgents.add(driver.getId()); this.network.simEngine.getMobsim().getEventsManager().processEvent( new PersonStuckEvent(now, driver.getId(), driver.getCurrentLinkId(), driver.getMode())); this.network.simEngine.getMobsim().getAgentCounter().incLost(); this.network.simEngine.getMobsim().getAgentCounter().decLiving(); } driversWaitingForPassengers.clear(); for (Queue<MobsimDriverAgent> queue : driversWaitingForCars.values()) { for (MobsimAgent driver : queue) { if (stuckAgents.contains(driver.getId())) continue; stuckAgents.add(driver.getId()); this.network.simEngine.getMobsim().getEventsManager().processEvent( new PersonStuckEvent(now, driver.getId(), driver.getCurrentLinkId(), driver.getMode())); this.network.simEngine.getMobsim().getAgentCounter().incLost(); this.network.simEngine.getMobsim().getAgentCounter().decLiving(); } } driversWaitingForCars.clear(); for (Set<MobsimAgent> passengers : passengersWaitingForCars.values()) { for (MobsimAgent passenger : passengers) { if (stuckAgents.contains(passenger.getId())) continue; else stuckAgents.add(passenger.getId()); this.network.simEngine.getMobsim().getEventsManager().processEvent(new PersonStuckEvent(now, passenger.getId(), passenger.getCurrentLinkId(), passenger.getMode())); this.network.simEngine.getMobsim().getAgentCounter().incLost(); this.network.simEngine.getMobsim().getAgentCounter().decLiving(); } } this.passengersWaitingForCars.clear(); for (QVehicle veh : this.waitingList) { if (stuckAgents.contains(veh.getDriver().getId())) continue; else stuckAgents.add(veh.getDriver().getId()); this.network.simEngine.getMobsim().getEventsManager().processEvent(new PersonStuckEvent(now, veh.getDriver().getId(), veh.getCurrentLink().getId(), veh.getDriver().getMode())); this.network.simEngine.getMobsim().getAgentCounter().incLost(); this.network.simEngine.getMobsim().getAgentCounter().decLiving(); } this.waitingList.clear(); for (QVehicle veh : this.vehQueue) { this.network.simEngine.getMobsim().getEventsManager().processEvent(new PersonStuckEvent(now, veh.getDriver().getId(), veh.getCurrentLink().getId(), veh.getDriver().getMode())); this.network.simEngine.getMobsim().getAgentCounter().incLost(); this.network.simEngine.getMobsim().getAgentCounter().decLiving(); } this.vehQueue.clear(); this.linkEnterTimeMap.clear(); for (QVehicle veh : this.buffer) { this.network.simEngine.getMobsim().getEventsManager().processEvent(new PersonStuckEvent(now, veh.getDriver().getId(), veh.getCurrentLink().getId(), veh.getDriver().getMode())); this.network.simEngine.getMobsim().getAgentCounter().incLost(); this.network.simEngine.getMobsim().getAgentCounter().decLiving(); } this.buffer.clear(); this.usedBufferStorageCapacity = 0; } boolean doSimStep(double now) { updateBufferCapacity(); if (this.insertingWaitingVehiclesBeforeDrivingVehicles) { moveWaitToBuffer(now); moveLaneToBuffer(now); } else { moveLaneToBuffer(now); moveWaitToBuffer(now); } // moveLaneToBuffer moves vehicles from lane to buffer. Includes possible vehicle arrival. Which, I think, would only be triggered // if this is the original lane. // moveWaitToBuffer moves waiting (i.e. just departed) vehicles into the buffer. this.active = this.isActive(); return active; } /** * Move vehicles from link to buffer, according to buffer capacity and * departure time of vehicle. Also removes vehicles from lane if the vehicle * arrived at its destination. * * @param now * The current time. */ private void moveLaneToBuffer(final double now) { QVehicle veh; this.moveTransitToQueue(now); // handle regular traffic while ((veh = this.vehQueue.peek()) != null) { if (veh.getEarliestLinkExitTime() > now) { return; } MobsimDriverAgent driver = veh.getDriver(); boolean handled = this.handleTransitStop(now, veh, driver); if (!handled) { // Check if veh has reached destination: if ((this.getLink().getId().equals(driver.getDestinationLinkId())) && (driver.chooseNextLinkId() == null)) { this.addParkedVehicle(veh); network.simEngine.letVehicleArrive(veh); this.makeVehicleAvailableToNextDriver(veh, now); // remove _after_ processing the arrival to keep link active this.vehQueue.poll(); this.usedStorageCapacity -= veh.getSizeInEquivalents(); if (HOLES) { Hole hole = new Hole(); hole.setEarliestLinkExitTime(now + this.link.getLength() * 3600. / 15. / 1000.); holes.add(hole); } continue; } /* is there still room left in the buffer, or is it overcrowded from the * last time steps? */ if (!hasFlowCapacityLeftAndBufferSpace()) { return; } if (driver instanceof TransitDriverAgent) { TransitDriverAgent trDriver = (TransitDriverAgent) driver; Id<Link> nextLinkId = trDriver.chooseNextLinkId(); if (nextLinkId == null || nextLinkId.equals(trDriver.getCurrentLinkId())) { // special case: transit drivers can specify the next link being the current link // this can happen when a transit-lines route leads over exactly one link // normally, vehicles would not even drive on that link, but transit vehicles must // "drive" on that link in order to handle the stops on that link // so allow them to return some non-null link id in chooseNextLink() in order to be // placed on the link, and here we'll remove them again if needed... // ugly hack, but I didn't find a nicer solution sadly... mrieser, 5mar2011 // Beispiel: Kanzler-Ubahn in Berlin. Im Visum-Netz mit nur 1 Kante, mit Haltestelle am Anfang und // am Ende der Kante. Zweite Haltestelle wird nur bedient, wenn das Fahrzeug im matsim-Sinne zum // zweiten Mal auf die Kante gesetzt wird (oder so hnlich, aber wir brauchen "nextLink==currentLink"). // kai & marcel, mar'12 network.simEngine.letVehicleArrive(veh); this.addParkedVehicle(veh); makeVehicleAvailableToNextDriver(veh, now); // remove _after_ processing the arrival to keep link active this.vehQueue.poll(); this.usedStorageCapacity -= veh.getSizeInEquivalents(); if (HOLES) { Hole hole = new Hole(); hole.setEarliestLinkExitTime(now + this.link.getLength() * 3600. / 15. / 1000.); holes.add(hole); } continue; } } addToBuffer(veh, now); this.vehQueue.poll(); this.usedStorageCapacity -= veh.getSizeInEquivalents(); if (HOLES) { Hole hole = new Hole(); double offset = this.link.getLength() * 3600. / 15. / 1000.; hole.setEarliestLinkExitTime( now + 0.9 * offset + 0.2 * MatsimRandom.getRandom().nextDouble() * offset); holes.add(hole); } } } // end while } /*package*/ final void addParkedVehicle(MobsimVehicle vehicle) { QVehicle qveh = (QVehicle) vehicle; // cast ok: when it gets here, it needs to be a qvehicle to work. this.parkedVehicles.put(qveh.getId(), qveh); qveh.setCurrentLink(this.link); } /*package*/ final QVehicle removeParkedVehicle(Id<Vehicle> vehicleId) { return this.parkedVehicles.remove(vehicleId); } /*package*/ QVehicle getParkedVehicle(Id<Vehicle> vehicleId) { return this.parkedVehicles.get(vehicleId); } final void letVehicleDepart(QVehicle vehicle, double now) { MobsimDriverAgent driver = vehicle.getDriver(); if (driver == null) throw new RuntimeException("Vehicle cannot depart without a driver!"); EventsManager eventsManager = network.simEngine.getMobsim().getEventsManager(); eventsManager.processEvent(new PersonEntersVehicleEvent(now, driver.getId(), vehicle.getId())); this.addDepartingVehicle(vehicle); } /* * If the vehicle is parked at the current link, insert the passenger, * create an enter event and return true. Otherwise add the agent to * the waiting list and return false. */ final boolean insertPassengerIntoVehicle(MobsimAgent passenger, Id<Vehicle> vehicleId, double now) { QVehicle vehicle = this.getParkedVehicle(vehicleId); // if the vehicle is not parked at the link, mark the agent as passenger waiting for vehicle if (vehicle == null) { registerPassengerAgentWaitingForCar(passenger, vehicleId); return false; } else { boolean added = vehicle.addPassenger((PassengerAgent) passenger); if (!added) { log.warn("Passenger " + passenger.getId().toString() + " could not be inserted into vehicle " + vehicleId.toString() + " since there is no free seat available!"); return false; } ((PassengerAgent) passenger).setVehicle(vehicle); EventsManager eventsManager = network.simEngine.getMobsim().getEventsManager(); eventsManager.processEvent(new PersonEntersVehicleEvent(now, passenger.getId(), vehicle.getId())); // TODO: allow setting passenger's currentLinkId to null return true; } } final boolean addTransitToBuffer(final double now, final QVehicle veh) { if (veh.getDriver() instanceof TransitDriverAgent) { TransitDriverAgent driver = (TransitDriverAgent) veh.getDriver(); while (true) { TransitStopFacility stop = driver.getNextTransitStop(); if ((stop != null) && (stop.getLinkId().equals(getLink().getId()))) { double delay = driver.handleTransitStop(stop, now); if (delay > 0.0) { veh.setEarliestLinkExitTime(now + delay); // add it to the stop queue, can do this as the waitQueue is also non-blocking anyway transitVehicleStopQueue.add(veh); return true; } } else { return false; } } } return false; } void makeVehicleAvailableToNextDriver(QVehicle veh, double now) { /* * Insert waiting passengers into vehicle. */ Id<Vehicle> vehicleId = veh.getId(); Set<MobsimAgent> passengers = this.passengersWaitingForCars.get(vehicleId); if (passengers != null) { // Copy set of passengers since otherwise we would modify it concurrently. List<MobsimAgent> passengersToHandle = new ArrayList<MobsimAgent>(passengers); for (MobsimAgent passenger : passengersToHandle) { this.unregisterPassengerAgentWaitingForCar(passenger, vehicleId); this.insertPassengerIntoVehicle(passenger, vehicleId, now); } } /* * If the next driver is already waiting for the vehicle, check whether * all passengers are also there. If not, the driver is not inserted * into the vehicle and the vehicle does not depart. */ final Queue<MobsimDriverAgent> driversWaitingForCar = driversWaitingForCars.get(veh.getId()); final boolean thereIsDriverWaiting = driversWaitingForCar != null && !driversWaitingForCar.isEmpty(); if (thereIsDriverWaiting) { MobsimDriverAgent driverWaitingForPassengers = driversWaitingForPassengers .get(driversWaitingForCar.element().getId()); if (driverWaitingForPassengers != null) return; } /* * If there is a driver waiting for its vehicle, and this car is not currently already leaving again with the * same vehicle, put the new driver into the vehicle and let it depart. */ if (thereIsDriverWaiting && veh.getDriver() == null) { // set agent as driver and then let the vehicle depart veh.setDriver(driversWaitingForCar.remove()); if (driversWaitingForCar.isEmpty()) { final Queue<MobsimDriverAgent> r = driversWaitingForCars.remove(veh.getId()); assert r == driversWaitingForCar; } removeParkedVehicle(veh.getId()); this.letVehicleDepart(veh, now); } } /** * Move as many waiting cars to the link as it is possible * * @param now * the current time */ private void moveWaitToBuffer(final double now) { while (hasFlowCapacityLeftAndBufferSpace()) { QVehicle veh = this.waitingList.poll(); if (veh == null) { return; } this.network.simEngine.getMobsim().getEventsManager().processEvent( new Wait2LinkEvent(now, veh.getDriver().getId(), this.getLink().getId(), veh.getId())); boolean handled = this.addTransitToBuffer(now, veh); if (!handled) { if (veh.getDriver() instanceof TransitDriverAgent) { TransitDriverAgent trDriver = (TransitDriverAgent) veh.getDriver(); Id<Link> nextLinkId = trDriver.chooseNextLinkId(); if (nextLinkId == null || nextLinkId.equals(trDriver.getCurrentLinkId())) { // special case: transit drivers can specify the next link being the current link // this can happen when a transit-lines route leads over exactly one link // normally, vehicles would not even drive on that link, but transit vehicles must // "drive" on that link in order to handle the stops on that link // so allow them to return some non-null link id in chooseNextLink() in order to be // placed on the link, and here we'll remove them again if needed... // ugly hack, but I didn't find a nicer solution sadly... mrieser, 5mar2011 trDriver.endLegAndComputeNextState(now); this.addParkedVehicle(veh); this.network.simEngine.internalInterface.arrangeNextAgentState(trDriver); this.makeVehicleAvailableToNextDriver(veh, now); // remove _after_ processing the arrival to keep link active this.vehQueue.poll(); this.usedStorageCapacity -= veh.getSizeInEquivalents(); if (HOLES) { Hole hole = new Hole(); hole.setEarliestLinkExitTime(now + this.link.getLength() * 3600. / 15. / 1000.); holes.add(hole); } continue; } } addToBuffer(veh, now); // this.linkEnterTimeMap.put(veh, now); // ( really?? kai, jan'11) } } } /** * This method * moves transit vehicles from the stop queue directly to the front of the * "queue" of the QLink. An advantage is that this will observe flow * capacity restrictions. */ private void moveTransitToQueue(final double now) { QVehicle veh; // handle transit traffic in stop queue List<QVehicle> departingTransitVehicles = null; while ((veh = transitVehicleStopQueue.peek()) != null) { // there is a transit vehicle. if (veh.getEarliestLinkExitTime() > now) { break; } if (departingTransitVehicles == null) { departingTransitVehicles = new LinkedList<QVehicle>(); } departingTransitVehicles.add(transitVehicleStopQueue.poll()); } if (departingTransitVehicles != null) { // add all departing transit vehicles at the front of the vehQueue ListIterator<QVehicle> iter = departingTransitVehicles.listIterator(departingTransitVehicles.size()); while (iter.hasPrevious()) { this.vehQueue.addFirst(iter.previous()); } } } private boolean handleTransitStop(final double now, final QVehicle veh, final MobsimDriverAgent driver) { boolean handled = false; // handle transit driver if necessary if (driver instanceof TransitDriverAgent) { TransitDriverAgent transitDriver = (TransitDriverAgent) veh.getDriver(); TransitStopFacility stop = transitDriver.getNextTransitStop(); if ((stop != null) && (stop.getLinkId().equals(getLink().getId()))) { double delay = transitDriver.handleTransitStop(stop, now); if (delay > 0.0) { veh.setEarliestLinkExitTime(now + delay); // (if the vehicle is not removed from the queue in the following lines, then this will effectively block the lane if (!stop.getIsBlockingLane()) { this.vehQueue.poll(); // remove the bus from the queue transitVehicleStopQueue.add(veh); // and add it to the stop queue } } /* start over: either this veh is still first in line, * but has another stop on this link, or on another link, then it is moved on */ handled = true; } } return handled; } /*package*/ final void addDepartingVehicle(MobsimVehicle mvehicle) { QVehicle vehicle = (QVehicle) mvehicle; this.waitingList.add(vehicle); vehicle.setCurrentLink(this.getLink()); this.activateLink(); } boolean isNotOfferingVehicle() { return this.buffer.isEmpty(); } boolean hasSpace() { double now = network.simEngine.getMobsim().getSimTimer().getTimeOfDay(); boolean storageOk = this.usedStorageCapacity < this.storageCapacity; if (!HOLES) { return storageOk; } // continue only if HOLES if (!storageOk) { return false; } // at this point, storage is ok, so start checking holes: QItem hole = holes.peek(); if (hole == null) { // no holes available at all; in theory, this should not happen since covered by !storageOk // log.warn( " !hasSpace since no holes available ") ; return false; } if (hole.getEarliestLinkExitTime() > now) { // log.warn( " !hasSpace since all hole arrival times lie in future ") ; return false; } return true; } @Override public void recalcTimeVariantAttributes(double now) { this.freespeedTravelTime = this.length / this.getLink().getFreespeed(now); calculateFlowCapacity(now); calculateStorageCapacity(now); } private void calculateCapacities() { calculateFlowCapacity(Time.UNDEFINED_TIME); calculateStorageCapacity(Time.UNDEFINED_TIME); this.flowcap_accumulate = (this.flowCapacityPerTimeStepFractionalPart == 0.0 ? 0.0 : 1.0); } private void calculateFlowCapacity(final double time) { this.flowCapacityPerTimeStep = ((LinkImpl) this.getLink()).getFlowCapacity(time); // we need the flow capacity per sim-tick and multiplied with flowCapFactor this.flowCapacityPerTimeStep = this.flowCapacityPerTimeStep * network.simEngine.getMobsim().getSimTimer().getSimTimestepSize() * network.simEngine.getMobsim().getScenario().getConfig().qsim().getFlowCapFactor(); this.inverseFlowCapacityPerTimeStep = 1.0 / this.flowCapacityPerTimeStep; this.flowCapacityPerTimeStepFractionalPart = this.flowCapacityPerTimeStep - (int) this.flowCapacityPerTimeStep; } private void calculateStorageCapacity(final double time) { double storageCapFactor = network.simEngine.getMobsim().getScenario().getConfig().qsim() .getStorageCapFactor(); this.bufferStorageCapacity = (int) Math.ceil(this.flowCapacityPerTimeStep); double numberOfLanes = this.getLink().getNumberOfLanes(time); // first guess at storageCapacity: this.storageCapacity = (this.length * numberOfLanes) / ((NetworkImpl) network.simEngine.getMobsim().getScenario().getNetwork()).getEffectiveCellSize() * storageCapFactor; // storage capacity needs to be at least enough to handle the cap_per_time_step: this.storageCapacity = Math.max(this.storageCapacity, this.bufferStorageCapacity); /* * If speed on link is relatively slow, then we need MORE cells than the * above spaceCap to handle the flowCap. Example: Assume freeSpeedTravelTime * (aka freeTravelDuration) is 2 seconds. Than I need the spaceCap = TWO times * the flowCap to handle the flowCap. */ double tempStorageCapacity = this.freespeedTravelTime * this.flowCapacityPerTimeStep; // yy note: freespeedTravelTime may be Inf. In this case, storageCapacity will also be set to Inf. This can still be // interpreted, but it means that the link will act as an infinite sink. kai, nov'10 if (this.storageCapacity < tempStorageCapacity) { if (spaceCapWarningCount <= 10) { log.warn("Link " + this.getLink().getId() + " too small: enlarge storage capacity from: " + this.storageCapacity + " Vehicles to: " + tempStorageCapacity + " Vehicles. This is not fatal, but modifies the traffic flow dynamics."); if (spaceCapWarningCount == 10) { log.warn("Additional warnings of this type are suppressed."); } spaceCapWarningCount++; } this.storageCapacity = tempStorageCapacity; } if (HOLES) { // yyyy number of initial holes (= max number of vehicles on link given bottleneck spillback) is, in fact, dicated // by the bottleneck flow capacity, together with the fundamental diagram. :-( kai, ???'10 // // Alternative would be to have link entry capacity constraint. This, however, does not work so well with the // current "parallel" logic, where capacity constraints are modeled only on the link. kai, nov'10 double bnFlowCap_s = ((LinkImpl) this.link).getFlowCapacity(); // ( c * n_cells - cap * L ) / (L * c) = (n_cells/L - cap/c) ; congestedDensity_veh_m = this.storageCapacity / this.link.getLength() - (bnFlowCap_s * 3600.) / (15. * 1000); if (congestedDensity_veh_m > 10.) { if (congDensWarnCnt2 < 1) { congDensWarnCnt2++; log.warn("congestedDensity_veh_m very large: " + congestedDensity_veh_m + "; does this make sense? Setting to 10 veh/m (which is still a lot but who knows). " + "Definitely can't have it at Inf."); } } // congestedDensity is in veh/m. If this is less than something reasonable (e.g. 1veh/50m) or even negative, // then this means that the link has not enough storageCapacity (essentially not enough lanes) to transport the given // flow capacity. Will increase the storageCapacity accordingly: if (congestedDensity_veh_m < 1. / 50) { if (congDensWarnCnt < 1) { congDensWarnCnt++; log.warn( "link not ``wide'' enough to process flow capacity with holes. increasing storage capacity ..."); log.warn(Gbl.ONLYONCE); } this.storageCapacity = (1. / 50 + bnFlowCap_s * 3600. / (15. * 1000)) * this.link.getLength(); congestedDensity_veh_m = this.storageCapacity / this.link.getLength() - (bnFlowCap_s * 3600.) / (15. * 1000); } nHolesMax = (int) Math.ceil(congestedDensity_veh_m * this.link.getLength()); log.warn( " nHoles: " + nHolesMax + " storCap: " + this.storageCapacity + " len: " + this.link.getLength() + " bnFlowCap: " + bnFlowCap_s + " congDens: " + congestedDensity_veh_m); for (int ii = 0; ii < nHolesMax; ii++) { Hole hole = new Hole(); hole.setEarliestLinkExitTime(0.); holes.add(hole); } // System.exit(-1); } } QVehicle getVehicle(Id<Vehicle> vehicleId) { QVehicle ret = this.parkedVehicles.get(vehicleId); if (ret != null) { return ret; } for (QVehicle veh : this.vehQueue) { if (veh.getId().equals(vehicleId)) return veh; } for (QVehicle veh : this.buffer) { if (veh.getId().equals(vehicleId)) return veh; } for (QVehicle veh : this.waitingList) { if (veh.getId().equals(vehicleId)) return veh; } return null; } @Override public final Collection<MobsimVehicle> getAllVehicles() { Collection<MobsimVehicle> vehicles = this.getAllNonParkedVehicles(); vehicles.addAll(this.parkedVehicles.values()); return vehicles; } @Override public Collection<MobsimVehicle> getAllNonParkedVehicles() { Collection<MobsimVehicle> vehicles = new ArrayList<MobsimVehicle>(); vehicles.addAll(this.transitVehicleStopQueue); vehicles.addAll(this.waitingList); vehicles.addAll(this.vehQueue); vehicles.addAll(this.buffer); return vehicles; } /** * @return the total space capacity available on that link (includes the space on lanes if available) */ double getSpaceCap() { return this.storageCapacity; } int vehOnLinkCount() { // called by one test case return this.vehQueue.size(); } @Override public Link getLink() { return this.link; } public QNode getToNode() { return this.toQueueNode; } /** * This method returns the normalized capacity of the link, i.e. the capacity * of vehicles per second. It is considering the capacity reduction factors * set in the config and the simulation's tick time. * * @return the flow capacity of this link per second, scaled by the config * values and in relation to the SimulationTimer's simticktime. */ double getSimulatedFlowCapacity() { return this.flowCapacityPerTimeStep; } private boolean isActive() { /* * Leave Link active as long as there are vehicles on the link (ignore * buffer because the buffer gets emptied by nodes and not links) and leave * link active until buffercap has accumulated (so a newly arriving vehicle * is not delayed). */ boolean active = (this.flowcap_accumulate < 1.0) || (!this.vehQueue.isEmpty()) || (!this.waitingList.isEmpty() || (!this.transitVehicleStopQueue.isEmpty())); return active; } private double effectiveVehicleFlowConsumptionInPCU(QVehicle veh) { // return Math.min(1.0, veh.getSizeInEquivalents() ) ; return veh.getSizeInEquivalents(); } private void addToBuffer(final QVehicle veh, final double now) { // We are trying to modify this so it also works for vehicles different from size one. The idea is that vehicles // _larger_ than size one can move as soon as at least one unit of flow or storage capacity is available. // kai/mz/amit, mar'12 // yy might make sense to just accumulate to "zero" and go into negative when something is used up. // kai/mz/amit, mar'12 if (this.remainingflowCap >= 1.0) { this.remainingflowCap -= this.effectiveVehicleFlowConsumptionInPCU(veh); } else if (this.flowcap_accumulate >= 1.0) { this.flowcap_accumulate -= this.effectiveVehicleFlowConsumptionInPCU(veh); } else { throw new IllegalStateException("Buffer of link " + this.getLink().getId() + " has no space left!"); } this.buffer.add(veh); this.usedBufferStorageCapacity = this.usedBufferStorageCapacity + veh.getSizeInEquivalents(); if (buffer.size() == 1) { this.bufferLastMovedTime = now; // (if there is one vehicle in the buffer now, there were zero vehicles in the buffer before. in consequence, // need to reset the lastMovedTime. If, in contrast, there was already a vehicle in the buffer before, we can // use the lastMovedTime that was (somehow) computed for that vehicle.) } this.getToNode().activateNode(); } QVehicle popFirstVehicle() { double now = this.network.simEngine.getMobsim().getSimTimer().getTimeOfDay(); QVehicle veh = this.buffer.poll(); this.usedBufferStorageCapacity = this.usedBufferStorageCapacity - veh.getSizeInEquivalents(); this.bufferLastMovedTime = now; // just in case there is another vehicle in the buffer that is now the new front-most this.linkEnterTimeMap.remove(veh); this.network.simEngine.getMobsim().getEventsManager().processEvent( new LinkLeaveEvent(now, veh.getDriver().getId(), this.getLink().getId(), veh.getId())); return veh; } QVehicle getFirstVehicle() { return this.buffer.peek(); } double getLastMovementTimeOfFirstVehicle() { return this.bufferLastMovedTime; } public boolean hasGreenForToLink(Id<Link> toLinkId) { if (this.qSignalizedItem != null) { return this.qSignalizedItem.isLinkGreenForToLink(toLinkId); } return true; //the lane is not signalized and thus always green } public void setSignalStateAllTurningMoves(SignalGroupState state) { this.qSignalizedItem.setSignalStateAllTurningMoves(state); this.thisTimeStepGreen = this.qSignalizedItem.isLinkGreen(); // (this is only for capacity accumulation) } public void setSignalStateForTurningMove(SignalGroupState state, Id<Link> toLinkId) { if (!this.getToNode().getNode().getOutLinks().containsKey(toLinkId)) { throw new IllegalArgumentException( "ToLink " + toLinkId + " is not reachable from QLink Id " + this.getLink().getId()); } this.qSignalizedItem.setSignalStateForTurningMove(state, toLinkId); this.thisTimeStepGreen = this.qSignalizedItem.isLinkGreen(); // (this is only for capacity accumulation. As soon as at least one turning relation is green, the "link" is considered // green). } public void setSignalized(boolean isSignalized) { this.qSignalizedItem = new DefaultSignalizeableItem(this.getLink().getToNode().getOutLinks().keySet()); } static class Hole extends QItem { private double earliestLinkEndTime; @Override public double getEarliestLinkExitTime() { return earliestLinkEndTime; } @Override public void setEarliestLinkExitTime(double earliestLinkEndTime) { this.earliestLinkEndTime = earliestLinkEndTime; } } final void updateBufferCapacity() { this.remainingflowCap = this.flowCapacityPerTimeStep; // if (this.thisTimeStepGreen && this.flowcap_accumulate < 1.0 && this.hasBufferSpaceLeft()) { if (this.thisTimeStepGreen && this.flowcap_accumulate < 1.0 && this.isNotOfferingVehicle()) { this.flowcap_accumulate += this.flowCapacityPerTimeStepFractionalPart; } } final boolean hasFlowCapacityLeftAndBufferSpace() { return (hasBufferSpaceLeft() && ((this.remainingflowCap >= 1.0) || (this.flowcap_accumulate >= 1.0))); } private boolean hasBufferSpaceLeft() { return usedBufferStorageCapacity < this.bufferStorageCapacity; } /*package*/ void registerDriverAgentWaitingForCar(final MobsimDriverAgent agent) { final Id<Vehicle> vehicleId = agent.getPlannedVehicleId(); Queue<MobsimDriverAgent> queue = driversWaitingForCars.get(vehicleId); if (queue == null) { queue = new LinkedList<MobsimDriverAgent>(); driversWaitingForCars.put(vehicleId, queue); } queue.add(agent); } /*package*/ void registerDriverAgentWaitingForPassengers(MobsimDriverAgent agent) { driversWaitingForPassengers.put(agent.getId(), agent); } /*package*/ MobsimAgent unregisterDriverAgentWaitingForPassengers(Id<Person> agentId) { return driversWaitingForPassengers.remove(agentId); } /*package*/ void registerPassengerAgentWaitingForCar(MobsimAgent agent, Id<Vehicle> vehicleId) { Set<MobsimAgent> passengers = passengersWaitingForCars.get(vehicleId); if (passengers == null) { passengers = new LinkedHashSet<MobsimAgent>(); passengersWaitingForCars.put(vehicleId, passengers); } passengers.add(agent); } /*package*/ MobsimAgent unregisterPassengerAgentWaitingForCar(MobsimAgent agent, Id<Vehicle> vehicleId) { Set<MobsimAgent> passengers = passengersWaitingForCars.get(vehicleId); if (passengers != null && passengers.remove(agent)) return agent; else return null; } /*package*/ Set<MobsimAgent> getAgentsWaitingForCar(Id<Vehicle> vehicleId) { Set<MobsimAgent> set = passengersWaitingForCars.get(vehicleId); if (set != null) return Collections.unmodifiableSet(set); else return null; } /*package*/ void registerAdditionalAgentOnLink(MobsimAgent planAgent) { this.additionalAgentsOnLink.put(planAgent.getId(), planAgent); } /*package*/ MobsimAgent unregisterAdditionalAgentOnLink(Id<Person> mobsimAgentId) { return this.additionalAgentsOnLink.remove(mobsimAgentId); } /*package*/ void setNetElementActivator(NetElementActivator qSimEngineRunner) { this.netElementActivator = qSimEngineRunner; } @Override public Map<String, Object> getCustomAttributes() { return customAttributes; } @Override public VisData getVisData() { return null; } /** * Adds a vehicle to the link (i.e. the "queue"), called by * {@link QNode#moveVehicleOverNode(QVehicle, QueueLane, double)}. * * @param veh * the vehicle */ private final static int[] TIMES = { 0, 1, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }; private final static double[] MEANS_R = { 22.32, 25.47, 21.01, 17.58, 16.23, 16.29, 18.53, 19.06, 18.75, 18.51, 18.12, 18.07, 18.05, 17.97, 16.25, 14.52, 15.68, 17.81, 18.93, 20.13, 22.13 }; private final static double[] STDS_R = { 6.08, 8.55, 6.33, 6.05, 5.64, 5.57, 5.92, 6.16, 5.95, 6.00, 6.02, 5.86, 5.70, 5.71, 5.42, 5.34, 5.63, 5.99, 6.14, 6.46, 6.71 }; private final static double[] MEANS_S = { 21.25, 21.92, 20.38, 19.30, 19.14, 19.20, 20.08, 20.61, 20.63, 20.54, 20.37, 20.32, 20.20, 20.11, 19.69, 19.03, 19.17, 19.74, 19.95, 20.15, 20.82 }; private static final double MIN_SPEED_BUS = 10 / 3.6; private final StopStopTime stopStopTime; final void addFromUpstream(final QVehicle veh) { double now = network.simEngine.getMobsim().getSimTimer().getTimeOfDay(); activateLink(); this.linkEnterTimeMap.put(veh, now); this.usedStorageCapacity += veh.getSizeInEquivalents(); String[] parts = link.getId().toString().split(TransitSheduleToNetwork.SEPARATOR); double earliestExitTime = now; if (stopStopTime != null) { if (parts.length == 2) { double variance = stopStopTime.getStopStopTimeVariance( Id.create(parts[0], TransitStopFacility.class), Id.create(parts[1], TransitStopFacility.class), now); if (variance != 0) { try { double r = MatsimRandom.getRandom().nextDouble(); earliestExitTime += new NormalDistributionImpl( stopStopTime.getStopStopTime(Id.create(parts[0], TransitStopFacility.class), Id.create(parts[1], TransitStopFacility.class), now), Math.sqrt(variance)).inverseCumulativeProbability(r); } catch (MathException e) { e.printStackTrace(); } } else earliestExitTime += stopStopTime.getStopStopTime(Id.create(parts[0], TransitStopFacility.class), Id.create(parts[1], TransitStopFacility.class), now); } else earliestExitTime += link.getLength() / 3.0; } else { double speed = veh.getMaximumVelocity(); if (speed == 7.22) { if (link.getNumberOfLanes() > 8) speed = 50 / 3.6; else if (link.getNumberOfLanes() > 6) speed = 40 / 3.6; else TIMES: for (int i = 0; i < TIMES.length; i++) if (TIMES[i] == (int) (now / 3600) % 24) try { double r = MatsimRandom.getRandom().nextDouble(); speed = new NormalDistributionImpl( veh.getMaximumVelocity() - 0.5556 - (MEANS_S[i] > MEANS_R[i] ? (MEANS_S[i] - MEANS_R[i]) / 3.6 : 0), STDS_R[i] * 1.1 / 3.6).inverseCumulativeProbability(r); break TIMES; } catch (MathException e) { e.printStackTrace(); } speed = Math.max(MIN_SPEED_BUS, speed); } double vehicleTravelTime = this.length / speed; earliestExitTime += Math.max(this.freespeedTravelTime, vehicleTravelTime); } earliestExitTime = Math.floor(earliestExitTime); veh.setEarliestLinkExitTime(earliestExitTime); veh.setCurrentLink(this.getLink()); this.vehQueue.add(veh); this.network.simEngine.getMobsim().getEventsManager().processEvent( new LinkEnterEvent(now, veh.getDriver().getId(), this.getLink().getId(), veh.getId())); if (HOLES) { holes.poll(); } } }