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