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         lmrsData.getTailGating().tailgate(perception, params);
121         if (!leaders.isEmpty() && lmrsData.isNewLeader(leaders.first()))
122         {
123             initHeadwayRelaxation(params, leaders.first());
124         }
125         Acceleration a = gtu.getCarFollowingAcceleration();
126 
127         // during a lane change, both leaders are followed
128         LateralDirectionality initiatedLaneChange;
129         TurnIndicatorIntent turnIndicatorStatus = TurnIndicatorIntent.NONE;
130         if (laneChange.isChangingLane())
131         {
132             RelativeLane secondLane = laneChange.getSecondLane(gtu);
133             initiatedLaneChange = LateralDirectionality.NONE;
134             PerceptionCollectable<HeadwayGTU, LaneBasedGTU> secondLeaders = neighbors.getLeaders(secondLane);
135             Acceleration aSecond = carFollowingModel.followingAcceleration(params, speed, sli, secondLeaders);
136             if (!secondLeaders.isEmpty() && lmrsData.isNewLeader(secondLeaders.first()))
137             {
138                 initHeadwayRelaxation(params, secondLeaders.first());
139             }
140             a = Acceleration.min(a, aSecond);
141         }
142         else
143         {
144 
145             // determine lane change desire based on incentives
146             Desire desire = getLaneChangeDesire(params, perception, carFollowingModel, mandatoryIncentives, voluntaryIncentives,
147                     lmrsData.desireMap);
148 
149             // lane change decision
150             double dFree = params.getParameter(DFREE);
151             initiatedLaneChange = LateralDirectionality.NONE;
152             turnIndicatorStatus = TurnIndicatorIntent.NONE;
153             if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dFree)
154             {
155                 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getLeft(), speed, a,
156                         LateralDirectionality.LEFT, lmrsData.getGapAcceptance()))
157                 {
158                     // change left
159                     initiatedLaneChange = LateralDirectionality.LEFT;
160                     turnIndicatorStatus = TurnIndicatorIntent.LEFT;
161                     params.setParameter(DLC, desire.getLeft());
162                     setDesiredHeadway(params, desire.getLeft());
163                     leaders = neighbors.getLeaders(RelativeLane.LEFT);
164                     if (!leaders.isEmpty())
165                     {
166                         // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next
167                         // step
168                         lmrsData.isNewLeader(leaders.first());
169                     }
170                     a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
171                             neighbors.getLeaders(RelativeLane.LEFT)));
172                 }
173             }
174             else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dFree)
175             {
176                 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getRight(), speed, a,
177                         LateralDirectionality.RIGHT, lmrsData.getGapAcceptance()))
178                 {
179                     // change right
180                     initiatedLaneChange = LateralDirectionality.RIGHT;
181                     turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
182                     params.setParameter(DLC, desire.getRight());
183                     setDesiredHeadway(params, desire.getRight());
184                     leaders = neighbors.getLeaders(RelativeLane.RIGHT);
185                     if (!leaders.isEmpty())
186                     {
187                         // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next
188                         // step
189                         lmrsData.isNewLeader(leaders.first());
190                     }
191                     a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
192                             neighbors.getLeaders(RelativeLane.RIGHT)));
193                 }
194             }
195             if (!initiatedLaneChange.isNone())
196             {
197                 SortedSet<InfrastructureLaneChangeInfo> set = infra.getInfrastructureLaneChangeInfo(RelativeLane.CURRENT);
198                 if (!set.isEmpty())
199                 {
200                     Length boundary = null;
201                     for (InfrastructureLaneChangeInfo info : set)
202                     {
203                         int n = info.getRequiredNumberOfLaneChanges();
204                         if (n > 1)
205                         {
206                             Length thisBoundary = info.getRemainingDistance()
207                                     .minus(Synchronization.requiredBufferSpace(speed, info.getRequiredNumberOfLaneChanges(),
208                                             params.getParameter(ParameterTypes.LOOKAHEAD),
209                                             params.getParameter(ParameterTypes.T0), params.getParameter(ParameterTypes.LCDUR),
210                                             params.getParameter(DCOOP)));
211                             if (thisBoundary.le0())
212                             {
213                                 thisBoundary = info.getRemainingDistance().divideBy(info.getRequiredNumberOfLaneChanges());
214                             }
215                             boundary = boundary == null || thisBoundary.si < boundary.si ? thisBoundary : boundary;
216                         }
217                     }
218                     laneChange.setBoundary(boundary);
219                 }
220                 params.setParameter(DLEFT, 0.0);
221                 params.setParameter(DRIGHT, 0.0);
222             }
223             else
224             {
225                 params.setParameter(DLEFT, desire.getLeft());
226                 params.setParameter(DRIGHT, desire.getRight());
227             }
228 
229             // take action if we cannot change lane
230             Acceleration aSync;
231 
232             // synchronize
233             double dSync = params.getParameter(DSYNC);
234             if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dSync)
235             {
236                 Synchronizable.State state;
237                 if (desire.getLeft() >= params.getParameter(DCOOP))
238                 {
239                     // switch on left indicator
240                     turnIndicatorStatus = TurnIndicatorIntent.LEFT;
241                     state = Synchronizable.State.INDICATING;
242                 }
243                 else
244                 {
245                     state = Synchronizable.State.SYNCHRONIZING;
246                 }
247                 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getLeft(),
248                         LateralDirectionality.LEFT, lmrsData);
249                 a = applyAcceleration(a, aSync, lmrsData, state);
250             }
251             else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dSync)
252             {
253                 Synchronizable.State state;
254                 if (desire.getRight() >= params.getParameter(DCOOP))
255                 {
256                     // switch on right indicator
257                     turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
258                     state = Synchronizable.State.INDICATING;
259                 }
260                 else
261                 {
262                     state = Synchronizable.State.SYNCHRONIZING;
263                 }
264                 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getRight(),
265                         LateralDirectionality.RIGHT, lmrsData);
266                 a = applyAcceleration(a, aSync, lmrsData, state);
267             }
268             else
269             {
270                 lmrsData.synchronizationState = Synchronizable.State.NONE;
271             }
272 
273             // cooperate
274             aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.LEFT,
275                     desire);
276             a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);
277             aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.RIGHT,
278                     desire);
279             a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);
280 
281             // relaxation
282             exponentialHeadwayRelaxation(params);
283 
284         }
285         lmrsData.finalizeStep();
286 
287         SimpleOperationalPlan simplePlan = new SimpleOperationalPlan(a, params.getParameter(DT), initiatedLaneChange);
288         if (turnIndicatorStatus.isLeft())
289         {
290             simplePlan.setIndicatorIntentLeft();
291         }
292         else if (turnIndicatorStatus.isRight())
293         {
294             simplePlan.setIndicatorIntentRight();
295         }
296         return simplePlan;
297 
298     }
299 
300     /**
301      * Minimizes the acceleration and sets the synchronization state if applicable.
302      * @param a Acceleration; previous acceleration
303      * @param aNew Acceleration; new acceleration
304      * @param lmrsData LmrsData; lmrs data
305      * @param state Synchronizable.State; synchronization state
306      * @return Acceleration; minimized acceleration
307      */
308     private static Acceleration applyAcceleration(final Acceleration a, final Acceleration aNew, final LmrsData lmrsData,
309             final Synchronizable.State state)
310     {
311         if (a.si < aNew.si)
312         {
313             return a;
314         }
315         lmrsData.synchronizationState = state;
316         return aNew;
317     }
318 
319     /**
320      * Sets the headway as a response to a new leader.
321      * @param params Parameters; parameters
322      * @param leader HeadwayGTU; leader
323      * @throws ParameterException if DLC is not present
324      */
325     private static void initHeadwayRelaxation(final Parameters params, final HeadwayGTU leader) throws ParameterException
326     {
327         Double dlc = leader.getParameters().getParameterOrNull(DLC);
328         if (dlc != null)
329         {
330             setDesiredHeadway(params, dlc);
331         }
332         // else could not be perceived
333     }
334 
335     /**
336      * Updates the desired headway following an exponential shape approximated with fixed time step <tt>DT</tt>.
337      * @param params Parameters; parameters
338      * @throws ParameterException in case of a parameter exception
339      */
340     private static void exponentialHeadwayRelaxation(final Parameters params) throws ParameterException
341     {
342         double ratio = params.getParameter(DT).si / params.getParameter(TAU).si;
343         params.setParameter(T,
344                 Duration.interpolate(params.getParameter(T), params.getParameter(TMAX), ratio <= 1.0 ? ratio : 1.0));
345     }
346 
347     /**
348      * Determines lane change desire for the given RSU. Mandatory desire is deduced as the maximum of a set of mandatory
349      * incentives, while voluntary desires are added. Depending on the level of mandatory lane change desire, voluntary desire
350      * may be included partially. If both are positive or negative, voluntary desire is fully included. Otherwise, voluntary
351      * desire is less considered within the range dSync &lt; |mandatory| &lt; dCoop. The absolute value is used as large
352      * negative mandatory desire may also dominate voluntary desire.
353      * @param parameters Parameters; parameters
354      * @param perception LanePerception; perception
355      * @param carFollowingModel CarFollowingModel; car-following model
356      * @param mandatoryIncentives LinkedHashSet&lt;MandatoryIncentive&gt;; mandatory incentives
357      * @param voluntaryIncentives LinkedHashSet&lt;VoluntaryIncentive&gt;; voluntary incentives
358      * @param desireMap Map&lt;Class&lt;? extends Incentive&gt;,Desire&gt;; map where calculated desires are stored in
359      * @return lane change desire for gtu
360      * @throws ParameterException if a parameter is not defined
361      * @throws GTUException if there is no mandatory incentive, the model requires at least one
362      * @throws OperationalPlanException perception exception
363      */
364     public static Desire getLaneChangeDesire(final Parameters parameters, final LanePerception perception,
365             final CarFollowingModel carFollowingModel, final LinkedHashSet<MandatoryIncentive> mandatoryIncentives,
366             final LinkedHashSet<VoluntaryIncentive> voluntaryIncentives,
367             final Map<Class<? extends Incentive>, Desire> desireMap)
368             throws ParameterException, GTUException, OperationalPlanException
369     {
370 
371         double dSync = parameters.getParameter(DSYNC);
372         double dCoop = parameters.getParameter(DCOOP);
373 
374         // Mandatory desire
375         double dLeftMandatory = 0.0;
376         double dRightMandatory = 0.0;
377         Desire mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
378         for (MandatoryIncentive incentive : mandatoryIncentives)
379         {
380             Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire);
381             desireMap.put(incentive.getClass(), d);
382             dLeftMandatory = Math.abs(d.getLeft()) > Math.abs(dLeftMandatory) ? d.getLeft() : dLeftMandatory;
383             dRightMandatory = Math.abs(d.getRight()) > Math.abs(dRightMandatory) ? d.getRight() : dRightMandatory;
384             mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
385         }
386 
387         // Voluntary desire
388         double dLeftVoluntary = 0;
389         double dRightVoluntary = 0;
390         Desire voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
391         for (VoluntaryIncentive incentive : voluntaryIncentives)
392         {
393             Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire, voluntaryDesire);
394             desireMap.put(incentive.getClass(), d);
395             dLeftVoluntary += d.getLeft();
396             dRightVoluntary += d.getRight();
397             voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
398         }
399 
400         // Total desire
401         double thetaLeft = 0;
402         double dLeftMandatoryAbs = Math.abs(dLeftMandatory);
403         double dRightMandatoryAbs = Math.abs(dRightMandatory);
404         if (dLeftMandatoryAbs <= dSync || dLeftMandatory * dLeftVoluntary >= 0)
405         {
406             // low mandatory desire, or same sign
407             thetaLeft = 1;
408         }
409         else if (dSync < dLeftMandatoryAbs && dLeftMandatoryAbs < dCoop && dLeftMandatory * dLeftVoluntary < 0)
410         {
411             // linear from 1 at dSync to 0 at dCoop
412             thetaLeft = (dCoop - dLeftMandatoryAbs) / (dCoop - dSync);
413         }
414         double thetaRight = 0;
415         if (dRightMandatoryAbs <= dSync || dRightMandatory * dRightVoluntary >= 0)
416         {
417             // low mandatory desire, or same sign
418             thetaRight = 1;
419         }
420         else if (dSync < dRightMandatoryAbs && dRightMandatoryAbs < dCoop && dRightMandatory * dRightVoluntary < 0)
421         {
422             // linear from 1 at dSync to 0 at dCoop
423             thetaRight = (dCoop - dRightMandatoryAbs) / (dCoop - dSync);
424         }
425         return new Desire(dLeftMandatory + thetaLeft * dLeftVoluntary, dRightMandatory + thetaRight * dRightVoluntary);
426 
427     }
428 
429     /**
430      * Determine whether a lane change is acceptable (gap, lane markings, etc.).
431      * @param perception LanePerception; perception
432      * @param params Parameters; parameters
433      * @param sli SpeedLimitInfo; speed limit info
434      * @param cfm CarFollowingModel; car-following model
435      * @param desire double; level of lane change desire
436      * @param ownSpeed Speed; own speed
437      * @param ownAcceleration Acceleration; current car-following acceleration
438      * @param lat LateralDirectionality; lateral direction for synchronization
439      * @param gapAcceptance GapAcceptance; gap-acceptance model
440      * @return whether a gap is acceptable
441      * @throws ParameterException if a parameter is not defined
442      * @throws OperationalPlanException perception exception
443      */
444     static boolean acceptLaneChange(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
445             final CarFollowingModel cfm, final double desire, final Speed ownSpeed, final Acceleration ownAcceleration,
446             final LateralDirectionality lat, final GapAcceptance gapAcceptance)
447             throws ParameterException, OperationalPlanException
448     {
449 
450         // beyond start distance
451         boolean beyond = Try.assign(() -> perception.getGtu().laneChangeAllowed(), "Cannot obtain GTU.");
452         if (!beyond)
453         {
454             return false;
455         }
456 
457         // legal?
458         InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
459         if (infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat).si <= 0.0)
460         {
461             return false;
462         }
463 
464         // other causes for deceleration
465         IntersectionPerception intersection = perception.getPerceptionCategoryOrNull(IntersectionPerception.class);
466         if (intersection != null)
467         {
468             // conflicts alongside?
469             if ((lat.isLeft() && intersection.isAlongsideConflictLeft())
470                     || (lat.isRight() && intersection.isAlongsideConflictRight()))
471             {
472                 return false;
473             }
474             if (quickIntersectionScan(params, sli, cfm, ownSpeed, lat, intersection).lt(params.getParameter(BCRIT).neg()))
475             {
476                 return false;
477             }
478         }
479 
480         // safe regarding neighbors?
481         return gapAcceptance.acceptGap(perception, params, sli, cfm, desire, ownSpeed, ownAcceleration, lat);
482     }
483 
484     /**
485      * Returns a quickly determined acceleration to consider on an adjacent lane, following from conflicts and traffic lights.
486      * @param params Parameters; parameters
487      * @param sli SpeedLimitInfo; speed limit info
488      * @param cfm CarFollowingModel; car-following model
489      * @param ownSpeed Speed; own speed
490      * @param lat LateralDirectionality; lateral direction for synchronization
491      * @param intersection IntersectionPerception; intersection perception
492      * @return a quickly determined acceleration to consider on an adjacent lane, following from conflicts and traffic lights
493      * @throws ParameterException if a parameter is not defined
494      */
495     private static Acceleration quickIntersectionScan(final Parameters params, final SpeedLimitInfo sli,
496             final CarFollowingModel cfm, final Speed ownSpeed, final LateralDirectionality lat,
497             final IntersectionPerception intersection) throws ParameterException
498     {
499         Acceleration a = Acceleration.POSITIVE_INFINITY;
500         if (intersection != null)
501         {
502             RelativeLane lane = lat.isRight() ? RelativeLane.RIGHT : RelativeLane.LEFT;
503             Iterable<HeadwayConflict> iterable = intersection.getConflicts(lane);
504             if (iterable != null)
505             {
506                 Iterator<HeadwayConflict> conflicts = iterable.iterator();
507                 if (conflicts.hasNext())
508                 {
509                     a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
510                             conflicts.next().getDistance(), Speed.ZERO));
511                 }
512                 Iterator<HeadwayTrafficLight> trafficLights = intersection.getTrafficLights(lane).iterator();
513                 if (trafficLights.hasNext())
514                 {
515                     HeadwayTrafficLight trafficLight = trafficLights.next();
516                     if (trafficLight.getTrafficLightColor().isRedOrYellow())
517                     {
518                         a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
519                                 trafficLight.getDistance(), Speed.ZERO));
520                     }
521                 }
522             }
523         }
524         return a;
525     }
526 
527     /**
528      * Sets value for T depending on level of lane change desire.
529      * @param params Parameters; parameters
530      * @param desire double; lane change desire
531      * @throws ParameterException if T, TMIN or TMAX is not in the parameters
532      */
533     static void setDesiredHeadway(final Parameters params, final double desire) throws ParameterException
534     {
535         double limitedDesire = desire < 0 ? 0 : desire > 1 ? 1 : desire;
536         double tDes = limitedDesire * params.getParameter(TMIN).si + (1 - limitedDesire) * params.getParameter(TMAX).si;
537         double t = params.getParameter(T).si;
538         params.setParameterResettable(T, Duration.createSI(tDes < t ? tDes : t));
539     }
540 
541     /**
542      * Resets value for T depending on level of lane change desire.
543      * @param params Parameters; parameters
544      * @throws ParameterException if T is not in the parameters
545      */
546     static void resetDesiredHeadway(final Parameters params) throws ParameterException
547     {
548         params.resetParameter(T);
549     }
550 
551     /**
552      * Determine acceleration from car-following with desire-adjusted headway.
553      * @param distance Length; distance from follower to leader
554      * @param followerSpeed Speed; speed of follower
555      * @param leaderSpeed Speed; speed of leader
556      * @param desire double; level of lane change desire
557      * @param params Parameters; parameters
558      * @param sli SpeedLimitInfo; speed limit info
559      * @param cfm CarFollowingModel; car-following model
560      * @return acceleration from car-following
561      * @throws ParameterException if a parameter is not defined
562      */
563     public static Acceleration singleAcceleration(final Length distance, final Speed followerSpeed, final Speed leaderSpeed,
564             final double desire, final Parameters params, final SpeedLimitInfo sli, final CarFollowingModel cfm)
565             throws ParameterException
566     {
567         // set T
568         setDesiredHeadway(params, desire);
569         // calculate acceleration
570         Acceleration a = CarFollowingUtil.followSingleLeader(cfm, params, followerSpeed, sli, distance, leaderSpeed);
571         // reset T
572         resetDesiredHeadway(params);
573         return a;
574     }
575 
576 }