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