GapAcceptance.java

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

import org.djunits.unit.AccelerationUnit;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Speed;
import org.opentrafficsim.base.parameters.ParameterException;
import org.opentrafficsim.base.parameters.ParameterTypes;
import org.opentrafficsim.base.parameters.Parameters;
import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
import org.opentrafficsim.core.network.LateralDirectionality;
import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtu;
import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
import org.opentrafficsim.road.network.speed.SpeedLimitInfo;

/**
 * Interface for LMRS gap-acceptance models.
 * <p>
 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
 * </p>
 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
 * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
 */
public interface GapAcceptance
{

    /** Being informed of the model and parameters of other drivers (default LMRS). */
    GapAcceptance INFORMED = new GapAcceptance()
    {
        /** {@inheritDoc} */
        @Override
        public boolean acceptGap(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
                final CarFollowingModel cfm, final double desire, final Speed ownSpeed, final Acceleration ownAcceleration,
                final LateralDirectionality lat) throws ParameterException, OperationalPlanException
        {
            NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
            if (neighbors.isGtuAlongside(lat))
            {
                // gtu alongside
                return false;
            }

            // TODO
            /*-
             * Followers and are accepted if the acceleration and speed is 0, a leader is accepted if the ego speed is 0. This 
             * is in place as vehicles that provide courtesy, will decelerate for us and overshoot the stand-still distance. As
             * a consequence, they will cease cooperation as they are too close. A pattern will arise where followers slow down
             * to (near) stand-still, and accelerate again, before we could ever accept the gap.
             * 
             * By accepting the gap in the moment that they reach stand-still, this vehicle can at least accept the gap at some 
             * point. All of this is only a problem if the own vehicle is standing still. Otherwise the stand-still distance is 
             * not important and movement of our own will create an acceptable situation.
             * 
             * What needs to be done, is to find a better way to deal with the cooperation and gap-acceptance, such that this  
             * hack is not required.
             */

            Acceleration b = params.getParameter(ParameterTypes.B);
            Acceleration aFollow = new Acceleration(Double.POSITIVE_INFINITY, AccelerationUnit.SI);
            for (HeadwayGtu follower : neighbors.getFirstFollowers(lat))
            {
                if (follower.getSpeed().gt0() || follower.getAcceleration().gt0() || follower.getDistance().si < 1.0)
                {
                    Acceleration a = LmrsUtil.singleAcceleration(follower.getDistance(), follower.getSpeed(), ownSpeed, desire,
                            follower.getParameters(), follower.getSpeedLimitInfo(), follower.getCarFollowingModel());
                    aFollow = Acceleration.min(aFollow, a);
                }
            }

            Acceleration aSelf = egoAcceleration(perception, params, sli, cfm, desire, ownSpeed, lat);
            Acceleration threshold = b.times(-desire);
            return aFollow.ge(threshold) && aSelf.ge(threshold) && ownAcceleration.ge(threshold);
        }

        /** {@inheritDoc} */
        @Override
        public String toString()
        {
            return "INFORMED";
        }
    };

    /** Being informed of the model and parameters of other drivers, but applying own headway value. */
    GapAcceptance EGO_HEADWAY = new GapAcceptance()
    {
        /** {@inheritDoc} */
        @Override
        public boolean acceptGap(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
                final CarFollowingModel cfm, final double desire, final Speed ownSpeed, final Acceleration ownAcceleration,
                final LateralDirectionality lat) throws ParameterException, OperationalPlanException
        {
            NeighborsPerception neigbors = perception.getPerceptionCategory(NeighborsPerception.class);
            if (neigbors.isGtuAlongside(lat))
            {
                // gtu alongside
                return false;
            }

            Acceleration b = params.getParameter(ParameterTypes.B);
            Acceleration aFollow = new Acceleration(Double.POSITIVE_INFINITY, AccelerationUnit.SI);
            for (HeadwayGtu follower : neigbors.getFirstFollowers(lat))
            {
                if (follower.getSpeed().gt0() || follower.getAcceleration().gt0())
                {
                    // Change headway parameter
                    Parameters folParams = follower.getParameters();
                    folParams.setParameterResettable(ParameterTypes.TMIN, params.getParameter(ParameterTypes.TMIN));
                    folParams.setParameterResettable(ParameterTypes.TMAX, params.getParameter(ParameterTypes.TMAX));
                    Acceleration a = LmrsUtil.singleAcceleration(follower.getDistance(), follower.getSpeed(), ownSpeed, desire,
                            folParams, follower.getSpeedLimitInfo(), follower.getCarFollowingModel());
                    aFollow = Acceleration.min(aFollow, a);
                    folParams.resetParameter(ParameterTypes.TMIN);
                    folParams.resetParameter(ParameterTypes.TMAX);
                }
            }

            Acceleration aSelf = egoAcceleration(perception, params, sli, cfm, desire, ownSpeed, lat);
            Acceleration threshold = b.times(-desire);
            return aFollow.ge(threshold) && aSelf.ge(threshold);
        }

        /** {@inheritDoc} */
        @Override
        public String toString()
        {
            return "EGO_HEADWAY";
        }
    };

    /**
     * Determine whether a gap is acceptable.
     * @param perception LanePerception; perception
     * @param params Parameters; parameters
     * @param sli SpeedLimitInfo; speed limit info
     * @param cfm CarFollowingModel; car-following model
     * @param desire double; level of lane change desire
     * @param ownSpeed Speed; own speed
     * @param lat LateralDirectionality; lateral direction for synchronization
     * @return whether a gap is acceptable
     * @throws ParameterException if a parameter is not defined
     * @throws OperationalPlanException perception exception
     */
    static Acceleration egoAcceleration(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
            final CarFollowingModel cfm, final double desire, final Speed ownSpeed, final LateralDirectionality lat)
            throws ParameterException, OperationalPlanException
    {
        Acceleration aSelf = new Acceleration(Double.POSITIVE_INFINITY, AccelerationUnit.SI);
        if (ownSpeed.gt0())
        {
            for (

            HeadwayGtu leader : perception.getPerceptionCategory(NeighborsPerception.class).getFirstLeaders(lat))
            {
                Acceleration a = LmrsUtil.singleAcceleration(leader.getDistance(), ownSpeed, leader.getSpeed(), desire, params,
                        sli, cfm);
                aSelf = Acceleration.min(aSelf, a);
            }
        }
        return aSelf;
    }

    /**
     * Determine whether a gap is acceptable.
     * @param perception LanePerception; perception
     * @param params Parameters; parameters
     * @param sli SpeedLimitInfo; speed limit info
     * @param cfm CarFollowingModel; car-following model
     * @param desire double; level of lane change desire
     * @param ownSpeed Speed; own speed
     * @param ownAcceleration Acceleration; current car-following acceleration
     * @param lat LateralDirectionality; lateral direction for synchronization
     * @return whether a gap is acceptable
     * @throws ParameterException if a parameter is not defined
     * @throws OperationalPlanException perception exception
     */
    boolean acceptGap(LanePerception perception, Parameters params, SpeedLimitInfo sli, CarFollowingModel cfm, double desire,
            Speed ownSpeed, Acceleration ownAcceleration, LateralDirectionality lat)
            throws ParameterException, OperationalPlanException;

}