LmrsUtil.java

  1. package org.opentrafficsim.road.gtu.lane.tactical.util.lmrs;

  2. import java.util.Iterator;
  3. import java.util.Map;
  4. import java.util.SortedSet;

  5. import org.djunits.value.vdouble.scalar.Acceleration;
  6. import org.djunits.value.vdouble.scalar.Duration;
  7. import org.djunits.value.vdouble.scalar.Length;
  8. import org.djunits.value.vdouble.scalar.Speed;
  9. import org.djunits.value.vdouble.scalar.Time;
  10. import org.djutils.exceptions.Try;
  11. import org.opentrafficsim.base.parameters.ParameterException;
  12. import org.opentrafficsim.base.parameters.ParameterTypeAcceleration;
  13. import org.opentrafficsim.base.parameters.ParameterTypeDuration;
  14. import org.opentrafficsim.base.parameters.ParameterTypes;
  15. import org.opentrafficsim.base.parameters.Parameters;
  16. import org.opentrafficsim.core.gtu.GTUException;
  17. import org.opentrafficsim.core.gtu.TurnIndicatorIntent;
  18. import org.opentrafficsim.core.gtu.perception.EgoPerception;
  19. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
  20. import org.opentrafficsim.core.network.LateralDirectionality;
  21. import org.opentrafficsim.core.network.NetworkException;
  22. import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
  23. import org.opentrafficsim.road.gtu.lane.perception.InfrastructureLaneChangeInfo;
  24. import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
  25. import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
  26. import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
  27. import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
  28. import org.opentrafficsim.road.gtu.lane.perception.categories.IntersectionPerception;
  29. import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
  30. import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayConflict;
  31. import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
  32. import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayTrafficLight;
  33. import org.opentrafficsim.road.gtu.lane.plan.operational.LaneChange;
  34. import org.opentrafficsim.road.gtu.lane.plan.operational.SimpleOperationalPlan;
  35. import org.opentrafficsim.road.gtu.lane.tactical.Synchronizable;
  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.gtu.lane.tactical.util.ConflictUtil;
  39. import org.opentrafficsim.road.gtu.lane.tactical.util.ConflictUtil.ConflictPlans;
  40. import org.opentrafficsim.road.network.lane.conflict.Conflict;
  41. import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
  42. import org.opentrafficsim.road.network.speed.SpeedLimitProspect;

  43. /**
  44.  * <p>
  45.  * Copyright (c) 2013-2022 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 implements LmrsParameters
  54. {

  55.     /** Fixed model time step. */
  56.     public static final ParameterTypeDuration DT = ParameterTypes.DT;

  57.     /** Minimum car-following headway. */
  58.     public static final ParameterTypeDuration TMIN = ParameterTypes.TMIN;

  59.     /** Current car-following headway. */
  60.     public static final ParameterTypeDuration T = ParameterTypes.T;

  61.     /** Maximum car-following headway. */
  62.     public static final ParameterTypeDuration TMAX = ParameterTypes.TMAX;

  63.     /** Headway relaxation time. */
  64.     public static final ParameterTypeDuration TAU = ParameterTypes.TAU;

  65.     /** Maximum critical deceleration, e.g. stop/go at traffic light. */
  66.     public static final ParameterTypeAcceleration BCRIT = ParameterTypes.BCRIT;

  67.     /**
  68.      * Do not instantiate.
  69.      */
  70.     private LmrsUtil()
  71.     {
  72.         //
  73.     }

  74.     /**
  75.      * Determines a simple representation of an operational plan.
  76.      * @param gtu LaneBasedGTU; gtu
  77.      * @param startTime Time; start time
  78.      * @param carFollowingModel CarFollowingModel; car-following model
  79.      * @param laneChange LaneChange; lane change status
  80.      * @param lmrsData LmrsData; LMRS data
  81.      * @param perception LanePerception; perception
  82.      * @param mandatoryIncentives Iterable&lt;MandatoryIncentive&gt;; set of mandatory lane change incentives
  83.      * @param voluntaryIncentives Iterable&lt;VoluntaryIncentive&gt;; set of voluntary lane change incentives
  84.      * @return simple operational plan
  85.      * @throws GTUException gtu exception
  86.      * @throws NetworkException network exception
  87.      * @throws ParameterException parameter exception
  88.      * @throws OperationalPlanException operational plan exception
  89.      */
  90.     @SuppressWarnings({ "checkstyle:parameternumber", "checkstyle:methodlength" })
  91.     public static SimpleOperationalPlan determinePlan(final LaneBasedGTU gtu, final Time startTime,
  92.             final CarFollowingModel carFollowingModel, final LaneChange laneChange, final LmrsData lmrsData,
  93.             final LanePerception perception, final Iterable<MandatoryIncentive> mandatoryIncentives,
  94.             final Iterable<VoluntaryIncentive> voluntaryIncentives)
  95.             throws GTUException, NetworkException, ParameterException, OperationalPlanException
  96.     {
  97.         // obtain objects to get info
  98.         InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
  99.         SpeedLimitProspect slp = infra.getSpeedLimitProspect(RelativeLane.CURRENT);
  100.         SpeedLimitInfo sli = slp.getSpeedLimitInfo(Length.ZERO);
  101.         Parameters params = gtu.getParameters();
  102.         EgoPerception<?, ?> ego = perception.getPerceptionCategory(EgoPerception.class);
  103.         Speed speed = ego.getSpeed();
  104.         NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
  105.         PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders = neighbors.getLeaders(RelativeLane.CURRENT);

  106.         // regular car-following
  107.         Acceleration a;
  108.         if (lmrsData.isHumanLongitudinalControl())
  109.         {
  110.             lmrsData.getTailgating().tailgate(perception, params);
  111.             if (!leaders.isEmpty() && lmrsData.isNewLeader(leaders.first()))
  112.             {
  113.                 initHeadwayRelaxation(params, leaders.first());
  114.             }
  115.             a = gtu.getCarFollowingAcceleration();
  116.         }
  117.         else
  118.         {
  119.             a = Acceleration.POS_MAXVALUE;
  120.         }

  121.         // during a lane change, both leaders are followed
  122.         LateralDirectionality initiatedLaneChange;
  123.         TurnIndicatorIntent turnIndicatorStatus = TurnIndicatorIntent.NONE;
  124.         if (laneChange.isChangingLane())
  125.         {
  126.             initiatedLaneChange = LateralDirectionality.NONE;
  127.             if (lmrsData.isHumanLongitudinalControl())
  128.             {
  129.                 RelativeLane secondLane = laneChange.getSecondLane(gtu);
  130.                 PerceptionCollectable<HeadwayGTU, LaneBasedGTU> secondLeaders = neighbors.getLeaders(secondLane);
  131.                 Acceleration aSecond = carFollowingModel.followingAcceleration(params, speed, sli, secondLeaders);
  132.                 if (!secondLeaders.isEmpty() && lmrsData.isNewLeader(secondLeaders.first()))
  133.                 {
  134.                     initHeadwayRelaxation(params, secondLeaders.first());
  135.                 }
  136.                 a = Acceleration.min(a, aSecond);
  137.             }
  138.         }
  139.         else
  140.         {

  141.             // determine lane change desire based on incentives
  142.             Desire desire = getLaneChangeDesire(params, perception, carFollowingModel, mandatoryIncentives, voluntaryIncentives,
  143.                     lmrsData.getDesireMap());

  144.             // lane change decision
  145.             double dFree = params.getParameter(DFREE);
  146.             initiatedLaneChange = LateralDirectionality.NONE;
  147.             turnIndicatorStatus = TurnIndicatorIntent.NONE;
  148.             if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dFree)
  149.             {
  150.                 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getLeft(), speed, a,
  151.                         LateralDirectionality.LEFT, lmrsData.getGapAcceptance(), laneChange))
  152.                 {
  153.                     // change left
  154.                     initiatedLaneChange = LateralDirectionality.LEFT;
  155.                     turnIndicatorStatus = TurnIndicatorIntent.LEFT;
  156.                     params.setParameter(DLC, desire.getLeft());
  157.                     setDesiredHeadway(params, desire.getLeft());
  158.                     leaders = neighbors.getLeaders(RelativeLane.LEFT);
  159.                     if (!leaders.isEmpty())
  160.                     {
  161.                         // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next
  162.                         // step
  163.                         lmrsData.isNewLeader(leaders.first());
  164.                     }
  165.                     a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
  166.                             neighbors.getLeaders(RelativeLane.LEFT)));
  167.                 }
  168.             }
  169.             else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dFree)
  170.             {
  171.                 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getRight(), speed, a,
  172.                         LateralDirectionality.RIGHT, lmrsData.getGapAcceptance(), laneChange))
  173.                 {
  174.                     // change right
  175.                     initiatedLaneChange = LateralDirectionality.RIGHT;
  176.                     turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
  177.                     params.setParameter(DLC, desire.getRight());
  178.                     setDesiredHeadway(params, desire.getRight());
  179.                     leaders = neighbors.getLeaders(RelativeLane.RIGHT);
  180.                     if (!leaders.isEmpty())
  181.                     {
  182.                         // don't respond on its lane change desire, but remember it such that it isn't a new leader in the next
  183.                         // step
  184.                         lmrsData.isNewLeader(leaders.first());
  185.                     }
  186.                     a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
  187.                             neighbors.getLeaders(RelativeLane.RIGHT)));
  188.                 }
  189.             }
  190.             if (!initiatedLaneChange.isNone())
  191.             {
  192.                 SortedSet<InfrastructureLaneChangeInfo> set = infra.getInfrastructureLaneChangeInfo(RelativeLane.CURRENT);
  193.                 if (!set.isEmpty())
  194.                 {
  195.                     Length boundary = null;
  196.                     for (InfrastructureLaneChangeInfo info : set)
  197.                     {
  198.                         int n = info.getRequiredNumberOfLaneChanges();
  199.                         if (n > 1)
  200.                         {
  201.                             Length thisBoundary = info.getRemainingDistance()
  202.                                     .minus(Synchronization.requiredBufferSpace(speed, info.getRequiredNumberOfLaneChanges(),
  203.                                             params.getParameter(ParameterTypes.LOOKAHEAD),
  204.                                             params.getParameter(ParameterTypes.T0), params.getParameter(ParameterTypes.LCDUR),
  205.                                             params.getParameter(DCOOP)));
  206.                             if (thisBoundary.le0())
  207.                             {
  208.                                 thisBoundary = info.getRemainingDistance().divide(info.getRequiredNumberOfLaneChanges());
  209.                             }
  210.                             boundary = boundary == null || thisBoundary.si < boundary.si ? thisBoundary : boundary;
  211.                         }
  212.                     }
  213.                     laneChange.setBoundary(boundary);
  214.                 }
  215.                 params.setParameter(DLEFT, 0.0);
  216.                 params.setParameter(DRIGHT, 0.0);
  217.             }
  218.             else
  219.             {
  220.                 params.setParameter(DLEFT, desire.getLeft());
  221.                 params.setParameter(DRIGHT, desire.getRight());
  222.             }

  223.             // take action if we cannot change lane
  224.             Acceleration aSync;

  225.             // synchronize
  226.             double dSync = params.getParameter(DSYNC);
  227.             if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dSync)
  228.             {
  229.                 Synchronizable.State state;
  230.                 if (desire.getLeft() >= params.getParameter(DCOOP))
  231.                 {
  232.                     // switch on left indicator
  233.                     turnIndicatorStatus = TurnIndicatorIntent.LEFT;
  234.                     state = Synchronizable.State.INDICATING;
  235.                 }
  236.                 else
  237.                 {
  238.                     state = Synchronizable.State.SYNCHRONIZING;
  239.                 }
  240.                 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getLeft(),
  241.                         LateralDirectionality.LEFT, lmrsData, laneChange, initiatedLaneChange);
  242.                 a = applyAcceleration(a, aSync, lmrsData, state);
  243.             }
  244.             else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dSync)
  245.             {
  246.                 Synchronizable.State state;
  247.                 if (desire.getRight() >= params.getParameter(DCOOP))
  248.                 {
  249.                     // switch on right indicator
  250.                     turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
  251.                     state = Synchronizable.State.INDICATING;
  252.                 }
  253.                 else
  254.                 {
  255.                     state = Synchronizable.State.SYNCHRONIZING;
  256.                 }
  257.                 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getRight(),
  258.                         LateralDirectionality.RIGHT, lmrsData, laneChange, initiatedLaneChange);
  259.                 a = applyAcceleration(a, aSync, lmrsData, state);
  260.             }
  261.             else
  262.             {
  263.                 lmrsData.setSynchronizationState(Synchronizable.State.NONE);
  264.             }

  265.             // cooperate
  266.             aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.LEFT,
  267.                     desire);
  268.             a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);
  269.             aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.RIGHT,
  270.                     desire);
  271.             a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);

  272.             // relaxation
  273.             exponentialHeadwayRelaxation(params);

  274.         }
  275.         lmrsData.finalizeStep();

  276.         SimpleOperationalPlan simplePlan = new SimpleOperationalPlan(a, params.getParameter(DT), initiatedLaneChange);
  277.         if (turnIndicatorStatus.isLeft())
  278.         {
  279.             simplePlan.setIndicatorIntentLeft();
  280.         }
  281.         else if (turnIndicatorStatus.isRight())
  282.         {
  283.             simplePlan.setIndicatorIntentRight();
  284.         }
  285.         return simplePlan;

  286.     }

  287.     /**
  288.      * Minimizes the acceleration and sets the synchronization state if applicable.
  289.      * @param a Acceleration; previous acceleration
  290.      * @param aNew Acceleration; new acceleration
  291.      * @param lmrsData LmrsData; lmrs data
  292.      * @param state Synchronizable.State; synchronization state
  293.      * @return Acceleration; minimized acceleration
  294.      */
  295.     private static Acceleration applyAcceleration(final Acceleration a, final Acceleration aNew, final LmrsData lmrsData,
  296.             final Synchronizable.State state)
  297.     {
  298.         if (a.si < aNew.si)
  299.         {
  300.             return a;
  301.         }
  302.         lmrsData.setSynchronizationState(state);
  303.         return aNew;
  304.     }

  305.     /**
  306.      * Sets the headway as a response to a new leader.
  307.      * @param params Parameters; parameters
  308.      * @param leader HeadwayGTU; leader
  309.      * @throws ParameterException if DLC is not present
  310.      */
  311.     private static void initHeadwayRelaxation(final Parameters params, final HeadwayGTU leader) throws ParameterException
  312.     {
  313.         Double dlc = leader.getParameters().getParameterOrNull(DLC);
  314.         if (dlc != null)
  315.         {
  316.             setDesiredHeadway(params, dlc);
  317.         }
  318.         // else could not be perceived
  319.     }

  320.     /**
  321.      * Updates the desired headway following an exponential shape approximated with fixed time step <code>DT</code>.
  322.      * @param params Parameters; parameters
  323.      * @throws ParameterException in case of a parameter exception
  324.      */
  325.     private static void exponentialHeadwayRelaxation(final Parameters params) throws ParameterException
  326.     {
  327.         double ratio = params.getParameter(DT).si / params.getParameter(TAU).si;
  328.         params.setParameter(T,
  329.                 Duration.interpolate(params.getParameter(T), params.getParameter(TMAX), ratio <= 1.0 ? ratio : 1.0));
  330.     }

  331.     /**
  332.      * Determines lane change desire for the given GtU. Mandatory desire is deduced as the maximum of a set of mandatory
  333.      * incentives, while voluntary desires are added. Depending on the level of mandatory lane change desire, voluntary desire
  334.      * may be included partially. If both are positive or negative, voluntary desire is fully included. Otherwise, voluntary
  335.      * desire is less considered within the range dSync &lt; |mandatory| &lt; dCoop. The absolute value is used as large
  336.      * negative mandatory desire may also dominate voluntary desire.
  337.      * @param parameters Parameters; parameters
  338.      * @param perception LanePerception; perception
  339.      * @param carFollowingModel CarFollowingModel; car-following model
  340.      * @param mandatoryIncentives Iterable&lt;MandatoryIncentive&gt;; mandatory incentives
  341.      * @param voluntaryIncentives Iterable&lt;VoluntaryIncentive&gt;; voluntary incentives
  342.      * @param desireMap Map&lt;Class&lt;? extends Incentive&gt;,Desire&gt;; map where calculated desires are stored in
  343.      * @return lane change desire for gtu
  344.      * @throws ParameterException if a parameter is not defined
  345.      * @throws GTUException if there is no mandatory incentive, the model requires at least one
  346.      * @throws OperationalPlanException perception exception
  347.      */
  348.     public static Desire getLaneChangeDesire(final Parameters parameters, final LanePerception perception,
  349.             final CarFollowingModel carFollowingModel, final Iterable<MandatoryIncentive> mandatoryIncentives,
  350.             final Iterable<VoluntaryIncentive> voluntaryIncentives, final Map<Class<? extends Incentive>, Desire> desireMap)
  351.             throws ParameterException, GTUException, OperationalPlanException
  352.     {

  353.         double dSync = parameters.getParameter(DSYNC);
  354.         double dCoop = parameters.getParameter(DCOOP);

  355.         // Mandatory desire
  356.         double dLeftMandatory = 0.0;
  357.         double dRightMandatory = 0.0;
  358.         Desire mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
  359.         for (MandatoryIncentive incentive : mandatoryIncentives)
  360.         {
  361.             Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire);
  362.             desireMap.put(incentive.getClass(), d);
  363.             dLeftMandatory = Math.abs(d.getLeft()) > Math.abs(dLeftMandatory) ? d.getLeft() : dLeftMandatory;
  364.             dRightMandatory = Math.abs(d.getRight()) > Math.abs(dRightMandatory) ? d.getRight() : dRightMandatory;
  365.             mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
  366.         }

  367.         // Voluntary desire
  368.         double dLeftVoluntary = 0;
  369.         double dRightVoluntary = 0;
  370.         Desire voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
  371.         for (VoluntaryIncentive incentive : voluntaryIncentives)
  372.         {
  373.             Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire, voluntaryDesire);
  374.             desireMap.put(incentive.getClass(), d);
  375.             dLeftVoluntary += d.getLeft();
  376.             dRightVoluntary += d.getRight();
  377.             voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
  378.         }

  379.         // Total desire
  380.         double thetaLeft = 0;
  381.         double dLeftMandatoryAbs = Math.abs(dLeftMandatory);
  382.         double dRightMandatoryAbs = Math.abs(dRightMandatory);
  383.         if (dLeftMandatoryAbs <= dSync || dLeftMandatory * dLeftVoluntary >= 0)
  384.         {
  385.             // low mandatory desire, or same sign
  386.             thetaLeft = 1;
  387.         }
  388.         else if (dSync < dLeftMandatoryAbs && dLeftMandatoryAbs < dCoop && dLeftMandatory * dLeftVoluntary < 0)
  389.         {
  390.             // linear from 1 at dSync to 0 at dCoop
  391.             thetaLeft = (dCoop - dLeftMandatoryAbs) / (dCoop - dSync);
  392.         }
  393.         double thetaRight = 0;
  394.         if (dRightMandatoryAbs <= dSync || dRightMandatory * dRightVoluntary >= 0)
  395.         {
  396.             // low mandatory desire, or same sign
  397.             thetaRight = 1;
  398.         }
  399.         else if (dSync < dRightMandatoryAbs && dRightMandatoryAbs < dCoop && dRightMandatory * dRightVoluntary < 0)
  400.         {
  401.             // linear from 1 at dSync to 0 at dCoop
  402.             thetaRight = (dCoop - dRightMandatoryAbs) / (dCoop - dSync);
  403.         }
  404.         return new Desire(dLeftMandatory + thetaLeft * dLeftVoluntary, dRightMandatory + thetaRight * dRightVoluntary);

  405.     }

  406.     /**
  407.      * Determine whether a lane change is acceptable (gap, lane markings, etc.).
  408.      * @param perception LanePerception; perception
  409.      * @param params Parameters; parameters
  410.      * @param sli SpeedLimitInfo; speed limit info
  411.      * @param cfm CarFollowingModel; car-following model
  412.      * @param desire double; level of lane change desire
  413.      * @param ownSpeed Speed; own speed
  414.      * @param ownAcceleration Acceleration; current car-following acceleration
  415.      * @param lat LateralDirectionality; lateral direction for synchronization
  416.      * @param gapAcceptance GapAcceptance; gap-acceptance model
  417.      * @param laneChange LaneChange; lane change
  418.      * @return whether a gap is acceptable
  419.      * @throws ParameterException if a parameter is not defined
  420.      * @throws OperationalPlanException perception exception
  421.      */
  422.     static boolean acceptLaneChange(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
  423.             final CarFollowingModel cfm, final double desire, final Speed ownSpeed, final Acceleration ownAcceleration,
  424.             final LateralDirectionality lat, final GapAcceptance gapAcceptance, final LaneChange laneChange)
  425.             throws ParameterException, OperationalPlanException
  426.     {
  427.         // beyond start distance
  428.         LaneBasedGTU gtu = Try.assign(() -> perception.getGtu(), "Cannot obtain GTU.");
  429.         if (!gtu.laneChangeAllowed())
  430.         {
  431.             return false;
  432.         }

  433.         // legal?
  434.         InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
  435.         if (infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat).si <= 0.0)
  436.         {
  437.             return false;
  438.         }

  439.         // neighbors and lane change distance (not gap-acceptance)
  440.         NeighborsPerception neighbors = perception.getPerceptionCategoryOrNull(NeighborsPerception.class);
  441.         PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
  442.         if (!leaders.isEmpty())
  443.         {
  444.             boolean ok = laneChange.checkRoom(gtu, leaders.first());
  445.             if (!ok)
  446.             {
  447.                 return false;
  448.             }
  449.         }
  450.         RelativeLane lane = new RelativeLane(lat, 1);
  451.         leaders = neighbors.getLeaders(lane);
  452.         if (!leaders.isEmpty())
  453.         {
  454.             boolean ok = laneChange.checkRoom(gtu, leaders.first());
  455.             if (!ok)
  456.             {
  457.                 return false;
  458.             }
  459.         }

  460.         // other causes for deceleration
  461.         IntersectionPerception intersection = perception.getPerceptionCategoryOrNull(IntersectionPerception.class);
  462.         if (intersection != null)
  463.         {
  464.             // // conflicts alongside?
  465.             // if ((lat.isLeft() && intersection.isAlongsideConflictLeft())
  466.             // || (lat.isRight() && intersection.isAlongsideConflictRight()))
  467.             // {
  468.             // return false;
  469.             // }
  470.             // if (quickIntersectionScan(params, sli, cfm, ownSpeed, lat, intersection).lt(params.getParameter(BCRIT).neg()))
  471.             // {
  472.             // return false;
  473.             // }

  474.             // conflicts
  475.             EgoPerception<?, ?> ego = perception.getPerceptionCategoryOrNull(EgoPerception.class);
  476.             PerceptionCollectable<HeadwayConflict, Conflict> conflicts = intersection.getConflicts(lane);
  477.             try
  478.             {
  479.                 Acceleration a = ConflictUtil.approachConflicts(params, conflicts, leaders, cfm, ego.getLength(),
  480.                         ego.getWidth(), ownSpeed, ownAcceleration, sli, new ConflictPlans(), perception.getGtu(), lane);
  481.                 if (a.lt(params.getParameter(ParameterTypes.BCRIT).neg()))
  482.                 {
  483.                     return false;
  484.                 }
  485.                 // gap-acceptance on merge conflicts
  486.                 // TODO: this approach is a hack
  487.                 for (HeadwayConflict conflict : conflicts)
  488.                 {
  489.                     if (conflict.isMerge() && conflict.getDistance().si < 10.0)
  490.                     {
  491.                         PerceptionCollectable<HeadwayGTU, LaneBasedGTU> down = conflict.getDownstreamConflictingGTUs();
  492.                         if (!down.isEmpty() && down.first().isParallel())
  493.                         {
  494.                             return false; // GTU on conflict
  495.                         }
  496.                         PerceptionCollectable<HeadwayGTU, LaneBasedGTU> up = conflict.getUpstreamConflictingGTUs();
  497.                         if (!up.isEmpty() && up.first().isParallel())
  498.                         {
  499.                             return false; // GTU on conflict
  500.                         }
  501.                     }
  502.                 }
  503.             }
  504.             catch (GTUException exception)
  505.             {
  506.                 throw new OperationalPlanException(exception);
  507.             }
  508.             conflicts = intersection.getConflicts(RelativeLane.CURRENT);
  509.             for (HeadwayConflict conflict : conflicts)
  510.             {
  511.                 if (conflict.getLane().getParentLink().equals(conflict.getConflictingLink()))
  512.                 {
  513.                     if (conflict.isMerge() && conflict.getDistance().le0()
  514.                             && conflict.getDistance().neg().gt(conflict.getLength()))
  515.                     {
  516.                         return false; // partially past the merge; adjacent lane might be ambiguous
  517.                     }
  518.                     else if (conflict.isSplit() && conflict.getDistance().le0()
  519.                             && conflict.getDistance().neg().lt(gtu.getLength()))
  520.                     {
  521.                         return false; // partially before the split; adjacent lane might be ambiguous
  522.                     }
  523.                 }
  524.             }

  525.             // traffic lights
  526.             Iterable<HeadwayTrafficLight> trafficLights = intersection.getTrafficLights(lane);
  527.             for (HeadwayTrafficLight trafficLight : trafficLights)
  528.             {
  529.                 if (trafficLight.getTrafficLightColor().isRedOrYellow())
  530.                 {
  531.                     boolean ok = laneChange.checkRoom(gtu, trafficLight);
  532.                     if (!ok)
  533.                     {
  534.                         return false;
  535.                     }
  536.                 }
  537.             }
  538.         }

  539.         // safe regarding neighbors?
  540.         return gapAcceptance.acceptGap(perception, params, sli, cfm, desire, ownSpeed, ownAcceleration, lat);

  541.     }

  542.     /**
  543.      * Returns a quickly determined acceleration to consider on an adjacent lane, following from conflicts and traffic lights.
  544.      * @param params Parameters; parameters
  545.      * @param sli SpeedLimitInfo; speed limit info
  546.      * @param cfm CarFollowingModel; car-following model
  547.      * @param ownSpeed Speed; own speed
  548.      * @param lat LateralDirectionality; lateral direction for synchronization
  549.      * @param intersection IntersectionPerception; intersection perception
  550.      * @return a quickly determined acceleration to consider on an adjacent lane, following from conflicts and traffic lights
  551.      * @throws ParameterException if a parameter is not defined
  552.      */
  553.     private static Acceleration quickIntersectionScan(final Parameters params, final SpeedLimitInfo sli,
  554.             final CarFollowingModel cfm, final Speed ownSpeed, final LateralDirectionality lat,
  555.             final IntersectionPerception intersection) throws ParameterException
  556.     {
  557.         Acceleration a = Acceleration.POSITIVE_INFINITY;
  558.         if (intersection != null)
  559.         {
  560.             RelativeLane lane = lat.isRight() ? RelativeLane.RIGHT : RelativeLane.LEFT;
  561.             Iterable<HeadwayConflict> iterable = intersection.getConflicts(lane);
  562.             if (iterable != null)
  563.             {
  564.                 Iterator<HeadwayConflict> conflicts = iterable.iterator();
  565.                 if (conflicts.hasNext())
  566.                 {
  567.                     a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
  568.                             conflicts.next().getDistance(), Speed.ZERO));
  569.                 }
  570.                 Iterator<HeadwayTrafficLight> trafficLights = intersection.getTrafficLights(lane).iterator();
  571.                 if (trafficLights.hasNext())
  572.                 {
  573.                     HeadwayTrafficLight trafficLight = trafficLights.next();
  574.                     if (trafficLight.getTrafficLightColor().isRedOrYellow())
  575.                     {
  576.                         a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
  577.                                 trafficLight.getDistance(), Speed.ZERO));
  578.                     }
  579.                 }
  580.             }
  581.         }
  582.         return a;
  583.     }

  584.     /**
  585.      * Sets value for T depending on level of lane change desire.
  586.      * @param params Parameters; parameters
  587.      * @param desire double; lane change desire
  588.      * @throws ParameterException if T, TMIN or TMAX is not in the parameters
  589.      */
  590.     static void setDesiredHeadway(final Parameters params, final double desire) throws ParameterException
  591.     {
  592.         double limitedDesire = desire < 0 ? 0 : desire > 1 ? 1 : desire;
  593.         double tDes = limitedDesire * params.getParameter(TMIN).si + (1 - limitedDesire) * params.getParameter(TMAX).si;
  594.         double t = params.getParameter(T).si;
  595.         params.setParameterResettable(T, Duration.instantiateSI(tDes < t ? tDes : t));
  596.     }

  597.     /**
  598.      * Resets value for T depending on level of lane change desire.
  599.      * @param params Parameters; parameters
  600.      * @throws ParameterException if T is not in the parameters
  601.      */
  602.     static void resetDesiredHeadway(final Parameters params) throws ParameterException
  603.     {
  604.         params.resetParameter(T);
  605.     }

  606.     /**
  607.      * Determine acceleration from car-following with desire-adjusted headway.
  608.      * @param distance Length; distance from follower to leader
  609.      * @param followerSpeed Speed; speed of follower
  610.      * @param leaderSpeed Speed; speed of leader
  611.      * @param desire double; level of lane change desire
  612.      * @param params Parameters; parameters
  613.      * @param sli SpeedLimitInfo; speed limit info
  614.      * @param cfm CarFollowingModel; car-following model
  615.      * @return acceleration from car-following
  616.      * @throws ParameterException if a parameter is not defined
  617.      */
  618.     public static Acceleration singleAcceleration(final Length distance, final Speed followerSpeed, final Speed leaderSpeed,
  619.             final double desire, final Parameters params, final SpeedLimitInfo sli, final CarFollowingModel cfm)
  620.             throws ParameterException
  621.     {
  622.         // set T
  623.         setDesiredHeadway(params, desire);
  624.         // calculate acceleration
  625.         Acceleration a = CarFollowingUtil.followSingleLeader(cfm, params, followerSpeed, sli, distance, leaderSpeed);
  626.         // reset T
  627.         resetDesiredHeadway(params);
  628.         return a;
  629.     }

  630. }