View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical.util.lmrs;
2   
3   import java.util.Iterator;
4   import java.util.Map;
5   import java.util.SortedSet;
6   
7   import org.djunits.value.vdouble.scalar.Acceleration;
8   import org.djunits.value.vdouble.scalar.Duration;
9   import org.djunits.value.vdouble.scalar.Length;
10  import org.djunits.value.vdouble.scalar.Speed;
11  import org.djunits.value.vdouble.scalar.Time;
12  import org.djutils.exceptions.Try;
13  import org.opentrafficsim.base.parameters.ParameterException;
14  import org.opentrafficsim.base.parameters.ParameterTypeAcceleration;
15  import org.opentrafficsim.base.parameters.ParameterTypeDuration;
16  import org.opentrafficsim.base.parameters.ParameterTypes;
17  import org.opentrafficsim.base.parameters.Parameters;
18  import org.opentrafficsim.core.gtu.GtuException;
19  import org.opentrafficsim.core.gtu.TurnIndicatorIntent;
20  import org.opentrafficsim.core.gtu.perception.EgoPerception;
21  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
22  import org.opentrafficsim.core.network.LateralDirectionality;
23  import org.opentrafficsim.core.network.NetworkException;
24  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
25  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
26  import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
27  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
28  import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
29  import org.opentrafficsim.road.gtu.lane.perception.categories.IntersectionPerception;
30  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
31  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayConflict;
32  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtu;
33  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayTrafficLight;
34  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneChange;
35  import org.opentrafficsim.road.gtu.lane.plan.operational.SimpleOperationalPlan;
36  import org.opentrafficsim.road.gtu.lane.tactical.Synchronizable;
37  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
38  import org.opentrafficsim.road.gtu.lane.tactical.util.CarFollowingUtil;
39  import org.opentrafficsim.road.gtu.lane.tactical.util.ConflictUtil;
40  import org.opentrafficsim.road.gtu.lane.tactical.util.ConflictUtil.ConflictPlans;
41  import org.opentrafficsim.road.network.LaneChangeInfo;
42  import org.opentrafficsim.road.network.lane.conflict.Conflict;
43  import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
44  import org.opentrafficsim.road.network.speed.SpeedLimitProspect;
45  
46  /**
47   * <p>
48   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
49   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
50   * </p>
51   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
52   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
53   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
54   */
55  public final class LmrsUtil implements LmrsParameters
56  {
57  
58      /** Fixed model time step. */
59      public static final ParameterTypeDuration DT = ParameterTypes.DT;
60  
61      /** Minimum car-following headway. */
62      public static final ParameterTypeDuration TMIN = ParameterTypes.TMIN;
63  
64      /** Current car-following headway. */
65      public static final ParameterTypeDuration T = ParameterTypes.T;
66  
67      /** Maximum car-following headway. */
68      public static final ParameterTypeDuration TMAX = ParameterTypes.TMAX;
69  
70      /** Headway relaxation time. */
71      public static final ParameterTypeDuration TAU = ParameterTypes.TAU;
72  
73      /** Maximum critical deceleration, e.g. stop/go at traffic light. */
74      public static final ParameterTypeAcceleration BCRIT = ParameterTypes.BCRIT;
75  
76      /**
77       * Do not instantiate.
78       */
79      private LmrsUtil()
80      {
81          //
82      }
83  
84      /**
85       * Determines a simple representation of an operational plan.
86       * @param gtu LaneBasedGtu; gtu
87       * @param startTime Time; start time
88       * @param carFollowingModel CarFollowingModel; car-following model
89       * @param laneChange LaneChange; lane change status
90       * @param lmrsData LmrsData; LMRS data
91       * @param perception LanePerception; perception
92       * @param mandatoryIncentives Iterable&lt;MandatoryIncentive&gt;; set of mandatory lane change incentives
93       * @param voluntaryIncentives Iterable&lt;VoluntaryIncentive&gt;; set of voluntary lane change incentives
94       * @return simple operational plan
95       * @throws GtuException gtu exception
96       * @throws NetworkException network exception
97       * @throws ParameterException parameter exception
98       * @throws OperationalPlanException operational plan exception
99       */
100     @SuppressWarnings({"checkstyle:parameternumber", "checkstyle:methodlength"})
101     public static SimpleOperationalPlan determinePlan(final LaneBasedGtu gtu, final Time startTime,
102             final CarFollowingModel carFollowingModel, final LaneChange laneChange, final LmrsData lmrsData,
103             final LanePerception perception, final Iterable<MandatoryIncentive> mandatoryIncentives,
104             final Iterable<VoluntaryIncentive> voluntaryIncentives)
105             throws GtuException, NetworkException, ParameterException, OperationalPlanException
106     {
107         // obtain objects to get info
108         InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
109         SpeedLimitProspect slp = infra.getSpeedLimitProspect(RelativeLane.CURRENT);
110         SpeedLimitInfo sli = slp.getSpeedLimitInfo(Length.ZERO);
111         Parameters params = gtu.getParameters();
112         EgoPerception<?, ?> ego = perception.getPerceptionCategory(EgoPerception.class);
113         Speed speed = ego.getSpeed();
114         NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
115         PerceptionCollectable<HeadwayGtu, LaneBasedGtu> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
116 
117         // regular car-following
118         Acceleration a;
119         if (lmrsData.isHumanLongitudinalControl())
120         {
121             lmrsData.getTailgating().tailgate(perception, params);
122             if (!leaders.isEmpty() && lmrsData.isNewLeader(leaders.first()))
123             {
124                 initHeadwayRelaxation(params, leaders.first());
125             }
126             a = gtu.getCarFollowingAcceleration();
127         }
128         else
129         {
130             a = Acceleration.POS_MAXVALUE;
131         }
132 
133         // during a lane change, both leaders are followed
134         LateralDirectionality initiatedLaneChange;
135         TurnIndicatorIntent turnIndicatorStatus = TurnIndicatorIntent.NONE;
136         if (laneChange.isChangingLane())
137         {
138             initiatedLaneChange = LateralDirectionality.NONE;
139             if (lmrsData.isHumanLongitudinalControl())
140             {
141                 RelativeLane secondLane = laneChange.getSecondLane(gtu);
142                 PerceptionCollectable<HeadwayGtu, LaneBasedGtu> secondLeaders = neighbors.getLeaders(secondLane);
143                 Acceleration aSecond = carFollowingModel.followingAcceleration(params, speed, sli, secondLeaders);
144                 if (!secondLeaders.isEmpty() && lmrsData.isNewLeader(secondLeaders.first()))
145                 {
146                     initHeadwayRelaxation(params, secondLeaders.first());
147                 }
148                 a = Acceleration.min(a, aSecond);
149             }
150             a = Acceleration.min(a, Synchronization.DEADEND.synchronize(perception, params, sli, carFollowingModel, 0.0,
151                     laneChange.getDirection(), lmrsData, laneChange, initiatedLaneChange));
152         }
153         else
154         {
155 
156             // determine lane change desire based on incentives
157             Desire desire = getLaneChangeDesire(params, perception, carFollowingModel, mandatoryIncentives, voluntaryIncentives,
158                     lmrsData.getDesireMap());
159 
160             // lane change decision
161             double dFree = params.getParameter(DFREE);
162             initiatedLaneChange = LateralDirectionality.NONE;
163             turnIndicatorStatus = TurnIndicatorIntent.NONE;
164             if (desire.leftIsLargerOrEqual() && desire.left() >= dFree)
165             {
166                 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.left(), speed, a,
167                         LateralDirectionality.LEFT, lmrsData.getGapAcceptance(), laneChange))
168                 {
169                     // change left
170                     initiatedLaneChange = LateralDirectionality.LEFT;
171                     turnIndicatorStatus = TurnIndicatorIntent.LEFT;
172                     params.setParameter(DLC, desire.left());
173                     setDesiredHeadway(params, desire.left());
174                     leaders = neighbors.getLeaders(RelativeLane.LEFT);
175                     if (!leaders.isEmpty())
176                     {
177                         // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next
178                         // step
179                         lmrsData.isNewLeader(leaders.first());
180                     }
181                     a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
182                             neighbors.getLeaders(RelativeLane.LEFT)));
183                 }
184             }
185             else if (!desire.leftIsLargerOrEqual() && desire.right() >= dFree)
186             {
187                 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.right(), speed, a,
188                         LateralDirectionality.RIGHT, lmrsData.getGapAcceptance(), laneChange))
189                 {
190                     // change right
191                     initiatedLaneChange = LateralDirectionality.RIGHT;
192                     turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
193                     params.setParameter(DLC, desire.right());
194                     setDesiredHeadway(params, desire.right());
195                     leaders = neighbors.getLeaders(RelativeLane.RIGHT);
196                     if (!leaders.isEmpty())
197                     {
198                         // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next
199                         // step
200                         lmrsData.isNewLeader(leaders.first());
201                     }
202                     a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
203                             neighbors.getLeaders(RelativeLane.RIGHT)));
204                 }
205             }
206             if (!initiatedLaneChange.isNone())
207             {
208                 SortedSet<LaneChangeInfo> set = infra.getLegalLaneChangeInfo(RelativeLane.CURRENT);
209                 if (!set.isEmpty())
210                 {
211                     Length boundary = null;
212                     for (LaneChangeInfo info : set)
213                     {
214                         int n = info.numberOfLaneChanges();
215                         if (n > 1)
216                         {
217                             Length thisBoundary = info.remainingDistance()
218                                     .minus(Synchronization.requiredBufferSpace(speed, info.numberOfLaneChanges(),
219                                             params.getParameter(ParameterTypes.LOOKAHEAD),
220                                             params.getParameter(ParameterTypes.T0), params.getParameter(ParameterTypes.LCDUR),
221                                             params.getParameter(DCOOP)));
222                             if (thisBoundary.le0())
223                             {
224                                 thisBoundary = info.remainingDistance().divide(info.numberOfLaneChanges());
225                             }
226                             boundary = boundary == null || thisBoundary.si < boundary.si ? thisBoundary : boundary;
227                         }
228                     }
229                     laneChange.setBoundary(boundary);
230                 }
231                 params.setParameter(DLEFT, 0.0);
232                 params.setParameter(DRIGHT, 0.0);
233             }
234             else
235             {
236                 params.setParameter(DLEFT, desire.left());
237                 params.setParameter(DRIGHT, desire.right());
238             }
239 
240             // take action if we cannot change lane
241             Acceleration aSync;
242 
243             // synchronize
244             double dSync = params.getParameter(DSYNC);
245             lmrsData.setSynchronizationState(Synchronizable.State.NONE);
246             if (desire.leftIsLargerOrEqual() && desire.left() >= dSync)
247             {
248                 Synchronizable.State state;
249                 if (desire.left() >= params.getParameter(DCOOP))
250                 {
251                     // switch on left indicator
252                     turnIndicatorStatus = TurnIndicatorIntent.LEFT;
253                     state = Synchronizable.State.INDICATING;
254                 }
255                 else
256                 {
257                     state = Synchronizable.State.SYNCHRONIZING;
258                 }
259                 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.left(),
260                         LateralDirectionality.LEFT, lmrsData, laneChange, initiatedLaneChange);
261                 a = applyAcceleration(a, aSync, lmrsData, state);
262             }
263             else if (!desire.leftIsLargerOrEqual() && desire.right() >= dSync)
264             {
265                 Synchronizable.State state;
266                 if (desire.right() >= params.getParameter(DCOOP))
267                 {
268                     // switch on right indicator
269                     turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
270                     state = Synchronizable.State.INDICATING;
271                 }
272                 else
273                 {
274                     state = Synchronizable.State.SYNCHRONIZING;
275                 }
276                 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.right(),
277                         LateralDirectionality.RIGHT, lmrsData, laneChange, initiatedLaneChange);
278                 a = applyAcceleration(a, aSync, lmrsData, state);
279             }
280 
281             // cooperate
282             aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.LEFT,
283                     desire);
284             a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);
285             aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.RIGHT,
286                     desire);
287             a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);
288 
289             // relaxation
290             exponentialHeadwayRelaxation(params);
291 
292         }
293         lmrsData.finalizeStep();
294 
295         SimpleOperationalPlan simplePlan = new SimpleOperationalPlan(a, params.getParameter(DT), initiatedLaneChange);
296         if (turnIndicatorStatus.isLeft())
297         {
298             simplePlan.setIndicatorIntentLeft();
299         }
300         else if (turnIndicatorStatus.isRight())
301         {
302             simplePlan.setIndicatorIntentRight();
303         }
304         return simplePlan;
305 
306     }
307 
308     /**
309      * Minimizes the acceleration and sets the synchronization state if applicable.
310      * @param a Acceleration; previous acceleration
311      * @param aNew Acceleration; new acceleration
312      * @param lmrsData LmrsData; lmrs data
313      * @param state Synchronizable.State; synchronization state
314      * @return Acceleration; minimized acceleration
315      */
316     private static Acceleration applyAcceleration(final Acceleration a, final Acceleration aNew, final LmrsData lmrsData,
317             final Synchronizable.State state)
318     {
319         if (a.si < aNew.si)
320         {
321             return a;
322         }
323         lmrsData.setSynchronizationState(state);
324         return aNew;
325     }
326 
327     /**
328      * Sets the headway as a response to a new leader.
329      * @param params Parameters; parameters
330      * @param leader HeadwayGtu; leader
331      * @throws ParameterException if DLC is not present
332      */
333     private static void initHeadwayRelaxation(final Parameters params, final HeadwayGtu leader) throws ParameterException
334     {
335         Double dlc = leader.getParameters().getParameterOrNull(DLC);
336         if (dlc != null)
337         {
338             setDesiredHeadway(params, dlc);
339         }
340         // else could not be perceived
341     }
342 
343     /**
344      * Updates the desired headway following an exponential shape approximated with fixed time step <code>DT</code>.
345      * @param params Parameters; parameters
346      * @throws ParameterException in case of a parameter exception
347      */
348     private static void exponentialHeadwayRelaxation(final Parameters params) throws ParameterException
349     {
350         double ratio = params.getParameter(DT).si / params.getParameter(TAU).si;
351         params.setParameter(T,
352                 Duration.interpolate(params.getParameter(T), params.getParameter(TMAX), ratio <= 1.0 ? ratio : 1.0));
353     }
354 
355     /**
356      * Determines lane change desire for the given GtU. Mandatory desire is deduced as the maximum of a set of mandatory
357      * incentives, while voluntary desires are added. Depending on the level of mandatory lane change desire, voluntary desire
358      * may be included partially. If both are positive or negative, voluntary desire is fully included. Otherwise, voluntary
359      * desire is less considered within the range dSync &lt; |mandatory| &lt; dCoop. The absolute value is used as large
360      * negative mandatory desire may also dominate voluntary desire.
361      * @param parameters Parameters; parameters
362      * @param perception LanePerception; perception
363      * @param carFollowingModel CarFollowingModel; car-following model
364      * @param mandatoryIncentives Iterable&lt;MandatoryIncentive&gt;; mandatory incentives
365      * @param voluntaryIncentives Iterable&lt;VoluntaryIncentive&gt;; voluntary incentives
366      * @param desireMap Map&lt;Class&lt;? extends Incentive&gt;,Desire&gt;; map where calculated desires are stored in
367      * @return lane change desire for gtu
368      * @throws ParameterException if a parameter is not defined
369      * @throws GtuException if there is no mandatory incentive, the model requires at least one
370      * @throws OperationalPlanException perception exception
371      */
372     public static Desire getLaneChangeDesire(final Parameters parameters, final LanePerception perception,
373             final CarFollowingModel carFollowingModel, final Iterable<MandatoryIncentive> mandatoryIncentives,
374             final Iterable<VoluntaryIncentive> voluntaryIncentives, final Map<Class<? extends Incentive>, Desire> desireMap)
375             throws ParameterException, GtuException, OperationalPlanException
376     {
377 
378         double dSync = parameters.getParameter(DSYNC);
379         double dCoop = parameters.getParameter(DCOOP);
380 
381         // Mandatory desire
382         double dLeftMandatory = 0.0;
383         double dRightMandatory = 0.0;
384         Desire mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
385         for (MandatoryIncentive incentive : mandatoryIncentives)
386         {
387             Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire);
388             desireMap.put(incentive.getClass(), d);
389             dLeftMandatory = Math.abs(d.left()) > Math.abs(dLeftMandatory) ? d.left() : dLeftMandatory;
390             dRightMandatory = Math.abs(d.right()) > Math.abs(dRightMandatory) ? d.right() : dRightMandatory;
391             mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
392         }
393 
394         // Voluntary desire
395         double dLeftVoluntary = 0;
396         double dRightVoluntary = 0;
397         Desire voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
398         for (VoluntaryIncentive incentive : voluntaryIncentives)
399         {
400             Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire, voluntaryDesire);
401             desireMap.put(incentive.getClass(), d);
402             dLeftVoluntary += d.left();
403             dRightVoluntary += d.right();
404             voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
405         }
406 
407         // Total desire
408         double thetaLeft = 0;
409         double dLeftMandatoryAbs = Math.abs(dLeftMandatory);
410         double dRightMandatoryAbs = Math.abs(dRightMandatory);
411         if (dLeftMandatoryAbs <= dSync || dLeftMandatory * dLeftVoluntary >= 0)
412         {
413             // low mandatory desire, or same sign
414             thetaLeft = 1;
415         }
416         else if (dSync < dLeftMandatoryAbs && dLeftMandatoryAbs < dCoop && dLeftMandatory * dLeftVoluntary < 0)
417         {
418             // linear from 1 at dSync to 0 at dCoop
419             thetaLeft = (dCoop - dLeftMandatoryAbs) / (dCoop - dSync);
420         }
421         double thetaRight = 0;
422         if (dRightMandatoryAbs <= dSync || dRightMandatory * dRightVoluntary >= 0)
423         {
424             // low mandatory desire, or same sign
425             thetaRight = 1;
426         }
427         else if (dSync < dRightMandatoryAbs && dRightMandatoryAbs < dCoop && dRightMandatory * dRightVoluntary < 0)
428         {
429             // linear from 1 at dSync to 0 at dCoop
430             thetaRight = (dCoop - dRightMandatoryAbs) / (dCoop - dSync);
431         }
432         return new Desire(dLeftMandatory + thetaLeft * dLeftVoluntary, dRightMandatory + thetaRight * dRightVoluntary);
433 
434     }
435 
436     /**
437      * Determine whether a lane change is acceptable (gap, lane markings, etc.).
438      * @param perception LanePerception; perception
439      * @param params Parameters; parameters
440      * @param sli SpeedLimitInfo; speed limit info
441      * @param cfm CarFollowingModel; car-following model
442      * @param desire double; level of lane change desire
443      * @param ownSpeed Speed; own speed
444      * @param ownAcceleration Acceleration; current car-following acceleration
445      * @param lat LateralDirectionality; lateral direction for synchronization
446      * @param gapAcceptance GapAcceptance; gap-acceptance model
447      * @param laneChange LaneChange; lane change
448      * @return whether a gap is acceptable
449      * @throws ParameterException if a parameter is not defined
450      * @throws OperationalPlanException perception exception
451      */
452     static boolean acceptLaneChange(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
453             final CarFollowingModel cfm, final double desire, final Speed ownSpeed, final Acceleration ownAcceleration,
454             final LateralDirectionality lat, final GapAcceptance gapAcceptance, final LaneChange laneChange)
455             throws ParameterException, OperationalPlanException
456     {
457         // beyond start distance
458         LaneBasedGtu gtu = Try.assign(() -> perception.getGtu(), "Cannot obtain GTU.");
459         if (!gtu.laneChangeAllowed())
460         {
461             return false;
462         }
463 
464         // legal?
465         InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
466         if (infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat).si <= 0.0)
467         {
468             return false;
469         }
470 
471         // neighbors and lane change distance (not gap-acceptance)
472         NeighborsPerception neighbors = perception.getPerceptionCategoryOrNull(NeighborsPerception.class);
473         PerceptionCollectable<HeadwayGtu, LaneBasedGtu> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
474         if (!leaders.isEmpty())
475         {
476             boolean ok = laneChange.checkRoom(gtu, leaders.first());
477             if (!ok)
478             {
479                 return false;
480             }
481         }
482         RelativeLane lane = new RelativeLane(lat, 1);
483         leaders = neighbors.getLeaders(lane);
484         if (!leaders.isEmpty())
485         {
486             boolean ok = laneChange.checkRoom(gtu, leaders.first());
487             if (!ok)
488             {
489                 return false;
490             }
491         }
492 
493         // other causes for deceleration
494         IntersectionPerception intersection = perception.getPerceptionCategoryOrNull(IntersectionPerception.class);
495         if (intersection != null)
496         {
497             // // conflicts alongside?
498             // if ((lat.isLeft() && intersection.isAlongsideConflictLeft())
499             // || (lat.isRight() && intersection.isAlongsideConflictRight()))
500             // {
501             // return false;
502             // }
503             // if (quickIntersectionScan(params, sli, cfm, ownSpeed, lat, intersection).lt(params.getParameter(BCRIT).neg()))
504             // {
505             // return false;
506             // }
507 
508             // conflicts
509             EgoPerception<?, ?> ego = perception.getPerceptionCategoryOrNull(EgoPerception.class);
510             PerceptionCollectable<HeadwayConflict, Conflict> conflicts = intersection.getConflicts(lane);
511             try
512             {
513                 Acceleration a = ConflictUtil.approachConflicts(params, conflicts, leaders, cfm, ego.getLength(),
514                         ego.getWidth(), ownSpeed, ownAcceleration, sli, new ConflictPlans(), perception.getGtu(), lane);
515                 if (a.lt(params.getParameter(ParameterTypes.BCRIT).neg()))
516                 {
517                     return false;
518                 }
519                 // gap-acceptance on merge conflicts
520                 // TODO: this approach is a hack
521                 for (HeadwayConflict conflict : conflicts)
522                 {
523                     if (conflict.isMerge() && conflict.getDistance().si < 10.0)
524                     {
525                         PerceptionCollectable<HeadwayGtu, LaneBasedGtu> down = conflict.getDownstreamConflictingGTUs();
526                         if (!down.isEmpty() && down.first().isParallel())
527                         {
528                             return false; // GTU on conflict
529                         }
530                         PerceptionCollectable<HeadwayGtu, LaneBasedGtu> up = conflict.getUpstreamConflictingGTUs();
531                         if (!up.isEmpty() && up.first().isParallel())
532                         {
533                             return false; // GTU on conflict
534                         }
535                     }
536                 }
537             }
538             catch (GtuException exception)
539             {
540                 throw new OperationalPlanException(exception);
541             }
542             conflicts = intersection.getConflicts(RelativeLane.CURRENT);
543             for (HeadwayConflict conflict : conflicts)
544             {
545                 if (conflict.getLane().getLink().equals(conflict.getConflictingLink()))
546                 {
547                     if (conflict.isMerge() && conflict.getDistance().le0()
548                             && conflict.getDistance().neg().gt(conflict.getLength()))
549                     {
550                         return false; // partially past the merge; adjacent lane might be ambiguous
551                     }
552                     else if (conflict.isSplit() && conflict.getDistance().le0()
553                             && conflict.getDistance().neg().lt(gtu.getLength()))
554                     {
555                         return false; // partially before the split; adjacent lane might be ambiguous
556                     }
557                 }
558             }
559 
560             // traffic lights
561             Iterable<HeadwayTrafficLight> trafficLights = intersection.getTrafficLights(lane);
562             for (HeadwayTrafficLight trafficLight : trafficLights)
563             {
564                 if (trafficLight.getTrafficLightColor().isRedOrYellow())
565                 {
566                     boolean ok = laneChange.checkRoom(gtu, trafficLight);
567                     if (!ok)
568                     {
569                         return false;
570                     }
571                 }
572             }
573         }
574 
575         // safe regarding neighbors?
576         return gapAcceptance.acceptGap(perception, params, sli, cfm, desire, ownSpeed, ownAcceleration, lat);
577 
578     }
579 
580     /**
581      * Returns a quickly determined acceleration to consider on an adjacent lane, following from conflicts and traffic lights.
582      * @param params Parameters; parameters
583      * @param sli SpeedLimitInfo; speed limit info
584      * @param cfm CarFollowingModel; car-following model
585      * @param ownSpeed Speed; own speed
586      * @param lat LateralDirectionality; lateral direction for synchronization
587      * @param intersection IntersectionPerception; intersection perception
588      * @return a quickly determined acceleration to consider on an adjacent lane, following from conflicts and traffic lights
589      * @throws ParameterException if a parameter is not defined
590      */
591     private static Acceleration quickIntersectionScan(final Parameters params, final SpeedLimitInfo sli,
592             final CarFollowingModel cfm, final Speed ownSpeed, final LateralDirectionality lat,
593             final IntersectionPerception intersection) throws ParameterException
594     {
595         Acceleration a = Acceleration.POSITIVE_INFINITY;
596         if (intersection != null)
597         {
598             RelativeLane lane = lat.isRight() ? RelativeLane.RIGHT : RelativeLane.LEFT;
599             Iterable<HeadwayConflict> iterable = intersection.getConflicts(lane);
600             if (iterable != null)
601             {
602                 Iterator<HeadwayConflict> conflicts = iterable.iterator();
603                 if (conflicts.hasNext())
604                 {
605                     a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
606                             conflicts.next().getDistance(), Speed.ZERO));
607                 }
608                 Iterator<HeadwayTrafficLight> trafficLights = intersection.getTrafficLights(lane).iterator();
609                 if (trafficLights.hasNext())
610                 {
611                     HeadwayTrafficLight trafficLight = trafficLights.next();
612                     if (trafficLight.getTrafficLightColor().isRedOrYellow())
613                     {
614                         a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
615                                 trafficLight.getDistance(), Speed.ZERO));
616                     }
617                 }
618             }
619         }
620         return a;
621     }
622 
623     /**
624      * Sets value for T depending on level of lane change desire.
625      * @param params Parameters; parameters
626      * @param desire double; lane change desire
627      * @throws ParameterException if T, TMIN or TMAX is not in the parameters
628      */
629     static void setDesiredHeadway(final Parameters params, final double desire) throws ParameterException
630     {
631         double limitedDesire = desire < 0 ? 0 : desire > 1 ? 1 : desire;
632         double tDes = limitedDesire * params.getParameter(TMIN).si + (1 - limitedDesire) * params.getParameter(TMAX).si;
633         double t = params.getParameter(T).si;
634         params.setParameterResettable(T, Duration.instantiateSI(tDes < t ? tDes : t));
635     }
636 
637     /**
638      * Resets value for T depending on level of lane change desire.
639      * @param params Parameters; parameters
640      * @throws ParameterException if T is not in the parameters
641      */
642     static void resetDesiredHeadway(final Parameters params) throws ParameterException
643     {
644         params.resetParameter(T);
645     }
646 
647     /**
648      * Determine acceleration from car-following with desire-adjusted headway.
649      * @param distance Length; distance from follower to leader
650      * @param followerSpeed Speed; speed of follower
651      * @param leaderSpeed Speed; speed of leader
652      * @param desire double; level of lane change desire
653      * @param params Parameters; parameters
654      * @param sli SpeedLimitInfo; speed limit info
655      * @param cfm CarFollowingModel; car-following model
656      * @return acceleration from car-following
657      * @throws ParameterException if a parameter is not defined
658      */
659     public static Acceleration singleAcceleration(final Length distance, final Speed followerSpeed, final Speed leaderSpeed,
660             final double desire, final Parameters params, final SpeedLimitInfo sli, final CarFollowingModel cfm)
661             throws ParameterException
662     {
663         // set T
664         setDesiredHeadway(params, desire);
665         // calculate acceleration
666         Acceleration a = CarFollowingUtil.followSingleLeader(cfm, params, followerSpeed, sli, distance, leaderSpeed);
667         // reset T
668         resetDesiredHeadway(params);
669         return a;
670     }
671 
672 }