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