View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical.util.lmrs;
2   
3   import static org.opentrafficsim.core.gtu.behavioralcharacteristics.AbstractParameterType.Check.UNITINTERVAL;
4   
5   import java.util.HashSet;
6   import java.util.LinkedHashSet;
7   import java.util.Set;
8   import java.util.SortedMap;
9   import java.util.SortedSet;
10  import java.util.TreeMap;
11  
12  import org.djunits.unit.AccelerationUnit;
13  import org.djunits.unit.TimeUnit;
14  import org.djunits.value.vdouble.scalar.Acceleration;
15  import org.djunits.value.vdouble.scalar.Duration;
16  import org.djunits.value.vdouble.scalar.Length;
17  import org.djunits.value.vdouble.scalar.Speed;
18  import org.djunits.value.vdouble.scalar.Time;
19  import org.opentrafficsim.core.gtu.GTUException;
20  import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
21  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
22  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
23  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypeDouble;
24  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
25  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
26  import org.opentrafficsim.core.network.LateralDirectionality;
27  import org.opentrafficsim.core.network.NetworkException;
28  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
29  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
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.NeighborsPerception;
33  import org.opentrafficsim.road.gtu.lane.perception.headway.AbstractHeadwayGTU;
34  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneOperationalPlanBuilder.LaneChange;
35  import org.opentrafficsim.road.gtu.lane.plan.operational.SimpleOperationalPlan;
36  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
37  import org.opentrafficsim.road.gtu.lane.tactical.util.CarFollowingUtil;
38  import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
39  import org.opentrafficsim.road.network.speed.SpeedLimitProspect;
40  
41  import nl.tudelft.simulation.language.Throw;
42  
43  /**
44   * <p>
45   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
46   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
47   * <p>
48   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 26, 2016 <br>
49   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
50   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
51   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
52   */
53  public final class LmrsUtil
54  {
55  
56      /** Free lane change desire threshold. */
57      public static final ParameterTypeDouble DFREE =
58              new ParameterTypeDouble("dFree", "Free lane change desire threshold.", 0.365, UNITINTERVAL)
59              {
60                  /** */
61                  private static final long serialVersionUID = 20160413L;
62  
63                  public void check(final double value, final BehavioralCharacteristics bc) throws ParameterException
64                  {
65                      if (bc.contains(DSYNC))
66                      {
67                          Throw.when(value >= bc.getParameter(DSYNC), ParameterException.class,
68                                  "Value of dFree is above or equal to dSync.");
69                      }
70                      if (bc.contains(DCOOP))
71                      {
72                          Throw.when(value >= bc.getParameter(DCOOP), ParameterException.class,
73                                  "Value of dFree is above or equal to dCoop.");
74                      }
75                  }
76              };
77  
78      /** Synchronized lane change desire threshold. */
79      public static final ParameterTypeDouble DSYNC =
80              new ParameterTypeDouble("dSync", "Synchronized lane change desire threshold.", 0.577, UNITINTERVAL)
81              {
82                  /** */
83                  private static final long serialVersionUID = 20160413L;
84  
85                  public void check(final double value, final BehavioralCharacteristics bc) throws ParameterException
86                  {
87                      if (bc.contains(DFREE))
88                      {
89                          Throw.when(value <= bc.getParameter(DFREE), ParameterException.class,
90                                  "Value of dSync is below or equal to dFree.");
91                      }
92                      if (bc.contains(DCOOP))
93                      {
94                          Throw.when(value >= bc.getParameter(DCOOP), ParameterException.class,
95                                  "Value of dSync is above or equal to dCoop.");
96                      }
97                  }
98              };
99  
100     /** Cooperative lane change desire threshold. */
101     public static final ParameterTypeDouble DCOOP =
102             new ParameterTypeDouble("dCoop", "Cooperative lane change desire threshold.", 0.788, UNITINTERVAL)
103             {
104                 /** */
105                 private static final long serialVersionUID = 20160413L;
106 
107                 public void check(final double value, final BehavioralCharacteristics bc) throws ParameterException
108                 {
109                     if (bc.contains(DFREE))
110                     {
111                         Throw.when(value <= bc.getParameter(DFREE), ParameterException.class,
112                                 "Value of dCoop is below or equal to dFree.");
113                     }
114                     if (bc.contains(DSYNC))
115                     {
116                         Throw.when(value <= bc.getParameter(DSYNC), ParameterException.class,
117                                 "Value of dCoop is below or equal to dSync.");
118                     }
119                 }
120             };
121 
122     /** Current left lane change desire. */
123     public static final ParameterTypeDouble DLEFT = new ParameterTypeDouble("dLeft", "Left lane change desire.", 0.0);
124 
125     /** Current right lane change desire. */
126     public static final ParameterTypeDouble DRIGHT = new ParameterTypeDouble("dRight", "Right lane change desire.", 0.0);
127 
128     /** Lane change desire of current lane change. */
129     public static final ParameterTypeDouble DLC = new ParameterTypeDouble("dLaneChange", "Desire of current lane change.", 0.0);
130 
131     /**
132      * Do not instantiate.
133      */
134     private LmrsUtil()
135     {
136         //
137     }
138 
139     /**
140      * Determines a simple representation of an operational plan.
141      * @param gtu gtu
142      * @param startTime start time
143      * @param carFollowingModel car-following model
144      * @param laneChange lane change status
145      * @param lmrsData LMRS data
146      * @param perception perception
147      * @param mandatoryIncentives set of mandatory lane change incentives
148      * @param voluntaryIncentives set of voluntary lane change incentives
149      * @return simple operational plan
150      * @throws GTUException gtu exception
151      * @throws NetworkException network exception
152      * @throws ParameterException parameter exception
153      * @throws OperationalPlanException operational plan exception
154      */
155     @SuppressWarnings("checkstyle:parameternumber")
156     public static SimpleOperationalPlan determinePlan(final LaneBasedGTU gtu, final Time startTime,
157             final CarFollowingModel carFollowingModel, final LaneChange laneChange, final LmrsData lmrsData,
158             final LanePerception perception, final LinkedHashSet<MandatoryIncentive> mandatoryIncentives,
159             final LinkedHashSet<VoluntaryIncentive> voluntaryIncentives)
160             throws GTUException, NetworkException, ParameterException, OperationalPlanException
161     {
162 
163         // TODO this is a hack to prevent right lane changes of all vehicles on the left lane when placed in network at t=0
164         if (startTime.si == 0.0)
165         {
166             return new SimpleOperationalPlan(Acceleration.ZERO, LateralDirectionality.NONE);
167         }
168 
169         // obtain objects to get info
170         SpeedLimitProspect slp =
171                 perception.getPerceptionCategory(InfrastructurePerception.class).getSpeedLimitProspect(RelativeLane.CURRENT);
172         SpeedLimitInfo sli = slp.getSpeedLimitInfo(Length.ZERO);
173         BehavioralCharacteristics bc = gtu.getBehavioralCharacteristics();
174 
175         // regular car-following
176         Speed speed = gtu.getSpeed();
177         SortedSet<AbstractHeadwayGTU> leaders =
178                 perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(RelativeLane.CURRENT);
179         if (!leaders.isEmpty() && lmrsData.isNewLeader(leaders.first()))
180         {
181             initHeadwayRelaxation(bc, leaders.first());
182         }
183         Acceleration a = CarFollowingUtil.followLeaders(carFollowingModel, bc, speed, sli, leaders);
184 
185         // during a lane change, both leaders are followed
186         LateralDirectionality initiatedLaneChange;
187         if (laneChange.isChangingLane())
188         {
189             RelativeLane secondLane = laneChange.getSecondLane(gtu);
190             initiatedLaneChange = LateralDirectionality.NONE;
191             SortedSet<AbstractHeadwayGTU> secondLeaders =
192                     perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(secondLane);
193             Acceleration aSecond = CarFollowingUtil.followLeaders(carFollowingModel, bc, speed, sli, secondLeaders);
194             if (!secondLeaders.isEmpty() && lmrsData.isNewLeader(secondLeaders.first()))
195             {
196                 initHeadwayRelaxation(bc, secondLeaders.first());
197             }
198             a = Acceleration.min(a, aSecond);
199         }
200         else
201         {
202 
203             // determine lane change desire based on incentives
204             Desire desire = getLaneChangeDesire(bc, perception, carFollowingModel, mandatoryIncentives, voluntaryIncentives);
205 
206             // gap acceptance
207             boolean acceptLeft = perception.getLaneStructure().getRootLSR().getLeft() != null
208                     && acceptGap(perception, bc, sli, carFollowingModel, desire.getLeft(), speed, LateralDirectionality.LEFT);
209             boolean acceptRight = perception.getLaneStructure().getRootLSR().getRight() != null
210                     && acceptGap(perception, bc, sli, carFollowingModel, desire.getRight(), speed, LateralDirectionality.RIGHT);
211 
212             // lane change decision
213             double dFree = bc.getParameter(DFREE);
214             double dSync = bc.getParameter(DSYNC);
215             double dCoop = bc.getParameter(DCOOP);
216             // decide
217             TurnIndicatorStatus turnIndicatorStatus;
218             if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dFree && acceptLeft)
219             {
220                 // change left
221                 initiatedLaneChange = LateralDirectionality.LEFT;
222                 turnIndicatorStatus = TurnIndicatorStatus.LEFT;
223                 bc.setParameter(DLC, desire.getLeft());
224                 setDesiredHeadway(bc, desire.getLeft());
225                 leaders = perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(RelativeLane.LEFT);
226                 if (!leaders.isEmpty())
227                 {
228                     // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next step
229                     lmrsData.isNewLeader(leaders.first());
230                 }
231             }
232             else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dFree && acceptRight)
233             {
234                 // change right
235                 initiatedLaneChange = LateralDirectionality.RIGHT;
236                 turnIndicatorStatus = TurnIndicatorStatus.RIGHT;
237                 bc.setParameter(DLC, desire.getRight());
238                 setDesiredHeadway(bc, desire.getRight());
239                 leaders = perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(RelativeLane.RIGHT);
240                 if (!leaders.isEmpty())
241                 {
242                     // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next step
243                     lmrsData.isNewLeader(leaders.first());
244                 }
245             }
246             else
247             {
248                 initiatedLaneChange = LateralDirectionality.NONE;
249                 turnIndicatorStatus = TurnIndicatorStatus.NONE;
250             }
251             laneChange.setLaneChangeDuration(gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LCDUR));
252 
253             // take action if we cannot change lane
254             Acceleration aSync;
255             if (initiatedLaneChange.equals(LateralDirectionality.NONE))
256             {
257                 // synchronize
258                 if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dSync)
259                 {
260                     aSync = synchronize(perception, bc, sli, carFollowingModel, desire.getLeft(), speed,
261                             LateralDirectionality.LEFT);
262                     a = Acceleration.min(a, aSync);
263                 }
264                 else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dSync)
265                 {
266                     aSync = synchronize(perception, bc, sli, carFollowingModel, desire.getRight(), speed,
267                             LateralDirectionality.RIGHT);
268                     a = Acceleration.min(a, aSync);
269                 }
270                 // use indicators to indicate lane change need
271                 if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dCoop)
272                 {
273                     // switch on left indicator
274                     turnIndicatorStatus = TurnIndicatorStatus.LEFT;
275                 }
276                 else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dCoop)
277                 {
278                     // switch on right indicator
279                     turnIndicatorStatus = TurnIndicatorStatus.RIGHT;
280                 }
281                 bc.setParameter(DLEFT, desire.getLeft());
282                 bc.setParameter(DRIGHT, desire.getRight());
283             }
284             else
285             {
286                 bc.setParameter(DLEFT, 0.0);
287                 bc.setParameter(DRIGHT, 0.0);
288             }
289             gtu.setTurnIndicatorStatus(turnIndicatorStatus);
290 
291             // cooperate
292             aSync = cooperate(perception, bc, sli, carFollowingModel, speed, LateralDirectionality.LEFT);
293             a = Acceleration.min(a, aSync);
294             aSync = cooperate(perception, bc, sli, carFollowingModel, speed, LateralDirectionality.RIGHT);
295             a = Acceleration.min(a, aSync);
296 
297             // relaxation
298             exponentialHeadwayRelaxation(bc);
299 
300         }
301         lmrsData.finalizeStep();
302 
303         return new SimpleOperationalPlan(a, initiatedLaneChange);
304 
305     }
306 
307     /**
308      * Sets the headway as a response to a new leader.
309      * @param bc behavioral characteristics
310      * @param leader leader
311      * @throws ParameterException if DLC is not present
312      */
313     private static void initHeadwayRelaxation(final BehavioralCharacteristics bc, final AbstractHeadwayGTU leader)
314             throws ParameterException
315     {
316         if (leader.getBehavioralCharacteristics().contains(DLC))
317         {
318             setDesiredHeadway(bc, leader.getBehavioralCharacteristics().getParameter(DLC));
319         }
320         // else could not be perceived
321     }
322 
323     /**
324      * Updates the desired headway following an exponential shape approximated with fixed time step <tt>DT</tt>.
325      * @param bc behavioral characteristics
326      * @throws ParameterException in case of a parameter exception
327      */
328     private static void exponentialHeadwayRelaxation(final BehavioralCharacteristics bc) throws ParameterException
329     {
330         double ratio = bc.getParameter(ParameterTypes.DT).si / bc.getParameter(ParameterTypes.TAU).si;
331         bc.setParameter(ParameterTypes.T, Duration.interpolate(bc.getParameter(ParameterTypes.T),
332                 bc.getParameter(ParameterTypes.TMAX), ratio <= 1.0 ? ratio : 1.0));
333     }
334 
335     /**
336      * Determines lane change desire for the given RSU. Mandatory desire is deduced as the maximum of a set of mandatory
337      * incentives, while voluntary desires are added. Depending on the level of mandatory lane change desire, voluntary desire
338      * may be included partially. If both are positive or negative, voluntary desire is fully included. Otherwise, voluntary
339      * desire is less considered within the range dSync &lt; |mandatory| &lt; dCoop. The absolute value is used as large
340      * negative mandatory desire may also dominate voluntary desire.
341      * @param behavioralCharacteristics behavioral characteristics
342      * @param perception perception
343      * @param carFollowingModel car-following model
344      * @param mandatoryIncentives mandatory incentives
345      * @param voluntaryIncentives voluntary incentives
346      * @return lane change desire for gtu
347      * @throws ParameterException if a parameter is not defined
348      * @throws GTUException if there is no mandatory incentive, the model requires at least one
349      * @throws OperationalPlanException perception exception
350      */
351     private static Desire getLaneChangeDesire(final BehavioralCharacteristics behavioralCharacteristics,
352             final LanePerception perception, final CarFollowingModel carFollowingModel,
353             final LinkedHashSet<MandatoryIncentive> mandatoryIncentives,
354             final LinkedHashSet<VoluntaryIncentive> voluntaryIncentives)
355             throws ParameterException, GTUException, OperationalPlanException
356     {
357 
358         double dSync = behavioralCharacteristics.getParameter(DSYNC);
359         double dCoop = behavioralCharacteristics.getParameter(DCOOP);
360 
361         // Mandatory desire
362         double dLeftMandatory = Double.NEGATIVE_INFINITY;
363         double dRightMandatory = Double.NEGATIVE_INFINITY;
364         Desire mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
365         for (MandatoryIncentive incentive : mandatoryIncentives)
366         {
367             Desire d = incentive.determineDesire(behavioralCharacteristics, perception, carFollowingModel, mandatoryDesire);
368             dLeftMandatory = d.getLeft() > dLeftMandatory ? d.getLeft() : dLeftMandatory;
369             dRightMandatory = d.getRight() > dRightMandatory ? d.getRight() : dRightMandatory;
370             mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
371         }
372 
373         // Voluntary desire
374         double dLeftVoluntary = 0;
375         double dRightVoluntary = 0;
376         Desire voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
377         for (VoluntaryIncentive incentive : voluntaryIncentives)
378         {
379             Desire d = incentive.determineDesire(behavioralCharacteristics, perception, carFollowingModel, mandatoryDesire,
380                     voluntaryDesire);
381             dLeftVoluntary += d.getLeft();
382             dRightVoluntary += d.getRight();
383             voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
384         }
385 
386         // Total desire
387         double thetaLeft = 0;
388         if (dLeftMandatory <= dSync || dLeftMandatory * dLeftVoluntary >= 0)
389         {
390             // low mandatory desire, or same sign
391             thetaLeft = 1;
392         }
393         else if (dSync < dLeftMandatory && dLeftMandatory < dCoop && dLeftMandatory * dLeftVoluntary < 0)
394         {
395             // linear from 1 at dSync to 0 at dCoop
396             thetaLeft = (dCoop - Math.abs(dLeftMandatory)) / (dCoop - dSync);
397         }
398         double thetaRight = 0;
399         if (dRightMandatory <= dSync || dRightMandatory * dRightVoluntary >= 0)
400         {
401             // low mandatory desire, or same sign
402             thetaRight = 1;
403         }
404         else if (dSync < dRightMandatory && dRightMandatory < dCoop && dRightMandatory * dRightVoluntary < 0)
405         {
406             // linear from 1 at dSync to 0 at dCoop
407             thetaRight = (dCoop - Math.abs(dRightMandatory)) / (dCoop - dSync);
408         }
409 
410         return new Desire(dLeftMandatory + thetaLeft * dLeftVoluntary, dRightMandatory + thetaRight * dRightVoluntary);
411 
412     }
413 
414     /**
415      * Determine whether a gap is acceptable.
416      * @param perception perception
417      * @param bc behavioral characteristics
418      * @param sli speed limit info
419      * @param cfm car-following model
420      * @param desire level of lane change desire
421      * @param ownSpeed own speed
422      * @param lat lateral direction for synchronization
423      * @return whether a gap is acceptable
424      * @throws ParameterException if a parameter is not defined
425      * @throws OperationalPlanException perception exception
426      */
427     private static boolean acceptGap(final LanePerception perception, final BehavioralCharacteristics bc,
428             final SpeedLimitInfo sli, final CarFollowingModel cfm, final double desire, final Speed ownSpeed,
429             final LateralDirectionality lat) throws ParameterException, OperationalPlanException
430     {
431         Acceleration b = bc.getParameter(ParameterTypes.B);
432         if (perception.getPerceptionCategory(InfrastructurePerception.class).getLegalLaneChangePossibility(RelativeLane.CURRENT,
433                 lat).si > 0 && !perception.getPerceptionCategory(NeighborsPerception.class).isGtuAlongside(lat))
434         {
435             Acceleration aFollow = new Acceleration(Double.POSITIVE_INFINITY, AccelerationUnit.SI);
436             for (AbstractHeadwayGTU follower : perception.getPerceptionCategory(NeighborsPerception.class)
437                     .getFirstFollowers(lat))
438             {
439                 Acceleration a = singleAcceleration(follower.getDistance(), follower.getSpeed(), ownSpeed, desire,
440                         follower.getBehavioralCharacteristics(), follower.getSpeedLimitInfo(), follower.getCarFollowingModel());
441                 aFollow = Acceleration.min(aFollow, a);
442             }
443             Acceleration aSelf = new Acceleration(Double.POSITIVE_INFINITY, AccelerationUnit.SI);
444             for (AbstractHeadwayGTU leader : perception.getPerceptionCategory(NeighborsPerception.class).getFirstLeaders(lat))
445             {
446                 Acceleration a = singleAcceleration(leader.getDistance(), ownSpeed, leader.getSpeed(), desire, bc, sli, cfm);
447                 aSelf = Acceleration.min(aSelf, a);
448             }
449             Acceleration threshold = b.multiplyBy(-desire);
450             return aFollow.ge(threshold) && aSelf.ge(threshold);
451         }
452         return false;
453     }
454 
455     /**
456      * Sets value for T depending on level of lane change desire.
457      * @param bc behavioral characteristics
458      * @param desire lane change desire
459      * @throws ParameterException if T, TMIN or TMAX is not in the behavioral characteristics
460      */
461     private static void setDesiredHeadway(final BehavioralCharacteristics bc, final double desire) throws ParameterException
462     {
463         double limitedDesire = desire < 0 ? 0 : desire > 1 ? 1 : desire;
464         double tDes = limitedDesire * bc.getParameter(ParameterTypes.TMIN).si
465                 + (1 - limitedDesire) * bc.getParameter(ParameterTypes.TMAX).si;
466         double t = bc.getParameter(ParameterTypes.T).si;
467         bc.setParameter(ParameterTypes.T, new Duration(tDes < t ? tDes : t, TimeUnit.SI));
468     }
469 
470     /**
471      * Resets value for T depending on level of lane change desire.
472      * @param bc behavioral characteristics
473      * @throws ParameterException if T is not in the behavioral characteristics
474      */
475     private static void resetDesiredHeadway(final BehavioralCharacteristics bc) throws ParameterException
476     {
477         bc.resetParameter(ParameterTypes.T);
478     }
479 
480     /**
481      * Determine acceleration for synchronization.
482      * @param perception perception
483      * @param bc behavioral characteristics
484      * @param sli speed limit info
485      * @param cfm car-following model
486      * @param desire level of lane change desire
487      * @param ownSpeed own speed
488      * @param lat lateral direction for synchronization
489      * @return acceleration for synchronization
490      * @throws ParameterException if a parameter is not defined
491      * @throws OperationalPlanException perception exception
492      */
493     private static Acceleration synchronize(final LanePerception perception, final BehavioralCharacteristics bc,
494             final SpeedLimitInfo sli, final CarFollowingModel cfm, final double desire, final Speed ownSpeed,
495             final LateralDirectionality lat) throws ParameterException, OperationalPlanException
496     {
497         if ((lat.isLeft() && !perception.getLaneStructure().getCrossSection().contains(RelativeLane.LEFT))
498                 || (lat.isRight() && !perception.getLaneStructure().getCrossSection().contains(RelativeLane.RIGHT)))
499         {
500             return new Acceleration(Double.POSITIVE_INFINITY, AccelerationUnit.SI);
501         }
502         Acceleration b = bc.getParameter(ParameterTypes.B);
503         Acceleration a = new Acceleration(Double.POSITIVE_INFINITY, AccelerationUnit.SI);
504         SortedSet<AbstractHeadwayGTU> set =
505                 perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(new RelativeLane(lat, 1));
506         if (!set.isEmpty())
507         {
508             Acceleration aSingle =
509                     singleAcceleration(set.first().getDistance(), ownSpeed, set.first().getSpeed(), desire, bc, sli, cfm);
510             a = Acceleration.min(a, aSingle);
511         }
512         return Acceleration.max(a, b.neg());
513     }
514 
515     /**
516      * Determine acceleration for cooperation.
517      * @param perception perception
518      * @param bc behavioral characteristics
519      * @param sli speed limit info
520      * @param cfm car-following model
521      * @param ownSpeed own speed
522      * @param lat lateral direction for cooperation
523      * @return acceleration for synchronization
524      * @throws ParameterException if a parameter is not defined
525      * @throws OperationalPlanException perception exception
526      */
527     private static Acceleration cooperate(final LanePerception perception, final BehavioralCharacteristics bc,
528             final SpeedLimitInfo sli, final CarFollowingModel cfm, final Speed ownSpeed, final LateralDirectionality lat)
529             throws ParameterException, OperationalPlanException
530     {
531         if ((lat.isLeft() && !perception.getLaneStructure().getCrossSection().contains(RelativeLane.LEFT))
532                 || (lat.isRight() && !perception.getLaneStructure().getCrossSection().contains(RelativeLane.RIGHT)))
533         {
534             return new Acceleration(Double.MAX_VALUE, AccelerationUnit.SI);
535         }
536         Acceleration b = bc.getParameter(ParameterTypes.B);
537         Acceleration a = new Acceleration(Double.POSITIVE_INFINITY, AccelerationUnit.SI);
538         double dCoop = bc.getParameter(DCOOP);
539         for (AbstractHeadwayGTU leader : perception.getPerceptionCategory(NeighborsPerception.class)
540                 .getLeaders(new RelativeLane(lat, 1)))
541         {
542             BehavioralCharacteristics bc2 = leader.getBehavioralCharacteristics();
543             double desire = lat.equals(LateralDirectionality.LEFT) && bc2.contains(DRIGHT) ? bc2.getParameter(DRIGHT)
544                     : lat.equals(LateralDirectionality.RIGHT) && bc2.contains(DLEFT) ? bc2.getParameter(DLEFT) : 0;
545             if (desire >= dCoop)
546             {
547                 Acceleration aSingle =
548                         singleAcceleration(leader.getDistance(), ownSpeed, leader.getSpeed(), desire, bc, sli, cfm);
549                 a = Acceleration.min(a, aSingle);
550             }
551         }
552 
553         return Acceleration.max(a, b.neg());
554     }
555 
556     /**
557      * Determine acceleration from car-following.
558      * @param distance distance from follower to leader
559      * @param followerSpeed speed of follower
560      * @param leaderSpeed speed of leader
561      * @param desire level of lane change desire
562      * @param bc behavioral characteristics
563      * @param sli speed limit info
564      * @param cfm car-following model
565      * @return acceleration from car-following
566      * @throws ParameterException if a parameter is not defined
567      */
568     private static Acceleration singleAcceleration(final Length distance, final Speed followerSpeed, final Speed leaderSpeed,
569             final double desire, final BehavioralCharacteristics bc, final SpeedLimitInfo sli, final CarFollowingModel cfm)
570             throws ParameterException
571     {
572         // set T
573         setDesiredHeadway(bc, desire);
574         // calculate acceleration
575         SortedMap<Length, Speed> leaders = new TreeMap<>();
576         leaders.put(distance, leaderSpeed);
577         Acceleration a = cfm.followingAcceleration(bc, followerSpeed, sli, leaders);
578         // reset T
579         resetDesiredHeadway(bc);
580         return a;
581     }
582 
583     /**
584      * Keeps data for LMRS for a specific GTU.
585      * <p>
586      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
587      * <br>
588      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
589      * <p>
590      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 8 nov. 2016 <br>
591      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
592      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
593      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
594      */
595     public static final class LmrsData
596     {
597 
598         /** Most recent leaders. */
599         private final Set<String> leaders = new HashSet<>();
600 
601         /** Current leaders. */
602         private final Set<String> tempLeaders = new HashSet<>();
603 
604         /**
605          * Checks if the given leader is a new leader.
606          * @param gtu gtu to check
607          * @return whether the gtu is a new leader
608          */
609         public boolean isNewLeader(final AbstractHeadwayGTU gtu)
610         {
611             this.tempLeaders.add(gtu.getId());
612             return !this.leaders.contains(gtu.getId());
613         }
614 
615         /**
616          * Remembers the leaders of the current time step (those forwarded to isNewLeader()) for the next time step.
617          */
618         public void finalizeStep()
619         {
620             this.leaders.clear();
621             this.leaders.addAll(this.tempLeaders);
622             this.tempLeaders.clear();
623         }
624 
625     }
626 
627 }