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