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