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