AbstractGtuFollowingModelMobil.java

package org.opentrafficsim.road.gtu.lane.tactical.following;

import java.util.Collection;

import org.djunits.unit.AccelerationUnit;
import org.djunits.unit.DurationUnit;
import org.djunits.unit.LengthUnit;
import org.djunits.unit.TimeUnit;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.djunits.value.vdouble.scalar.Time;
import org.opentrafficsim.core.gtu.GtuException;
import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
import org.opentrafficsim.road.gtu.lane.tactical.AbstractLaneBasedTacticalPlanner;
import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlanner;

/**
 * Code shared between various car following models. <br>
 * Note: many of the methods have a maxDistance, which may be "behind" the location of the next GTU, or stand-alone. Note that
 * the maxDistance is equivalent to a GTU with zero speed, and not equivalent to a moving GTU.
 * <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>
 */
public abstract class AbstractGtuFollowingModelMobil implements GtuFollowingModelOld
{

    /** Prohibitive deceleration used to construct the TOODANGEROUS result below. */
    private static final AccelerationStep PROHIBITIVEACCELERATIONSTEP =
            new AccelerationStep(new Acceleration(Double.NEGATIVE_INFINITY, AccelerationUnit.SI),
                    new Time(Double.NaN, TimeUnit.DEFAULT), new Duration(Double.NaN, DurationUnit.SI));

    /** Return value if lane change causes immediate collision. */
    public static final DualAccelerationStep TOODANGEROUS =
            new DualAccelerationStep(PROHIBITIVEACCELERATIONSTEP, PROHIBITIVEACCELERATIONSTEP);

    /** {@inheritDoc} */
    @Override
    public final DualAccelerationStep computeDualAccelerationStep(final LaneBasedGtu referenceGTU,
            final Collection<Headway> otherGTUs, final Length maxDistance, final Speed speedLimit) throws GtuException
    {
        return computeDualAccelerationStep(referenceGTU, otherGTUs, maxDistance, speedLimit, getStepSize());
    }

    /** {@inheritDoc} */
    @Override
    public final DualAccelerationStep computeDualAccelerationStep(final LaneBasedGtu referenceGTU,
            final Collection<Headway> otherHeadways, final Length maxDistance, final Speed speedLimit, final Duration stepSize)
            throws GtuException
    {
        // Find out if there is an immediate collision
        for (Headway headway : otherHeadways)
        {
            // XXX: Under which circumstances can getDistance() be NULL? Should that indeed result in TOODANGEROUS?
            if (headway.getDistance() == null)
            {
                return TOODANGEROUS;
            }
            if (!headway.getId().equals(referenceGTU.getId()) && Double.isNaN(headway.getDistance().si))
            {
                return TOODANGEROUS;
            }
        }
        AccelerationStep followerAccelerationStep = null;
        AccelerationStep referenceGTUAccelerationStep = null;
        LaneBasedTacticalPlanner tp = referenceGTU.getTacticalPlanner();
        if (null == tp)
        {
            referenceGTU.getTacticalPlanner();
            System.err.println("tactical planner is null");
        }
        GtuFollowingModelOld gfm = (GtuFollowingModelOld) ((AbstractLaneBasedTacticalPlanner) referenceGTU.getTacticalPlanner())
                .getCarFollowingModel();
        // Find the leader and the follower that cause/experience the least positive (most negative) acceleration.
        for (Headway headway : otherHeadways)
        {
            if (headway.getId().equals(referenceGTU.getId()))
            {
                continue;
            }
            if (headway.getDistance().si < 0)
            {
                // This one is behind; assume our CFM holds also for the GTU behind us
                AccelerationStep as = gfm.computeAccelerationStep(headway.getSpeed(), referenceGTU.getSpeed(),
                        new Length(-headway.getDistance().si, LengthUnit.SI), speedLimit,
                        referenceGTU.getSimulator().getSimulatorAbsTime(), stepSize);
                if (null == followerAccelerationStep || as.getAcceleration().lt(followerAccelerationStep.getAcceleration()))
                {
                    followerAccelerationStep = as;
                }
            }
            else
            {
                // This one is ahead
                AccelerationStep as = gfm.computeAccelerationStep(referenceGTU, headway.getSpeed(), headway.getDistance(),
                        maxDistance, speedLimit, stepSize);
                if (null == referenceGTUAccelerationStep
                        || as.getAcceleration().lt(referenceGTUAccelerationStep.getAcceleration()))
                {
                    referenceGTUAccelerationStep = as;
                }
            }
        }
        if (null == followerAccelerationStep)
        {
            followerAccelerationStep = gfm.computeAccelerationStepWithNoLeader(referenceGTU, maxDistance, speedLimit, stepSize);
        }
        if (null == referenceGTUAccelerationStep)
        {
            referenceGTUAccelerationStep =
                    gfm.computeAccelerationStepWithNoLeader(referenceGTU, maxDistance, speedLimit, stepSize);
        }
        return new DualAccelerationStep(referenceGTUAccelerationStep, followerAccelerationStep);
    }

    /** {@inheritDoc} */
    @Override
    public final AccelerationStep computeAccelerationStep(final LaneBasedGtu gtu, final Speed leaderSpeed, final Length headway,
            final Length maxDistance, final Speed speedLimit) throws GtuException
    {
        return computeAccelerationStep(gtu, leaderSpeed, headway, maxDistance, speedLimit, getStepSize());
    }

