AbstractIncentivesTacticalPlanner.java

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

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;

import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Length;
import org.djutils.immutablecollections.Immutable;
import org.djutils.immutablecollections.ImmutableLinkedHashMap;
import org.djutils.immutablecollections.ImmutableLinkedHashSet;
import org.djutils.immutablecollections.ImmutableSet;
import org.opentrafficsim.base.parameters.ParameterException;
import org.opentrafficsim.core.gtu.GtuException;
import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
import org.opentrafficsim.road.gtu.LaneBasedGtu;
import org.opentrafficsim.road.gtu.perception.LanePerception;
import org.opentrafficsim.road.gtu.perception.RelativeLane;
import org.opentrafficsim.road.gtu.tactical.AbstractLaneBasedTacticalPlanner;
import org.opentrafficsim.road.gtu.tactical.TacticalContextEgo;
import org.opentrafficsim.road.gtu.tactical.following.CarFollowingModel;
import org.opentrafficsim.road.gtu.tactical.util.lmrs.Desire;
import org.opentrafficsim.road.gtu.tactical.util.lmrs.Incentive;
import org.opentrafficsim.road.gtu.tactical.util.lmrs.MandatoryIncentive;
import org.opentrafficsim.road.gtu.tactical.util.lmrs.VoluntaryIncentive;

/**
 * Tactical planner with mandatory lane change incentives, voluntary lane change incentives, and acceleration incentives. The
 * order in which the incentives are given is relevant. A later incentive can request the result from an earlier incentive at
 * the same time instance. Otherwise, it can only obtain the value of another incentive from the previous time step.
 * <p>
 * Copyright (c) 2013-2026 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 Alexander Verbraeck
 * @author Peter Knoppers
 * @author Wouter Schakel
 */
