MATSIM
QNodeImpl.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.*
3  * QueueNode.java
4  * *
5  * *********************************************************************** *
6  * *
7  * copyright : (C) 2007 by the members listed in the COPYING, *
8  * LICENSE and WARRANTY file. *
9  * email : info at matsim dot org *
10  * *
11  * *********************************************************************** *
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * See also COPYING, LICENSE and WARRANTY file *
18  * *
19  * *********************************************************************** */
20 
21 package org.matsim.core.mobsim.qsim.qnetsimengine;
22 
23 import org.apache.logging.log4j.LogManager;
24 import org.apache.logging.log4j.Logger;
25 import org.matsim.api.core.v01.Id;
33 import org.matsim.core.gbl.Gbl;
40 
41 import java.util.Arrays;
42 import java.util.Comparator;
43 import java.util.Random;
44 
48 final class QNodeImpl extends AbstractQNode {
49  private static final Logger log = LogManager.getLogger(QNodeImpl.class);
50  public static class Builder {
53  private final QSimConfigGroup qsimConfig;
54  public Builder( NetsimInternalInterface netsimEngine2, NetsimEngineContext context, QSimConfigGroup qsimConfig ) {
55  this.netsimEngine = netsimEngine2;
56  this.context = context;
57  this.qsimConfig = qsimConfig;
58  }
60  public final void setTurnAcceptanceLogic( TurnAcceptanceLogic turnAcceptanceLogic ) {
61  this.turnAcceptanceLogic = turnAcceptanceLogic ;
62  }
63  public QNodeImpl build( Node n ) {
64  return new QNodeImpl( n, context, netsimEngine, turnAcceptanceLogic, qsimConfig ) ;
65  }
66  }
67 
68  private final QLinkI[] inLinksArrayCache;
69  private final QLinkI[] tempLinks;
70  private Double[] inLinkPriorities;
71 
72  private final Random random;
73  private final NetsimEngineContext context;
75 
77  private NodeTransition nodeTransitionLogic;
78  private boolean stopMoveNodeWhenSingleOutlinkFull;
79  private boolean atLeastOneOutgoingLaneIsJammed;
80 
86  private static boolean capacityLeft(double x) {
87  return x > 1E-10;
88  }
89 
90  private QNodeImpl(final Node n, NetsimEngineContext context, NetsimInternalInterface netsimEngine2,
91  TurnAcceptanceLogic turnAcceptanceLogic, QSimConfigGroup qsimConfig) {
92  super(n) ;
93  this.netsimEngine = netsimEngine2 ;
94  this.context = context ;
95  this.turnAcceptanceLogic = turnAcceptanceLogic;
96  this.nodeTransitionLogic = qsimConfig.getNodeTransitionLogic();
97 
98  switch (nodeTransitionLogic) {
99  case emptyBufferAfterBufferRandomDistribution_nodeBlockedWhenSingleOutlinkFull:
100  case moveVehByVehRandomDistribution_nodeBlockedWhenSingleOutlinkFull:
101  case moveVehByVehDeterministicPriorities_nodeBlockedWhenSingleOutlinkFull:
102  this.stopMoveNodeWhenSingleOutlinkFull = true;
103  break;
104  case emptyBufferAfterBufferRandomDistribution_dontBlockNode:
105  case moveVehByVehRandomDistribution_dontBlockNode:
106  this.stopMoveNodeWhenSingleOutlinkFull = false;
107  break;
108  default:
109  throw new UnsupportedOperationException("Node transition logic " + nodeTransitionLogic + " is not implemented.");
110  }
111 
112  int nofInLinks = n.getInLinks().size();
113  this.inLinksArrayCache = new QLinkI[nofInLinks];
114  this.tempLinks = new QLinkI[nofInLinks];
115  if (this.context.qsimConfig.getNumberOfThreads() > 1) {
116  // This could just as well be the "normal" case. The second alternative
117  // is just there so some scenarios / test cases stay
118  // "event-file-compatible". Consider removing the second alternative.
119  this.random = MatsimRandom.getLocalInstance();
120  } else {
121  this.random = MatsimRandom.getRandom();
122  }
123  }
124 
131  @Override
132  public void init() {
133  int i = 0;
134  for (Link l : this.node.getInLinks().values()) {
135  QNetwork network = netsimEngine.getNetsimNetwork() ;
136  this.inLinksArrayCache[i] = network.getNetsimLinks().get(l.getId());
137  i++;
138  }
139  /* As the order of links has an influence on the simulation results,
140  * the nodes are sorted to avoid indeterministic simulations. dg[april08]
141  */
142  Arrays.sort(this.inLinksArrayCache, new Comparator<NetsimLink>() {
143  @Override
144  public int compare(NetsimLink o1, NetsimLink o2) {
145  return o1.getLink().getId().compareTo(o2.getLink().getId());
146  }
147  });
148  }
149 
167  @Override
168  public boolean doSimStep(final double now) {
169 
170  if (inLinkPriorities == null) {
171  /* initialize inLink priorities.
172  * unfortunately, this can't be done in init() because when capacities are changed
173  * before controler.run() init() uses the old capacities. theresa, may'20 */
174  inLinkPriorities = new Double[inLinksArrayCache.length];
175  for (int inLinkCounter=0; inLinkCounter<this.inLinksArrayCache.length; inLinkCounter++) {
176  double linkCap = this.inLinksArrayCache[inLinkCounter].getLink().getCapacity(now);
177  inLinkPriorities[inLinkCounter] = 1. / linkCap;
178  }
179  }
180 
181  // reset congestion flag
182  this.atLeastOneOutgoingLaneIsJammed = false;
183 
184  double inLinksCapSum = 0.0;
185  // Check all incoming links for buffered agents
186  for (int inLinkIndex = 0; inLinkIndex < this.inLinksArrayCache.length; inLinkIndex++) {
187  QLinkI link = this.inLinksArrayCache[inLinkIndex];
188  if (!link.isNotOfferingVehicle()) {
189  this.tempLinks[inLinkIndex] = link;
190  inLinksCapSum += link.getLink().getCapacity(now);
191  }
192  }
193 
194  if (!capacityLeft(inLinksCapSum)) {
195  this.setActive(false);
196  return false; // Nothing to do
197  }
198 
199  // select vehicles to be moved over the node. The order of vehicles selected depends on the chosen node transition logic.
200  switch (nodeTransitionLogic) {
201  case emptyBufferAfterBufferRandomDistribution_dontBlockNode:
202  case emptyBufferAfterBufferRandomDistribution_nodeBlockedWhenSingleOutlinkFull:
203  // randomize based on link capacity: select a link; if next links have enough space all vehicles from the buffer are allowed to pass the node
204  while (capacityLeft(inLinksCapSum)) {
205  double rndNum = random.nextDouble() * inLinksCapSum;
206  double selCap = 0.0;
207  for (int i = 0; i < this.inLinksArrayCache.length; i++) {
208  QLinkI link = this.tempLinks[i];
209  if (link != null) {
210  // link is offering vehicles
211  selCap += link.getLink().getCapacity(now);
212  if (selCap >= rndNum) {
213  // try to move vehicles from this link over the node
214  inLinksCapSum -= link.getLink().getCapacity(now);
215  this.tempLinks[i] = null;
216  this.moveLink(link, now);
217  if (this.stopMoveNodeWhenSingleOutlinkFull && this.atLeastOneOutgoingLaneIsJammed) {
218  // consider intersection as blocked; stop this node sim step
219  return true;
220  }
221  break;
222  }
223  }
224  }
225  }
226  break;
227  case moveVehByVehRandomDistribution_dontBlockNode:
228  case moveVehByVehRandomDistribution_nodeBlockedWhenSingleOutlinkFull:
229  // randomize based on link capacity: select the vehicles (one by one) that are allowed to pass the node
230  while (capacityLeft(inLinksCapSum)) {
231  double rndNum = random.nextDouble() * inLinksCapSum;
232  double selCap = 0.0;
233  for (int i = 0; i < this.inLinksArrayCache.length; i++) {
234  QLinkI link = this.tempLinks[i];
235  if (link != null) {
236  // link is offering vehicles
237  selCap += link.getLink().getCapacity(now);
238  if (selCap >= rndNum) {
239  // try to move a vehicle from this link over the node
240  if ( ! moveFirstVehicleOnLink(now, link)) {
241  // the link is not able to move (more) vehicles in this time step
242  inLinksCapSum -= link.getLink().getCapacity(now);
243  this.tempLinks[i] = null;
244  } else {
245  // a vehicle has been moved
246  }
247  if (this.stopMoveNodeWhenSingleOutlinkFull && this.atLeastOneOutgoingLaneIsJammed) {
248  // consider intersection as blocked; stop this node sim step
249  return true;
250  }
251  // select the next link (could be the same again) that is allowed to move a vehicle. I.e. start again with the beginning of the while-loop
252  break;
253  }
254  }
255  }
256  }
257  break;
258  case moveVehByVehDeterministicPriorities_nodeBlockedWhenSingleOutlinkFull:
259  // deterministically choose the inLinks vehicle by vehicle based on their capacity and also account for decisions made in previous time steps (i.e. update priorities) to approximate the correct distribution over time.
260  double prioWithWhichTheLastVehWasSent = 0.;
261  while (capacityLeft(inLinksCapSum)) {
262  // look for the inLink with minimal priority that has vehicles in the buffer (use first one when equal, because links are sorted by ID)
263  double minPrio = Float.MAX_VALUE;
264  int prioInLinkIndex = -1;
265  for (int i = 0; i < this.inLinksArrayCache.length; i++) {
266  if (tempLinks[i] != null &&
267  /* compare link priorities with some tolerance (done by comparing the float part of the double).
268  * otherwise, priorities that should be equal are different when compared as double because its fixed precision
269  * (e.g. 1/7200 + 1/7200 as double is greater than 1/3600).
270  * for equal priorities the tie breaking rule is applied (lower link id, see above)
271  */
272  (float) this.inLinkPriorities[i].doubleValue() < (float) minPrio) {
273  // link is offering vehicles and has lowest priority so far
274  minPrio = this.inLinkPriorities[i];
275  prioInLinkIndex = i;
276  }
277  }
278  QLinkI selectedLink = this.inLinksArrayCache[prioInLinkIndex];
279 
280  // try to move a vehicle from this selected link over the node
281  if ( ! moveFirstVehicleOnLink(now, selectedLink)) {
282  // the link is not able to move (more) vehicles in this time step
283  inLinksCapSum -= selectedLink.getLink().getCapacity(now);
284  this.tempLinks[prioInLinkIndex] = null;
285  } else {
286  // a vehicle has been moved; update priority of the selected link
287  prioWithWhichTheLastVehWasSent = minPrio;
288  this.inLinkPriorities[prioInLinkIndex] += 1. / selectedLink.getLink().getCapacity(now);
289  }
290  if (this.atLeastOneOutgoingLaneIsJammed) {
291  // stopMoveNodeWhenSingleOutlinkFull is always true for this node transition
292  // consider intersection as blocked; stop this node sim step; (*)
293  updatePriorities(now, prioWithWhichTheLastVehWasSent, prioInLinkIndex);
294  return true;
295  }
296 
297  // select the next link (could be the same again) that is allowed to move a vehicle. I.e. start again with the beginning of the while-loop
298  }
299  // vehicle moving done for this time step. update priorities
300  updatePriorities(now, prioWithWhichTheLastVehWasSent, -1);
301  break;
302  default:
303  throw new UnsupportedOperationException("Node transition logic " + nodeTransitionLogic + " is not implemented.");
304  }
305 
306  return true;
307  }
308 
309  private void updatePriorities(final double now, double prioWithWhichTheLastVehWasSent, int linkIndexToBeExcluded) {
310 
311  for (int linkIndex=0; linkIndex < this.inLinkPriorities.length; linkIndex++) {
312  // when the node was blocked (see * above) no priority updating is necessary for the blocked links
313  if (linkIndex != linkIndexToBeExcluded && tempLinks[linkIndex] == null) {
314  // shift priorities of links that where disabled earlier because of other reasons, e.g. because their buffer was empty or a traffic light showed red,
315  // to the level of the other priorities such that they are not overprioritized in next time steps
316  inLinkPriorities[linkIndex] = prioWithWhichTheLastVehWasSent
317  + 1. / inLinksArrayCache[linkIndex].getLink().getCapacity(now);
318  }
319  // shift all priorities around zero accordingly to avoid overflow of double at some time step
320  inLinkPriorities[linkIndex] -= prioWithWhichTheLastVehWasSent;
321  }
322  }
323 
330  private boolean moveFirstVehicleOnLink(final double now, QLinkI link) {
331  if (link.getOfferingQLanes().size() > 1) {
332  throw new RuntimeException("The qsim node transition parameter " + NodeTransition.moveVehByVehRandomDistribution_dontBlockNode + ", "
335  + " are only implemented for the case without lanes. But link " + link.getLink().getId()
336  + " in your scenario has more than one lane. "
337  + "Use the default node transiton " + NodeTransition.emptyBufferAfterBufferRandomDistribution_dontBlockNode
339  + " or adapt the implementation such that it also works for lanes.");
340  }
341  for (QLaneI lane : link.getOfferingQLanes()) {
342  if (! lane.isNotOfferingVehicle()) {
343  QVehicle veh = lane.getFirstVehicle();
344  if (moveVehicleOverNode(veh, link, lane, now)) {
345  // vehicle was moved. stop here
346  return true;
347  } else {
348  // vehicle was not moved, e.g. because the next link is jammed or a traffic light on this link shows red.
349  if (this.stopMoveNodeWhenSingleOutlinkFull && this.atLeastOneOutgoingLaneIsJammed) {
350  // next link/lane is jammed. stop vehicle moving for the whole node
351  return false;
352  } else {
353  // try next lane
354  continue;
355  }
356  }
357  }
358  }
359  return false;
360  }
361 
362  private void moveLink(final QLinkI link, final double now){
363  for (QLaneI lane : link.getOfferingQLanes()) {
364  while (! lane.isNotOfferingVehicle()) {
365  QVehicle veh = lane.getFirstVehicle();
366  if (! moveVehicleOverNode(veh, link, lane, now )) {
367  // vehicle was not moved, e.g. because the next link is jammed or a traffic light on this link shows red.
368  if (this.stopMoveNodeWhenSingleOutlinkFull && this.atLeastOneOutgoingLaneIsJammed) {
369  // next link/lane is jammed. stop vehicle moving for the whole node
370  return;
371  } else {
372  // try next lane
373  break;
374  }
375  }
376  }
377  }
378  }
379 
380 
381 
382  // ////////////////////////////////////////////////////////////////////
383  // Queue related movement code
384  // ////////////////////////////////////////////////////////////////////
389  private boolean moveVehicleOverNode( final QVehicle veh, QLinkI fromLink, final QLaneI fromLane, final double now ) {
390  Id<Link> nextLinkId = veh.getDriver().chooseNextLinkId();
391  Link currentLink = fromLink.getLink() ;
392 
393  AcceptTurn turn = turnAcceptanceLogic.isAcceptingTurn(currentLink, fromLane, nextLinkId, veh, this.netsimEngine.getNetsimNetwork(), now);
394  if ( turn.equals(AcceptTurn.ABORT) ) {
395  moveVehicleFromInlinkToAbort( veh, fromLane, now, currentLink.getId() ) ;
396  return true ;
397  } else if ( turn.equals(AcceptTurn.WAIT) ) {
398  return false;
399  }
400 
401  QLinkI nextQueueLink = this.netsimEngine.getNetsimNetwork().getNetsimLinks().get(nextLinkId);
402  QLaneI nextQueueLane = nextQueueLink.getAcceptingQLane() ;
403  if (nextQueueLane.isAcceptingFromUpstream()) {
404  moveVehicleFromInlinkToOutlink(veh, currentLink.getId(), fromLane, nextLinkId, nextQueueLane);
405  return true;
406  }
407  // else, i.e. next link or lane is jammed
408  this.atLeastOneOutgoingLaneIsJammed = true;
409 
410  if (vehicleIsStuck(fromLane, now)) {
411  /* We just push the vehicle further after stucktime is over, regardless
412  * of if there is space on the next link or not.. optionally we let them
413  * die here, we have a config setting for that!
414  */
415  if (this.context.qsimConfig.isRemoveStuckVehicles()) {
416  moveVehicleFromInlinkToAbort(veh, fromLane, now, currentLink.getId());
417  return false ;
418  } else {
419  if (this.context.qsimConfig.isNotifyAboutStuckVehicles()) {
420  // first treat the passengers:
421  for ( PassengerAgent pp : veh.getPassengers() ) {
422  this.context.getEventsManager().processEvent(new PersonStuckAndContinueEvent(now, pp.getId(), fromLink.getLink().getId(), veh.getDriver().getMode()));
423  }
424  // now treat the driver:
425  this.context.getEventsManager().processEvent(new PersonStuckAndContinueEvent(now, veh.getDriver().getId(), fromLink.getLink().getId(), veh.getDriver().getMode()));
426  }
427  moveVehicleFromInlinkToOutlink(veh, currentLink.getId(), fromLane, nextLinkId, nextQueueLane);
428  return true;
429  // (yyyy why is this returning `true'? Since this is a fix to avoid gridlock, this should proceed in small steps.
430  // kai, feb'12)
431  }
432  }
433 
434  return false;
435 
436  }
437 
438  private static int wrnCnt = 0 ;
439  private void moveVehicleFromInlinkToAbort(final QVehicle veh, final QLaneI fromLane, final double now, Id<Link> currentLinkId) {
440  fromLane.popFirstVehicle();
441  // -->
442  this.context.getEventsManager().processEvent(new LinkLeaveEvent(now, veh.getId(), currentLinkId));
443  // <--
444 
445  // first treat the passengers:
446  for ( PassengerAgent pp : veh.getPassengers() ) {
447  if ( pp instanceof MobsimAgent ) {
448  ((MobsimAgent)pp).setStateToAbort(now);
449  netsimEngine.arrangeNextAgentState((MobsimAgent)pp) ;
450  } else if ( wrnCnt < 1 ) {
451  wrnCnt++ ;
452  log.warn("encountering PassengerAgent that cannot be cast into a MobsimAgent; cannot say if this is a problem" ) ;
453  log.warn(Gbl.ONLYONCE) ;
454  }
455  }
456 
457  // now treat the driver:
458  veh.getDriver().setStateToAbort(now) ;
459  netsimEngine.arrangeNextAgentState(veh.getDriver()) ;
460 
461  }
462 
463  private void moveVehicleFromInlinkToOutlink(final QVehicle veh, Id<Link> currentLinkId, final QLaneI fromLane, Id<Link> nextLinkId, QLaneI nextQueueLane) {
464  double now = this.context.getSimTimer().getTimeOfDay() ;
465 
466  fromLane.popFirstVehicle();
467  // -->
468  // network.simEngine.getMobsim().getEventsManager().processEvent(new LaneLeaveEvent(now, veh.getId(), currentLinkId, fromLane.getId()));
469  this.context.getEventsManager().processEvent(new LinkLeaveEvent(now, veh.getId(), currentLinkId));
470  // <--
471 
472  veh.getDriver().notifyMoveOverNode( nextLinkId );
473 
474  // -->
475  this.context.getEventsManager().processEvent(new LinkEnterEvent(now, veh.getId(), nextLinkId));
476  // <--
477  nextQueueLane.addFromUpstream(veh);
478  }
479 
480  private boolean vehicleIsStuck(final QLaneI fromLaneBuffer, final double now) {
481  // final double stuckTime = network.simEngine.getStuckTime();
482  final double stuckTime = this.context.qsimConfig.getStuckTime() ;
483  return (now - fromLaneBuffer.getLastMovementTimeOfFirstVehicle()) > stuckTime;
484  }
485 
486 
487 }
static final String ONLYONCE
Definition: Gbl.java:42
void notifyMoveOverNode(Id< Link > newLinkId)
Map< Id< Link >, QLinkI > getNetsimLinks()
Definition: QNetwork.java:84
Map< Id< Link >, ? extends Link > getInLinks()
Collection<? extends PassengerAgent > getPassengers()
AcceptTurn isAcceptingTurn(Link currentLink, QLaneI currentLane, Id< Link > nextLinkId, QVehicle veh, QNetwork qNetwork, double now)
final void setTurnAcceptanceLogic(TurnAcceptanceLogic turnAcceptanceLogic)
Definition: QNodeImpl.java:60
Builder(NetsimInternalInterface netsimEngine2, NetsimEngineContext context, QSimConfigGroup qsimConfig)
Definition: QNodeImpl.java:54