    /** {@inheritDoc} */
    @Override
    public final AccelerationStep computeAccelerationStep(final LaneBasedGtu gtu, final Speed leaderSpeed, final Length headway,
            final Length maxDistance, final Speed speedLimit, final Duration stepSize) throws GtuException
    {
        Length distance;
        Speed leaderOrBlockSpeed;
        if (maxDistance.lt(headway) || headway == null)
        {
            distance = maxDistance;
            leaderOrBlockSpeed = Speed.ZERO;
        }
        else
        {
            distance = headway;
            leaderOrBlockSpeed = leaderSpeed;
        }
        final Speed followerSpeed = gtu.getSpeed();
        final Speed followerMaximumSpeed = gtu.getMaximumSpeed();
        Acceleration newAcceleration =
                computeAcceleration(followerSpeed, followerMaximumSpeed, leaderOrBlockSpeed, distance, speedLimit, stepSize);
        Time nextEvaluationTime = gtu.getSimulator().getSimulatorAbsTime().plus(stepSize);
        return new AccelerationStep(newAcceleration, nextEvaluationTime, stepSize);
    }

    /** {@inheritDoc} */
    @Override
    public final AccelerationStep computeAccelerationStep(final Speed followerSpeed, final Speed leaderSpeed,
            final Length headway, final Speed speedLimit, final Time currentTime)
    {
        return computeAccelerationStep(followerSpeed, leaderSpeed, headway, speedLimit, currentTime, getStepSize());
    }

    /** {@inheritDoc} */
    @Override
    public final AccelerationStep computeAccelerationStep(final Speed followerSpeed, final Speed leaderSpeed,
            final Length headway, final Speed speedLimit, final Time currentTime, final Duration stepSize)
    {
        final Speed followerMaximumSpeed = speedLimit; // the best approximation we can do...
        Acceleration newAcceleration =
                computeAcceleration(followerSpeed, followerMaximumSpeed, leaderSpeed, headway, speedLimit, stepSize);
        Time nextEvaluationTime = currentTime.plus(stepSize);
        return new AccelerationStep(newAcceleration, nextEvaluationTime, stepSize);
    }

    /** {@inheritDoc} */
    @Override
    public final AccelerationStep computeAccelerationStepWithNoLeader(final LaneBasedGtu gtu, final Length maxDistance,
            final Speed speedLimit) throws GtuException
    {
        return computeAccelerationStepWithNoLeader(gtu, maxDistance, speedLimit, getStepSize());
    }

    /** {@inheritDoc} */
    @Override
    public final AccelerationStep computeAccelerationStepWithNoLeader(final LaneBasedGtu gtu, final Length maxDistance,
            final Speed speedLimit, final Duration stepSize) throws GtuException
    {
        Length stopDistance = new Length(
                gtu.getMaximumSpeed().si * gtu.getMaximumSpeed().si / (2.0 * getMaximumSafeDeceleration().si), LengthUnit.SI);
        return computeAccelerationStep(gtu, gtu.getSpeed(), stopDistance, maxDistance, speedLimit, stepSize);
        /*-
        return computeAcceleration(gtu, gtu.getSpeed(), Calc.speedSquaredDividedByDoubleAcceleration(gtu
            .getMaximumSpeed(), maximumSafeDeceleration()), speedLimit);
         */
    }

    /** {@inheritDoc} */
    @Override
    public final Length minimumHeadway(final Speed followerSpeed, final Speed leaderSpeed, final Length precision,
            final Length maxDistance, final Speed speedLimit, final Speed followerMaximumSpeed)
    {
        if (precision.getSI() <= 0)
        {
            throw new Error("Precision has bad value (must be > 0; got " + precision + ")");
        }
        double maximumDeceleration = -getMaximumSafeDeceleration().getSI();
        // Find a decent interval to bisect
        double minimumSI = 0;
        double minimumSIDeceleration =
                computeAcceleration(followerSpeed, followerSpeed, leaderSpeed, new Length(minimumSI, LengthUnit.SI), speedLimit)
                        .getSI();
        if (minimumSIDeceleration >= maximumDeceleration)
        {
            // Weird... The GTU following model allows zero headway
            return Length.ZERO;
        }
        double maximumSI = 1; // this is - deliberately - way too small
        double maximumSIDeceleration = Double.NaN;
        // Double the value of maximumSI until the resulting deceleration is less severe than the maximum
        for (int step = 0; step < 20; step++)
        {
            maximumSIDeceleration = computeAcceleration(followerSpeed, followerMaximumSpeed, leaderSpeed,
                    new Length(maximumSI, LengthUnit.SI), speedLimit).getSI();
            if (maximumSIDeceleration > maximumDeceleration)
            {
                break;
            }
            maximumSI *= 2;
        }
        if (maximumSIDeceleration < maximumDeceleration)
        {
            System.out.println();
            maximumSIDeceleration = computeAcceleration(followerSpeed, followerMaximumSpeed, leaderSpeed,
                    new Length(maximumSI, LengthUnit.SI), speedLimit).getSI();
            throw new Error("Cannot find headway that results in an acceptable deceleration");
        }
        // Now bisect until the error is less than the requested precision
        final int maximumStep = (int) Math.ceil(Math.log((maximumSI - minimumSI) / precision.getSI()) / Math.log(2));
        for (int step = 0; step < maximumStep; step++)
        {
            double midSI = (minimumSI + maximumSI) / 2;
            double midSIAcceleration = computeAcceleration(followerSpeed, followerMaximumSpeed, leaderSpeed,
                    new Length(midSI, LengthUnit.SI), speedLimit).getSI();
            if (midSIAcceleration < maximumDeceleration)
            {
                minimumSI = midSI;
            }
            else
            {
                maximumSI = midSI;
            }
        }
        Length result = new Length(Math.min((minimumSI + maximumSI) / 2, maxDistance.si), LengthUnit.SI);
        return result;
    }

}