public abstract class AbstractIncentivesTacticalPlanner extends AbstractLaneBasedTacticalPlanner
        implements DesireBased, AccelerationBased
{

    /** Set of mandatory lane change incentives. */
    private final LinkedHashSet<MandatoryIncentive> mandatoryIncentives = new LinkedHashSet<>();

    /** Set of voluntary lane change incentives. */
    private final LinkedHashSet<VoluntaryIncentive> voluntaryIncentives = new LinkedHashSet<>();

    /** Set of acceleration incentives. */
    private final LinkedHashSet<AccelerationIncentive> accelerationIncentives = new LinkedHashSet<>();

    /** Immutable set of mandatory lane change incentives. */
    private ImmutableSet<MandatoryIncentive> mandatoryIncentivesImmutable =
            new ImmutableLinkedHashSet<>(this.mandatoryIncentives, Immutable.WRAP);

    /** Immutable set of voluntary lane change incentives. */
    private ImmutableSet<VoluntaryIncentive> voluntaryIncentivesImmutable =
            new ImmutableLinkedHashSet<>(this.voluntaryIncentives, Immutable.WRAP);

    /** Immutable set of acceleration incentives. */
    private ImmutableSet<AccelerationIncentive> accelerationIncentivesImmutable =
            new ImmutableLinkedHashSet<>(this.accelerationIncentives, Immutable.WRAP);

    /** Map of mandatory desire. */
    private Map<Class<? extends MandatoryIncentive>, Desire> mandatoryDesire = new LinkedHashMap<>();

    /** Map of voluntary desire. */
    private Map<Class<? extends VoluntaryIncentive>, Desire> voluntaryDesire = new LinkedHashMap<>();

    /** Map of acceleration. */
    private Map<Class<? extends AccelerationIncentive>, Acceleration> acceleration = new LinkedHashMap<>();

    /** Immutable map of mandatory desire. */
    private ImmutableLinkedHashMap<Class<? extends MandatoryIncentive>, Desire> mandatoryDesireImmutable =
            new ImmutableLinkedHashMap<>(this.mandatoryDesire, Immutable.WRAP);

    /** Immutable map of voluntary desire. */
    private ImmutableLinkedHashMap<Class<? extends VoluntaryIncentive>, Desire> voluntaryDesireImmutable =
            new ImmutableLinkedHashMap<>(this.voluntaryDesire, Immutable.WRAP);

    /** Latest total mandatory desire. */
    private Desire mandatory;

    /** Latest total voluntary desire. */
    private Desire voluntary;

    /**
     * Constructor.
     * @param carFollowingModel Car-following model.
     * @param gtu GTU
     * @param lanePerception perception
     */
    public AbstractIncentivesTacticalPlanner(final CarFollowingModel carFollowingModel, final LaneBasedGtu gtu,
            final LanePerception lanePerception)
    {
        super(carFollowingModel, gtu, lanePerception);
    }

    /**
     * Adds a mandatory incentive. Ignores {@code null}.
     * @param incentive Incentive to add.
     */
    public void addMandatoryIncentive(final MandatoryIncentive incentive)
    {
        if (incentive != null)
        {
            this.mandatoryIncentives.add(incentive);
        }
    }

    /**
     * Adds a voluntary incentive. Ignores {@code null}.
     * @param incentive Incentive to add.
     */
    public void addVoluntaryIncentive(final VoluntaryIncentive incentive)
    {
        if (incentive != null)
        {
            this.voluntaryIncentives.add(incentive);
        }
    }

    /**
     * Adds an acceleration incentive. Ignores {@code null}.
     * @param incentive Incentive to add.
     */
    public void addAccelerationIncentive(final AccelerationIncentive incentive)
    {
        if (incentive != null)
        {
            this.accelerationIncentives.add(incentive);
        }
    }

    /**
     * Returns the mandatory incentives.
     * @return mandatory incentives
     */
    public ImmutableSet<MandatoryIncentive> getMandatoryIncentives()
    {
        return this.mandatoryIncentivesImmutable;
    }

    /**
     * Returns the voluntary incentives.
     * @return voluntary incentives
     */
    public ImmutableSet<VoluntaryIncentive> getVoluntaryIncentives()
    {
        return this.voluntaryIncentivesImmutable;
    }

    /**
     * Returns the acceleration incentives.
     * @return acceleration incentives
     */
    public ImmutableSet<AccelerationIncentive> getAccelerationIncentives()
    {
        return this.accelerationIncentivesImmutable;
    }

    /**
     * Determines level of lane change desire for mandatory lane change incentives.
     * @param context tactical information such as parameters and car-following model
     * @return level of lane change desire for this incentive
     * @throws ParameterException if a parameter is not given or out of bounds
     * @throws OperationalPlanException in case of a perception exception
     */
    public Desire getMandatoryDesire(final TacticalContextEgo context) throws OperationalPlanException, ParameterException
    {
        double dLeftMandatory = 0.0;
        double dRightMandatory = 0.0;
        for (MandatoryIncentive incentive : this.mandatoryIncentives)
        {
            Desire d = incentive.determineDesire(context, this.mandatoryDesireImmutable);
            this.mandatoryDesire.put(incentive.getClass(), d);
            dLeftMandatory = Math.abs(d.left()) > Math.abs(dLeftMandatory) ? d.left() : dLeftMandatory;
            dRightMandatory = Math.abs(d.right()) > Math.abs(dRightMandatory) ? d.right() : dRightMandatory;
        }
        this.mandatory = new Desire(dLeftMandatory, dRightMandatory);
        return this.mandatory;
    }

    /**
     * Determines level of lane change desire for voluntary lane change incentives.
     * @param context tactical information such as parameters and car-following model
     * @return level of lane change desire for this incentive
     * @throws ParameterException if a parameter is not given or out of bounds
     * @throws OperationalPlanException in case of a perception exception
     */
    public Desire getVoluntaryDesire(final TacticalContextEgo context) throws OperationalPlanException, ParameterException
    {
        double dLeftVoluntary = 0;
        double dRightVoluntary = 0;
        for (VoluntaryIncentive incentive : this.voluntaryIncentives)
        {
            Desire d = incentive.determineDesire(context, this.mandatory, this.voluntaryDesireImmutable);
            this.voluntaryDesire.put(incentive.getClass(), d);
            dLeftVoluntary += d.left();
            dRightVoluntary += d.right();
        }
        this.voluntary = new Desire(dLeftVoluntary, dRightVoluntary);
        return this.voluntary;
    }

    /**
     * Returns the latest total mandatory desire.
     * @return latest total mandatory desire
     */
    public Desire getLatestMandatoryDesire()
    {
        return this.mandatory;
    }

    /**
     * Returns the latest total voluntary desire.
     * @return latest total voluntary desire
     */
    public Desire getLatestVoluntaryDesire()
    {
        return this.voluntary;
    }

    @Override
    public Optional<Desire> getLatestDesire(final Class<? extends Incentive> incentive)
    {
        if (this.voluntaryDesire.containsKey(incentive))
        {
            return Optional.ofNullable(this.voluntaryDesire.get(incentive));
        }
        return Optional.ofNullable(this.mandatoryDesire.get(incentive));
    }

    /**
     * Determine acceleration.
     * @param context tactical information such as parameters and car-following model
     * @param lane lane on which to consider the acceleration
     * @param mergeDistance distance over which a lane change is impossible towards the lane, zero if current lane
     * @return acceleration
     * @throws ParameterException on missing parameter
     * @throws GtuException when there is a problem with the state of the GTU when planning a path
     */
    public Acceleration getAcceleration(final TacticalContextEgo context, final RelativeLane lane, final Length mergeDistance)
            throws ParameterException, GtuException
    {
        Acceleration a = Acceleration.POS_MAXVALUE;
        for (AccelerationIncentive incentive : this.accelerationIncentives)
        {
            Acceleration aIncentive = incentive.accelerate(context, lane, mergeDistance);
            this.acceleration.put(incentive.getClass(), aIncentive);
            a = Acceleration.min(a, aIncentive);
        }
        return a;
    }

    @Override
    public Optional<Acceleration> getLatestAcceleration(final Class<? extends AccelerationIncentive> incentiveClass)
    {
        return Optional.ofNullable(this.acceleration.get(incentiveClass));
    }

}