View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical.util.lmrs;
2   
3   import java.util.Iterator;
4   import java.util.LinkedHashSet;
5   import java.util.Map;
6   import java.util.SortedSet;
7   
8   import org.djunits.value.vdouble.scalar.Acceleration;
9   import org.djunits.value.vdouble.scalar.Duration;
10  import org.djunits.value.vdouble.scalar.Length;
11  import org.djunits.value.vdouble.scalar.Speed;
12  import org.djunits.value.vdouble.scalar.Time;
13  import org.djutils.exceptions.Try;
14  import org.opentrafficsim.base.parameters.ParameterException;
15  import org.opentrafficsim.base.parameters.ParameterTypeAcceleration;
16  import org.opentrafficsim.base.parameters.ParameterTypeDuration;
17  import org.opentrafficsim.base.parameters.ParameterTypes;
18  import org.opentrafficsim.base.parameters.Parameters;
19  import org.opentrafficsim.core.gtu.GTUException;
20  import org.opentrafficsim.core.gtu.TurnIndicatorIntent;
21  import org.opentrafficsim.core.gtu.perception.EgoPerception;
22  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
23  import org.opentrafficsim.core.network.LateralDirectionality;
24  import org.opentrafficsim.core.network.NetworkException;
25  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
26  import org.opentrafficsim.road.gtu.lane.perception.InfrastructureLaneChangeInfo;
27  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
28  import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
29  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
30  import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
31  import org.opentrafficsim.road.gtu.lane.perception.categories.IntersectionPerception;
32  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
33  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayConflict;
34  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
35  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayTrafficLight;
36  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneChange;
37  import org.opentrafficsim.road.gtu.lane.plan.operational.SimpleOperationalPlan;
38  import org.opentrafficsim.road.gtu.lane.tactical.Synchronizable;
39  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
40  import org.opentrafficsim.road.gtu.lane.tactical.util.CarFollowingUtil;
41  import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
42  import org.opentrafficsim.road.network.speed.SpeedLimitProspect;
43  
44  /**
45   * <p>
46   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
47   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
48   * <p>
49   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 26, 2016 <br>
50   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
51   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
52   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
53   */
54  public final class LmrsUtil implements LmrsParameters
55  {
56  
57      /** Fixed model time step. */
58      public static final ParameterTypeDuration DT = ParameterTypes.DT;
59  
60      /** Minimum car-following headway. */
61      public static final ParameterTypeDuration TMIN = ParameterTypes.TMIN;
62  
63      /** Current car-following headway. */
64      public static final ParameterTypeDuration T = ParameterTypes.T;
65  
66      /** Maximum car-following headway. */
67      public static final ParameterTypeDuration TMAX = ParameterTypes.TMAX;
68  
69      /** Headway relaxation time. */
70      public static final ParameterTypeDuration TAU = ParameterTypes.TAU;
71  
72      /** Maximum critical deceleration, e.g. stop/go at traffic light. */
73      public static final ParameterTypeAcceleration BCRIT = ParameterTypes.BCRIT;
74  
75      /**
76       * Do not instantiate.
77       */
78      private LmrsUtil()
79      {
80          //
81      }
82  
83      /**
84       * Determines a simple representation of an operational plan.
85       * @param gtu LaneBasedGTU; gtu
86       * @param startTime Time; start time
87       * @param carFollowingModel CarFollowingModel; car-following model
88       * @param laneChange LaneChange; lane change status
89       * @param lmrsData LmrsData; LMRS data
90       * @param perception LanePerception; perception
91       * @param mandatoryIncentives LinkedHashSet&lt;MandatoryIncentive&gt;; set of mandatory lane change incentives
92       * @param voluntaryIncentives LinkedHashSet&lt;VoluntaryIncentive&gt;; set of voluntary lane change incentives
93       * @return simple operational plan
94       * @throws GTUException gtu exception
95       * @throws NetworkException network exception
96       * @throws ParameterException parameter exception
97       * @throws OperationalPlanException operational plan exception
98       */
99      @SuppressWarnings({ "checkstyle:parameternumber", "checkstyle:methodlength" })
100     public static SimpleOperationalPlan determinePlan(final LaneBasedGTU gtu, final Time startTime,
101             final CarFollowingModel carFollowingModel, final LaneChange laneChange, final LmrsData lmrsData,
102             final LanePerception perception, final LinkedHashSet<MandatoryIncentive> mandatoryIncentives,
103             final LinkedHashSet<VoluntaryIncentive> voluntaryIncentives)
104             throws GTUException, NetworkException, ParameterException, OperationalPlanException
105     {
106 
107         // TODO: make LMRS available for lane change only with controlled car-following, e.g. ACC.
108 
109         // obtain objects to get info
110         InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
111         SpeedLimitProspect slp = infra.getSpeedLimitProspect(RelativeLane.CURRENT);
112         SpeedLimitInfo sli = slp.getSpeedLimitInfo(Length.ZERO);
113         Parameters params = gtu.getParameters();
114         EgoPerception<?, ?> ego = perception.getPerceptionCategory(EgoPerception.class);
115         Speed speed = ego.getSpeed();
116         NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
117         PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
118 
119         // regular car-following
120         Acceleration a;
121         if (lmrsData.isHumanLongitudinalControl())
122         {
123             lmrsData.getTailGating().tailgate(perception, params);
124             if (!leaders.isEmpty() && lmrsData.isNewLeader(leaders.first()))
125             {
126                 initHeadwayRelaxation(params, leaders.first());
127             }
128             a = gtu.getCarFollowingAcceleration();
129         }
130         else
131         {
132             a = Acceleration.POS_MAXVALUE;
133         }
134 
135         // during a lane change, both leaders are followed
136         LateralDirectionality initiatedLaneChange;
137         TurnIndicatorIntent turnIndicatorStatus = TurnIndicatorIntent.NONE;
138         if (laneChange.isChangingLane())
139         {
140             RelativeLane secondLane = laneChange.getSecondLane(gtu);
141             initiatedLaneChange = LateralDirectionality.NONE;
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         else
151         {
152 
153             // determine lane change desire based on incentives
154             Desire desire = getLaneChangeDesire(params, perception, carFollowingModel, mandatoryIncentives, voluntaryIncentives,
155                     lmrsData.getDesireMap());
156 
157             // lane change decision
158             double dFree = params.getParameter(DFREE);
159             initiatedLaneChange = LateralDirectionality.NONE;
160             turnIndicatorStatus = TurnIndicatorIntent.NONE;
161             if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dFree)
162             {
163                 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getLeft(), speed, a,
164                         LateralDirectionality.LEFT, lmrsData.getGapAcceptance()))
165                 {
166                     // change left
167                     initiatedLaneChange = LateralDirectionality.LEFT;
168                     turnIndicatorStatus = TurnIndicatorIntent.LEFT;
169                     params.setParameter(DLC, desire.getLeft());
170                     setDesiredHeadway(params, desire.getLeft());
171                     leaders = neighbors.getLeaders(RelativeLane.LEFT);
172                     if (!leaders.isEmpty())
173                     {
174                         // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next
175                         // step
176                         lmrsData.isNewLeader(leaders.first());
177                     }
178                     a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
179                             neighbors.getLeaders(RelativeLane.LEFT)));
180                 }
181             }
182             else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dFree)
183             {
184                 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getRight(), speed, a,
185                         LateralDirectionality.RIGHT, lmrsData.getGapAcceptance()))
186                 {
187                     // change right
188                     initiatedLaneChange = LateralDirectionality.RIGHT;
189                     turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
190                     params.setParameter(DLC, desire.getRight());
191                     setDesiredHeadway(params, desire.getRight());
192                     leaders = neighbors.getLeaders(RelativeLane.RIGHT);
193                     if (!leaders.isEmpty())
194                     {
195                         // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next
196                         // step
197                         lmrsData.isNewLeader(leaders.first());
198                     }
199                     a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
200                             neighbors.getLeaders(RelativeLane.RIGHT)));
201                 }
202             }
203             if (!initiatedLaneChange.isNone())
204             {
205                 SortedSet<InfrastructureLaneChangeInfo> set = infra.getInfrastructureLaneChangeInfo(RelativeLane.CURRENT);
206                 if (!set.isEmpty())
207                 {
208                     Length boundary = null;
209                     for (InfrastructureLaneChangeInfo info : set)
210                     {
211                         int n = info.getRequiredNumberOfLaneChanges();
212                         if (n > 1)
213                         {
214                             Length thisBoundary = info.getRemainingDistance()
215                                     .minus(Synchronization.requiredBufferSpace(speed, info.getRequiredNumberOfLaneChanges(),
216                                             params.getParameter(ParameterTypes.LOOKAHEAD),
217                                             params.getParameter(ParameterTypes.T0), params.getParameter(ParameterTypes.LCDUR),
218                                             params.getParameter(DCOOP)));
219                             if (thisBoundary.le0())
220                             {
221                                 thisBoundary = info.getRemainingDistance().divideBy(info.getRequiredNumberOfLaneChanges());
222                             }
223                             boundary = boundary == null || thisBoundary.si < boundary.si ? thisBoundary : boundary;
224                         }
225                     }
226                     laneChange.setBoundary(boundary);
227                 }
228                 params.setParameter(DLEFT, 0.0);
229                 params.setParameter(DRIGHT, 0.0);
230             }
231             else
232             {
233                 params.setParameter(DLEFT, desire.getLeft());
234                 params.setParameter(DRIGHT, desire.getRight());
235             }
236 
237             // take action if we cannot change lane
238             Acceleration aSync;
239 
240             // synchronize
241             double dSync = params.getParameter(DSYNC);
242             if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dSync)
243             {
244                 Synchronizable.State state;
245                 if (desire.getLeft() >= params.getParameter(DCOOP))
246                 {
247                     // switch on left indicator
248                     turnIndicatorStatus = TurnIndicatorIntent.LEFT;
249                     state = Synchronizable.State.INDICATING;
250                 }
251                 else
252                 {
253                     state = Synchronizable.State.SYNCHRONIZING;
254                 }
255                 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getLeft(),
256                         LateralDirectionality.LEFT, lmrsData);
257                 a = applyAcceleration(a, aSync, lmrsData, state);
258             }
259             else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dSync)
260             {
261                 Synchronizable.State state;
262                 if (desire.getRight() >= params.getParameter(DCOOP))
263                 {
264                     // switch on right indicator
265                     turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
266                     state = Synchronizable.State.INDICATING;
267                 }
268                 else
269                 {
270                     state = Synchronizable.State.SYNCHRONIZING;
271                 }
272                 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getRight(),
273                         LateralDirectionality.RIGHT, lmrsData);
274                 a = applyAcceleration(a, aSync, lmrsData, state);
275             }
276             else
277             {
278                 lmrsData.setSynchronizationState(Synchronizable.State.NONE);
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 <tt>DT</tt>.
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 RSU. 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 LinkedHashSet&lt;MandatoryIncentive&gt;; mandatory incentives
365      * @param voluntaryIncentives LinkedHashSet&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 LinkedHashSet<MandatoryIncentive> mandatoryIncentives,
374             final LinkedHashSet<VoluntaryIncentive> voluntaryIncentives,
375             final Map<Class<? extends Incentive>, Desire> desireMap)
376             throws ParameterException, GTUException, OperationalPlanException
377     {
378 
379         double dSync = parameters.getParameter(DSYNC);
380         double dCoop = parameters.getParameter(DCOOP);
381 
382         // Mandatory desire
383         double dLeftMandatory = 0.0;
384         double dRightMandatory = 0.0;
385         Desire mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
386         for (MandatoryIncentive incentive : mandatoryIncentives)
387         {
388             Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire);
389             desireMap.put(incentive.getClass(), d);
390             dLeftMandatory = Math.abs(d.getLeft()) > Math.abs(dLeftMandatory) ? d.getLeft() : dLeftMandatory;
391             dRightMandatory = Math.abs(d.getRight()) > Math.abs(dRightMandatory) ? d.getRight() : dRightMandatory;
392             mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
393         }
394 
395         // Voluntary desire
396         double dLeftVoluntary = 0;
397         double dRightVoluntary = 0;
398         Desire voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
399         for (VoluntaryIncentive incentive : voluntaryIncentives)
400         {
401             Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire, voluntaryDesire);
402             desireMap.put(incentive.getClass(), d);
403             dLeftVoluntary += d.getLeft();
404             dRightVoluntary += d.getRight();
405             voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
406         }
407 
408         // Total desire
409         double thetaLeft = 0;
410         double dLeftMandatoryAbs = Math.abs(dLeftMandatory);
411         double dRightMandatoryAbs = Math.abs(dRightMandatory);
412         if (dLeftMandatoryAbs <= dSync || dLeftMandatory * dLeftVoluntary >= 0)
413         {
414             // low mandatory desire, or same sign
415             thetaLeft = 1;
416         }
417         else if (dSync < dLeftMandatoryAbs && dLeftMandatoryAbs < dCoop && dLeftMandatory * dLeftVoluntary < 0)
418         {
419             // linear from 1 at dSync to 0 at dCoop
420             thetaLeft = (dCoop - dLeftMandatoryAbs) / (dCoop - dSync);
421         }
422         double thetaRight = 0;
423         if (dRightMandatoryAbs <= dSync || dRightMandatory * dRightVoluntary >= 0)
424         {
425             // low mandatory desire, or same sign
426             thetaRight = 1;
427         }
428         else if (dSync < dRightMandatoryAbs && dRightMandatoryAbs < dCoop && dRightMandatory * dRightVoluntary < 0)
429         {
430             // linear from 1 at dSync to 0 at dCoop
431             thetaRight = (dCoop - dRightMandatoryAbs) / (dCoop - dSync);
432         }
433         return new Desire(dLeftMandatory + thetaLeft * dLeftVoluntary, dRightMandatory + thetaRight * dRightVoluntary);
434 
435     }
436 
437     /**
438      * Determine whether a lane change is acceptable (gap, lane markings, etc.).
439      * @param perception LanePerception; perception
440      * @param params Parameters; parameters
441      * @param sli SpeedLimitInfo; speed limit info
442      * @param cfm CarFollowingModel; car-following model
443      * @param desire double; level of lane change desire
444      * @param ownSpeed Speed; own speed
445      * @param ownAcceleration Acceleration; current car-following acceleration
446      * @param lat LateralDirectionality; lateral direction for synchronization
447      * @param gapAcceptance GapAcceptance; gap-acceptance model
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)
455             throws ParameterException, OperationalPlanException
456     {
457 
458         // beyond start distance
459         boolean beyond = Try.assign(() -> perception.getGtu().laneChangeAllowed(), "Cannot obtain GTU.");
460         if (!beyond)
461         {
462             return false;
463         }
464 
465         // legal?
466         InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
467         if (infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat).si <= 0.0)
468         {
469             return false;
470         }
471 
472         // other causes for deceleration
473         IntersectionPerception intersection = perception.getPerceptionCategoryOrNull(IntersectionPerception.class);
474         if (intersection != null)
475         {
476             // conflicts alongside?
477             if ((lat.isLeft() && intersection.isAlongsideConflictLeft())
478                     || (lat.isRight() && intersection.isAlongsideConflictRight()))
479             {
480                 return false;
481             }
482             if (quickIntersectionScan(params, sli, cfm, ownSpeed, lat, intersection).lt(params.getParameter(BCRIT).neg()))
483             {
484                 return false;
485             }
486         }
487 
488         // safe regarding neighbors?
489         return gapAcceptance.acceptGap(perception, params, sli, cfm, desire, ownSpeed, ownAcceleration, lat);
490     }
491 
492     /**
493      * Returns a quickly determined acceleration to consider on an adjacent lane, following from conflicts and traffic lights.
494      * @param params Parameters; parameters
495      * @param sli SpeedLimitInfo; speed limit info
496      * @param cfm CarFollowingModel; car-following model
497      * @param ownSpeed Speed; own speed
498      * @param lat LateralDirectionality; lateral direction for synchronization
499      * @param intersection IntersectionPerception; intersection perception
500      * @return a quickly determined acceleration to consider on an adjacent lane, following from conflicts and traffic lights
501      * @throws ParameterException if a parameter is not defined
502      */
503     private static Acceleration quickIntersectionScan(final Parameters params, final SpeedLimitInfo sli,
504             final CarFollowingModel cfm, final Speed ownSpeed, final LateralDirectionality lat,
505             final IntersectionPerception intersection) throws ParameterException
506     {
507         Acceleration a = Acceleration.POSITIVE_INFINITY;
508         if (intersection != null)
509         {
510             RelativeLane lane = lat.isRight() ? RelativeLane.RIGHT : RelativeLane.LEFT;
511             Iterable<HeadwayConflict> iterable = intersection.getConflicts(lane);
512             if (iterable != null)
513             {
514                 Iterator<HeadwayConflict> conflicts = iterable.iterator();
515                 if (conflicts.hasNext())
516                 {
517                     a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
518                             conflicts.next().getDistance(), Speed.ZERO));
519                 }
520                 Iterator<HeadwayTrafficLight> trafficLights = intersection.getTrafficLights(lane).iterator();
521                 if (trafficLights.hasNext())
522                 {
523                     HeadwayTrafficLight trafficLight = trafficLights.next();
524                     if (trafficLight.getTrafficLightColor().isRedOrYellow())
525                     {
526                         a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
527                                 trafficLight.getDistance(), Speed.ZERO));
528                     }
529                 }
530             }
531         }
532         return a;
533     }
534 
535     /**
536      * Sets value for T depending on level of lane change desire.
537      * @param params Parameters; parameters
538      * @param desire double; lane change desire
539      * @throws ParameterException if T, TMIN or TMAX is not in the parameters
540      */
541     static void setDesiredHeadway(final Parameters params, final double desire) throws ParameterException
542     {
543         double limitedDesire = desire < 0 ? 0 : desire > 1 ? 1 : desire;
544         double tDes = limitedDesire * params.getParameter(TMIN).si + (1 - limitedDesire) * params.getParameter(TMAX).si;
545         double t = params.getParameter(T).si;
546         params.setParameterResettable(T, Duration.createSI(tDes < t ? tDes : t));
547     }
548 
549     /**
550      * Resets value for T depending on level of lane change desire.
551      * @param params Parameters; parameters
552      * @throws ParameterException if T is not in the parameters
553      */
554     static void resetDesiredHeadway(final Parameters params) throws ParameterException
555     {
556         params.resetParameter(T);
557     }
558 
559     /**
560      * Determine acceleration from car-following with desire-adjusted headway.
561      * @param distance Length; distance from follower to leader
562      * @param followerSpeed Speed; speed of follower
563      * @param leaderSpeed Speed; speed of leader
564      * @param desire double; level of lane change desire
565      * @param params Parameters; parameters
566      * @param sli SpeedLimitInfo; speed limit info
567      * @param cfm CarFollowingModel; car-following model
568      * @return acceleration from car-following
569      * @throws ParameterException if a parameter is not defined
570      */
571     public static Acceleration singleAcceleration(final Length distance, final Speed followerSpeed, final Speed leaderSpeed,
572             final double desire, final Parameters params, final SpeedLimitInfo sli, final CarFollowingModel cfm)
573             throws ParameterException
574     {
575         // set T
576         setDesiredHeadway(params, desire);
577         // calculate acceleration
578         Acceleration a = CarFollowingUtil.followSingleLeader(cfm, params, followerSpeed, sli, distance, leaderSpeed);
579         // reset T
580         resetDesiredHeadway(params);
581         return a;
582     }
583 
584 }