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