LmrsFactory.java
package org.opentrafficsim.road.gtu.lane.tactical.lmrs;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.djunits.unit.SpeedUnit;
import org.djunits.value.vdouble.scalar.Duration;
import org.djutils.exceptions.Throw;
import org.djutils.exceptions.Try;
import org.opentrafficsim.base.parameters.ParameterException;
import org.opentrafficsim.base.parameters.ParameterSet;
import org.opentrafficsim.base.parameters.ParameterTypes;
import org.opentrafficsim.base.parameters.Parameters;
import org.opentrafficsim.core.definitions.DefaultsNl;
import org.opentrafficsim.core.gtu.GtuException;
import org.opentrafficsim.core.gtu.GtuType;
import org.opentrafficsim.core.gtu.perception.DirectEgoPerception;
import org.opentrafficsim.core.parameters.ParameterFactoryOneShot;
import org.opentrafficsim.core.units.distributions.ContinuousDistSpeed;
import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
import org.opentrafficsim.road.gtu.lane.perception.CategoricalLanePerception;
import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
import org.opentrafficsim.road.gtu.lane.perception.categories.AnticipationTrafficPerception;
import org.opentrafficsim.road.gtu.lane.perception.categories.DirectInfrastructurePerception;
import org.opentrafficsim.road.gtu.lane.perception.categories.DirectIntersectionPerception;
import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.Anticipation;
import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.DirectNeighborsPerception;
import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.Estimation;
import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.PerceivedGtuType;
import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.PerceivedGtuType.AnticipationPerceivedGtuType;
import org.opentrafficsim.road.gtu.lane.perception.mental.AdaptationHeadway;
import org.opentrafficsim.road.gtu.lane.perception.mental.AdaptationLaneChangeDesire;
import org.opentrafficsim.road.gtu.lane.perception.mental.AdaptationSituationalAwareness;
import org.opentrafficsim.road.gtu.lane.perception.mental.AdaptationSpeed;
import org.opentrafficsim.road.gtu.lane.perception.mental.BehavioralAdaptation;
import org.opentrafficsim.road.gtu.lane.perception.mental.FactorEstimation;
import org.opentrafficsim.road.gtu.lane.perception.mental.Fuller;
import org.opentrafficsim.road.gtu.lane.perception.mental.Mental;
import org.opentrafficsim.road.gtu.lane.perception.mental.SumFuller;
import org.opentrafficsim.road.gtu.lane.perception.mental.ar.ArFuller;
import org.opentrafficsim.road.gtu.lane.perception.mental.ar.ArTask;
import org.opentrafficsim.road.gtu.lane.perception.mental.ar.ArTaskCarFollowing;
import org.opentrafficsim.road.gtu.lane.perception.mental.ar.ArTaskCarFollowingExp;
import org.opentrafficsim.road.gtu.lane.perception.mental.ar.ArTaskLaneChanging;
import org.opentrafficsim.road.gtu.lane.perception.mental.ar.ArTaskLaneChangingD;
import org.opentrafficsim.road.gtu.lane.perception.mental.ar.ArTaskRoadSideDistraction;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.AdaptationSpeedChannel;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.AdaptationUpdateTime;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelFuller;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelMental;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTask;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskAcceleration;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskCarFollowing;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskConflict;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskCooperation;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskLaneChange;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskRoadSideDistraction;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskScan;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskSignal;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskTrafficLight;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.IntersectionPerceptionChannel;
import org.opentrafficsim.road.gtu.lane.perception.mental.channel.NeighborsPerceptionChannel;
import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlannerFactory;
import org.opentrafficsim.road.gtu.lane.tactical.following.AbstractIdm;
import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
import org.opentrafficsim.road.gtu.lane.tactical.following.DesiredHeadwayModel;
import org.opentrafficsim.road.gtu.lane.tactical.following.DesiredSpeedModel;
import org.opentrafficsim.road.gtu.lane.tactical.following.Idm;
import org.opentrafficsim.road.gtu.lane.tactical.following.IdmPlus;
import org.opentrafficsim.road.gtu.lane.tactical.util.ConflictUtil;
import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.Cooperation;
import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.GapAcceptance;
import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.LmrsParameters;
import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.LmrsUtil;
import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.MandatoryIncentive;
import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.Synchronization;
import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.Tailgating;
import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.VoluntaryIncentive;
import nl.tudelft.simulation.jstats.distributions.DistLogNormal;
import nl.tudelft.simulation.jstats.distributions.DistNormalTrunc;
import nl.tudelft.simulation.jstats.distributions.DistTriangular;
import nl.tudelft.simulation.jstats.streams.StreamInterface;
import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.Option;
/**
* This tactical planner is general purpose for the use of the LMRS with any combination of defined sub-components. The class
* seconds as a parameter factory. For both model components and parameter values, the factory can be used specific to the GTU
* type. Model components can be set through command line arguments, or through calling {@link #set(Setting, Object)}.
* Parameters can be set through standard parameter factory methods.
* <p>
* <b>Usage</b><br>
* The factory can be used as:
*
* <pre>
* LmrsFactory<Lmrs> factory = new LmrsFactory<>(List.of(DefaultsNl.CAR, DefaultsNl.VAN), Lmrs::new);
*
* factory.set(Setting.ADAPTATION_SPEED, false);
* </pre>
*
* The GTU list is optional (default is {@code List.of(DefaultsNl.CAR, DefaultsNl.TRUCK)}), and the second
* {@link TacticalPlannerProvider} argument can also be given as a list to specifiy different providers per GTU type. All
* settings must be provided either for all GTU types, or for a specific GTU type with which the factory was initialized. GTUs
* of other types can be generated within the simulation, so long as any of their parent types is within the list of the
* factory.
* <p>
* <b>Command line arguments</b><br>
* This class can be mixed in with any program using command line arguments:
*
* <pre>
* @Mixin
* private LmrsFactory<Lmrs> factory = new LmrsFactory<>(Lmrs::new);
*
* public static void main(String[] args)
* {
* Program program = new Program();
* CliUtil.changeOptionDefault(program, "gtuTypes", "NL.CAR|NL.VAN|NL.TRUCK");
* CliUtil.execute(program, args);
* }
* </pre>
*
* <i>Note: command line arguments and programmatically setting settings (set method) should in principle not both be used
* within the same program. Default command line arguments should be changed by the option default as in the example above,
* before the input args are executed.</i>
* <p>
* Use command line argument {@code --help} to get a list of all available command line arguments. In the example the factory is
* initialized using the constructor of {@link Lmrs} as a supplier of the tactical planner class. Within the context of a
* program it may be necessary to use a supplier of an another implementation of a tactical planner instead. The supplier can be
* any function that follows the {@link TacticalPlannerProvider} signature, supplying an extension of
* {@link AbstractIncentivesTacticalPlanner}.
* <p>
* Command line arguments can be defined to contain values for specific GTU types. The following arguments allow a simulation
* with GTU types {@code NL.CAR} and {@code NL.TRUCK}, or any of their sub types. All GTUs will not generally keep to the slow
* lane ({@code incentiveKeep}), while trucks will limit themselves on the rightmost two lanes ({@code incentiveStayRight}). GTU
* types and the values that apply to them should be given in the same order. The number of values given should either be 1, or
* equal to the number of GTU types given.
*
* <pre>
* --gtuTypes=NL.CAR|NL.TRUCK --incentiveKeep=false --incentiveStayRight=false|true
* </pre>
*
* The pipe character ({@code |}) is used to separate values. If the pipe character is part of any value, values can be quoted.
* If a quote is part of a any value, the value can be quoted and the quote of the value can be escaped with a backslash \. The
* following code will set the {@code --gtuTypes} to {@code [NL.CAR, NL.VAN, NL.T|RUCK]}.
*
* <pre>
* CliUtil.execute(new CommandLine(myProgram).setTrimQuotes(true), new String[] {"--gtuTypes=NL.CAR|NL.VAN|\"NL.T|RUCK\""});
* </pre>
* <b>One-shot mode</b>
* <p>
* This class extends {@link ParameterFactoryOneShot} and can thus be used as a parameter factory. It supports one-shot mode.
* Any parameter set after {@link #setOneShotMode()} is called and before the next GTU is generated is only applied to said GTU.
* At any other moment parameters that are set are fixed. This class implements {@link #setOneShotMode()} to apply the same mode
* on model settings, applying only to the next GTU generated, after which settings are reverted to the state when
* {@link #setOneShotMode()} was called.
* <p>
* <b>Settings applicability</b><br>
* Many settings on perception components may only apply to some implementations of Fuller, see Table 1.<br>
* <br>
* <table border="1" style="text-align:center; padding:2px; border-spacing:0px; border-width:1px; border-style:solid">
* <caption><i>Table 1: Perception components for Fuller implementations</i></caption>
* <tr>
* <td style="text-align:left"><b>Setting</b></td>
* <td colspan="4" style="text-align:center"><b>Fuller implementation</b> (FULLER_IMPLEMENTATION)</td>
* </tr>
* <tr>
* <td></td>
* <td>NONE</td>
* <td>SUMMATIVE</td>
* <td>ANTICIPATION_RELIANCE</td>
* <td>ATTENTION_MATRIX</td>
* </tr>
* <tr>
* <td style="text-align:left">PRIMARY_TASK</td>
* <td>-</td>
* <td>-</td>
* <td>✓</td>
* <td>-</td>
* </tr>
* <tr>
* <td style="text-align:left">TEMPORAL_ANTICIPATION</td>
* <td>-</td>
* <td>✓</td>
* <td>✓</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">FRACTION_OVERESTIMATION</td>
* <td>-</td>
* <td>✓</td>
* <td>✓</td>
* <td>✓</td>
* </tr>
* <tr>
* <td colspan="5"><i>Tasks for mental model</i></td>
* </tr>
* <tr>
* <td style="text-align:left">TASK_CAR_FOLLOWING</td>
* <td>-</td>
* <td>✓</td>
* <td>✓</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">TASK_FREE_ACCELERATION</td>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">TASK_TRAFFIC_LIGHTS</td>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">TASK_SIGNAL</td>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">TASK_LANE_CHANGE</td>
* <td>-</td>
* <td>✓</td>
* <td>✓</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">TASK_COOPERATION</td>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">TASK_CONFLICTS</td>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">TASK_ROADSIDE_DISTRACTION</td>
* <td>-</td>
* <td>✓</td>
* <td>✓</td>
* <td>✓</td>
* </tr>
* <tr>
* <td colspan="5"><i>Behavioral adaptations for mental model</i></td>
* </tr>
* <tr>
* <td style="text-align:left">ADAPTATION_SPEED</td>
* <td>-</td>
* <td>✓</td>
* <td>✓</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">ADAPTATION_HEADWAY</td>
* <td>-</td>
* <td>✓</td>
* <td>✓</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">ADAPTATION_LANE_CHANGE</td>
* <td>-</td>
* <td>✓</td>
* <td>✓</td>
* <td>✓</td>
* </tr>
* <tr>
* <td style="text-align:left">ADAPTATION_UPDATE_TIME</td>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td>✓</td>
* </tr>
* </table>
* <p>
* Copyright (c) 2026-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 <a href="https://github.com/wjschakel">Wouter Schakel</a>
* @param <T> tactical planner type
*/
public class LmrsFactory<T extends AbstractIncentivesTacticalPlanner> extends ParameterFactoryOneShot
implements LaneBasedTacticalPlannerFactory<T>
{
/** Remembered state to reset in one-shot mode. */
private Map<Setting<?>, List<?>> state;
/** Random number stream. */
private StreamInterface stream;
/** Distribution of vGain. */
private ContinuousDistSpeed vGainDist;
/** Distribution of sigma. */
private DistTriangular sigmaDist;
/** Distribution of fSpeed. */
private DistNormalTrunc fSpeedDist;
/** LMRS provider. */
private final List<TacticalPlannerProvider<T>> lmrsProvider;
/** GTU type IDs in order of multi-valued arguments. */
@Option(names = {"--gtuTypes"}, description = "GTU type IDs in order of multi-valued arguments.",
defaultValue = "NL.CAR|NL.TRUCK", split = "\\|", splitSynopsisLabel = "|")
private final List<String> gtuTypes;
// LMRS
/** Car-following model: IDM or IDM_PLUS (default). */
@Option(names = {"--carFollowingModel"}, description = "Car-following model: IDM or IDM_PLUS.", defaultValue = "IDM_PLUS",
split = "\\|", splitSynopsisLabel = "|", converter = CarFollowingModelConverter.class)
private List<BiFunction<DesiredHeadwayModel, DesiredSpeedModel, CarFollowingModel>> carFollowingModel =
listOf((h, v) -> new IdmPlus(h, v));
/** Lane change synchronization: PASSIVE (default), PASSIVE_MOVING, ALIGN_GAP or ACTIVE. */
@Option(names = {"--synchronization"},
description = "Lane change synchronization: PASSIVE, PASSIVE_MOVING, ALIGN_GAP or ACTIVE.",
defaultValue = "PASSIVE", split = "\\|", splitSynopsisLabel = "|", converter = SynchronizationConverter.class)
private List<Synchronization> synchronization = listOf(Synchronization.PASSIVE);
/** Lane change cooperation: PASSIVE (default), PASSIVE_MOVING or ACTIVE. */
@Option(names = {"--cooperation"}, description = "Lane change cooperation: PASSIVE, PASSIVE_MOVING or ACTIVE.",
defaultValue = "PASSIVE", split = "\\|", splitSynopsisLabel = "|", converter = CooperationConverter.class)
private List<Cooperation> cooperation = listOf(Cooperation.PASSIVE);
/** Lane change gap-acceptance: INFORMED (default) or EGO_HEADWAY. */
@Option(names = {"--gapAcceptance"}, description = "Lane change gap-acceptance: INFORMED or EGO_HEADWAY.",
defaultValue = "INFORMED", split = "\\|", splitSynopsisLabel = "|", converter = GapAcceptanceConverter.class)
private List<GapAcceptance> gapAcceptance = listOf(GapAcceptance.INFORMED);
// LMRS -> Mandatory incentives
/** Mandatory lane change incentive for route (default: true). */
@Option(names = {"--incentiveRoute"}, description = "Mandatory lane change incentive for route.", defaultValue = "true",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> incentiveRoute = listOf(true);
/** Mandatory lane change incentive to join slow traffic at split and not block other traffic. */
@Option(names = {"--incentiveGetInLane"},
description = "Mandatory lane change incentive to join slow traffic at split and not block other traffic.",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> incentiveGetInLane = listOf(false);
/** Custom mandatory incentives. */
private List<Set<Supplier<MandatoryIncentive>>> customMandatoryIncentives = List.of(new LinkedHashSet<>());
// LMRS -> Voluntary incentives
/** Voluntary lane change incentive for speed with courtesy (default: true). */
@Option(names = {"--incentiveSpeedWithCourtesy"}, description = "Voluntary lane change incentive for speed with courtesy.",
defaultValue = "true", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> incentiveSpeedWithCourtesy = listOf(true);
/** Voluntary lane change incentive for cooperative lane changes (default: false). */
@Option(names = {"--incentiveCourtesy"}, description = "Voluntary lane change incentive for cooperative lane changes.",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> incentiveCourtesy = listOf(false);
/** Voluntary lane change incentive to join the shortest queue (default: false). */
@Option(names = {"--incentiveQueue"}, description = "Voluntary lane change incentive to join the shortest queue.",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> incentiveQueue = listOf(false);
/** Voluntary lane change incentive for trucks to stay in slowest rightmost two lanes (default: false). */
@Option(names = {"--incentiveStayRight"},
description = "Voluntary lane change incentive for trucks to stay in rightmost two lanes.", defaultValue = "false",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> incentiveStayRight = listOf(false);
/** Voluntary lane change incentive to keep to the slow lane (default: true). */
@Option(names = {"--incentiveKeep"}, description = "Voluntary lane change incentive to keep to the slow lane.",
defaultValue = "true", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> incentiveKeep = listOf(true);
/** Custom voluntary incentives. */
private List<Set<Supplier<VoluntaryIncentive>>> customVoluntaryIncentives = List.of(new LinkedHashSet<>());
// LMRS -> Acceleration incentives
/** Acceleration incentive to slow down prior to a lower speed limit (default: false). */
@Option(names = {"--accelerationSpeedLimitTransition"},
description = "Acceleration incentive to slow down prior to a lower speed limit.", defaultValue = "false",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> accelerationSpeedLimitTransition = listOf(false);
/** Acceleration incentive to approach traffic lights (default: false). */
@Option(names = {"--accelerationTrafficLights"}, description = "Acceleration incentive to approach traffic lights.",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> accelerationTrafficLights = listOf(false);
/** Acceleration incentive to approach intersection conflicts (default: false). */
@Option(names = {"--accelerationConflicts"}, description = "Acceleration incentive to approach intersection conflicts.",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> accelerationConflicts = listOf(false);
/** Acceleration incentive to not overtake traffic in the left lane (default: false). */
@Option(names = {"--accelerationNoRightOvertake"},
description = "Acceleration incentive to not overtake traffic in the left lane.", defaultValue = "false",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> accelerationNoRightOvertake = listOf(false);
/** Custom acceleration incentives. */
private List<Set<Supplier<AccelerationIncentive>>> customAccelerationIncentives = List.of(new LinkedHashSet<>());
// Fuller
/** Implementation of Fuller: NONE (default), SUMMATIVE, ANTICIPATION_RELIANCE or ATTENTION_MATRIX. */
@Option(names = {"--fullerImplementation"},
description = "Implementation of Fuller: NONE, SUMMATIVE, ANTICIPATION_RELIANCE or ATTENTION_MATRIX.",
defaultValue = "ATTENTION_MATRIX", split = "\\|", splitSynopsisLabel = "|")
private List<FullerImplementation> fullerImplementation = listOf(FullerImplementation.NONE);
/** Id of primary task under ANTICIPATION_RELIANCE (default: lane-changing). */
@Option(names = {"--primaryTask"}, description = "Id of primary task under ANTICIPATION_RELIANCE.",
defaultValue = "lane-changing", split = "\\|", splitSynopsisLabel = "|")
private List<String> primaryTask = listOf("lane-changing");
/** Enables temporal constant-speed anticipation (default: true). */
@Option(names = {"--anticipation"}, description = "Enables temporal constant-speed anticipation.", defaultValue = "true",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> temporalAnticipation = listOf(true);
/** Fraction of drivers over-estimating speed and distance [0..1] (default: 1). */
@Option(names = {"--fractionOverEstimation"},
description = "Fraction of drivers over-estimating speed and distance [0..1].", defaultValue = "0.0", split = "\\|",
splitSynopsisLabel = "|")
private List<Double> fractionOverEstimation = listOf(1.0);
// Fuller -> Tasks
/** Enables car-following task (default: true). */
@Option(names = {"--carFollowingTask"}, description = "Enables car-following task.", defaultValue = "true", split = "\\|",
splitSynopsisLabel = "|", negatable = true)
private List<Boolean> carFollowingTask = listOf(true);
/** Enables alternate car-following task under SUMMATIVE and ANTICIPATION_RELIANCE (default: false). */
@Option(names = {"--alternateCarFollowingTask"}, description = "Enables alternate car-following task (exponential).",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> alternateCarFollowingTask = listOf(false);
/** Enables free acceleration task under ATTENTION_MATRIX (default: false). */
@Option(names = {"--freeAccelerationTask"},
description = "Enables free acceleration task, useful when updateTimeAdaptation is true.", defaultValue = "false",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> freeAccelerationTask = listOf(false);
/** Enables traffic lights task under ATTENTION_MATRIX (default: false). */
@Option(names = {"--trafficLightsTask"}, description = "Enables traffic light task.", defaultValue = "false", split = "\\|",
splitSynopsisLabel = "|", negatable = true)
private List<Boolean> trafficLightsTask = listOf(false);
/** Enables signal task under ATTENTION_MATRIX (default: true). */
@Option(names = {"--signalTask"}, description = "Enables signal task.", defaultValue = "true", split = "\\|",
splitSynopsisLabel = "|", negatable = true)
private List<Boolean> signalTask = listOf(true);
/** Enables lane-changing task (default: true). */
@Option(names = {"--laneChangingTask"}, description = "Enables lane-change task.", defaultValue = "true", split = "\\|",
splitSynopsisLabel = "|", negatable = true)
private List<Boolean> laneChangingTask = listOf(true);
/** Enables alternate lane-changing task under SUMMATIVE and ANTICIPATION_RELIANCE (default: false). */
@Option(names = {"--alternateLaneChangingTask"}, description = "Enables alternate lane-changing task (lane change desire).",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> alternateLaneChangingTask = listOf(false);
/** Enables cooperation task under ATTENTION_MATRIX (default: true). */
@Option(names = {"--cooperationTask"}, description = "Enables cooperation task.", defaultValue = "true", split = "\\|",
splitSynopsisLabel = "|", negatable = true)
private List<Boolean> cooperationTask = listOf(true);
/** Enables conflicts task under ATTENTION_MATRIX (default: false). */
@Option(names = {"--conflictsTask"}, description = "Enables conflict task.", defaultValue = "false", split = "\\|",
splitSynopsisLabel = "|", negatable = true)
private List<Boolean> conflictsTask = listOf(false);
/** Enables road-side distraction task (default: false). */
@Option(names = {"--roadSideDistractionTask"}, description = "Enables road-side distraction task.", defaultValue = "false",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> roadSideDistractionTask = listOf(false);
// Fuller -> Adaptations
/** Enables behavioral speed adaptation. */
@Option(names = {"--speedAdaptation"}, description = "Enables behavioral speed adaptation.", defaultValue = "true",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> speedAdaptation = listOf(true);
/** Enables behavioral headway adaptation. */
@Option(names = {"--headwayAdaptation"}, description = "Enables behavioral headway adaptation.", defaultValue = "true",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> headwayAdaptation = listOf(true);
/** Enables behavioral voluntary lane change adaptation. */
@Option(names = {"--laneChangeAdaptation"}, description = "Enables behavioral voluntary lane change adaptation.",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> laneChangeAdaptation = listOf(false);
/** Enables behavioral update time adaptation under ATTENTION_MATRIX. */
@Option(names = {"--updateTimeAdaptation"}, description = "Enables behavioral update time adaptation.",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> updateTimeAdaptation = listOf(false);
// Social interactions
/** Enables tailgating. Without tailgating, any social interaction still results in social pressure. */
@Option(names = {"--tailgating"},
description = "Enables tailgating. Without tailgating, any social interaction still results in social pressure.",
defaultValue = "false", split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> tailgating = listOf(false);
/** Enables lane changes due to social pressure. */
@Option(names = {"--socioLaneChange"}, description = "Enables lane changes due to social pressure.", defaultValue = "false",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> socioLaneChange = listOf(false);
/** Enables speed increase due to social pressure. */
@Option(names = {"--socioSpeed"}, description = "Enables speed increase due to social pressure.", defaultValue = "false",
split = "\\|", splitSynopsisLabel = "|", negatable = true)
private List<Boolean> socioSpeed = listOf(false);
/**
* Shorthand to create a list of default value(s). This is used instead of {@code List.of()} to return a mutable list.
* @param <V> value type
* @param values values
* @return list of default value(s)
*/
@SafeVarargs
private static <V> List<V> listOf(final V... values)
{
return Stream.of(values).collect(Collectors.toCollection(ArrayList::new));
}
/**
* Constructor that will use default NL.CAR and NL.TRUCK GTU types.
* @param lmrsProvider provider for LMRS implementation, e.g. {@code Lmrs::new}
* @throws NullPointerException when lmrsProvider is null
*/
public LmrsFactory(final TacticalPlannerProvider<T> lmrsProvider)
{
this(List.of(DefaultsNl.CAR, DefaultsNl.TRUCK), lmrsProvider);
}
/**
* Constructor with given GTU types and default LMRS implementation class.
* @param gtuTypes GTU types
* @param lmrsProvider provider for LMRS implementation, e.g. {@code Lmrs::new}
* @throws NullPointerException when gtuTypes or lmrsProvider is null
*/
public LmrsFactory(final List<GtuType> gtuTypes, final TacticalPlannerProvider<T> lmrsProvider)
{
Throw.whenNull(gtuTypes, "gtuTypes");
Throw.whenNull(lmrsProvider, "lmrsProvider");
this.gtuTypes = gtuTypes.stream().map((g) -> g.getId()).collect(Collectors.toCollection(ArrayList::new));
this.lmrsProvider = listOf(lmrsProvider);
}
/**
* Constructor with given GTU types and LMRS implementation class per GTU type.
* @param gtuTypes GTU types
* @param lmrsProviders providers for LMRS implementation for each GTU type, e.g. {@code Lmrs::new}, may contain
* {@code null} values (except first element)
* @throws NullPointerException when gtuTypes or lmrsProviders is null
* @throws IllegalArgumentException when gtuTypes and lmrsProviders are not of equal size
*/
public LmrsFactory(final List<GtuType> gtuTypes, final List<TacticalPlannerProvider<T>> lmrsProviders)
{
Throw.whenNull(gtuTypes, "gtuTypes");
Throw.whenNull(lmrsProviders, "lmrsProviders");
Throw.when(gtuTypes.size() != lmrsProviders.size(), IllegalArgumentException.class,
"gtuTypes and lmrsProviders are not of equal size");
this.gtuTypes = gtuTypes.stream().map((g) -> g.getId()).collect(Collectors.toList());
this.lmrsProvider = new ArrayList<>(lmrsProviders);
}
/**
* Sets the factory to one-shot mode. All {@linkplain Setting}s, parameters and correlations set or added between a call to
* this method and to {@link #create(LaneBasedGtu) create()} are removed from the factory after the call to
* {@link #create(LaneBasedGtu) create()}.
*/
@Override
public void setOneShotMode()
{
super.setOneShotMode();
this.state = new LinkedHashMap<>();
}
/**
* Resets the factory to the state when {@link #setOneShotMode()} was called. Note that the parameter factory resets its own
* state when it has set parameter values.
* @param <V> setting value type
*/
@SuppressWarnings("unchecked")
protected <V> void resetState()
{
if (this.state == null)
{
return;
}
for (Entry<Setting<?>, List<?>> entry : this.state.entrySet())
{
List<V> values = (List<V>) entry.getKey().getListFunction().apply(this);
values.clear();
values.addAll((List<V>) entry.getValue());
}
this.state = null;
}
/**
* Sets the random number stream.
* @param stream random number stream
* @return this factory for method chaining
*/
@SuppressWarnings("hiddenfield")
public LmrsFactory<T> setStream(final StreamInterface stream)
{
this.stream = stream;
this.vGainDist = new ContinuousDistSpeed(new DistLogNormal(stream, 3.379, 0.4), SpeedUnit.KM_PER_HOUR);
this.sigmaDist = new DistTriangular(stream, 0.0, 0.25, 1.0);
this.fSpeedDist = new DistNormalTrunc(stream, 123.7 / 120.0, 0.1, 0.8, 50.0);
return this;
}
/**
* Sets the setting value for all GTU types.
* @param <V> value type
* @param setting setting
* @param value value used for all GTU types
* @return this factory for method chaining
*/
public <V> LmrsFactory<T> set(final Setting<V> setting, final V value)
{
Throw.whenNull(value, "value");
final List<V> values = setting.getListFunction().apply(this);
saveState(setting, values);
values.clear();
values.add(value);
return this;
}
/**
* Sets the setting value for given GTU type.
* @param <V> value type
* @param setting setting
* @param value value
* @param gtuType GTU type
* @return this factory for method chaining
* @throws IllegalArgumentException when the GTU type is not known
*/
public <V> LmrsFactory<T> set(final Setting<V> setting, final V value, final GtuType gtuType)
{
Throw.whenNull(value, "value");
Throw.whenNull(gtuType, "gtuType");
int gtuTypeIndex = this.gtuTypes.indexOf(gtuType.getId());
Throw.when(gtuTypeIndex < 0, IllegalArgumentException.class, "GTU type %s not defined.", gtuType.getId());
final List<V> values = setting.getListFunction().apply(this);
saveState(setting, values);
/*
* Append with null values if not going from 1 to N but from M to N. This is a weird state with an incomplete command
* line argument, and code setting a setting. In this state a default or global value is unknown. We try to deal with
* it, but when the setting is required for any GTU type M+1 through N an exception might arise later. This depends on
* whether the setting will also be set for those GTU types, or whether the setting is known for a parent type.
*/
while (values.size() < this.gtuTypes.size())
{
values.add(null);
}
values.set(gtuTypeIndex, value);
return this;
}
/**
* Saves the state of given setting when in one-shot mode and the setting was not already saved.
* @param <V> value type
* @param setting setting
* @param values value list
*/
protected <V> void saveState(final Setting<V> setting, final List<V> values)
{
if (this.state != null && !this.state.containsKey(setting))
{
this.state.put(setting, new ArrayList<>(values));
}
}
/**
* Returns value of setting for the GTU type.
* @param <V> value type
* @param setting setting
* @param gtuType GTU type
* @return value from value array for the GTU type
*/
// This method is for sub classes, which cannot access the value lists directly
protected <V> V get(final Setting<V> setting, final GtuType gtuType)
{
return get(setting.getListFunction().apply(this), gtuType, gtuType);
}
/**
* Returns value from value array for the GTU type.
* @param <V> value type
* @param values values
* @param gtuType GTU type
* @return value from value array for the GTU type
*/
protected <V> V get(final List<V> values, final GtuType gtuType)
{
return get(values, gtuType, gtuType);
}
/**
* Returns value from value array for the GTU type.
* @param <V> value type
* @param values values
* @param gtuType GTU type
* @param originalGtuType original GTU type (only for possible exception message)
* @return value from value array for the GTU type
*/
private <V> V get(final List<V> values, final GtuType gtuType, final GtuType originalGtuType)
{
Throw.when(values.size() > 1 && this.gtuTypes.size() > values.size(), IllegalArgumentException.class,
"Argument has %s values %s but %s GTU types are defined.", values.size(), values, this.gtuTypes.size());
if (values.size() == 1)
{
return values.get(0);
}
int index = this.gtuTypes.indexOf(gtuType.getId());
if (index < 0 || values.get(index) == null)
{
return get(values,
gtuType.getParent().orElseThrow(() -> new IllegalArgumentException(
"Unable to obtain setting value for GTU type " + originalGtuType.getId() + " or any parent.")),
originalGtuType);
}
return values.get(index);
}
@Override
public Parameters getParameters(final GtuType gtuType) throws ParameterException
{
ParameterSet parameters = new ParameterSet();
parameters.setDefaultParameters(LmrsUtil.class);
parameters.setDefaultParameters(LmrsParameters.class);
parameters.setDefaultParameters(AbstractIdm.class);
parameters.setDefaultParameters(ConflictUtil.class);
parameters.setDefaultParameter(ParameterTypes.T0);
parameters.setDefaultParameter(ParameterTypes.LANE_STRUCTURE);
parameters.setDefaultParameter(ParameterTypes.LOOKBACK);
parameters.setDefaultParameter(ParameterTypes.LOOKAHEAD);
parameters.setDefaultParameter(ParameterTypes.VCONG);
parameters.setDefaultParameter(ParameterTypes.LCDUR);
// Fuller
if (!FullerImplementation.NONE.equals(get(this.fullerImplementation, gtuType)))
{
parameters.setParameter(ParameterTypes.TR, Duration.ZERO);
parameters.setDefaultParameter(AdaptationSituationalAwareness.TR_MAX);
parameters.setParameter(ChannelFuller.EST_FACTOR, 1.0);
parameters.setParameter(Fuller.OVER_EST,
this.stream.nextDouble() <= get(this.fractionOverEstimation, gtuType) ? 1.0 : -1.0);
parameters.setDefaultParameter(ChannelTaskScan.TDSCAN);
if (get(this.headwayAdaptation, gtuType))
{
parameters.setDefaultParameter(AdaptationHeadway.BETA_T);
}
if (get(this.speedAdaptation, gtuType))
{
parameters.setDefaultParameter(AdaptationSpeed.BETA_V0);
}
if (FullerImplementation.ATTENTION_MATRIX.equals(get(this.fullerImplementation, gtuType)))
{
// Attention matrix
parameters.setDefaultParameters(ChannelFuller.class);
parameters.setDefaultParameters(ChannelMental.class);
if (get(this.updateTimeAdaptation, gtuType))
{
parameters.setDefaultParameter(AdaptationUpdateTime.DT_MIN);
parameters.setDefaultParameter(AdaptationUpdateTime.DT_MAX);
}
if (get(this.carFollowingTask, gtuType))
{
parameters.setDefaultParameter(ArTaskCarFollowingExp.HEXP);
}
if (get(this.trafficLightsTask, gtuType) || get(this.conflictsTask, gtuType))
{
// TODO parameters of enhanced intersection model
parameters.setDefaultParameter(ChannelTaskConflict.HEGO);
parameters.setDefaultParameter(ChannelTaskConflict.HCONF);
}
if (get(this.signalTask, gtuType))
{
parameters.setDefaultParameter(ChannelTaskSignal.TDSIGNAL);
}
}
else
{
// Summative or anticipation reliance
parameters.setDefaultParameters(Fuller.class);
parameters.setDefaultParameters(SumFuller.class);
parameters.setDefaultParameter(AdaptationSituationalAwareness.SA);
parameters.setDefaultParameter(AdaptationSituationalAwareness.SA_MIN);
parameters.setDefaultParameter(AdaptationSituationalAwareness.SA_MAX);
if (FullerImplementation.ANTICIPATION_RELIANCE.equals(get(this.fullerImplementation, gtuType)))
{
parameters.setDefaultParameter(ArFuller.ALPHA);
parameters.setDefaultParameter(ArFuller.BETA);
}
if (get(this.alternateCarFollowingTask, gtuType))
{
parameters.setDefaultParameter(ArTaskCarFollowingExp.HEXP);
}
}
}
// Social interactions
if (anySocialInteractions())
{
parameters.setDefaultParameter(Tailgating.RHO);
}
// Alternate default values in case of social interactions, including distributions for the speed-leading strategy
if (get(this.tailgating, gtuType))
{
parameters.setParameter(ParameterTypes.TMAX, Duration.ofSI(1.6));
}
if (get(this.socioLaneChange, gtuType) || get(this.socioSpeed, gtuType))
{
parameters.setParameter(LmrsParameters.VGAIN, this.vGainDist.get());
parameters.setParameter(LmrsParameters.SOCIO, this.sigmaDist.draw());
}
parameters.setParameter(ParameterTypes.FSPEED, this.fSpeedDist.draw());
return parameters;
}
@Override
public T create(final LaneBasedGtu gtu) throws GtuException
{
GtuType gtuType = gtu.getType();
// Car-following model
DesiredSpeedModel desiredSpeedModel =
get(this.socioSpeed, gtuType) ? new SocioDesiredSpeed(AbstractIdm.DESIRED_SPEED) : AbstractIdm.DESIRED_SPEED;
CarFollowingModel cfModel = get(this.carFollowingModel, gtuType).apply(AbstractIdm.HEADWAY, desiredSpeedModel);
// Perception
LanePerception perception = getPerception(gtu);
// Tactical planner
Synchronization sync = get(this.synchronization, gtuType);
Cooperation coop = get(this.cooperation, gtuType);
GapAcceptance gapAccept = get(this.gapAcceptance, gtuType);
Tailgating tail = get(this.tailgating, gtuType) ? Tailgating.PRESSURE
: (anySocialInteractions() ? Tailgating.RHO_ONLY : Tailgating.NONE);
T tacticalPlanner = get(this.lmrsProvider, gtuType).from(cfModel, gtu, perception, sync, coop, gapAccept, tail);
// Mandatory incentives
if (get(this.incentiveRoute, gtuType))
{
tacticalPlanner.addMandatoryIncentive(IncentiveRoute.SINGLETON);
}
if (get(this.incentiveGetInLane, gtuType))
{
tacticalPlanner.addMandatoryIncentive(IncentiveGetInLane.SINGLETON);
}
get(this.customMandatoryIncentives, gtuType).forEach((s) -> tacticalPlanner.addMandatoryIncentive(s.get()));
// Voluntary incentives
if (get(this.incentiveSpeedWithCourtesy, gtuType))
{
tacticalPlanner.addVoluntaryIncentive(IncentiveSpeedWithCourtesy.SINGLETON);
}
if (get(this.incentiveCourtesy, gtuType))
{
tacticalPlanner.addVoluntaryIncentive(IncentiveCourtesy.SINGLETON);
}
if (get(this.incentiveQueue, gtuType))
{
tacticalPlanner.addVoluntaryIncentive(IncentiveQueue.SINGLETON);
}
if (get(this.incentiveStayRight, gtuType))
{
tacticalPlanner.addVoluntaryIncentive(IncentiveStayRight.SINGLETON);
}
if (get(this.incentiveKeep, gtuType))
{
tacticalPlanner.addVoluntaryIncentive(IncentiveKeep.SINGLETON);
}
if (get(this.socioLaneChange, gtuType))
{
tacticalPlanner.addVoluntaryIncentive(IncentiveSocioSpeed.SINGLETON);
}
get(this.customVoluntaryIncentives, gtuType).forEach((s) -> tacticalPlanner.addVoluntaryIncentive(s.get()));
// Acceleration incentives
if (get(this.accelerationSpeedLimitTransition, gtuType))
{
tacticalPlanner.addAccelerationIncentive(AccelerationSpeedLimitTransition.SINGLETON);
}
if (get(this.accelerationTrafficLights, gtuType))
{
tacticalPlanner.addAccelerationIncentive(AccelerationTrafficLights.SINGLETON);
}
if (get(this.accelerationConflicts, gtuType))
{
tacticalPlanner.addAccelerationIncentive(new AccelerationConflicts());
}
if (get(this.accelerationNoRightOvertake, gtuType))
{
tacticalPlanner.addAccelerationIncentive(AccelerationNoRightOvertake.SINGLETON);
}
get(this.customAccelerationIncentives, gtuType).forEach((s) -> tacticalPlanner.addAccelerationIncentive(s.get()));
resetState();
return tacticalPlanner;
}
/**
* Returns whether social interactions are at play for any of the GTU types.
* @return whether social interactions are at play for any of the GTU types
*/
private boolean anySocialInteractions()
{
return this.tailgating.contains(true) || this.socioLaneChange.contains(true) || this.socioSpeed.contains(true);
}
/**
* Returns perception for the GTU.
* @param gtu GTU
* @return perception for the GTU
*/
protected LanePerception getPerception(final LaneBasedGtu gtu)
{
GtuType gtuType = gtu.getType();
Mental mental;
Estimation estimation;
Anticipation anticipation;
if (FullerImplementation.ATTENTION_MATRIX.equals(get(this.fullerImplementation, gtuType)))
{
// tasks
LinkedHashSet<Function<LanePerception, Set<ChannelTask>>> taskSuppliers = new LinkedHashSet<>();
addChannelTask(taskSuppliers, get(this.carFollowingTask, gtuType), ChannelTaskCarFollowing.SUPPLIER);
addChannelTask(taskSuppliers, get(this.freeAccelerationTask, gtuType), ChannelTaskAcceleration.SUPPLIER);
addChannelTask(taskSuppliers, get(this.trafficLightsTask, gtuType), ChannelTaskTrafficLight.SUPPLIER);
addChannelTask(taskSuppliers, get(this.signalTask, gtuType), ChannelTaskSignal.SUPPLIER);
addChannelTask(taskSuppliers, get(this.laneChangingTask, gtuType), ChannelTaskLaneChange.SUPPLIER);
addChannelTask(taskSuppliers, get(this.cooperationTask, gtuType), ChannelTaskCooperation.SUPPLIER);
// TODO update conflict channel task to better intersection/infrastructure based model
addChannelTask(taskSuppliers, get(this.conflictsTask, gtuType), ChannelTaskConflict.SUPPLIER);
addChannelTask(taskSuppliers, get(this.roadSideDistractionTask, gtuType),
new ChannelTaskRoadSideDistraction.Supplier(gtu));
addChannelTask(taskSuppliers, true, ChannelTaskScan.SUPPLIER);
// behavioral adaptation
Set<BehavioralAdaptation> behavioralAdapatations = new LinkedHashSet<>();
if (get(this.speedAdaptation, gtuType))
{
behavioralAdapatations.add(new AdaptationSpeedChannel());
}
if (get(this.headwayAdaptation, gtuType))
{
behavioralAdapatations.add(new AdaptationHeadway());
}
if (get(this.laneChangeAdaptation, gtuType))
{
behavioralAdapatations.add(AdaptationLaneChangeDesire.SINGLETON);
}
if (get(this.updateTimeAdaptation, gtuType))
{
behavioralAdapatations.add(AdaptationUpdateTime.SINGLETON);
}
mental = new ChannelFuller(taskSuppliers, behavioralAdapatations);
estimation = FactorEstimation.SINGLETON;
anticipation = get(this.temporalAnticipation, gtuType) ? Anticipation.CONSTANT_SPEED : Anticipation.NONE;
}
else if (FullerImplementation.NONE.equals(get(this.fullerImplementation, gtuType)))
{
mental = null;
estimation = Estimation.NONE;
anticipation = Anticipation.NONE;
}
else
{
// SUMMATIVE or ANTICIPATION_RELIANCE
Set<ArTask> tasks = new LinkedHashSet<>();
FullerImplementation fullerImpl = get(this.fullerImplementation, gtuType);
// tasks
if (get(this.carFollowingTask, gtuType))
{
Throw.when(get(this.alternateCarFollowingTask, gtuType), IllegalStateException.class,
"Both carFollowingTask and alternateCarFollowingTask are true");
tasks.add(ArTaskCarFollowing.SINGLETON);
}
else if (get(this.alternateCarFollowingTask, gtuType))
{
tasks.add(ArTaskCarFollowingExp.SINGLETON);
}
if (get(this.laneChangingTask, gtuType))
{
Throw.when(get(this.alternateLaneChangingTask, gtuType), IllegalStateException.class,
"Both laneChangingTask and alternateLaneChangingTask are true");
tasks.add(ArTaskLaneChanging.SINGLETON);
}
else if (get(this.alternateLaneChangingTask, gtuType))
{
tasks.add(ArTaskLaneChangingD.SINGLETON);
}
if (get(this.roadSideDistractionTask, gtuType))
{
tasks.add(new ArTaskRoadSideDistraction(gtu));
}
// behavioral adaptation
Set<BehavioralAdaptation> behavioralAdapatations = new LinkedHashSet<>();
if (get(this.speedAdaptation, gtuType))
{
behavioralAdapatations.add(new AdaptationSpeed());
}
if (get(this.headwayAdaptation, gtuType))
{
behavioralAdapatations.add(new AdaptationHeadway());
}
if (get(this.laneChangeAdaptation, gtuType))
{
behavioralAdapatations.add(AdaptationLaneChangeDesire.SINGLETON);
}
behavioralAdapatations.add(new AdaptationSituationalAwareness());
// summative or anticipation reliance
if (FullerImplementation.SUMMATIVE.equals(fullerImpl))
{
mental = new SumFuller<>(tasks, behavioralAdapatations);
}
else if (FullerImplementation.ANTICIPATION_RELIANCE.equals(fullerImpl))
{
mental = new ArFuller(tasks, behavioralAdapatations, get(this.primaryTask, gtuType));
}
else
{
throw new IllegalArgumentException("Unable to load Fuller model from setting " + fullerImpl);
}
estimation = FactorEstimation.SINGLETON;
anticipation = get(this.temporalAnticipation, gtuType) ? Anticipation.CONSTANT_SPEED : Anticipation.NONE;
}
// categories
LanePerception perception = new CategoricalLanePerception(gtu, mental);
perception.addPerceptionCategory(new DirectEgoPerception<>(perception));
perception.addPerceptionCategory(new DirectInfrastructurePerception(perception));
perception.addPerceptionCategory(new AnticipationTrafficPerception(perception));
if (FullerImplementation.NONE.equals(get(this.fullerImplementation, gtuType)))
{
perception.addPerceptionCategory(new DirectNeighborsPerception(perception, PerceivedGtuType.WRAP));
perception.addPerceptionCategory(new DirectIntersectionPerception(perception, PerceivedGtuType.WRAP));
}
else if (FullerImplementation.ATTENTION_MATRIX.equals(get(this.fullerImplementation, gtuType)))
{
perception.addPerceptionCategory(new NeighborsPerceptionChannel(perception, estimation, anticipation));
perception.addPerceptionCategory(new IntersectionPerceptionChannel(perception, estimation, anticipation));
}
else
{
PerceivedGtuType headwayGtuType = new AnticipationPerceivedGtuType(estimation, anticipation, () ->
{
return Try.assign(() -> gtu.getParameters().getParameter(ParameterTypes.TR),
"Unable to obtain reaction time parameter");
});
perception.addPerceptionCategory(new DirectNeighborsPerception(perception, headwayGtuType));
perception.addPerceptionCategory(new DirectIntersectionPerception(perception, headwayGtuType));
}
return perception;
}
/**
* Add channel task if required.
* @param taskSuppliers suppliers to add task suppliers to
* @param task whether to add task
* @param supplier supplier for the task
*/
private void addChannelTask(final LinkedHashSet<Function<LanePerception, Set<ChannelTask>>> taskSuppliers,
final boolean task, final Function<LanePerception, Set<ChannelTask>> supplier)
{
if (task)
{
taskSuppliers.add(supplier);
}
}
// *************************************************************************************************************************
// *********************************************** Nested Classes and Enums ************************************************
// *************************************************************************************************************************
/**
* Provider for the correct tactical planner implementation.
* @param <P> tactical planner class
*/
public interface TacticalPlannerProvider<P extends AbstractIncentivesTacticalPlanner>
{
/**
* Constructs an instance of the correct tactical planner class.
* @param carFollowingModel car-following model
* @param gtu GTU
* @param lanePerception perception
* @param synchronization type of synchronization
* @param cooperation type of cooperation
* @param gapAcceptance gap-acceptance
* @param tailgating tailgating
* @return instance of the correct tactical planner class
*/
P from(CarFollowingModel carFollowingModel, LaneBasedGtu gtu, LanePerception lanePerception,
Synchronization synchronization, Cooperation cooperation, GapAcceptance gapAcceptance, Tailgating tailgating);
}
/**
* Settings class with static instances.
* @param <V> setting value type
*/
public static final class Setting<V>
{
// LMRS
/** Car-following model: IDM or IDM_PLUS (default). */
public static final Setting<BiFunction<DesiredHeadwayModel, DesiredSpeedModel, CarFollowingModel>> CAR_FOLLOWING_MODEL =
new Setting<>((factory) -> factory.carFollowingModel);
/** Lane change synchronization: PASSIVE (default), PASSIVE_MOVING, ALIGN_GAP or ACTIVE. */
public static final Setting<Synchronization> SYNCHRONIZATION = new Setting<>((factory) -> factory.synchronization);
/** Lane change cooperation: PASSIVE (default), PASSIVE_MOVING or ACTIVE. */
public static final Setting<Cooperation> COOPERATION = new Setting<>((factory) -> factory.cooperation);
/** Lane change gap-acceptance: INFORMED (default) or EGO_HEADWAY. */
public static final Setting<GapAcceptance> GAP_ACCEPTANCE = new Setting<>((factory) -> factory.gapAcceptance);
// LMRS -> Mandatory incentives
/** Mandatory lane change incentive for route (default: true). */
public static final Setting<Boolean> INCENTIVE_ROUTE = new Setting<>((factory) -> factory.incentiveRoute);
/** Mandatory lane change incentive to join slow traffic at split and not block other traffic (default: false). */
public static final Setting<Boolean> INCENTIVE_GET_IN_LANE = new Setting<>((factory) -> factory.incentiveGetInLane);
/** Custom mandatory lane change incentives. */
public static final Setting<Set<Supplier<MandatoryIncentive>>> CUSTOM_MANDATORY_INCENTIVES =
new Setting<>((factory) -> factory.customMandatoryIncentives);
// LMRS -> Voluntary incentives
/** Voluntary lane change incentive for speed with courtesy (default: true). */
public static final Setting<Boolean> INCENTIVE_SPEED_WITH_COURTESY =
new Setting<>((factory) -> factory.incentiveSpeedWithCourtesy);
/** Voluntary lane change incentive for cooperative lane changes (default: false). */
public static final Setting<Boolean> INCENTIVE_COURTESY = new Setting<>((factory) -> factory.incentiveCourtesy);
/** Voluntary lane change incentive to join the shortest queue (default: false). */
public static final Setting<Boolean> INCENTIVE_QUEUE = new Setting<>((factory) -> factory.incentiveQueue);
/** Voluntary lane change incentive for trucks to stay in slowest rightmost two lanes (default: false). */
public static final Setting<Boolean> INCENTIVE_STAY_RIGHT = new Setting<>((factory) -> factory.incentiveStayRight);
/** Voluntary lane change incentive to keep to the slow lane (default: true). */
public static final Setting<Boolean> INCENTIVE_KEEP = new Setting<>((factory) -> factory.incentiveKeep);
/** Custom voluntary lane change incentives. */
public static final Setting<Set<Supplier<VoluntaryIncentive>>> CUSTOM_VOLUNTARY_INCENTIVES =
new Setting<>((factory) -> factory.customVoluntaryIncentives);
// LMRS -> Acceleration incentives
/** Acceleration incentive to slow down prior to a lower speed limit (default: false). */
public static final Setting<Boolean> ACCELERATION_SPEED_LIMIT_TRANSITION =
new Setting<>((factory) -> factory.accelerationSpeedLimitTransition);
/** Acceleration incentive to approach traffic lights (default: false). */
public static final Setting<Boolean> ACCELERATION_TRAFFIC_LIGHTS =
new Setting<>((factory) -> factory.accelerationTrafficLights);
/** Acceleration incentive to approach intersection conflicts (default: false). */
public static final Setting<Boolean> ACCELERATION_CONFLICTS = new Setting<>((factory) -> factory.accelerationConflicts);
/** Acceleration incentive to not overtake traffic in the left lane (default: false). */
public static final Setting<Boolean> ACCELERATION_NO_RIGHT_OVERTAKE =
new Setting<>((factory) -> factory.accelerationNoRightOvertake);
/** Custom acceleration incentives. */
public static final Setting<Set<Supplier<AccelerationIncentive>>> CUSTOM_ACCELERATION_INCENTIVES =
new Setting<>((factory) -> factory.customAccelerationIncentives);
// Fuller
/** Implementation of Fuller: NONE (default), SUMMATIVE, ANTICIPATION_RELIANCE or ATTENTION_MATRIX. */
public static final Setting<FullerImplementation> FULLER_IMPLEMENTATION =
new Setting<>((factory) -> factory.fullerImplementation);
/** Id of primary task under ANTICIPATION_RELIANCE (default: lane-changing). */
public static final Setting<String> PRIMARY_TASK = new Setting<>((factory) -> factory.primaryTask);
/** Enables temporal constant-speed anticipation (default: true). */
public static final Setting<Boolean> TEMPORAL_ANTICIPATION = new Setting<>((factory) -> factory.temporalAnticipation);
/** Fraction of drivers over-estimating speed and distance [0..1] (default: 0). */
public static final Setting<Double> FRACTION_OVERESTIMATION =
new Setting<>((factory) -> factory.fractionOverEstimation);
// Fuller -> Tasks
/** Enables car-following task (default: true). */
public static final Setting<Boolean> TASK_CAR_FOLLOWING = new Setting<>((factory) -> factory.carFollowingTask);
/** Enables alternate car-following task (default: false). */
public static final Setting<Boolean> TASK_CAR_FOLLOWING_ALTERNATE =
new Setting<>((factory) -> factory.alternateCarFollowingTask);
/** Enables free acceleration task under ATTENTION_MATRIX (default: false). */
public static final Setting<Boolean> TASK_FREE_ACCELERATION = new Setting<>((factory) -> factory.freeAccelerationTask);
/** Enables traffic lights task under ATTENTION_MATRIX (default: false). */
public static final Setting<Boolean> TASK_TRAFFIC_LIGHTS = new Setting<>((factory) -> factory.trafficLightsTask);
/** Enables signal task under ATTENTION_MATRIX (default: true). */
public static final Setting<Boolean> TASK_SIGNAL = new Setting<>((factory) -> factory.signalTask);
/** Enables lane-changing task (default: true). */
public static final Setting<Boolean> TASK_LANE_CHANGE = new Setting<>((factory) -> factory.laneChangingTask);
/** Enables alternate lane-changing task (default: false). */
public static final Setting<Boolean> TASK_LANE_CHANGE_ALTERNATE =
new Setting<>((factory) -> factory.alternateLaneChangingTask);
/** Enables cooperation task under ATTENTION_MATRIX (default: true). */
public static final Setting<Boolean> TASK_COOPERATION = new Setting<>((factory) -> factory.cooperationTask);
/** Enables conflicts task under ATTENTION_MATRIX (default: false). */
public static final Setting<Boolean> TASK_CONFLICTS = new Setting<>((factory) -> factory.conflictsTask);
/** Enables road-side distraction task (default: false). */
public static final Setting<Boolean> TASK_ROADSIDE_DISTRACTION =
new Setting<>((factory) -> factory.roadSideDistractionTask);
// Fuller -> Adaptations
/** Enables behavioral speed adaptation (default: true). */
public static final Setting<Boolean> ADAPTATION_SPEED = new Setting<>((factory) -> factory.speedAdaptation);
/** Enables behavioral headway adaptation (default: true). */
public static final Setting<Boolean> ADAPTATION_HEADWAY = new Setting<>((factory) -> factory.headwayAdaptation);
/** Enables behavioral voluntary lane change adaptation (default: false). */
public static final Setting<Boolean> ADAPTATION_LANE_CHANGE = new Setting<>((factory) -> factory.laneChangeAdaptation);
/** Enables behavioral update time adaptation under ATTENTION_MATRIX (default: false). */
public static final Setting<Boolean> ADAPTATION_UPDATE_TIME = new Setting<>((factory) -> factory.updateTimeAdaptation);
// Social interactions
/** Enables tailgating. Without tailgating, any social interaction still results in social pressure (default: false). */
public static final Setting<Boolean> SOCIO_TAILGATING = new Setting<>((factory) -> factory.tailgating);
/** Enables lane changes due to social pressure (default: false). */
public static final Setting<Boolean> SOCIO_LANE_CHANGE = new Setting<>((factory) -> factory.socioLaneChange);
/** Enables speed increase due to social pressure (default: false). */
public static final Setting<Boolean> SOCIO_SPEED = new Setting<>((factory) -> factory.socioSpeed);
/** Function to return the right list. */
private final Function<LmrsFactory<?>, List<V>> listFunction;
/**
* Constructor.
* @param listFunction function to return the right list from the factory
*/
private Setting(final Function<LmrsFactory<?>, List<V>> listFunction)
{
this.listFunction = listFunction;
}
/**
* Returns the list function to return the right list from the factory.
* @return list function to return the right list from the factory
*/
public Function<LmrsFactory<?>, List<V>> getListFunction()
{
return this.listFunction;
}
}
/**
* Type of management of different tasks.
*/
public enum FullerImplementation
{
/** No perception model. */
NONE,
/** Task demands are summed. */
SUMMATIVE,
/**
* Task demand based on one primary and multiple auxiliary tasks. Requires parameters ALPHA and BETA and the id of the
* primary task.
*/
ANTICIPATION_RELIANCE,
/** Task demand as steady-state in the attention matrix. */
ATTENTION_MATRIX;
}
/**
* Argument converter for car-following supplier.
*/
private static class CarFollowingModelConverter
implements ITypeConverter<BiFunction<DesiredHeadwayModel, DesiredSpeedModel, CarFollowingModel>>
{
/**
* Constructor.
*/
@SuppressWarnings({"unused", "redundantModifier"}) // referred to in @Option annotation
public CarFollowingModelConverter()
{
//
}
@Override
public BiFunction<DesiredHeadwayModel, DesiredSpeedModel, CarFollowingModel> convert(final String value)
throws Exception
{
switch (value.toLowerCase())
{
case "idm":
return (h, v) -> new Idm(h, v);
case "idm_plus":
return (h, v) -> new IdmPlus(h, v);
default:
throw new IllegalArgumentException(
"Unable to parse car following model " + value + ". Use any of IDM or IDM_PLUS.");
}
}
}
/**
* Argument converter for {@link Synchronization}.
*/
private static class SynchronizationConverter implements ITypeConverter<Synchronization>
{
/**
* Constructor.
*/
@SuppressWarnings({"unused", "redundantModifier"}) // referred to in @Option annotation
public SynchronizationConverter()
{
//
}
@Override
public Synchronization convert(final String value) throws Exception
{
switch (value.toLowerCase())
{
case "passive":
return Synchronization.PASSIVE;
case "passive_moving":
return Synchronization.PASSIVE_MOVING;
case "align_gap":
return Synchronization.ALIGN_GAP;
case "active":
return Synchronization.ACTIVE;
default:
throw new IllegalArgumentException("Unable to parse synchronization " + value
+ ". Use any of PASSIVE, PASSIVE_MOVING, ALIGN_GAP or ACTIVE.");
}
}
}
/**
* Argument converter for {@link Cooperation}.
*/
private static class CooperationConverter implements ITypeConverter<Cooperation>
{
/**
* Constructor.
*/
@SuppressWarnings({"unused", "redundantModifier"}) // referred to in @Option annotation
public CooperationConverter()
{
//
}
@Override
public Cooperation convert(final String value) throws Exception
{
switch (value.toLowerCase())
{
case "passive":
return Cooperation.PASSIVE;
case "passive_moving":
return Cooperation.PASSIVE_MOVING;
case "active":
return Cooperation.ACTIVE;
default:
throw new IllegalArgumentException(
"Unable to parse cooperation " + value + ". Use any of PASSIVE, PASSIVE_MOVING or ACTIVE.");
}
}
}
/**
* Argument converter for {@link GapAcceptance}.
*/
private static class GapAcceptanceConverter implements ITypeConverter<GapAcceptance>
{
/**
* Constructor.
*/
@SuppressWarnings({"unused", "redundantModifier"}) // referred to in @Option annotation
public GapAcceptanceConverter()
{
//
}
@Override
public GapAcceptance convert(final String value) throws Exception
{
switch (value.toLowerCase())
{
case "informed":
return GapAcceptance.INFORMED;
case "ego_headway":
return GapAcceptance.EGO_HEADWAY;
default:
throw new IllegalArgumentException(
"Unable to parse gap-acceptance " + value + ". Use any of INFORMED or EGO_HEADWAY.");
}
}
}
}