View Javadoc
1   package org.opentrafficsim.road.gtu.tactical.util.lmrs;
2   
3   import java.util.NoSuchElementException;
4   
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.opentrafficsim.base.OtsRuntimeException;
9   import org.opentrafficsim.base.parameters.ParameterException;
10  import org.opentrafficsim.base.parameters.ParameterTypeBoolean;
11  import org.opentrafficsim.base.parameters.ParameterTypeDouble;
12  import org.opentrafficsim.base.parameters.ParameterTypes;
13  import org.opentrafficsim.base.parameters.constraint.ConstraintInterface;
14  import org.opentrafficsim.road.gtu.LaneBasedGtu;
15  import org.opentrafficsim.road.gtu.perception.PerceptionCollectable;
16  import org.opentrafficsim.road.gtu.perception.RelativeLane;
17  import org.opentrafficsim.road.gtu.perception.categories.neighbors.NeighborsPerception;
18  import org.opentrafficsim.road.gtu.perception.object.PerceivedGtu;
19  import org.opentrafficsim.road.gtu.tactical.TacticalContextEgo;
20  import org.opentrafficsim.road.network.LanePosition;
21  
22  /**
23   * Interface for LMRS tailgating behavior.
24   * <p>
25   * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
26   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
27   * </p>
28   * @author Alexander Verbraeck
29   * @author Peter Knoppers
30   * @author Wouter Schakel
31   */
32  public interface Tailgating
33  {
34  
35      /** Social pressure applied to the leader. */
36      ParameterTypeDouble RHO = new ParameterTypeDouble("rho", "Social pressure", 0.0, ConstraintInterface.UNITINTERVAL);
37  
38      /** Parameter to deviate laterally for social pressure. */
39      ParameterTypeBoolean DEV_RHO = new ParameterTypeBoolean("dev_split", "Deviate laterally for social pressure.", false);
40  
41      /** No tailgating. */
42      Tailgating NONE = new Tailgating()
43      {
44          @Override
45          public void tailgate(final TacticalContextEgo context)
46          {
47              //
48          }
49  
50          @Override
51          public String toString()
52          {
53              return "NONE";
54          }
55      };
56  
57      /** No tailgating, but social pressure exists. */
58      Tailgating RHO_ONLY = new Tailgating()
59      {
60          @Override
61          public void tailgate(final TacticalContextEgo context)
62          {
63              PerceptionCollectable<PerceivedGtu,
64                      LaneBasedGtu> leaders = context.getPerception().getPerceptionCategoryOptional(NeighborsPerception.class)
65                              .orElseThrow(() -> new NoSuchElementException("No neighbors perception category."))
66                              .getLeaders(RelativeLane.CURRENT);
67              try
68              {
69                  if (leaders == null || leaders.isEmpty())
70                  {
71                      context.getParameters().setClaimedParameter(RHO, 0.0, this);
72                      Tailgating.deviate(context, 0.0);
73                      return;
74                  }
75                  Length x0 = context.getParameters().getParameter(ParameterTypes.LOOKAHEAD);
76                  Speed vGain = context.getParameters().getParameter(LmrsParameters.VGAIN);
77                  PerceivedGtu leader = leaders.first();
78                  Speed desiredSpeed = context.getDesiredSpeed();
79                  double rho = Tailgating.socialPressure(desiredSpeed, leader.getSpeed(), vGain, leader.getDistance(), x0);
80                  context.getParameters().setClaimedParameter(RHO, rho, this);
81                  Tailgating.deviate(context, rho);
82              }
83              catch (ParameterException exception)
84              {
85                  throw new OtsRuntimeException("Could not obtain or set parameter value.", exception);
86              }
87          }
88  
89          @Override
90          public String toString()
91          {
92              return "RHO_ONLY";
93          }
94      };
95  
96      /** Tailgating based on speed pressure. */
97      Tailgating PRESSURE = new Tailgating()
98      {
99          @Override
100         public void tailgate(final TacticalContextEgo context)
101         {
102             PerceptionCollectable<PerceivedGtu,
103                     LaneBasedGtu> leaders = context.getPerception().getPerceptionCategoryOptional(NeighborsPerception.class)
104                             .orElseThrow(() -> new NoSuchElementException("No neighbors perception category."))
105                             .getLeaders(RelativeLane.CURRENT);
106             try
107             {
108                 if (leaders == null || leaders.isEmpty())
109                 {
110                     context.getParameters().setClaimedParameter(RHO, 0.0, this);
111                     Tailgating.deviate(context, 0.0);
112                     return;
113                 }
114                 Duration t = context.getParameters().getParameter(ParameterTypes.T);
115                 Duration tMin = context.getParameters().getParameter(ParameterTypes.TMIN);
116                 Duration tMax = context.getParameters().getParameter(ParameterTypes.TMAX);
117                 Length x0 = context.getParameters().getParameter(ParameterTypes.LOOKAHEAD);
118                 Speed vGain = context.getParameters().getParameter(LmrsParameters.VGAIN);
119                 PerceivedGtu leader = leaders.first();
120                 Speed desiredSpeed = context.getDesiredSpeed();
121                 double rho = Tailgating.socialPressure(desiredSpeed, leader.getSpeed(), vGain, leader.getDistance(), x0);
122                 context.getParameters().setClaimedParameter(RHO, rho, this);
123                 double tNew = rho * tMin.si + (1.0 - rho) * tMax.si;
124                 if (tNew < t.si)
125                 {
126                     context.getParameters().setClaimedParameter(ParameterTypes.T, Duration.ofSI(tNew), LmrsUtil.T_KEY);
127                 }
128                 Tailgating.deviate(context, rho);
129             }
130             catch (ParameterException exception)
131             {
132                 throw new OtsRuntimeException("Could not obtain or set parameter value.", exception);
133             }
134         }
135 
136         @Override
137         public String toString()
138         {
139             return "PRESSURE";
140         }
141     };
142 
143     /**
144      * Returns a normalized social pressure, equal to (vDesired - vLead) / vGain.
145      * @param desiredSpeed desired speed
146      * @param leaderSpeed leader speed
147      * @param vGain vGain parameter
148      * @param headway headway to the leader
149      * @param x0 anticipation distance
150      * @return normalized social pressure
151      */
152     static double socialPressure(final Speed desiredSpeed, final Speed leaderSpeed, final Speed vGain, final Length headway,
153             final Length x0)
154     {
155         double dv = desiredSpeed.si - leaderSpeed.si;
156         if (dv < 0 || headway.gt(x0)) // larger headway may happen due to perception errors
157         {
158             return 0.0;
159         }
160         return 1.0 - Math.exp(-(dv / vGain.si) * (1.0 - (headway.si / x0.si)));
161     }
162 
163     /**
164      * Apply tailgating.
165      * @param context tactical information such as parameters and car-following model
166      */
167     void tailgate(TacticalContextEgo context);
168 
169     /**
170      * Add lateral deviation intent based on level of social pressure.
171      * @param context tactical information such as parameters and car-following model
172      * @param rho level of social pressure
173      * @throws ParameterException when the deviation parameter is not present
174      */
175     static void deviate(final TacticalContextEgo context, final double rho) throws ParameterException
176     {
177         if (context.getParameters().getParameter(DEV_RHO))
178         {
179             LanePosition position = context.getPosition();
180             // TODO: direction depends on left/right traffic
181             Length deviation = position.lane().getWidth(position.position()).minus(context.getWidth()).times(0.5 * rho);
182             context.addIntent(deviation, Length.ZERO);
183         }
184     }
185 
186 }