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