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