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