Synchronization.java

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

  2. import java.util.SortedSet;

  3. import org.djunits.unit.AccelerationUnit;
  4. import org.djunits.value.vdouble.scalar.Acceleration;
  5. import org.djunits.value.vdouble.scalar.Duration;
  6. import org.djunits.value.vdouble.scalar.Length;
  7. import org.djunits.value.vdouble.scalar.Speed;
  8. import org.djutils.exceptions.Try;
  9. import org.opentrafficsim.base.parameters.ParameterException;
  10. import org.opentrafficsim.base.parameters.ParameterTypes;
  11. import org.opentrafficsim.base.parameters.Parameters;
  12. import org.opentrafficsim.core.gtu.perception.EgoPerception;
  13. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
  14. import org.opentrafficsim.core.network.LateralDirectionality;
  15. import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
  16. import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
  17. import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
  18. import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
  19. import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
  20. import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
  21. import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
  22. import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtu;
  23. import org.opentrafficsim.road.gtu.lane.plan.operational.LaneChange;
  24. import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
  25. import org.opentrafficsim.road.gtu.lane.tactical.util.CarFollowingUtil;
  26. import org.opentrafficsim.road.network.LaneChangeInfo;
  27. import org.opentrafficsim.road.network.speed.SpeedLimitInfo;

  28. /**
  29.  * Different forms of synchronization.
  30.  * <p>
  31.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  32.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  33.  * </p>
  34.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  35.  * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  36.  * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  37.  */
  38. public interface Synchronization extends LmrsParameters
  39. {

  40.     /** Synchronization that only includes stopping for a dead-end. */
  41.     Synchronization DEADEND = new Synchronization()
  42.     {
  43.         /** {@inheritDoc} */
  44.         @Override
  45.         public Acceleration synchronize(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
  46.                 final CarFollowingModel cfm, final double desire, final LateralDirectionality lat, final LmrsData lmrsData,
  47.                 final LaneChange laneChange, final LateralDirectionality initiatedLaneChange)
  48.                 throws ParameterException, OperationalPlanException
  49.         {
  50.             Acceleration a = Acceleration.POSITIVE_INFINITY;
  51.             // stop for end
  52.             Length remainingDist = Length.POSITIVE_INFINITY;
  53.             int dn = laneChange.isChangingLane() ? -1 : 0;
  54.             Length lcLength = laneChange.getMinimumLaneChangeDistance();
  55.             for (LaneChangeInfo lcInfo : perception.getPerceptionCategory(InfrastructurePerception.class)
  56.                     .getPhysicalLaneChangeInfo(RelativeLane.CURRENT))
  57.             {
  58.                 // TODO replace this hack with something that properly accounts for overshoot this method also
  59.                 // introduces very strong deceleration at low speeds, as the time step makes bMin go from 3.4 (ignored,
  60.                 // so maybe 1.25 acceleration applied) to >10
  61.                 remainingDist = Length.min(remainingDist,
  62.                         lcInfo.remainingDistance().minus(lcLength.times(lcInfo.numberOfLaneChanges() + dn)));
  63.             }
  64.             Speed speed = perception.getPerceptionCategory(EgoPerception.class).getSpeed();
  65.             Acceleration bCrit = params.getParameter(ParameterTypes.BCRIT);
  66.             remainingDist = remainingDist.minus(params.getParameter(ParameterTypes.S0));
  67.             if (remainingDist.le0())
  68.             {
  69.                 if (speed.gt0())
  70.                 {
  71.                     a = Acceleration.min(a, bCrit.neg());
  72.                 }
  73.                 else
  74.                 {
  75.                     a = Acceleration.ONE; // prevent dead-lock
  76.                 }
  77.             }
  78.             else
  79.             {
  80.                 Acceleration bMin = new Acceleration(.5 * speed.si * speed.si / remainingDist.si, AccelerationUnit.SI);
  81.                 if (bMin.ge(bCrit))
  82.                 {
  83.                     a = Acceleration.min(a, bMin.neg());
  84.                 }
  85.             }
  86.             return a;
  87.         }

  88.         /** {@inheritDoc} */
  89.         @Override
  90.         public String toString()
  91.         {
  92.             return "DEADEND";
  93.         }
  94.     };

  95.     /** Synchronization where current leaders are taken. */
  96.     Synchronization PASSIVE = new Synchronization()
  97.     {
  98.         /** {@inheritDoc} */
  99.         @Override
  100.         public Acceleration synchronize(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
  101.                 final CarFollowingModel cfm, final double desire, final LateralDirectionality lat, final LmrsData lmrsData,
  102.                 final LaneChange laneChange, final LateralDirectionality initiatedLaneChange)
  103.                 throws ParameterException, OperationalPlanException
  104.         {
  105.             Acceleration a =
  106.                     DEADEND.synchronize(perception, params, sli, cfm, desire, lat, lmrsData, laneChange, initiatedLaneChange);
  107.             double dCoop = params.getParameter(DCOOP);
  108.             RelativeLane relativeLane = new RelativeLane(lat, 1);

  109.             PerceptionCollectable<HeadwayGtu, LaneBasedGtu> set =
  110.                     perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(relativeLane);
  111.             HeadwayGtu leader = null;
  112.             if (set != null)
  113.             {
  114.                 if (desire >= dCoop && !set.isEmpty())
  115.                 {
  116.                     leader = set.first();
  117.                 }
  118.                 else
  119.                 {
  120.                     for (HeadwayGtu gtu : set)
  121.                     {
  122.                         if (gtu.getSpeed().gt0())
  123.                         {
  124.                             leader = gtu;
  125.                             break;
  126.                         }
  127.                     }
  128.                 }
  129.             }
  130.             Speed ownSpeed = perception.getPerceptionCategory(EgoPerception.class).getSpeed();
  131.             if (leader != null)
  132.             {
  133.                 Length headway = headwayWithLcSpace(leader, params, laneChange);
  134.                 Acceleration aSingle =
  135.                         LmrsUtil.singleAcceleration(headway, ownSpeed, leader.getSpeed(), desire, params, sli, cfm);
  136.                 a = Acceleration.min(a, aSingle);
  137.                 a = gentleUrgency(a, desire, params);
  138.             }
  139.             // keep some space ahead to perform lane change
  140.             PerceptionCollectable<HeadwayGtu, LaneBasedGtu> leaders =
  141.                     perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(RelativeLane.CURRENT);
  142.             if (!leaders.isEmpty() && leaders.first().getSpeed().lt(params.getParameter(ParameterTypes.VCONG)))
  143.             {
  144.                 Length headway = leaders.first().getDistance().minus(laneChange.getMinimumLaneChangeDistance());
  145.                 Acceleration aSingle =
  146.                         LmrsUtil.singleAcceleration(headway, ownSpeed, leaders.first().getSpeed(), desire, params, sli, cfm);
  147.                 aSingle = gentleUrgency(aSingle, desire, params);
  148.                 a = Acceleration.min(a, aSingle);
  149.             }

  150.             // check merge distance
  151.             Length xMerge =
  152.                     getMergeDistance(perception, lat).minus(perception.getPerceptionCategory(EgoPerception.class).getLength());
  153.             if (xMerge.gt0())
  154.             {
  155.                 Acceleration aMerge = LmrsUtil.singleAcceleration(xMerge, ownSpeed, Speed.ZERO, desire, params, sli, cfm);
  156.                 a = Acceleration.max(a, aMerge);
  157.             }
  158.             return a;
  159.         }

  160.         /** {@inheritDoc} */
  161.         @Override
  162.         public String toString()
  163.         {
  164.             return "PASSIVE";
  165.         }
  166.     };

  167.     /**
  168.      * Synchronization by following the adjacent leader or aligning with the middle of the gap, whichever allows the largest
  169.      * acceleration. Note that aligning with the middle of the gap then means the gap is too small, as following would cause
  170.      * lower acceleration. Aligning with the middle of the gap will however provide a better starting point for the rest of the
  171.      * process. Mainly, the adjacent follower can decelerate less, allowing more smooth merging.
  172.      */
  173.     Synchronization ALIGN_GAP = new Synchronization()
  174.     {
  175.         /** {@inheritDoc} */
  176.         @Override
  177.         public Acceleration synchronize(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
  178.                 final CarFollowingModel cfm, final double desire, final LateralDirectionality lat, final LmrsData lmrsData,
  179.                 final LaneChange laneChange, final LateralDirectionality initiatedLaneChange)
  180.                 throws ParameterException, OperationalPlanException
  181.         {
  182.             Acceleration a = Acceleration.POSITIVE_INFINITY;
  183.             EgoPerception<?, ?> ego = perception.getPerceptionCategory(EgoPerception.class);
  184.             Speed ownSpeed = ego.getSpeed();
  185.             RelativeLane relativeLane = new RelativeLane(lat, 1);
  186.             PerceptionCollectable<HeadwayGtu, LaneBasedGtu> leaders =
  187.                     perception.getPerceptionCategory(NeighborsPerception.class).getLeaders(relativeLane);
  188.             if (!leaders.isEmpty())
  189.             {
  190.                 HeadwayGtu leader = leaders.first();
  191.                 Length gap = leader.getDistance();
  192.                 LmrsUtil.setDesiredHeadway(params, desire);
  193.                 PerceptionCollectable<HeadwayGtu, LaneBasedGtu> followers =
  194.                         perception.getPerceptionCategory(NeighborsPerception.class).getFollowers(relativeLane);
  195.                 if (!followers.isEmpty())
  196.                 {
  197.                     HeadwayGtu follower = followers.first();
  198.                     Length netGap = leader.getDistance().plus(follower.getDistance()).times(0.5);
  199.                     gap = Length.max(gap, leader.getDistance().minus(netGap).plus(cfm.desiredHeadway(params, ownSpeed)));
  200.                 }
  201.                 a = CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli, gap, leader.getSpeed());
  202.                 LmrsUtil.resetDesiredHeadway(params);
  203.                 // limit deceleration based on desire
  204.                 a = gentleUrgency(a, desire, params);
  205.             }
  206.             a = Acceleration.min(a,
  207.                     DEADEND.synchronize(perception, params, sli, cfm, desire, lat, lmrsData, laneChange, initiatedLaneChange));
  208.             // never stop before we can actually merge
  209.             Length xMerge = getMergeDistance(perception, lat);
  210.             if (xMerge.gt0())
  211.             {
  212.                 Acceleration aMerge = LmrsUtil.singleAcceleration(xMerge, ownSpeed, Speed.ZERO, desire, params, sli, cfm);
  213.                 a = Acceleration.max(a, aMerge);
  214.             }
  215.             return a;
  216.         }

  217.         /** {@inheritDoc} */
  218.         @Override
  219.         public String toString()
  220.         {
  221.             return "ALIGN_GAP";
  222.         }
  223.     };

  224.     /** Synchronization where current leaders are taken. Synchronization is disabled for d_sync&lt;d&lt;d_coop at low speeds. */
  225.     Synchronization PASSIVE_MOVING = new Synchronization()
  226.     {
  227.         /** {@inheritDoc} */
  228.         @Override
  229.         public Acceleration synchronize(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
  230.                 final CarFollowingModel cfm, final double desire, final LateralDirectionality lat, final LmrsData lmrsData,
  231.                 final LaneChange laneChange, final LateralDirectionality initiatedLaneChange)
  232.                 throws ParameterException, OperationalPlanException
  233.         {
  234.             double dCoop = params.getParameter(DCOOP);
  235.             Speed ownSpeed = perception.getPerceptionCategory(EgoPerception.class).getSpeed();
  236.             if (desire < dCoop && ownSpeed.si < params.getParameter(ParameterTypes.LOOKAHEAD).si
  237.                     / params.getParameter(ParameterTypes.T0).si)
  238.             {
  239.                 return DEADEND.synchronize(perception, params, sli, cfm, desire, lat, lmrsData, laneChange,
  240.                         initiatedLaneChange);
  241.             }
  242.             return PASSIVE.synchronize(perception, params, sli, cfm, desire, lat, lmrsData, laneChange, initiatedLaneChange);
  243.         }

  244.         /** {@inheritDoc} */
  245.         @Override
  246.         public String toString()
  247.         {
  248.             return "PASSIVE_MOVING";
  249.         }
  250.     };

  251.     /** Synchronization where a suitable leader is actively targeted, in relation to infrastructure. */
  252.     Synchronization ACTIVE = new Synchronization()
  253.     {
  254.         /** {@inheritDoc} */
  255.         @Override
  256.         public Acceleration synchronize(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
  257.                 final CarFollowingModel cfm, final double desire, final LateralDirectionality lat, final LmrsData lmrsData,
  258.                 final LaneChange laneChange, final LateralDirectionality initiatedLaneChange)
  259.                 throws ParameterException, OperationalPlanException
  260.         {

  261.             Acceleration b = params.getParameter(ParameterTypes.B);
  262.             Duration tMin = params.getParameter(ParameterTypes.TMIN);
  263.             Duration tMax = params.getParameter(ParameterTypes.TMAX);
  264.             Speed vCong = params.getParameter(ParameterTypes.VCONG);
  265.             Length x0 = params.getParameter(ParameterTypes.LOOKAHEAD);
  266.             Duration t0 = params.getParameter(ParameterTypes.T0);
  267.             Duration lc = params.getParameter(ParameterTypes.LCDUR);
  268.             Speed tagSpeed = x0.divide(t0);
  269.             double dCoop = params.getParameter(DCOOP);
  270.             EgoPerception<?, ?> ego = perception.getPerceptionCategory(EgoPerception.class);
  271.             Speed ownSpeed = ego.getSpeed();
  272.             Length ownLength = ego.getLength();
  273.             Length dx = perception.getGtu().getFront().dx();

  274.             // get xMergeSync, the distance within which a gap is pointless as the lane change is not possible
  275.             InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
  276.             SortedSet<LaneChangeInfo> info = infra.getLegalLaneChangeInfo(RelativeLane.CURRENT);
  277.             // Length xMerge = infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat).minus(dx);
  278.             // xMerge = xMerge.lt0() ? xMerge.neg() : Length.ZERO; // zero or positive value where lane change is not possible
  279.             Length xMerge = getMergeDistance(perception, lat);
  280.             int nCur = 0;
  281.             Length xCur = Length.POSITIVE_INFINITY;
  282.             for (LaneChangeInfo lcInfo : info)
  283.             {
  284.                 int nCurTmp = lcInfo.numberOfLaneChanges();
  285.                 // subtract minimum lane change distance per lane change
  286.                 Length xCurTmp = lcInfo.remainingDistance().minus(ownLength.times(2.0 * nCurTmp)).minus(dx);
  287.                 if (xCurTmp.lt(xCur))
  288.                 {
  289.                     nCur = nCurTmp;
  290.                     xCur = xCurTmp;
  291.                 }
  292.             }

  293.             // for short ramps, include braking distance, i.e. we -do- select a gap somewhat upstream of the merge point;
  294.             // should we abandon this gap, we still have braking distance and minimum lane change distance left
  295.             Length xMergeSync = xCur.minus(Length.instantiateSI(.5 * ownSpeed.si * ownSpeed.si / b.si));
  296.             xMergeSync = Length.min(xMerge, xMergeSync);

  297.             // abandon the gap if the sync vehicle is no longer adjacent, in congestion within xMergeSync, or too far
  298.             NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
  299.             RelativeLane lane = new RelativeLane(lat, 1);
  300.             PerceptionCollectable<HeadwayGtu, LaneBasedGtu> leaders = neighbors.getLeaders(lane);
  301.             HeadwayGtu syncVehicle = lmrsData.getSyncVehicle(leaders);
  302.             if (syncVehicle != null && ((syncVehicle.getSpeed().lt(vCong) && syncVehicle.getDistance().lt(xMergeSync))
  303.                     || syncVehicle.getDistance().gt(xCur)))
  304.             {
  305.                 syncVehicle = null;
  306.             }

  307.             // if there is no sync vehicle, select the first one to which current deceleration < b (it may become larger later)
  308.             if (leaders != null && syncVehicle == null)
  309.             {
  310.                 Length maxDistance = Length.min(x0, xCur);
  311.                 for (HeadwayGtu leader : leaders)
  312.                 {
  313.                     if (leader.getDistance().lt(maxDistance))
  314.                     {
  315.                         if ((leader.getDistance().gt(xMergeSync) || leader.getSpeed().gt(vCong))
  316.                                 && tagAlongAcceleration(leader, ownSpeed, ownLength, tagSpeed, desire, params, sli, cfm)
  317.                                         .gt(b.neg()))
  318.                         {
  319.                             syncVehicle = leader;
  320.                             break;
  321.                         }
  322.                     }
  323.                     else
  324.                     {
  325.                         break;
  326.                     }
  327.                 }
  328.             }

  329.             // select upstream vehicle if we can safely follow that, or if we cannot stay ahead of it (infrastructure, in coop)
  330.             HeadwayGtu up;
  331.             PerceptionCollectable<HeadwayGtu, LaneBasedGtu> followers = neighbors.getFollowers(lane);
  332.             HeadwayGtu follower = followers == null || followers.isEmpty() ? null
  333.                     : followers.first().moved(
  334.                             followers.first().getDistance().plus(ownLength).plus(followers.first().getLength()).neg(),
  335.                             followers.first().getSpeed(), followers.first().getAcceleration());
  336.             boolean upOk;
  337.             if (syncVehicle == null)
  338.             {
  339.                 up = null;
  340.                 upOk = false;
  341.             }
  342.             else
  343.             {
  344.                 up = getFollower(syncVehicle, leaders, follower, ownLength);
  345.                 upOk = up == null ? false
  346.                         : tagAlongAcceleration(up, ownSpeed, ownLength, tagSpeed, desire, params, sli, cfm).gt(b.neg());
  347.             }
  348.             while (syncVehicle != null
  349.                     && up != null && (upOk || (!canBeAhead(up, xCur, nCur, ownSpeed, ownLength, tagSpeed, dCoop, b, tMin, tMax,
  350.                             x0, t0, lc, desire) && desire > dCoop))
  351.                     && (up.getDistance().gt(xMergeSync) || up.getSpeed().gt(vCong)))
  352.             {
  353.                 if (up.equals(follower))
  354.                 {
  355.                     // no suitable downstream vehicle to follow found
  356.                     syncVehicle = null;
  357.                     up = null;
  358.                     break;
  359.                 }
  360.                 syncVehicle = up;
  361.                 up = getFollower(syncVehicle, leaders, follower, ownLength);
  362.                 upOk = up == null ? false
  363.                         : tagAlongAcceleration(up, ownSpeed, ownLength, tagSpeed, desire, params, sli, cfm).gt(b.neg());
  364.             }
  365.             lmrsData.setSyncVehicle(syncVehicle);

  366.             // actual synchronization
  367.             Acceleration a =
  368.                     DEADEND.synchronize(perception, params, sli, cfm, desire, lat, lmrsData, laneChange, initiatedLaneChange);
  369.             if (syncVehicle != null)
  370.             {
  371.                 a = gentleUrgency(tagAlongAcceleration(syncVehicle, ownSpeed, ownLength, tagSpeed, desire, params, sli, cfm),
  372.                         desire, params);
  373.             }
  374.             else if (nCur > 0 && (follower != null || (leaders != null && !leaders.isEmpty())))
  375.             {
  376.                 // no gap to synchronize with, but there is a follower to account for
  377.                 if (follower != null && !canBeAhead(follower, xCur, nCur, ownSpeed, ownLength, tagSpeed, dCoop, b, tMin, tMax,
  378.                         x0, t0, lc, desire))
  379.                 {
  380.                     // get behind follower
  381.                     double c = requiredBufferSpace(ownSpeed, nCur, x0, t0, lc, dCoop).si;
  382.                     double t = (xCur.si - follower.getDistance().si - c) / follower.getSpeed().si;
  383.                     double xGap = ownSpeed.si * (tMin.si + desire * (tMax.si - tMin.si));
  384.                     Acceleration acc = Acceleration.instantiateSI(2 * (xCur.si - c - ownSpeed.si * t - xGap) / (t * t));
  385.                     if (follower.getSpeed().eq0() || acc.si < -ownSpeed.si / t || t < 0)
  386.                     {
  387.                         // inappropriate to get behind
  388.                         // note: if minimum lane change space is more than infrastructure, deceleration will simply be limited
  389.                         a = stopForEnd(xCur, xMerge, params, ownSpeed, cfm, sli);
  390.                     }
  391.                     else
  392.                     {
  393.                         a = gentleUrgency(acc, desire, params);
  394.                     }
  395.                 }
  396.                 else if (!LmrsUtil.acceptLaneChange(perception, params, sli, cfm, desire, ownSpeed, Acceleration.ZERO, lat,
  397.                         lmrsData.getGapAcceptance(), laneChange))
  398.                 {
  399.                     a = stopForEnd(xCur, xMerge, params, ownSpeed, cfm, sli);
  400.                     // but no stronger than getting behind the leader
  401.                     if (leaders != null && !leaders.isEmpty())
  402.                     {
  403.                         double c = requiredBufferSpace(ownSpeed, nCur, x0, t0, lc, dCoop).si;
  404.                         double t = (xCur.si - leaders.first().getDistance().si - c) / leaders.first().getSpeed().si;
  405.                         double xGap = ownSpeed.si * (tMin.si + desire * (tMax.si - tMin.si));
  406.                         Acceleration acc = Acceleration.instantiateSI(2 * (xCur.si - c - ownSpeed.si * t - xGap) / (t * t));
  407.                         if (!(leaders.first().getSpeed().eq0() || acc.si < -ownSpeed.si / t || t < 0))
  408.                         {
  409.                             a = Acceleration.max(a, acc);
  410.                         }
  411.                     }
  412.                 }
  413.             }

  414.             // slow down to have sufficient time for further lane changes
  415.             if (nCur > 1)
  416.             {
  417.                 if (xMerge.gt0())
  418.                 {
  419.                     // achieve speed to have sufficient time as soon as a lane change becomes possible (infrastructure)
  420.                     Speed vMerge = xCur.lt(xMerge) ? Speed.ZERO
  421.                             : xCur.minus(xMerge).divide(t0.times((1 - dCoop) * (nCur - 1)).plus(lc));
  422.                     vMerge = Speed.max(vMerge, x0.divide(t0));
  423.                     a = Acceleration.min(a, CarFollowingUtil.approachTargetSpeed(cfm, params, ownSpeed, sli, xMerge, vMerge));
  424.                 }
  425.                 else
  426.                 {
  427.                     // slow down by b if our speed is too high beyond the merge point
  428.                     Length c = requiredBufferSpace(ownSpeed, nCur, x0, t0, lc, dCoop);
  429.                     if (xCur.lt(c))
  430.                     {
  431.                         a = Acceleration.min(a, b.neg());
  432.                     }
  433.                 }
  434.             }
  435.             return a;
  436.         }

  437.         /** {@inheritDoc} */
  438.         @Override
  439.         public String toString()
  440.         {
  441.             return "ACTIVE";
  442.         }
  443.     };

  444.     /**
  445.      * Returns the distance to the next merge, stopping within this distance is futile for a lane change.
  446.      * @param perception LanePerception; perception
  447.      * @param lat LateralDirectionality; lateral direction
  448.      * @return Length; distance to the next merge
  449.      * @throws OperationalPlanException if there is no infrastructure perception
  450.      */
  451.     public static Length getMergeDistance(final LanePerception perception, final LateralDirectionality lat)
  452.             throws OperationalPlanException
  453.     {
  454.         InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
  455.         Length dx = Try.assign(() -> perception.getGtu().getFront().dx(), "Could not obtain GTU.");
  456.         Length xMergeRef = infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat);
  457.         if (xMergeRef.gt0() && xMergeRef.lt(dx))
  458.         {
  459.             return Length.ZERO;
  460.         }
  461.         Length xMerge = xMergeRef.minus(dx);
  462.         return xMerge.lt0() ? xMerge.neg() : Length.ZERO; // positive value where lane change is not possible
  463.     }

  464.     /**
  465.      * Determine acceleration for synchronization.
  466.      * @param perception LanePerception; perception
  467.      * @param params Parameters; parameters
  468.      * @param sli SpeedLimitInfo; speed limit info
  469.      * @param cfm CarFollowingModel; car-following model
  470.      * @param desire double; level of lane change desire
  471.      * @param lat LateralDirectionality; lateral direction for synchronization
  472.      * @param lmrsData LmrsData; LMRS data
  473.      * @param laneChange LaneChange; lane change
  474.      * @param initiatedLaneChange LateralDirectionality; lateral direction of initiated lane change
  475.      * @return acceleration for synchronization
  476.      * @throws ParameterException if a parameter is not defined
  477.      * @throws OperationalPlanException perception exception
  478.      */
  479.     Acceleration synchronize(LanePerception perception, Parameters params, SpeedLimitInfo sli, CarFollowingModel cfm,
  480.             double desire, LateralDirectionality lat, LmrsData lmrsData, LaneChange laneChange,
  481.             LateralDirectionality initiatedLaneChange) throws ParameterException, OperationalPlanException;

  482.     /**
  483.      * Returns a headway (length) to allow space to perform a lane change at low speeds.
  484.      * @param headway Headway; headway
  485.      * @param parameters Parameters; parameters
  486.      * @param laneChange LaneChange; lane change
  487.      * @return Length; distance to allow space to perform a lane change at low speeds
  488.      * @throws ParameterException if parameter VCONG is not available
  489.      */
  490.     static Length headwayWithLcSpace(final Headway headway, final Parameters parameters, final LaneChange laneChange)
  491.             throws ParameterException
  492.     {
  493.         if (headway.getSpeed().gt(parameters.getParameter(ParameterTypes.VCONG)))
  494.         {
  495.             return headway.getDistance();
  496.         }
  497.         return headway.getDistance().minus(laneChange.getMinimumLaneChangeDistance());
  498.     }

  499.     /**
  500.      * Return limited deceleration. Deceleration is limited to {@code b} for {@code d < dCoop}. Beyond {@code dCoop} the limit
  501.      * is a linear interpolation between {@code b} and {@code bCrit}.
  502.      * @param a Acceleration; acceleration to limit
  503.      * @param desire double; lane change desire
  504.      * @param params Parameters; parameters
  505.      * @return limited deceleration
  506.      * @throws ParameterException when parameter is no available or value out of range
  507.      */
  508.     static Acceleration gentleUrgency(final Acceleration a, final double desire, final Parameters params)
  509.             throws ParameterException
  510.     {
  511.         Acceleration b = params.getParameter(ParameterTypes.B);
  512.         if (a.si > -b.si)
  513.         {
  514.             return a;
  515.         }
  516.         double dCoop = params.getParameter(DCOOP);
  517.         if (desire < dCoop)
  518.         {
  519.             return b.neg();
  520.         }
  521.         Acceleration bCrit = params.getParameter(ParameterTypes.BCRIT);
  522.         double f = (desire - dCoop) / (1.0 - dCoop);
  523.         Acceleration lim = Acceleration.interpolate(b.neg(), bCrit.neg(), f);
  524.         return Acceleration.max(a, lim);
  525.     }

  526.     /**
  527.      * Returns the upstream gtu of the given gtu.
  528.      * @param gtu HeadwayGtu; gtu
  529.      * @param leaders PerceptionCollectable&lt;HeadwayGtu,LaneBasedGtu&gt;; leaders of own vehicle
  530.      * @param follower HeadwayGtu; following vehicle of own vehicle
  531.      * @param ownLength Length; own vehicle length
  532.      * @return upstream gtu of the given gtu
  533.      */
  534.     static HeadwayGtu getFollower(final HeadwayGtu gtu, final PerceptionCollectable<HeadwayGtu, LaneBasedGtu> leaders,
  535.             final HeadwayGtu follower, final Length ownLength)
  536.     {
  537.         HeadwayGtu last = null;
  538.         for (HeadwayGtu leader : leaders)
  539.         {
  540.             if (leader.equals(gtu))
  541.             {
  542.                 return last == null ? follower : last;
  543.             }
  544.             last = leader;
  545.         }
  546.         return null;
  547.     }

  548.     /**
  549.      * Calculates acceleration by following an adjacent vehicle, with tagging along if desire is not very high and speed is low.
  550.      * @param leader HeadwayGtu; leader
  551.      * @param followerSpeed Speed; follower speed
  552.      * @param followerLength Length; follower length
  553.      * @param tagSpeed Speed; maximum tag along speed
  554.      * @param desire double; lane change desire
  555.      * @param params Parameters; parameters
  556.      * @param sli SpeedLimitInfo; speed limit info
  557.      * @param cfm CarFollowingModel; car-following model
  558.      * @return acceleration by following an adjacent vehicle including tagging along
  559.      * @throws ParameterException if a parameter is not present
  560.      */
  561.     @SuppressWarnings("checkstyle:parameternumber")
  562.     static Acceleration tagAlongAcceleration(final HeadwayGtu leader, final Speed followerSpeed, final Length followerLength,
  563.             final Speed tagSpeed, final double desire, final Parameters params, final SpeedLimitInfo sli,
  564.             final CarFollowingModel cfm) throws ParameterException
  565.     {
  566.         double dCoop = params.getParameter(DCOOP);
  567.         double tagV = followerSpeed.lt(tagSpeed) ? 1.0 - followerSpeed.si / tagSpeed.si : 0.0;
  568.         double tagD = desire <= dCoop ? 1.0 : 1.0 - (desire - dCoop) / (1.0 - dCoop);
  569.         double tagExtent = tagV < tagD ? tagV : tagD;

  570.         /*-
  571.          * Maximum extent is half a vehicle length, being the minimum of the own vehicle or adjacent vehicle length. At
  572.          * standstill we get:
  573.          *
  574.          * car>car:    __       car>truck:       ______
  575.          *            __                        __
  576.          *                                                   driving direction -->
  577.          * truck>car:      __   truck>truck:       ______
  578.          *            ______                    ______
  579.          */
  580.         Length headwayAdjustment = params.getParameter(ParameterTypes.S0)
  581.                 .plus(Length.min(followerLength, leader.getLength()).times(0.5)).times(tagExtent);
  582.         Acceleration a = LmrsUtil.singleAcceleration(leader.getDistance().plus(headwayAdjustment), followerSpeed,
  583.                 leader.getSpeed(), desire, params, sli, cfm);
  584.         return a;
  585.     }

  586.     /**
  587.      * Returns whether a driver estimates it can be ahead of an adjacent vehicle for merging.
  588.      * @param adjacentVehicle HeadwayGtu; adjacent vehicle
  589.      * @param xCur Length; remaining distance
  590.      * @param nCur int; number of lane changes to perform
  591.      * @param ownSpeed Speed; own speed
  592.      * @param ownLength Length; own length
  593.      * @param tagSpeed Speed; maximum tag along speed
  594.      * @param dCoop double; cooperation threshold
  595.      * @param b Acceleration; critical deceleration
  596.      * @param tMin Duration; minimum headway
  597.      * @param tMax Duration; normal headway
  598.      * @param x0 Length; anticipation distance
  599.      * @param t0 Duration; anticipation time
  600.      * @param lc Duration; lane change duration
  601.      * @param desire double; lane change desire
  602.      * @return whether a driver estimates it can be ahead of an adjacent vehicle for merging
  603.      * @throws ParameterException if parameter is not defined
  604.      */
  605.     static boolean canBeAhead(final HeadwayGtu adjacentVehicle, final Length xCur, final int nCur, final Speed ownSpeed,
  606.             final Length ownLength, final Speed tagSpeed, final double dCoop, final Acceleration b, final Duration tMin,
  607.             final Duration tMax, final Length x0, final Duration t0, final Duration lc, final double desire)
  608.             throws ParameterException
  609.     {

  610.         // always true if adjacent vehicle is behind and i) both vehicles very slow, or ii) cooperation assumed and possible
  611.         boolean tmp = LmrsUtil
  612.                 .singleAcceleration(adjacentVehicle.getDistance().neg().minus(adjacentVehicle.getLength()).minus(ownLength),
  613.                         adjacentVehicle.getSpeed(), ownSpeed, desire, adjacentVehicle.getParameters(),
  614.                         adjacentVehicle.getSpeedLimitInfo(), adjacentVehicle.getCarFollowingModel())
  615.                 .gt(b.neg());
  616.         if (adjacentVehicle.getDistance().lt(ownLength.neg())
  617.                 && ((desire > dCoop && tmp) || (ownSpeed.lt(tagSpeed) && adjacentVehicle.getSpeed().lt(tagSpeed))))
  618.         {
  619.             return true;
  620.         }
  621.         /*-
  622.          * Check that we cover distance (xCur - c) before adjacent vehicle will no longer leave a space of xGap.
  623.          * _______________________________________________________________________________
  624.          *                 ___b           ___b (at +t)
  625.          * _____________ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _______
  626.          *       _____x                              _____x (at +t)                /
  627.          * _______________________________________________________________________/
  628.          *            (---------------------------xCur---------------------------)
  629.          *            (-s-)(l)               (-xGap-)(-l-)(----------c-----------)
  630.          *            
  631.          *            (----------------------------------) x should cover this distance before
  632.          *                    (-------------) b covers this distance; then we can be ahead (otherwise, follow b)
  633.          */
  634.         Length c = requiredBufferSpace(ownSpeed, nCur, x0, t0, lc, dCoop);
  635.         double t = (xCur.si - c.si) / ownSpeed.si;
  636.         double xGap = adjacentVehicle.getSpeed().si * (tMin.si + desire * (tMax.si - tMin.si));
  637.         return 0.0 < t && t < (xCur.si - adjacentVehicle.getDistance().si - ownLength.si - adjacentVehicle.getLength().si - c.si
  638.                 - xGap) / adjacentVehicle.getSpeed().si;
  639.     }

  640.     /**
  641.      * Returns the required buffer space to perform a lane change and further lane changes.
  642.      * @param speed Speed; representative speed
  643.      * @param nCur int; number of required lane changes
  644.      * @param x0 Length; anticipation distance
  645.      * @param t0 Duration; anticipation time
  646.      * @param lc Duration; lane change duration
  647.      * @param dCoop double; cooperation threshold
  648.      * @return required buffer space to perform a lane change and further lane changes
  649.      */
  650.     static Length requiredBufferSpace(final Speed speed, final int nCur, final Length x0, final Duration t0, final Duration lc,
  651.             final double dCoop)
  652.     {
  653.         Length xCrit = speed.times(t0);
  654.         xCrit = Length.max(xCrit, x0);
  655.         return speed.times(lc).plus(xCrit.times((nCur - 1.0) * (1.0 - dCoop)));
  656.     }

  657.     /**
  658.      * Calculates acceleration to stop for a split or dead-end, accounting for infrastructure.
  659.      * @param xCur Length; remaining distance to end
  660.      * @param xMerge Length; distance until merge point
  661.      * @param params Parameters; parameters
  662.      * @param ownSpeed Speed; own speed
  663.      * @param cfm CarFollowingModel; car-following model
  664.      * @param sli SpeedLimitInfo; speed limit info
  665.      * @return acceleration to stop for a split or dead-end, accounting for infrastructure
  666.      * @throws ParameterException if parameter is not defined
  667.      */
  668.     static Acceleration stopForEnd(final Length xCur, final Length xMerge, final Parameters params, final Speed ownSpeed,
  669.             final CarFollowingModel cfm, final SpeedLimitInfo sli) throws ParameterException
  670.     {
  671.         if (xCur.lt0())
  672.         {
  673.             // missed our final lane change spot, but space remains
  674.             return Acceleration.max(params.getParameter(ParameterTypes.BCRIT).neg(),
  675.                     CarFollowingUtil.stop(cfm, params, ownSpeed, sli, xMerge));
  676.         }
  677.         LmrsUtil.setDesiredHeadway(params, 1.0);
  678.         Acceleration a = CarFollowingUtil.stop(cfm, params, ownSpeed, sli, xCur);
  679.         if (a.lt0())
  680.         {
  681.             // decelerate even more if still comfortable, leaving space for acceleration later
  682.             a = Acceleration.min(a, params.getParameter(ParameterTypes.B).neg());
  683.             // but never decelerate such that stand-still is reached within xMerge
  684.             if (xMerge.gt0())
  685.             {
  686.                 a = Acceleration.max(a, CarFollowingUtil.stop(cfm, params, ownSpeed, sli, xMerge));
  687.             }
  688.         }
  689.         else
  690.         {
  691.             a = Acceleration.POSITIVE_INFINITY;
  692.         }
  693.         LmrsUtil.resetDesiredHeadway(params);
  694.         return a;
  695.     }

  696.     /**
  697.      * Returns the leader of one gtu from a set.
  698.      * @param gtu HeadwayGtu; gtu
  699.      * @param leaders SortedSet&lt;HeadwayGtu&gt;; leaders
  700.      * @return leader of one gtu from a set
  701.      */
  702.     static HeadwayGtu getTargetLeader(final HeadwayGtu gtu, final SortedSet<HeadwayGtu> leaders)
  703.     {
  704.         for (HeadwayGtu leader : leaders)
  705.         {
  706.             if (leader.getDistance().gt(gtu.getDistance()))
  707.             {
  708.                 return leader;
  709.             }
  710.         }
  711.         return null;
  712.     }

  713. }