View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical.util.lmrs;
2   
3   import java.util.SortedSet;
4   
5   import org.djunits.unit.AccelerationUnit;
6   import org.djunits.value.vdouble.scalar.Acceleration;
7   import org.djunits.value.vdouble.scalar.Duration;
8   import org.djunits.value.vdouble.scalar.Length;
9   import org.djunits.value.vdouble.scalar.Speed;
10  import org.opentrafficsim.core.gtu.GTUException;
11  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
12  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
13  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
14  import org.opentrafficsim.core.gtu.perception.EgoPerception;
15  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
16  import org.opentrafficsim.core.network.LateralDirectionality;
17  import org.opentrafficsim.road.gtu.lane.perception.InfrastructureLaneChangeInfo;
18  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
19  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
20  import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
21  import org.opentrafficsim.road.gtu.lane.perception.categories.IntersectionPerception;
22  import org.opentrafficsim.road.gtu.lane.perception.categories.NeighborsPerception;
23  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayConflict;
24  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
25  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
26  import org.opentrafficsim.road.gtu.lane.tactical.util.CarFollowingUtil;
27  import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
28  
29  /**
30   * Different forms of synchronization, which includes cooperation.
31   * <p>
32   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
33   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
34   * <p>
35   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 3 apr. 2017 <br>
36   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
37   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
38   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
39   */
40  public enum Synchronization implements LmrsParameters
41  {
42  
43      /** Synchronization where current leaders are taken. */
44      PASSIVE
45      {
46  
47          @Override
48          Acceleration synchronize(final LanePerception perception, final BehavioralCharacteristics bc, final SpeedLimitInfo sli,
49                  final CarFollowingModel cfm, final double desire, final LateralDirectionality lat, final LmrsData lmrsData)
50                  throws ParameterException, OperationalPlanException
51          {
52              Acceleration a = new Acceleration(Double.POSITIVE_INFINITY, AccelerationUnit.SI);
53              double dCoop = bc.getParameter(DCOOP);
54              RelativeLane relativeLane = new RelativeLane(lat, 1);
55              SortedSet<HeadwayGTU> set = removeAllUpstreamOfConflicts(removeAllUpstreamOfConflicts(
56                      perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(relativeLane), perception,
57                      relativeLane), perception, RelativeLane.CURRENT);
58              HeadwayGTU leader = null;
59              if (desire >= dCoop && !set.isEmpty())
60              {
61                  leader = set.first();
62              }
63              else
64              {
65                  for (HeadwayGTU gtu : set)
66                  {
67                      if (gtu.getSpeed().gt0())
68                      {
69                          leader = gtu;
70                          break;
71                      }
72                  }
73              }
74              if (leader != null)
75              {
76                  Speed ownSpeed = perception.getPerceptionCategory(EgoPerception.class).getSpeed();
77                  Acceleration aSingle =
78                          LmrsUtil.singleAcceleration(leader.getDistance(), ownSpeed, leader.getSpeed(), desire, bc, sli, cfm);
79                  a = Acceleration.min(a, aSingle);
80              }
81              return gentleUrgency(a, desire, bc);
82          }
83  
84          @Override
85          Acceleration cooperate(final LanePerception perception, final BehavioralCharacteristics bc, final SpeedLimitInfo sli,
86                  final CarFollowingModel cfm, final LateralDirectionality lat)
87                  throws ParameterException, OperationalPlanException
88          {
89              if ((lat.isLeft() && !perception.getLaneStructure().getCrossSection().contains(RelativeLane.LEFT))
90                      || (lat.isRight() && !perception.getLaneStructure().getCrossSection().contains(RelativeLane.RIGHT)))
91              {
92                  return new Acceleration(Double.MAX_VALUE, AccelerationUnit.SI);
93              }
94              Acceleration b = bc.getParameter(ParameterTypes.B);
95              Acceleration a = new Acceleration(Double.MAX_VALUE, AccelerationUnit.SI);
96              double dCoop = bc.getParameter(DCOOP);
97              Speed ownSpeed = perception.getPerceptionCategory(EgoPerception.class).getSpeed();
98              RelativeLane relativeLane = new RelativeLane(lat, 1);
99              for (HeadwayGTU leader : removeAllUpstreamOfConflicts(removeAllUpstreamOfConflicts(
100                     perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(relativeLane), perception,
101                     relativeLane), perception, RelativeLane.CURRENT))
102             {
103                 BehavioralCharacteristics bc2 = leader.getBehavioralCharacteristics();
104                 double desire = lat.equals(LateralDirectionality.LEFT) && bc2.contains(DRIGHT) ? bc2.getParameter(DRIGHT)
105                         : lat.equals(LateralDirectionality.RIGHT) && bc2.contains(DLEFT) ? bc2.getParameter(DLEFT) : 0;
106                 if (desire >= dCoop && (leader.getSpeed().gt0() || leader.getDistance().gt0()))
107                 {
108                     Acceleration aSingle = LmrsUtil.singleAcceleration(leader.getDistance(), ownSpeed, leader.getSpeed(),
109                             desire, bc, sli, cfm);
110                     a = Acceleration.min(a, aSingle);
111                 }
112             }
113 
114             return Acceleration.max(a, b.neg());
115         }
116 
117     },
118 
119     /** Synchronization where a suitable leader is actively targeted, in relation to infrastructure. */
120     ACTIVE
121     {
122 
123         @Override
124         Acceleration synchronize(final LanePerception perception, final BehavioralCharacteristics bc, final SpeedLimitInfo sli,
125                 final CarFollowingModel cfm, final double desire, final LateralDirectionality lat, final LmrsData lmrsData)
126                 throws ParameterException, OperationalPlanException
127         {
128 
129             Acceleration b = bc.getParameter(ParameterTypes.B);
130             Acceleration bCrit = bc.getParameter(ParameterTypes.BCRIT);
131             Duration tMin = bc.getParameter(ParameterTypes.TMIN);
132             Duration tMax = bc.getParameter(ParameterTypes.TMAX);
133             Speed vCong = bc.getParameter(ParameterTypes.VCONG);
134             Length x0 = bc.getParameter(ParameterTypes.LOOKAHEAD);
135             Duration t0 = bc.getParameter(ParameterTypes.T0);
136             Duration lc = bc.getParameter(ParameterTypes.LCDUR);
137             Speed tagSpeed = x0.divideBy(t0);
138             double dCoop = bc.getParameter(DCOOP);
139             Speed ownSpeed = perception.getPerceptionCategory(EgoPerception.class).getSpeed();
140             Length ownLength = perception.getPerceptionCategory(EgoPerception.class).getLength();
141             Length dx;
142             try
143             {
144                 dx = perception.getGtu().getFront().getDx();
145             }
146             catch (GTUException exception)
147             {
148                 throw new OperationalPlanException(exception);
149             }
150 
151             // get xMergeSync, the distance within which a gap is pointless as the lane change is not possible
152             InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
153             SortedSet<InfrastructureLaneChangeInfo> info = infra.getInfrastructureLaneChangeInfo(RelativeLane.CURRENT);
154             Length xMerge = infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat).minus(dx);
155             xMerge = xMerge.lt0() ? xMerge.neg() : Length.ZERO; // zero, or positive value where lane change is not possible
156             int nCur;
157             Length xCur;
158             if (info.isEmpty())
159             {
160                 nCur = 0;
161                 xCur = Length.createSI(Double.POSITIVE_INFINITY);
162             }
163             else
164             {
165                 nCur = info.first().getRequiredNumberOfLaneChanges();
166                 // subtract minimum lane change distance per lane changes
167                 xCur = info.first().getRemainingDistance().minus(ownLength.multiplyBy(2.0 * nCur)).minus(dx);
168             }
169             // for short ramps, include braking distance, i.e. we -do- select a gap somewhat upstream of the merge point;
170             // should we abandon this gap, we still have braking distance and minimum lane change distance left
171             Length xMergeSync = xCur.minus(Length.createSI(.5 * ownSpeed.si * ownSpeed.si / b.si));
172             xMergeSync = Length.min(xMerge, xMergeSync);
173 
174             // abandon the gap if the sync vehicle is no longer adjacent, in congestion within xMergeSync, or too far
175             NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
176             RelativeLane lane = new RelativeLane(lat, 1);
177             SortedSet<HeadwayGTU> leaders =
178                     removeAllUpstreamOfConflicts(removeAllUpstreamOfConflicts(neighbors.getLeaders(lane), perception, lane),
179                             perception, RelativeLane.CURRENT);
180             HeadwayGTU syncVehicle = lmrsData.getSyncVehicle(leaders);
181             if (syncVehicle != null && ((syncVehicle.getSpeed().lt(vCong) && syncVehicle.getDistance().lt(xMergeSync))
182                     || syncVehicle.getDistance().gt(xCur)))
183             {
184                 syncVehicle = null;
185             }
186 
187             // if there is no sync vehicle, select the first one to which current deceleration < b (it may become larger later)
188             if (syncVehicle == null)
189             {
190                 Length maxDistance = Length.min(x0, xCur);
191                 for (HeadwayGTU leader : leaders)
192                 {
193                     if ((leader.getDistance().lt(maxDistance) || leader.getSpeed().gt(vCong))
194                             && tagAlongAcceleration(leader, ownSpeed, ownLength, tagSpeed, desire, bc, sli, cfm).gt(b.neg()))
195                     {
196                         syncVehicle = leader;
197                         break;
198                     }
199                 }
200             }
201 
202             // select upstream vehicle if we can safely follow that, or if we cannot stay ahead of it (infrastructure, in coop)
203             HeadwayGTU up;
204             SortedSet<HeadwayGTU> followers =
205                     removeAllUpstreamOfConflicts(removeAllUpstreamOfConflicts(neighbors.getFollowers(lane), perception, lane),
206                             perception, RelativeLane.CURRENT);
207             HeadwayGTU follower = followers.isEmpty() ? null
208                     : followers.first().moved(
209                             followers.first().getDistance().plus(ownLength).plus(followers.first().getLength()).neg(),
210                             followers.first().getSpeed(), followers.first().getAcceleration());
211             boolean upOk;
212             if (syncVehicle == null)
213             {
214                 up = null;
215                 upOk = false;
216             }
217             else
218             {
219                 up = getFollower(syncVehicle, leaders, follower, ownLength);
220                 upOk = up == null ? false
221                         : tagAlongAcceleration(up, ownSpeed, ownLength, tagSpeed, desire, bc, sli, cfm).gt(b.neg());
222             }
223             while (syncVehicle != null && up != null
224                     && (upOk || (!canBeAhead(up, xCur, nCur, ownSpeed, ownLength, tagSpeed, dCoop, bCrit, tMin, tMax, x0, t0,
225                             lc, desire) && desire > dCoop))
226                     && (up.getDistance().plus(up.getLength()).gt(xMergeSync) || up.getSpeed().gt(vCong)))
227             {
228                 if (up.equals(follower))
229                 {
230                     // no suitable downstream vehicle to follow found
231                     syncVehicle = null;
232                     up = null;
233                     break;
234                 }
235                 syncVehicle = up;
236                 up = getFollower(syncVehicle, leaders, follower, ownLength);
237                 upOk = up == null ? false
238                         : tagAlongAcceleration(up, ownSpeed, ownLength, tagSpeed, desire, bc, sli, cfm).gt(b.neg());
239             }
240 
241             // actual synchronization
242             Acceleration a = Acceleration.POSITIVE_INFINITY;
243             if (syncVehicle != null)
244             {
245                 a = gentleUrgency(tagAlongAcceleration(syncVehicle, ownSpeed, ownLength, tagSpeed, desire, bc, sli, cfm),
246                         desire, bc);
247             }
248             else if (follower != null)
249             {
250                 // no gap to synchronize with, but there is a follower to account for
251                 if (!canBeAhead(follower, xCur, nCur, ownSpeed, ownLength, tagSpeed, dCoop, bCrit, tMin, tMax, x0, t0, lc,
252                         desire))
253                 {
254                     // get behind follower
255                     double c = requiredBufferSpace(follower.getSpeed(), nCur, x0, t0, lc, dCoop).si;
256                     double t = (xCur.si - follower.getDistance().si - c) / follower.getSpeed().si;
257                     Acceleration acc = Acceleration.createSI(2 * (xCur.si - c - ownSpeed.si * t) / (t * t));
258                     if (follower.getSpeed().eq0() || acc.si < -ownSpeed.si / t || t < 0)
259                     {
260                         // inappropriate to get behind
261                         a = stopForEnd(xCur, xMerge, bc, ownSpeed, cfm, sli);
262                     }
263                     else
264                     {
265                         a = gentleUrgency(acc, desire, bc);
266                     }
267                 }
268                 else
269                 {
270                     // check gap acceptance based on accelerations
271                     boolean acceptFollower;
272                     if (followers.first().getDistance().gt0()) // obtain with unadjusted distance
273                     {
274                         BehavioralCharacteristics bcFollower = follower.getBehavioralCharacteristics();
275                         LmrsUtil.setDesiredHeadway(bc, desire);
276                         acceptFollower = LmrsUtil.singleAcceleration(followers.first().getDistance(), follower.getSpeed(),
277                                 ownSpeed, desire, bcFollower, follower.getSpeedLimitInfo(),
278                                 follower.getCarFollowingModel()).si > -b.si * desire;
279                     }
280                     else
281                     {
282                         acceptFollower = false;
283                     }
284                     boolean acceptSelf;
285                     if (!leaders.isEmpty() && leaders.first().getDistance().gt0())
286                     {
287                         acceptSelf = LmrsUtil.singleAcceleration(leaders.first().getDistance(), ownSpeed,
288                                 leaders.first().getSpeed(), desire, bc, sli, cfm).si > -b.si * desire;
289                     }
290                     else
291                     {
292                         acceptSelf = false;
293                     }
294                     if (!acceptFollower || !acceptSelf)
295                     {
296                         // can be ahead of follower, but no suitable leader for synchronization
297                         a = stopForEnd(xCur, xMerge, bc, ownSpeed, cfm, sli);
298                     }
299                 }
300             }
301 
302             // slow down to have sufficient time for further lane changes
303             if (nCur > 1)
304             {
305                 if (xMerge.lt0())
306                 {
307                     // achieve speed to have sufficient time as soon as a lane change becomes possible (infrastructure)
308                     Speed vMerge = xCur.minus(xMerge).divideBy(t0.multiplyBy((1 - dCoop) * (nCur - 1)).plus(lc));
309                     vMerge = Speed.min(vMerge, x0.divideBy(t0));
310                     a = Acceleration.min(a, CarFollowingUtil.approachTargetSpeed(cfm, bc, ownSpeed, sli, xMerge, vMerge));
311                 }
312                 else
313                 {
314                     // slow down by b if our speed is too high beyond the merge point
315                     Speed speed;
316                     if (up != null)
317                     {
318                         speed = up.getSpeed();
319                     }
320                     else if (syncVehicle != null)
321                     {
322                         speed = syncVehicle.getSpeed();
323                     }
324                     else
325                     {
326                         speed = ownSpeed;
327                     }
328                     Length c = requiredBufferSpace(speed, nCur, x0, t0, lc, dCoop);
329                     if (xCur.lt(c))
330                     {
331                         a = Acceleration.min(a, b.neg());
332                     }
333                 }
334             }
335 
336             return a;
337         }
338 
339         @Override
340         Acceleration cooperate(final LanePerception perception, final BehavioralCharacteristics bc, final SpeedLimitInfo sli,
341                 final CarFollowingModel cfm, final LateralDirectionality lat)
342                 throws ParameterException, OperationalPlanException
343         {
344             if ((lat.isLeft() && !perception.getLaneStructure().getCrossSection().contains(RelativeLane.LEFT))
345                     || (lat.isRight() && !perception.getLaneStructure().getCrossSection().contains(RelativeLane.RIGHT)))
346             {
347                 return new Acceleration(Double.MAX_VALUE, AccelerationUnit.SI);
348             }
349             Acceleration b = bc.getParameter(ParameterTypes.B);
350             Acceleration a = new Acceleration(Double.MAX_VALUE, AccelerationUnit.SI);
351             double dCoop = bc.getParameter(DCOOP);
352             Length x0 = bc.getParameter(ParameterTypes.LOOKAHEAD);
353             Duration t0 = bc.getParameter(ParameterTypes.T0);
354             Speed tagSpeed = x0.divideBy(t0);
355             Speed ownSpeed = perception.getPerceptionCategory(EgoPerception.class).getSpeed();
356             Length ownLength = perception.getPerceptionCategory(EgoPerception.class).getLength();
357             RelativeLane relativeLane = new RelativeLane(lat, 1);
358             SortedSet<HeadwayGTU> targetLeaders =
359                     perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(RelativeLane.CURRENT);
360             for (HeadwayGTU leader : removeAllUpstreamOfConflicts(removeAllUpstreamOfConflicts(
361                     perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(relativeLane), perception,
362                     relativeLane), perception, RelativeLane.CURRENT))
363             {
364                 BehavioralCharacteristics bc2 = leader.getBehavioralCharacteristics();
365                 double desire = lat.equals(LateralDirectionality.LEFT) && bc2.contains(DRIGHT) ? bc2.getParameter(DRIGHT)
366                         : lat.equals(LateralDirectionality.RIGHT) && bc2.contains(DLEFT) ? bc2.getParameter(DLEFT) : 0;
367                 if (desire >= dCoop && (perception.getPerceptionCategory(EgoPerception.class).getSpeed().gt0()
368                         || leader.getSpeed().gt0() || leader.getDistance().gt0()))
369                 {
370                     Acceleration aSingle;
371                     if (leader.getSpeed().eq0())
372                     {
373                         HeadwayGTU targetLeader = getTargetLeader(leader, targetLeaders);
374                         if (targetLeader == null || LmrsUtil.singleAcceleration(
375                                 targetLeader.getDistance().minus(leader.getDistance()).minus(leader.getLength()),
376                                 leader.getSpeed(), targetLeader.getSpeed(), desire, leader.getBehavioralCharacteristics(),
377                                 leader.getSpeedLimitInfo(), leader.getCarFollowingModel()).gt(b.neg()))
378                         {
379                             // cooperate without tagging along, adjacent vehicle stands still due to remaining space
380                             aSingle = LmrsUtil.singleAcceleration(leader.getDistance(), ownSpeed, leader.getSpeed(), desire, bc,
381                                     sli, cfm);
382                         }
383                         else
384                         {
385                             aSingle = tagAlongAcceleration(leader, ownSpeed, ownLength, tagSpeed, desire, bc, sli, cfm);
386                         }
387                     }
388                     else
389                     {
390                         aSingle = tagAlongAcceleration(leader, ownSpeed, ownLength, tagSpeed, desire, bc, sli, cfm);
391                     }
392                     a = Acceleration.min(a, aSingle);
393                 }
394             }
395 
396             return Acceleration.max(a, b.neg());
397         }
398 
399     };
400 
401     /**
402      * Determine acceleration for synchronization.
403      * @param perception perception
404      * @param bc behavioral characteristics
405      * @param sli speed limit info
406      * @param cfm car-following model
407      * @param desire level of lane change desire
408      * @param lat lateral direction for synchronization
409      * @param lmrsData LMRS data
410      * @return acceleration for synchronization
411      * @throws ParameterException if a parameter is not defined
412      * @throws OperationalPlanException perception exception
413      */
414     abstract Acceleration synchronize(LanePerception perception, BehavioralCharacteristics bc, SpeedLimitInfo sli,
415             CarFollowingModel cfm, double desire, LateralDirectionality lat, LmrsData lmrsData)
416             throws ParameterException, OperationalPlanException;
417 
418     /**
419      * Determine acceleration for cooperation.
420      * @param perception perception
421      * @param bc behavioral characteristics
422      * @param sli speed limit info
423      * @param cfm car-following model
424      * @param lat lateral direction for cooperation
425      * @return acceleration for synchronization
426      * @throws ParameterException if a parameter is not defined
427      * @throws OperationalPlanException perception exception
428      */
429     abstract Acceleration cooperate(LanePerception perception, BehavioralCharacteristics bc, SpeedLimitInfo sli,
430             CarFollowingModel cfm, LateralDirectionality lat) throws ParameterException, OperationalPlanException;
431 
432     /**
433      * Removes all GTUs from the set, that are found upstream on the conflicting lane of a conflict in the current lane.
434      * @param set set of GTUs
435      * @param perception perception
436      * @param relativeLane relative lane
437      * @return the input set, for chained use
438      * @throws OperationalPlanException if the {@code IntersectionPerception} category is not present
439      */
440     static SortedSet<HeadwayGTU> removeAllUpstreamOfConflicts(final SortedSet<HeadwayGTU> set, final LanePerception perception,
441             final RelativeLane relativeLane) throws OperationalPlanException
442     {
443         if (!perception.contains(IntersectionPerception.class))
444         {
445             return set;
446         }
447         for (HeadwayConflict conflict : perception.getPerceptionCategory(IntersectionPerception.class)
448                 .getConflicts(relativeLane))
449         {
450             if (conflict.isCrossing() || conflict.isMerge())
451             {
452                 for (HeadwayGTU conflictGtu : conflict.getUpstreamConflictingGTUs())
453                 {
454                     for (HeadwayGTU gtu : set)
455                     {
456                         if (conflictGtu.getId().equals(gtu.getId()))
457                         {
458                             set.remove(gtu);
459                             break;
460                         }
461                     }
462                 }
463             }
464         }
465         return set;
466     }
467 
468     /**
469      * Return limited deceleration. Deceleration is limited to {@code b} for {@code d < dCoop}. Beyond {@code dCoop} the limit
470      * is a linear interpolation between {@code b} and {@code bCrit}.
471      * @param a acceleration to limit
472      * @param desire lane change desire
473      * @param bc behavioral characteristics
474      * @return limited deceleration
475      * @throws ParameterException when parameter is no available or value out of range
476      */
477     static Acceleration gentleUrgency(final Acceleration a, final double desire, final BehavioralCharacteristics bc)
478             throws ParameterException
479     {
480         Acceleration b = bc.getParameter(ParameterTypes.B);
481         if (a.si > -b.si)
482         {
483             return a;
484         }
485         double dCoop = bc.getParameter(DCOOP);
486         if (desire < dCoop)
487         {
488             return b.neg();
489         }
490         Acceleration bCrit = bc.getParameter(ParameterTypes.BCRIT);
491         double f = (desire - dCoop) / (1.0 - dCoop);
492         Acceleration lim = Acceleration.interpolate(b.neg(), bCrit.neg(), f);
493         return Acceleration.max(a, lim);
494     }
495 
496     /**
497      * Returns the upstream gtu of the given gtu.
498      * @param gtu gtu
499      * @param leaders leaders of own vehicle
500      * @param follower following vehicle of own vehicle
501      * @param ownLength own vehicle length
502      * @return upstream gtu of the given gtu
503      */
504     static HeadwayGTU getFollower(final HeadwayGTU gtu, final SortedSet<HeadwayGTU> leaders, final HeadwayGTU follower,
505             final Length ownLength)
506     {
507         HeadwayGTU last = null;
508         for (HeadwayGTU leader : leaders)
509         {
510             if (leader.equals(gtu))
511             {
512                 return last == null ? follower : last;
513             }
514             last = leader;
515         }
516         return null;
517     }
518 
519     /**
520      * Calculates acceleration by following an adjacent vehicle, with tagging along if desire is not very high and speed is low.
521      * @param leader leader
522      * @param followerSpeed follower speed
523      * @param followerLength follower length
524      * @param tagSpeed maximum tag along speed
525      * @param desire lane change desire
526      * @param bc behavioral characteristics
527      * @param sli speed limit info
528      * @param cfm car-following model
529      * @return acceleration by following an adjacent vehicle including tagging along
530      * @throws ParameterException if a parameter is not present
531      */
532     static Acceleration tagAlongAcceleration(final HeadwayGTU leader, final Speed followerSpeed, final Length followerLength,
533             final Speed tagSpeed, final double desire, final BehavioralCharacteristics bc, final SpeedLimitInfo sli,
534             final CarFollowingModel cfm) throws ParameterException
535     {
536         double dCoop = bc.getParameter(DCOOP);
537         double tagV = followerSpeed.lt(tagSpeed) ? 1.0 - followerSpeed.si / tagSpeed.si : 0.0;
538         double tagD = desire <= dCoop ? 1.0 : 1.0 - (desire - dCoop) / (1.0 - dCoop);
539         double tagExtent = tagV < tagD ? tagV : tagD;
540         /*-
541          * Maximum extent is half a vehicle length, being the minimum of the own vehicle or adjacent vehicle length. At
542          * standstill we get: 
543          *
544          * car>car:    __       car>truck:       ______ 
545          *            __                        __ 
546          *                                                   driving direction -->
547          * truck>car:      __   truck>truck:       ______ 
548          *            ______                    ______
549          */
550         Length headwayAdjustment = bc.getParameter(ParameterTypes.S0)
551                 .plus(Length.min(followerLength, leader.getLength()).multiplyBy(0.5)).multiplyBy(tagExtent);
552         Acceleration a = LmrsUtil.singleAcceleration(leader.getDistance().plus(headwayAdjustment), followerSpeed,
553                 leader.getSpeed(), desire, bc, sli, cfm);
554         return a;
555     }
556 
557     /**
558      * Returns whether a driver estimates it can be ahead of an adjacent vehicle for merging.
559      * @param adjacentVehicle adjacent vehicle
560      * @param xCur remaining distance
561      * @param nCur number of lane changes to perform
562      * @param ownSpeed own speed
563      * @param ownLength own length
564      * @param tagSpeed maximum tag along speed
565      * @param dCoop cooperation threshold
566      * @param bCrit critical deceleration
567      * @param tMin minimum headway
568      * @param tMax normal headway
569      * @param x0 anticipation distance
570      * @param t0 anticipation time
571      * @param lc lane change duration
572      * @param desire lane change desire
573      * @return whether a driver estimates it can be ahead of an adjacent vehicle for merging
574      * @throws ParameterException if parameter is not defined
575      */
576     static boolean canBeAhead(final HeadwayGTU adjacentVehicle, final Length xCur, final int nCur, final Speed ownSpeed,
577             final Length ownLength, final Speed tagSpeed, final double dCoop, final Acceleration bCrit, final Duration tMin,
578             final Duration tMax, final Length x0, final Duration t0, final Duration lc, final double desire)
579             throws ParameterException
580     {
581         // always true if adjacent vehicle is behind and i) both vehicles very slow, or ii) cooperation assumed and possible
582         if (adjacentVehicle
583                 .getDistance().gt(
584                         adjacentVehicle.getLength()
585                                 .neg())
586                 && ((desire > dCoop && LmrsUtil.singleAcceleration(
587                         adjacentVehicle.getDistance().neg().minus(adjacentVehicle.getLength()).minus(ownLength),
588                         adjacentVehicle.getSpeed(), ownSpeed, desire, adjacentVehicle.getBehavioralCharacteristics(),
589                         adjacentVehicle.getSpeedLimitInfo(), adjacentVehicle.getCarFollowingModel()).gt(bCrit.neg()))
590                         || (ownSpeed.lt(tagSpeed) && adjacentVehicle.getSpeed().lt(tagSpeed))))
591         {
592             return true;
593         }
594         /*-
595          * Check that we cover distance (xCur - c) before adjacent vehicle will no longer leave a space of xGap.
596          * _______________________________________________________________________________
597          *                 ___b           ___b (at +t)
598          * _____________ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _______
599          *       _____x                              _____x (at +t)                /
600          * _______________________________________________________________________/
601          *            (---------------------------xCur---------------------------)
602          *            (-s-)(l)               (-xGap-)(-l-)(----------c-----------)
603          *            
604          *            (----------------------------------) x should cover this distance before
605          *                    (-------------) b covers this distance; then we can be ahead (otherwise, follow b)
606          */
607         Length c = requiredBufferSpace(adjacentVehicle.getSpeed(), nCur, x0, t0, lc, dCoop);
608         double t = (xCur.si - c.si) / ownSpeed.si;
609         double xGap = adjacentVehicle.getSpeed().si * (tMin.si + desire * (tMax.si - tMin.si));
610         return 0.0 < t && t < (xCur.si - adjacentVehicle.getDistance().si - ownLength.si - adjacentVehicle.getLength().si - c.si
611                 - xGap) / adjacentVehicle.getSpeed().si;
612     }
613 
614     /**
615      * Returns the required buffer space to perform a lane change and further lane changes.
616      * @param speed representative speed
617      * @param nCur number of required lane changes
618      * @param x0 anticipation distance
619      * @param t0 anticipation time
620      * @param lc lane change duration
621      * @param dCoop cooperation threshold
622      * @return required buffer space to perform a lane change and further lane changes
623      */
624     static Length requiredBufferSpace(final Speed speed, final int nCur, final Length x0, final Duration t0, final Duration lc,
625             final double dCoop)
626     {
627         Length xCrit = speed.multiplyBy(t0);
628         xCrit = Length.max(xCrit, x0);
629         return speed.multiplyBy(lc).plus(xCrit.multiplyBy((nCur - 1.0) * (1.0 - dCoop)));
630     }
631 
632     /**
633      * Calculates acceleration to stop for a split or dead-end, accounting for infrastructure.
634      * @param xCur remaining distance to end
635      * @param xMerge distance until merge point
636      * @param bc behavioral characteristics
637      * @param ownSpeed own speed
638      * @param cfm car-following model
639      * @param sli speed limit info
640      * @return acceleration to stop for a split or dead-end, accounting for infrastructure
641      * @throws ParameterException if parameter is not defined
642      */
643     static Acceleration stopForEnd(final Length xCur, final Length xMerge, final BehavioralCharacteristics bc,
644             final Speed ownSpeed, final CarFollowingModel cfm, final SpeedLimitInfo sli) throws ParameterException
645     {
646         LmrsUtil.setDesiredHeadway(bc, 1.0);
647         Acceleration a = CarFollowingUtil.stop(cfm, bc, ownSpeed, sli, xCur);
648         if (a.lt0())
649         {
650             // decelerate even more if still comfortable, leaving space for acceleration later
651             a = Acceleration.max(a, bc.getParameter(ParameterTypes.B).neg());
652             // but never decelerate such that stand-still is reached within xMerge
653             if (xMerge.gt0())
654             {
655                 a = Acceleration.max(a, CarFollowingUtil.stop(cfm, bc, ownSpeed, sli, xMerge));
656             }
657         }
658         else
659         {
660             a = Acceleration.POSITIVE_INFINITY;
661         }
662         LmrsUtil.resetDesiredHeadway(bc);
663         return a;
664     }
665 
666     /**
667      * Returns the leader of one gtu from a set.
668      * @param gtu gtu
669      * @param leaders leaders
670      * @return leader of one gtu from a set
671      */
672     static HeadwayGTU getTargetLeader(final HeadwayGTU gtu, final SortedSet<HeadwayGTU> leaders)
673     {
674         for (HeadwayGTU leader : leaders)
675         {
676             if (leader.getDistance().gt(gtu.getDistance()))
677             {
678                 return leader;
679             }
680         }
681         return null;
682     }
683 
684 }