View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical.util;
2   
3   import static org.opentrafficsim.core.gtu.behavioralcharacteristics.AbstractParameterType.Check.ATLEASTONE;
4   import static org.opentrafficsim.core.gtu.behavioralcharacteristics.AbstractParameterType.Check.POSITIVE;
5   
6   import java.io.Serializable;
7   import java.util.ArrayList;
8   import java.util.HashMap;
9   import java.util.HashSet;
10  import java.util.Iterator;
11  import java.util.List;
12  import java.util.Map;
13  import java.util.Set;
14  import java.util.SortedMap;
15  import java.util.SortedSet;
16  import java.util.TreeMap;
17  import java.util.UUID;
18  
19  import org.djunits.unit.AccelerationUnit;
20  import org.djunits.unit.LengthUnit;
21  import org.djunits.unit.TimeUnit;
22  import org.djunits.value.vdouble.scalar.Acceleration;
23  import org.djunits.value.vdouble.scalar.Duration;
24  import org.djunits.value.vdouble.scalar.Length;
25  import org.djunits.value.vdouble.scalar.Speed;
26  import org.djunits.value.vdouble.scalar.Time;
27  import org.opentrafficsim.core.gtu.GTUException;
28  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
29  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
30  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypeDouble;
31  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypeDuration;
32  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypeLength;
33  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
34  import org.opentrafficsim.core.network.Node;
35  import org.opentrafficsim.core.network.route.Route;
36  import org.opentrafficsim.road.gtu.lane.RoadGTUTypes;
37  import org.opentrafficsim.road.gtu.lane.perception.headway.AbstractHeadwayGTU;
38  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayConflict;
39  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTUSimple;
40  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayStopLine;
41  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
42  import org.opentrafficsim.road.network.lane.CrossSectionLink;
43  import org.opentrafficsim.road.network.lane.conflict.ConflictRule;
44  import org.opentrafficsim.road.network.lane.conflict.ConflictType;
45  import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
46  
47  import nl.tudelft.simulation.language.Throw;
48  
49  /**
50   * This class implements default behavior for intersection conflicts for use in tactical planners.
51   * <p>
52   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
53   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
54   * <p>
55   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jun 3, 2016 <br>
56   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
57   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
58   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
59   */
60  // TODO conflict over multiple lanes (longitudinal in own direction)
61  // TODO stop length depending on speed?
62  // TODO vehicle generation at speed in T-junction demo
63  // TODO turbo roundabout demo
64  // TODO a) yielding while having priority happens only when leaders is standing still on conflict (then its useless...)
65  // b) two vehicles can remain upstream of merge if vehicle stands on merge but leaves some space to move
66  // probably 1 is yielding, and 1 is courtesy yielding as the other stands still
67  // c) they might start moving together and collide further down (no response to negative headway on merge)
68  public final class ConflictUtil
69  {
70  
71      /** Minimum time gap between events. */
72      public static final ParameterTypeDuration MIN_GAP = new ParameterTypeDuration("minGap", "Minimum gap for conflicts.",
73              new Duration(0.000001, TimeUnit.SECOND), POSITIVE);
74  
75      /** Stopping distance at conflicts. */
76      public static final ParameterTypeLength S0_CONF =
77              new ParameterTypeLength("s0 conf", "Stopping distance at conflicts.", new Length(1.5, LengthUnit.METER), POSITIVE);
78  
79      /** Multiplication factor on time for conservative assessment. */
80      public static final ParameterTypeDouble TIME_FACTOR =
81              new ParameterTypeDouble("timeFactor", "Safety factor on estimated time.", 1.1, ATLEASTONE);
82  
83      /** Area before stop line where one is considered arrived at the intersection. */
84      public static final ParameterTypeLength STOP_AREA =
85              new ParameterTypeLength("stopArea", "Area before stop line where one is considered arrived at the intersection.",
86                      new Length(4, LengthUnit.METER), POSITIVE);
87  
88      /** Time step for free acceleration anticipation. */
89      private static final Duration TIME_STEP = new Duration(0.5, TimeUnit.SI);
90  
91      /** Acceleration that is equal to ignoring. */
92      private static final Acceleration IGNORE = new Acceleration(Double.MAX_VALUE, AccelerationUnit.SI);
93  
94      /**
95       * Do not instantiate.
96       */
97      private ConflictUtil()
98      {
99          //
100     }
101 
102     /**
103      * Approach conflicts by applying appropriate acceleration (or deceleration). The model may yield for a vehicle even while
104      * having priority. Such a 'yield plan' is remembered in <tt>YieldPlans</tt>. By forwarding the same <tt>YieldPlans</tt> for
105      * a GTU consistency of such plans is provided. If any conflict is not accepted to pass, stopping before a more upstream
106      * conflict is applied if there not sufficient stopping length in between conflicts.
107      * @param behavioralCharacteristics behavioral characteristics
108      * @param conflicts set of conflicts to approach
109      * @param leaders leading vehicles
110      * @param carFollowingModel car-following model
111      * @param vehicleLength length of vehicle
112      * @param speed current speed
113      * @param acceleration current acceleration
114      * @param speedLimitInfo speed limit info
115      * @param conflictPlans set of plans for conflict
116      * @return acceleration appropriate for approaching the conflicts
117      * @throws GTUException in case of an unsupported conflict rule
118      * @throws ParameterException if a parameter is not defined or out of bounds
119      */
120     @SuppressWarnings("checkstyle:parameternumber")
121     public static Acceleration approachConflicts(final BehavioralCharacteristics behavioralCharacteristics,
122             final SortedSet<HeadwayConflict> conflicts, final SortedSet<AbstractHeadwayGTU> leaders,
123             final CarFollowingModel carFollowingModel, final Length vehicleLength, final Speed speed,
124             final Acceleration acceleration, final SpeedLimitInfo speedLimitInfo, final ConflictPlans conflictPlans)
125             throws GTUException, ParameterException
126     {
127 
128         Acceleration a = IGNORE;
129         if (conflicts.isEmpty())
130         {
131             return a;
132         }
133 
134         conflictPlans.cleanYieldPlans();
135         List<Length> prevStarts = new ArrayList<>();
136         List<Length> prevEnds = new ArrayList<>();
137 
138         for (HeadwayConflict conflict : conflicts)
139         {
140 
141             // adjust acceleration for situations where stopping might not be required
142             if (conflict.isCrossing())
143             {
144                 // avoid collision if crossing is occupied
145                 a = Acceleration.min(a,
146                         avoidCrossingCollision(behavioralCharacteristics, conflict, carFollowingModel, speed, speedLimitInfo));
147             }
148             else
149             {
150                 // follow leading GTUs on merge or split
151                 a = Acceleration.min(a, followConflictingLeaderOnMergeOrSplit(conflict, behavioralCharacteristics,
152                         carFollowingModel, speed, speedLimitInfo));
153             }
154             if (conflict.getDistance().lt(Length.ZERO))
155             {
156                 // ignore conflicts we are on (i.e. negative distance to start of conflict)
157                 continue;
158             }
159 
160             // determine if we need to stop
161             boolean stop;
162             switch (conflict.getConflictRule())
163             {
164                 case PRIORITY:
165                 {
166                     stop = stopForPriorityConflict(conflict, leaders, speed, vehicleLength, behavioralCharacteristics,
167                             conflictPlans);
168                     break;
169                 }
170                 case GIVE_WAY:
171                 {
172                     stop = stopForGiveWayConflict(conflict, leaders, speed, acceleration, vehicleLength,
173                             behavioralCharacteristics, speedLimitInfo, carFollowingModel);
174                     break;
175                 }
176                 case STOP:
177                 {
178                     stop = stopForStopConflict(conflict, leaders, speed, acceleration, vehicleLength, behavioralCharacteristics,
179                             speedLimitInfo, carFollowingModel);
180                     break;
181                 }
182                 case ALL_STOP:
183                 {
184                     stop = stopForAllStopConflict(conflict, conflictPlans);
185                     break;
186                 }
187                 case SPLIT:
188                 {
189                     stop = false; // skipped anyway
190                     break;
191                 }
192                 default:
193                 {
194                     throw new GTUException("Unsupported conflict rule encountered while approaching conflicts.");
195                 }
196             }
197 
198             // stop if required, account for upstream conflicts to keep clear
199             if (!conflict.getConflictType().equals(ConflictType.SPLIT))
200             {
201 
202                 if (stop)
203                 {
204                     prevStarts.add(conflict.getDistance());
205                     // stop for first conflict looking upstream of this blocked conflict that allows sufficient space
206                     int j = 0; // most upstream conflict if not in between conflicts
207                     for (int i = prevEnds.size() - 1; i >= 0; i--) // downstream to upstream
208                     {
209                         // note, at this point prevStarts contains one more conflict than prevEnds
210                         if (prevStarts.get(i + 1).minus(prevEnds.get(i))
211                                 .gt(passableDistance(Speed.ZERO, vehicleLength, behavioralCharacteristics)))
212                         {
213                             j = i + 1;
214                             break;
215                         }
216                     }
217 
218                     // TODO
219                     // if this lowers our acceleration, we need to check if we are able to pass upstream conflicts still in time
220 
221                     // stop for j'th conflict, if deceleration is too strong, for next one
222                     behavioralCharacteristics.setParameter(ParameterTypes.S0, behavioralCharacteristics.getParameter(S0_CONF));
223                     Acceleration aCF = new Acceleration(Double.NEGATIVE_INFINITY, AccelerationUnit.SI);
224                     while (aCF.si < -6.0 && j < prevStarts.size())
225                     {
226                         aCF = Acceleration.max(aCF, CarFollowingUtil.stop(carFollowingModel, behavioralCharacteristics, speed,
227                                 speedLimitInfo, prevStarts.get(j)));
228                         j++;
229                     }
230                     behavioralCharacteristics.resetParameter(ParameterTypes.S0);
231                     a = Acceleration.min(a, aCF);
232                     break;
233                 }
234                 if (conflict.getConflictRule().equals(ConflictRule.GIVE_WAY)
235                         || conflict.getConflictRule().equals(ConflictRule.STOP))
236                 {
237                     prevStarts.add(conflict.getDistance());
238                     prevEnds.add(conflict.getDistance().plus(conflict.getLength()));
239                 }
240             }
241 
242         }
243 
244         if (a.si < -6.0)
245         {
246             return IGNORE;
247         }
248         return a;
249     }
250 
251     /**
252      * Determines acceleration for following conflicting vehicles <i>on</i> a merge or split conflict.
253      * @param conflict merge or split conflict
254      * @param behavioralCharacteristics behavioral characteristics
255      * @param carFollowingModel car-following model
256      * @param speed current speed
257      * @param speedLimitInfo speed limit info
258      * @return acceleration for following conflicting vehicles <i>on</i> a merge or split conflict
259      * @throws ParameterException if a parameter is not given or out of bounds
260      */
261     private static Acceleration followConflictingLeaderOnMergeOrSplit(final HeadwayConflict conflict,
262             final BehavioralCharacteristics behavioralCharacteristics, final CarFollowingModel carFollowingModel,
263             final Speed speed, final SpeedLimitInfo speedLimitInfo) throws ParameterException
264     {
265         // ignore if no conflicting GTU's
266         if (conflict.getDownstreamConflictingGTUs().isEmpty())
267         {
268             return IGNORE;
269         }
270         // get the most upstream GTU to consider
271         AbstractHeadwayGTU c = conflict.getDownstreamConflictingGTUs().first();
272         if (c.isAhead())
273         {
274             // conflict GTU completely downstream of conflict (i.e. regular car-following, ignore here)
275             return IGNORE;
276         }
277         // conflict GTU (partially) on the conflict
278         // {@formatter:off}
279         // ______________________________________________ 
280         //   ___      virtual headway   |  ___  |
281         //  |___|(-----------------------)|___|(vehicle from south, on lane from south)
282         // _____________________________|_______|________
283         //                              /       / 
284         //                             /       /
285         // {@formatter:on}
286         Length virtualHeadway = conflict.getDistance().plus(c.getOverlapRear());
287         if (virtualHeadway.le(Length.ZERO) && conflict.getDistance().le(Length.ZERO))
288         {
289             // conflict GTU downstream of start of conflict, but upstream of us
290             return IGNORE;
291         }
292         // follow leader
293         SortedMap<Length, Speed> leaders = new TreeMap<>();
294         leaders.put(virtualHeadway, c.getSpeed());
295         Acceleration a = carFollowingModel.followingAcceleration(behavioralCharacteristics, speed, speedLimitInfo, leaders);
296         // if conflicting GTU is partially upstream of the conflict and at (near) stand-still, stop for the conflict rather than
297         // following the tail of the conflicting GTU
298         if (conflict.isMerge() && virtualHeadway.lt(conflict.getDistance()))
299         {
300             // {@formatter:off}
301             /*
302              * ______________________________________________
303              *    ___    stop for conflict  |       | 
304              *   |___|(--------------------)|   ___ |
305              * _____________________________|__/  /_|________ 
306              *                              / /__/  /
307              *                             /       /
308              */
309             // {@formatter:on}
310             behavioralCharacteristics.setParameter(ParameterTypes.S0, behavioralCharacteristics.getParameter(S0_CONF));
311             Acceleration aStop = CarFollowingUtil.stop(carFollowingModel, behavioralCharacteristics, speed, speedLimitInfo,
312                     conflict.getDistance());
313             behavioralCharacteristics.resetParameter(ParameterTypes.S0);
314             a = Acceleration.max(a, aStop); // max, which ever allows the largest acceleration
315         }
316         return a;
317     }
318 
319     /**
320      * Determines an acceleration required to avoid a collision with GTUs <i>on</i> a crossing conflict.
321      * @param behavioralCharacteristics behavioral characteristics
322      * @param conflict conflict
323      * @param carFollowingModel car-following model
324      * @param speed current speed
325      * @param speedLimitInfo speed limit info
326      * @return acceleration required to avoid a collision
327      * @throws ParameterException if parameter is not defined
328      */
329     private static Acceleration avoidCrossingCollision(final BehavioralCharacteristics behavioralCharacteristics,
330             final HeadwayConflict conflict, final CarFollowingModel carFollowingModel, final Speed speed,
331             final SpeedLimitInfo speedLimitInfo) throws ParameterException
332     {
333 
334         // TODO only within visibility
335         List<AbstractHeadwayGTU> conflictingGTUs = new ArrayList<>();
336         for (AbstractHeadwayGTU gtu : conflict.getUpstreamConflictingGTUs())
337         {
338             if (isOnRoute(conflict.getConflictingLink(), gtu))
339             {
340                 // first upstream vehicle on route to this conflict
341                 conflictingGTUs.add(gtu);
342                 break;
343             }
344         }
345         for (AbstractHeadwayGTU gtu : conflict.getDownstreamConflictingGTUs())
346         {
347             if (gtu.isParallel())
348             {
349                 conflictingGTUs.add(gtu);
350             }
351             else
352             {
353                 // vehicles beyond conflict are not a thread
354                 break;
355             }
356         }
357 
358         if (conflictingGTUs.isEmpty())
359         {
360             return IGNORE;
361         }
362 
363         Acceleration a = IGNORE;
364         for (AbstractHeadwayGTU conflictingGTU : conflictingGTUs)
365         {
366             AnticipationInfo tteC;
367             Length distance;
368             if (conflictingGTU.isParallel())
369             {
370                 tteC = new AnticipationInfo(Duration.ZERO, conflictingGTU.getSpeed());
371                 distance = conflictingGTU.getOverlapRear().abs().plus(conflictingGTU.getOverlap())
372                         .plus(conflictingGTU.getOverlapFront().abs());
373             }
374             else
375             {
376                 tteC = AnticipationInfo.anticipateMovement(conflictingGTU.getDistance(), conflictingGTU.getSpeed(),
377                         Acceleration.ZERO);
378                 distance = conflictingGTU.getDistance().plus(conflict.getLength()).plus(conflictingGTU.getLength());
379             }
380             AnticipationInfo ttcC = AnticipationInfo.anticipateMovement(distance, conflictingGTU.getSpeed(), Acceleration.ZERO);
381             AnticipationInfo tteO = AnticipationInfo.anticipateMovementFreeAcceleration(conflict.getDistance(), speed,
382                     behavioralCharacteristics, carFollowingModel, speedLimitInfo, TIME_STEP);
383             // enter before cleared
384             // TODO safety factor?
385             if (tteC.getDuration().lt(tteO.getDuration()) && tteO.getDuration().lt(ttcC.getDuration()))
386             {
387                 if (!conflictingGTU.getSpeed().eq(Speed.ZERO))
388                 {
389                     // solve parabolic speed profile s = v*t + .5*a*t*t, a =
390                     double acc = 2 * (conflict.getDistance().si - speed.si * ttcC.getDuration().si)
391                             / (ttcC.getDuration().si * ttcC.getDuration().si);
392                     // time till zero speed > time to avoid conflict?
393                     if (speed.si / -acc > ttcC.getDuration().si)
394                     {
395                         a = Acceleration.min(a, new Acceleration(acc, AccelerationUnit.SI));
396                     }
397                     else
398                     {
399                         // will reach zero speed ourselves
400                         a = Acceleration.min(a, CarFollowingUtil.stop(carFollowingModel, behavioralCharacteristics, speed,
401                                 speedLimitInfo, conflict.getDistance()));
402                     }
403                 }
404                 // conflicting vehicle stand-still, ignore even at conflict
405             }
406         }
407         return a;
408     }
409 
410     /**
411      * Approach a priority conflict. Stopping is applied to give way to conflicting traffic in case congestion is present on the
412      * own lane. This is courtesy yielding.
413      * @param conflict conflict to approach
414      * @param leaders leading vehicles in own lane
415      * @param speed current speed
416      * @param vehicleLength vehicle length
417      * @param behavioralCharacteristics behavioral characteristics
418      * @param yieldPlans set of plans for yielding with priority
419      * @return whether to stop for this conflict
420      * @throws ParameterException if parameter B is not defined
421      */
422     private static boolean stopForPriorityConflict(final HeadwayConflict conflict, final SortedSet<AbstractHeadwayGTU> leaders,
423             final Speed speed, final Length vehicleLength, final BehavioralCharacteristics behavioralCharacteristics,
424             final ConflictPlans yieldPlans) throws ParameterException
425     {
426 
427         // Escape if no conflicting traffic or traffic ahead
428         if (leaders.isEmpty() || conflict.getUpstreamConflictingGTUs().isEmpty())
429         {
430             // no leader, or no conflicting vehicle
431             return false;
432         }
433 
434         // Consider factors where we don't yield, even if this was our plan before
435         Length distance = conflict.getDistance().minus(leaders.first().getDistance())
436                 .plus(passableDistance(speed, vehicleLength, behavioralCharacteristics));
437         if (conflict.isCrossing())
438         {
439             // Merge is cleared at start, crossing at end
440             distance = distance.plus(conflict.getLength());
441         }
442         AnticipationInfo ttpD =
443                 AnticipationInfo.anticipateMovement(distance, leaders.first().getSpeed(), leaders.first().getAcceleration());
444         AbstractHeadwayGTU first = conflict.getUpstreamConflictingGTUs().first();
445         boolean conflictGtuCanReachConflict =
446                 first.getDistance().lt(behavioralCharacteristics.getParameter(STOP_AREA)) || first.getSpeed().gt(Speed.ZERO);
447         boolean leaderStandsOnConflict = leaders.first().getSpeed().eq(Speed.ZERO) && !leaders.first().isAhead();
448         // Do not courtesy yield, even if this was the plan, if:
449         // 1) Leader leaves conflict passable now
450         // 2) Leader stands still on conflict (no point in yielding, conflict is blocked anyway)
451         // 3) Conflict vehicle stand still away from conflict (i.e. blocked by something else)
452         if (ttpD.getDuration().eq(Duration.ZERO) || leaderStandsOnConflict || !conflictGtuCanReachConflict)
453         {
454             return false;
455         }
456 
457         // If yielding was the plan, keep yielding
458         if (yieldPlans.isYieldPlan(conflict, first))
459         {
460             yieldPlans.setYieldPlan(conflict, first); // this makes sure the plan is not cleared
461             return true;
462         }
463 
464         // Check factors to initiate a courtesy yield plan (in parts, to not calculate it all if not needed)
465         // 1) Conflict vehicle does not stand still away from conflict (i.e. might recht conflict)
466         // 2) Leader is ahead of conflict or still moving
467         if (conflictGtuCanReachConflict && !leaderStandsOnConflict)
468         {
469             AnticipationInfo ttcC = AnticipationInfo.anticipateMovementFreeAcceleration(
470                     first.getDistance().plus(conflict.getConflictingLength()).plus(first.getLength()), first.getSpeed(),
471                     first.getBehavioralCharacteristics(), first.getCarFollowingModel(), first.getSpeedLimitInfo(), TIME_STEP);
472             // 3) Conflict vehicle will pass conflict before leader makes it passable, we may courtesy yield without loss
473             if (ttpD.getDuration().ge(ttcC.getDuration()))// .multiplyBy(behavioralCharacteristics.getParameter(TIME_FACTOR))
474             // .plus(behavioralCharacteristics.getParameter(MIN_GAP))))
475             {
476                 // 4) Acceleration (constant) is comfortable (at initialization of courtesy yield)
477                 Acceleration b = behavioralCharacteristics.getParameter(ParameterTypes.B);
478                 Acceleration bReq = new Acceleration(.5 * speed.si * speed.si / conflict.getDistance().si, AccelerationUnit.SI);
479                 if (bReq.lt(b))
480                 {
481                     yieldPlans.setYieldPlan(conflict, first);
482                     return true;
483                 }
484             }
485         }
486 
487         // No initialization of courtesy yield plan
488         return false;
489 
490     }
491 
492     /**
493      * Approach a give-way conflict.
494      * @param conflict conflict
495      * @param leaders leaders
496      * @param speed current speed
497      * @param acceleration current acceleration
498      * @param vehicleLength vehicle length
499      * @param behavioralCharacteristics behavioral characteristics
500      * @param speedLimitInfo speed limit info
501      * @param carFollowingModel car-following model
502      * @return whether to stop for this conflict
503      * @throws ParameterException if a parameter is not defined
504      */
505     @SuppressWarnings("checkstyle:parameternumber")
506     private static boolean stopForGiveWayConflict(final HeadwayConflict conflict, final SortedSet<AbstractHeadwayGTU> leaders,
507             final Speed speed, final Acceleration acceleration, final Length vehicleLength,
508             final BehavioralCharacteristics behavioralCharacteristics, final SpeedLimitInfo speedLimitInfo,
509             final CarFollowingModel carFollowingModel) throws ParameterException
510     {
511 
512         // TODO conflicting vehicle on conflict, but will leave sooner then we enter, so no problem?
513         if (conflict.getConflictType().equals(ConflictType.CROSSING) && !conflict.getDownstreamConflictingGTUs().isEmpty()
514                 && conflict.getDownstreamConflictingGTUs().first().isParallel())
515         {
516             // vehicle on the conflict
517             return true;
518         }
519 
520         // Get data independent of conflicting vehicle
521         // parameters
522         Acceleration b = behavioralCharacteristics.getParameter(ParameterTypes.B).neg();
523         double f = behavioralCharacteristics.getParameter(TIME_FACTOR);
524         Duration gap = behavioralCharacteristics.getParameter(MIN_GAP);
525         // time till conflict is cleared
526         Length distance = conflict.getDistance().plus(vehicleLength);
527         if (conflict.isCrossing())
528         {
529             // merge is cleared at start, crossing at end
530             distance = distance.plus(conflict.getLength());
531         }
532         // based on current acceleration, but limited by free acceleration
533         // TODO maybe only constant acceleration? What did MOTUS do?
534         AnticipationInfo ttcOa = AnticipationInfo.anticipateMovementFreeAcceleration(distance, speed, behavioralCharacteristics,
535                 carFollowingModel, speedLimitInfo, TIME_STEP);
536         AnticipationInfo ttcO;
537         if (acceleration.lt(Acceleration.ZERO))
538         {
539             AnticipationInfo ttcOb = AnticipationInfo.anticipateMovement(distance, speed, acceleration);
540             ttcO = ttcOa.getDuration().gt(ttcOb.getDuration()) ? ttcOa : ttcOb;
541         }
542         else
543         {
544             ttcO = ttcOa;
545         }
546         // time till downstream vehicle will make the conflict passible, under constant speed or safe deceleration
547         AnticipationInfo ttpDz = null;
548         AnticipationInfo ttpDs = null;
549         if (conflict.isCrossing())
550         {
551             if (!leaders.isEmpty())
552             {
553                 distance = conflict.getDistance().minus(leaders.first().getDistance()).plus(conflict.getLength())
554                         .plus(passableDistance(Speed.ZERO, vehicleLength, behavioralCharacteristics));
555                 ttpDz = AnticipationInfo.anticipateMovement(distance, leaders.first().getSpeed(), Acceleration.ZERO);
556                 // TODO not b but b_critical?
557                 ttpDs = AnticipationInfo.anticipateMovement(distance, leaders.first().getSpeed(), b);
558                 if (Double.isInfinite(ttpDs.getDuration().si))
559                 {
560                     // if leader decelerates to stand-still, not sufficient space after conflict
561                     // stop, even without conflicting vehicles
562                     return true;
563                 }
564             }
565             else
566             {
567                 // no leader so conflict is passible within a duration of 0
568                 ttpDz = new AnticipationInfo(Duration.ZERO, Speed.ZERO);
569                 ttpDs = new AnticipationInfo(Duration.ZERO, Speed.ZERO);
570             }
571         }
572 
573         List<AbstractHeadwayGTU> conflictingVehicles = new ArrayList<>();
574         if (!conflict.getUpstreamConflictingGTUs().isEmpty())
575         {
576             for (AbstractHeadwayGTU vehicle : conflict.getUpstreamConflictingGTUs())
577             {
578                 if (isOnRoute(conflict.getConflictingLink(), vehicle))
579                 {
580                     conflictingVehicles.add(vehicle);
581                 }
582             }
583         }
584         else
585         {
586             // none within visibility, assume a conflicting vehicle just outside of visibility driving at speed limit
587             try
588             {
589                 HeadwayGTUSimple conflictGtu = new HeadwayGTUSimple("virtual " + UUID.randomUUID().toString(), RoadGTUTypes.CAR,
590                         conflict.getConflictingVisibility(), new Length(4.0, LengthUnit.SI),
591                         conflict.getConflictingSpeedLimit(), Acceleration.ZERO);
592                 conflictingVehicles.add(conflictGtu);
593             }
594             catch (GTUException exception)
595             {
596                 throw new RuntimeException("Could not create a virtual conflicting vehicle at visibility range.", exception);
597             }
598         }
599 
600         // Loop over conflicting vehicles
601         for (AbstractHeadwayGTU conflictingVehicle : conflictingVehicles)
602         {
603 
604             // time till conflict vehicle will enter, under free acceleration and safe deceleration
605             // TODO what does the c stand for? not constant acceleration, i.e. also free acceleration is used
606             AnticipationInfo tteCc;
607             if (conflictingVehicle instanceof HeadwayGTUSimple)
608             {
609                 tteCc = AnticipationInfo.anticipateMovement(conflictingVehicle.getDistance(), conflictingVehicle.getSpeed(),
610                         conflictingVehicle.getAcceleration());
611             }
612             else
613             {
614                 BehavioralCharacteristics bc = conflictingVehicle.getBehavioralCharacteristics();
615                 SpeedLimitInfo sli = conflictingVehicle.getSpeedLimitInfo();
616                 CarFollowingModel cfm = conflictingVehicle.getCarFollowingModel();
617                 // Constant acceleration creates inf at stand still, triggering passing trough a congested stream
618                 // tteCc = AnticipationInfo.anticipateMovement(conflictingVehicle.getDistance(), conflictingVehicle.getSpeed(),
619                 // conflictingVehicle.getAcceleration());
620                 tteCc = AnticipationInfo.anticipateMovementFreeAcceleration(conflictingVehicle.getDistance(),
621                         conflictingVehicle.getSpeed(), bc, cfm, sli, TIME_STEP);
622             }
623             AnticipationInfo tteCs =
624                     AnticipationInfo.anticipateMovement(conflictingVehicle.getDistance(), conflictingVehicle.getSpeed(), b);
625 
626             // check gap
627             if (conflict.isMerge())
628             {
629 
630                 // Merge, will be each others followers, add time to overcome speed difference
631                 double vSelf = ttcO.getEndSpeed().si;
632                 double speedDiff = conflictingVehicle.getSpeed().si - vSelf;
633                 speedDiff = speedDiff > 0 ? speedDiff : 0;
634                 Duration additionalTime = new Duration(speedDiff / -b.si, TimeUnit.SI);
635                 // check if conflict vehicle will be upstream after that time, position beyond conflict after additional time
636                 double followerFront = conflictingVehicle.getSpeed().si * ttcO.getDuration().si
637                         - conflictingVehicle.getDistance().si + (conflictingVehicle.getSpeed().si * additionalTime.si
638                                 + 0.5 * b.si * additionalTime.si * additionalTime.si);
639                 double ownRear = vSelf * additionalTime.si; // constant speed after clearing
640                 Duration tMax = behavioralCharacteristics.getParameter(ParameterTypes.TMAX);
641                 Length s0 = behavioralCharacteristics.getParameter(ParameterTypes.S0);
642                 // 1) will clear the conflict after the conflict vehicle enters
643                 // 2) not sufficient time to overcome speed difference
644                 // 3) conflict vehicle will be too near after adjusting speed
645                 if (ttcO.getDuration().multiplyBy(f).plus(gap).gt(tteCc.getDuration())
646                         || ttcOa.getDuration().plus(additionalTime).multiplyBy(f).plus(gap).gt(tteCs.getDuration())
647                         || ownRear < (followerFront + (tMax.si + gap.si) * vSelf + s0.si) * f)
648                 {
649                     return true;
650                 }
651 
652             }
653             else if (conflict.isCrossing())
654             {
655 
656                 // Crossing, stop if order of events is not ok
657                 // 1) downstream vehicle must supply sufficient space before conflict vehicle will enter
658                 // 2) must clear the conflict before the conflict vehicle will enter
659                 // 3) if leader decelerates with b??, conflict vehicle should be able to safely delay entering conflict
660                 if (ttpDz.getDuration().multiplyBy(f).plus(gap).gt(tteCc.getDuration())
661                         || ttcOa.getDuration().multiplyBy(f).plus(gap).gt(tteCc.getDuration())
662                         || ttpDs.getDuration().multiplyBy(f).plus(gap).gt(tteCs.getDuration()))
663                 {
664                     return true;
665                 }
666 
667             }
668             else
669             {
670                 throw new RuntimeException(
671                         "Conflict is of unknown type " + conflict.getConflictType() + ", which is not merge nor a crossing.");
672             }
673         }
674 
675         // No conflict vehicle triggered stopping
676         return false;
677 
678     }
679 
680     /**
681      * Approach a stop conflict. Currently this is equal to approaching a give-way conflict.
682      * @param conflict conflict
683      * @param leaders leaders
684      * @param speed current speed
685      * @param acceleration current acceleration
686      * @param vehicleLength vehicle length
687      * @param behavioralCharacteristics behavioral characteristics
688      * @param speedLimitInfo speed limit info
689      * @param carFollowingModel car-following model
690      * @return whether to stop for this conflict
691      * @throws ParameterException if a parameter is not defined
692      */
693     @SuppressWarnings("checkstyle:parameternumber")
694     private static boolean stopForStopConflict(final HeadwayConflict conflict, final SortedSet<AbstractHeadwayGTU> leaders,
695             final Speed speed, final Acceleration acceleration, final Length vehicleLength,
696             final BehavioralCharacteristics behavioralCharacteristics, final SpeedLimitInfo speedLimitInfo,
697             final CarFollowingModel carFollowingModel) throws ParameterException
698     {
699         return stopForGiveWayConflict(conflict, leaders, speed, acceleration, vehicleLength, behavioralCharacteristics,
700                 speedLimitInfo, carFollowingModel);
701     }
702 
703     /**
704      * Approach an all-stop conflict.
705      * @param conflict conflict to approach
706      * @param conflictPlans set of plans for conflict
707      * @return whether to stop for this conflict
708      */
709     private static boolean stopForAllStopConflict(final HeadwayConflict conflict, final ConflictPlans conflictPlans)
710     {
711         // TODO all-stop behavior
712 
713         if (conflictPlans.isStopPhaseRun(conflict.getStopLine()))
714         {
715             return false;
716         }
717 
718         return false;
719     }
720 
721     /**
722      * Returns whether the conflicting link is on the route of the given gtu.
723      * @param conflictingLink CrossSectionLink; conflicting link
724      * @param gtu AbstractHeadwayGTU; gtu
725      * @return whether the conflict is on the route of the given gtu
726      */
727     private static boolean isOnRoute(final CrossSectionLink conflictingLink, final AbstractHeadwayGTU gtu)
728     {
729         try
730         {
731             Route route = gtu.getRoute();
732             if (route == null)
733             {
734                 // conservative assumption: it's on the route (gtu should be upstream of the conflict)
735                 return true;
736             }
737             Node startNode = conflictingLink.getStartNode();
738             Node endNode = conflictingLink.getEndNode();
739             return route.contains(startNode) && route.contains(endNode)
740                     && Math.abs(route.indexOf(endNode) - route.indexOf(startNode)) == 1;
741         }
742         catch (@SuppressWarnings("unused") UnsupportedOperationException uoe)
743         {
744             // conservative assumption: it's on the route (gtu should be upstream of the conflict)
745             return true;
746         }
747     }
748 
749     /**
750      * Returns a speed dependent distance needed behind the leader to completely pass the conflict.
751      * @param speed speed
752      * @param vehicleLength vehicle length
753      * @param behavioralCharacteristics behavioral characteristics
754      * @return speed dependent distance needed behind the leader to completely pass the conflict
755      * @throws ParameterException if parameter is not available
756      */
757     private static Length passableDistance(final Speed speed, final Length vehicleLength,
758             final BehavioralCharacteristics behavioralCharacteristics) throws ParameterException
759     {
760         return behavioralCharacteristics.getParameter(ParameterTypes.T).multiplyBy(speed)
761                 .plus(behavioralCharacteristics.getParameter(ParameterTypes.S0)).plus(vehicleLength);
762     }
763 
764     /**
765      * Holds the tactical plans of a driver considering conflicts. These are remembered for consistency. For instance, if the
766      * decision is made to yield as current deceleration suggests it's safe to do so, but the trajectory for stopping in front
767      * of the conflict results in deceleration slightly above what is considered safe deceleration, the plan should not be
768      * abandoned. Decelerations above what is considered safe deceleration may result due to numerical overshoot or other factor
769      * coming into play in car-following models. Many other examples exist where a driver sticks to a certain plan.
770      * <p>
771      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
772      * <br>
773      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
774      * <p>
775      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jun 7, 2016 <br>
776      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
777      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
778      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
779      */
780     public static final class ConflictPlans implements Serializable
781     {
782 
783         /** */
784         private static final long serialVersionUID = 20160811L;
785 
786         /** Set of current plans. */
787         private final Map<String, String> yieldPlans = new HashMap<>();
788 
789         /** Set of conflicts that are still actively considered for a yield plan. */
790         private final Set<String> activeYieldPlans = new HashSet<>();
791 
792         /** Phases of navigating an all-stop intersection per intersection. */
793         private final HashMap<String, StopPhase> stopPhases = new HashMap<>();
794 
795         /** Estimated arrival times of vehicles at all-stop intersection. */
796         private final HashMap<String, Time> arrivalTimes = new HashMap<>();
797 
798         /**
799          * Returns whether a plan exists for yielding at the conflict for the given conflict GTU.
800          * @param conflict conflict
801          * @param gtu conflicting GTU
802          * @return whether a plan exists for yielding at the conflict for the given conflict GTU
803          */
804         boolean isYieldPlan(final HeadwayConflict conflict, final AbstractHeadwayGTU gtu)
805         {
806             return this.yieldPlans.containsKey(conflict.getId()) && this.yieldPlans.get(conflict.getId()).equals(gtu.getId());
807         }
808 
809         /**
810          * Sets or maintains the plan to yield at the conflict for the given conflict RSU.
811          * @param conflict conflict to yield at
812          * @param gtu conflicting GTU
813          */
814         void setYieldPlan(final HeadwayConflict conflict, final AbstractHeadwayGTU gtu)
815         {
816             this.yieldPlans.put(conflict.getId(), gtu.getId());
817             this.activeYieldPlans.add(conflict.getId());
818         }
819 
820         /**
821          * Clears any yield plan that was no longer kept active in the last evaluation of conflicts.
822          */
823         void cleanYieldPlans()
824         {
825             // remove any plan not represented in activePlans
826             Iterator<String> iterator = this.yieldPlans.keySet().iterator();
827             while (iterator.hasNext())
828             {
829                 String conflictId = iterator.next();
830                 if (!this.activeYieldPlans.contains(conflictId))
831                 {
832                     iterator.remove();
833                 }
834             }
835             // clear the activePlans for the next consideration of conflicts
836             this.activeYieldPlans.clear();
837         }
838 
839         /**
840          * Sets the estimated arrival time of a GTU.
841          * @param gtu GTU
842          * @param time estimated arrival time
843          */
844         void setArrivalTime(final AbstractHeadwayGTU gtu, final Time time)
845         {
846             this.arrivalTimes.put(gtu.getId(), time);
847         }
848 
849         /**
850          * Returns the estimated arrival time of given GTU.
851          * @param gtu GTU
852          * @return estimated arrival time of given GTU
853          */
854         Time getArrivalTime(final AbstractHeadwayGTU gtu)
855         {
856             return this.arrivalTimes.get(gtu.getId());
857         }
858 
859         /**
860          * Sets the current phase to 'approach' for the given stop line.
861          * @param stopLine stop line
862          */
863         void setStopPhaseApproach(final HeadwayStopLine stopLine)
864         {
865             this.stopPhases.put(stopLine.getId(), StopPhase.APPROACH);
866         }
867 
868         /**
869          * Sets the current phase to 'yield' for the given stop line.
870          * @param stopLine stop line
871          * @throws RuntimeException if the phase was not set to approach before
872          */
873         void setStopPhaseYield(final HeadwayStopLine stopLine)
874         {
875             Throw.when(
876                     !this.stopPhases.containsKey(stopLine.getId())
877                             || !this.stopPhases.get(stopLine.getId()).equals(StopPhase.APPROACH),
878                     RuntimeException.class, "Yield stop phase is set for stop line that was not approached.");
879             this.stopPhases.put(stopLine.getId(), StopPhase.YIELD);
880         }
881 
882         /**
883          * Sets the current phase to 'run' for the given stop line.
884          * @param stopLine stop line
885          * @throws RuntimeException if the phase was not set to approach before
886          */
887         void setStopPhaseRun(final HeadwayStopLine stopLine)
888         {
889             Throw.when(!this.stopPhases.containsKey(stopLine.getId()), RuntimeException.class,
890                     "Run stop phase is set for stop line that was not approached.");
891             this.stopPhases.put(stopLine.getId(), StopPhase.YIELD);
892         }
893 
894         /**
895          * @param stopLine stop line
896          * @return whether the current phase is 'approach' for the given stop line
897          */
898         boolean isStopPhaseApproach(final HeadwayStopLine stopLine)
899         {
900             return this.stopPhases.containsKey(stopLine.getId())
901                     && this.stopPhases.get(stopLine.getId()).equals(StopPhase.APPROACH);
902         }
903 
904         /**
905          * @param stopLine stop line
906          * @return whether the current phase is 'yield' for the given stop line
907          */
908         boolean isStopPhaseYield(final HeadwayStopLine stopLine)
909         {
910             return this.stopPhases.containsKey(stopLine.getId())
911                     && this.stopPhases.get(stopLine.getId()).equals(StopPhase.YIELD);
912         }
913 
914         /**
915          * @param stopLine stop line
916          * @return whether the current phase is 'run' for the given stop line
917          */
918         boolean isStopPhaseRun(final HeadwayStopLine stopLine)
919         {
920             return this.stopPhases.containsKey(stopLine.getId()) && this.stopPhases.get(stopLine.getId()).equals(StopPhase.RUN);
921         }
922 
923         /** {@inheritDoc} */
924         @Override
925         public String toString()
926         {
927             return "ConflictPlans";
928         }
929 
930     }
931 
932     /**
933      * Phases of navigating an all-stop intersection.
934      * <p>
935      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
936      * <br>
937      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
938      * <p>
939      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jun 30, 2016 <br>
940      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
941      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
942      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
943      */
944     private enum StopPhase
945     {
946         /** Approaching stop intersection. */
947         APPROACH,
948 
949         /** Yielding for stop intersection. */
950         YIELD,
951 
952         /** Running over stop intersection. */
953         RUN;
954     }
955 
956 }