View Javadoc
1   package org.opentrafficsim.road.gtu.lane.changing;
2   
3   import static org.junit.jupiter.api.Assertions.assertEquals;
4   
5   import java.util.Collection;
6   import java.util.LinkedHashSet;
7   import java.util.Map;
8   
9   import javax.naming.NamingException;
10  
11  import org.djunits.unit.DurationUnit;
12  import org.djunits.unit.LengthUnit;
13  import org.djunits.unit.util.UNITS;
14  import org.djunits.value.vdouble.scalar.Acceleration;
15  import org.djunits.value.vdouble.scalar.Direction;
16  import org.djunits.value.vdouble.scalar.Duration;
17  import org.djunits.value.vdouble.scalar.Length;
18  import org.djunits.value.vdouble.scalar.Speed;
19  import org.djunits.value.vdouble.scalar.Time;
20  import org.djutils.draw.point.Point2d;
21  import org.junit.jupiter.api.Test;
22  import org.opentrafficsim.base.geometry.OtsLine2d;
23  import org.opentrafficsim.base.parameters.ParameterSet;
24  import org.opentrafficsim.base.parameters.ParameterTypes;
25  import org.opentrafficsim.core.definitions.DefaultsNl;
26  import org.opentrafficsim.core.dsol.AbstractOtsModel;
27  import org.opentrafficsim.core.dsol.OtsSimulator;
28  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
29  import org.opentrafficsim.core.gtu.GtuType;
30  import org.opentrafficsim.core.gtu.RelativePosition;
31  import org.opentrafficsim.core.network.LateralDirectionality;
32  import org.opentrafficsim.core.network.NetworkException;
33  import org.opentrafficsim.core.network.Node;
34  import org.opentrafficsim.core.perception.HistoryManagerDevs;
35  import org.opentrafficsim.road.DefaultTestParameters;
36  import org.opentrafficsim.road.definitions.DefaultsRoadNl;
37  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
38  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
39  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtuSimple;
40  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedCfLcTacticalPlanner;
41  import org.opentrafficsim.road.gtu.lane.tactical.following.AbstractIdm;
42  import org.opentrafficsim.road.gtu.lane.tactical.following.IdmPlusOld;
43  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.AbstractLaneChangeModel;
44  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Altruistic;
45  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Egoistic;
46  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneMovementStep;
47  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
48  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalRoutePlanner;
49  import org.opentrafficsim.road.network.RoadNetwork;
50  import org.opentrafficsim.road.network.lane.CrossSectionLink;
51  import org.opentrafficsim.road.network.lane.Lane;
52  import org.opentrafficsim.road.network.lane.LaneGeometryUtil;
53  import org.opentrafficsim.road.network.lane.LanePosition;
54  import org.opentrafficsim.road.network.lane.LaneType;
55  import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
56  
57  import nl.tudelft.simulation.dsol.SimRuntimeException;
58  
59  /**
60   * Test some very basic properties of lane change models.
61   * <p>
62   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
63   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
64   * </p>
65   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
66   */
67  public class LaneChangeModelTest extends AbstractOtsModel implements UNITS
68  {
69      /** */
70      private static final long serialVersionUID = 20150313;
71  
72      /** The network. */
73      private RoadNetwork network;
74  
75      /**
76       */
77      public LaneChangeModelTest()
78      {
79          super(new OtsSimulator("LaneChangeModelTest"));
80          this.network = new RoadNetwork("lane change model test network", getSimulator());
81      }
82  
83      /**
84       * Create a Link.
85       * @param network the network
86       * @param name name of the new Link
87       * @param from start node of the new Link
88       * @param to end node of the new Link
89       * @param width the width of the new Link
90       * @param simulator the simulator
91       * @return Link
92       * @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
93       *             or the end node of the link are not registered in the network
94       */
95      private static CrossSectionLink makeLink(final RoadNetwork network, final String name, final Node from, final Node to,
96              final Length width, final OtsSimulatorInterface simulator) throws NetworkException
97      {
98          // TODO create a LinkAnimation if the simulator is compatible with that.
99          // FIXME The current LinkAnimation is too bad to use...
100         Point2d[] coordinates = new Point2d[] {new Point2d(from.getPoint().x, from.getPoint().y),
101                 new Point2d(to.getPoint().x, to.getPoint().y)};
102         OtsLine2d line = new OtsLine2d(coordinates);
103         CrossSectionLink link =
104                 new CrossSectionLink(network, name, from, to, DefaultsNl.ROAD, line, null, LaneKeepingPolicy.KEEPRIGHT);
105         return link;
106     }
107 
108     /**
109      * Create one Lane.
110      * @param link the link that owns the new Lane
111      * @param id the id of the lane, has to be unique within the link
112      * @param laneType the type of the new Lane
113      * @param latPos the lateral position of the new Lane with respect to the design line of the link
114      * @param width the width of the new Lane
115      * @return Lane
116      * @throws NamingException on ???
117      * @throws NetworkException on ??
118      */
119     private static Lane makeLane(final CrossSectionLink link, final String id, final LaneType laneType, final Length latPos,
120             final Length width) throws NamingException, NetworkException
121     {
122         // XXX Decide what type of overtaking conditions we want in this test
123         Lane result = LaneGeometryUtil.createStraightLane(link, id, latPos, latPos, width, width, laneType,
124                 Map.of(DefaultsNl.VEHICLE, new Speed(100, KM_PER_HOUR)));
125         return result;
126     }
127 
128     /**
129      * Create a simple straight road with the specified number of Lanes.
130      * @param network the network
131      * @param name name of the Link
132      * @param from starting node of the new Lane
133      * @param to ending node of the new Lane
134      * @param laneType the type of GTU that can use the lanes
135      * @param laneCount number of lanes in the road
136      * @param simulator the simulator
137      * @return array containing the new Lanes
138      * @throws Exception when something goes wrong (should not happen)
139      */
140     public static Lane[] makeMultiLane(final RoadNetwork network, final String name, final Node from, final Node to,
141             final LaneType laneType, final int laneCount, final OtsSimulatorInterface simulator) throws Exception
142     {
143         Length width = new Length(laneCount * 4.0, METER);
144         final CrossSectionLink link = makeLink(network, name, from, to, width, simulator);
145         Lane[] result = new Lane[laneCount];
146         width = new Length(4.0, METER);
147         for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
148         {
149             // successive lanes have a more negative offset => more to the RIGHT
150             Length latPos = new Length((-0.5 - laneIndex) * width.getSI(), METER);
151             result[laneIndex] = makeLane(link, "lane." + laneIndex, laneType, latPos, width);
152         }
153         return result;
154     }
155 
156     /**
157      * Test that a vehicle in the left lane changes to the right lane if that is empty, or there is enough room.
158      * @throws Exception when something goes wrong (should not happen)
159      */
160     @Test
161     public final void changeRight() throws Exception
162     {
163         GtuType gtuType = DefaultsNl.CAR;
164         LaneType laneType = DefaultsRoadNl.TWO_WAY_LANE;
165         int laneCount = 2;
166         this.simulator.initialize(Time.ZERO, Duration.ZERO, new Duration(3600.0, DurationUnit.SECOND), this,
167                 HistoryManagerDevs.noHistory(this.simulator));
168         Lane[] lanes = makeMultiLane(this.network, "Road with two lanes",
169                 new Node(this.network, "From", new Point2d(0, 0), Direction.ZERO),
170                 new Node(this.network, "To", new Point2d(200, 0), Direction.ZERO), laneType, laneCount, this.simulator);
171 
172         // Let's see if adjacent lanes are accessible
173         // lanes: | 0 : 1 : 2 | in case of three lanes
174         assertEquals(0, lanes[0].accessibleAdjacentLanesLegal(LateralDirectionality.LEFT, gtuType).size(),
175                 "Leftmost lane should not have accessible adjacent lanes on the LEFT side");
176         assertEquals(1, lanes[0].accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT, gtuType).size(),
177                 "Leftmost lane should have one accessible adjacent lane on the RIGHT side");
178         assertEquals(1, lanes[1].accessibleAdjacentLanesLegal(LateralDirectionality.LEFT, gtuType).size(),
179                 "Rightmost lane should have one accessible adjacent lane on the LEFT side");
180         assertEquals(0, lanes[1].accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT, gtuType).size(),
181                 "Rightmost lane should not have accessible adjacent lanes on the RIGHT side");
182 
183         AbstractLaneChangeModel laneChangeModel = new Egoistic();
184         ParameterSet parameters = DefaultTestParameters.create();
185         // LaneBasedBehavioralCharacteristics drivingCharacteristics =
186         // new LaneBasedBehavioralCharacteristics(new IDMPlusOld(new Acceleration(1, METER_PER_SECOND_2), new Acceleration(
187         // 1.5, METER_PER_SECOND_2), new Length(2, METER), new Duration(1, SECOND), 1d), laneChangeModel);
188         LaneBasedGtu car = new LaneBasedGtu("ReferenceCar", gtuType, new Length(4, METER), new Length(2, METER),
189                 new Speed(150, KM_PER_HOUR), Length.instantiateSI(2.0), this.network);
190         LaneBasedStrategicalPlanner strategicalPlanner = new LaneBasedStrategicalRoutePlanner(
191                 new LaneBasedCfLcTacticalPlanner(new IdmPlusOld(), laneChangeModel, car), car);
192         car.setParameters(parameters);
193         car.init(strategicalPlanner, new LanePosition(lanes[1], new Length(100, METER)), new Speed(100, KM_PER_HOUR));
194         car.getTacticalPlanner().getPerception().perceive();
195         Collection<Headway> sameLaneGTUs = new LinkedHashSet<>();
196         sameLaneGTUs.add(new HeadwayGtuSimple(car.getId(), car.getType(), Length.ZERO, Length.ZERO, car.getLength(),
197                 car.getSpeed(), car.getAcceleration(), null));
198         Collection<Headway> preferredLaneGTUs = new LinkedHashSet<>();
199         Collection<Headway> nonPreferredLaneGTUs = new LinkedHashSet<>();
200         LaneMovementStep laneChangeModelResult = laneChangeModel.computeLaneChangeAndAcceleration(car, sameLaneGTUs,
201                 preferredLaneGTUs, nonPreferredLaneGTUs, new Speed(100, KM_PER_HOUR), new Acceleration(0.3, METER_PER_SECOND_2),
202                 new Acceleration(0.1, METER_PER_SECOND_2), new Acceleration(-0.3, METER_PER_SECOND_2));
203         // System.out.println(laneChangeModelResult.toString());
204         assertEquals(LateralDirectionality.RIGHT, laneChangeModelResult.getLaneChangeDirection(),
205                 "Vehicle want to change to the right lane");
206         Length rear = car.position(lanes[0], car.getRear());
207         Length front = car.position(lanes[0], car.getFront());
208         Length reference = car.position(lanes[0], RelativePosition.REFERENCE_POSITION);
209         // System.out.println("rear: " + rear);
210         // System.out.println("front: " + front);
211         // System.out.println("reference: " + reference);
212         Length vehicleLength = front.minus(rear);
213         Length collisionStart = reference.minus(vehicleLength);
214         Length collisionEnd = reference.plus(vehicleLength);
215         for (double pos = collisionStart.getSI() + 0.01; pos < collisionEnd.getSI() - 0.01; pos += 0.1)
216         {
217             parameters = DefaultTestParameters.create();
218             // parameters = new BehavioralCharacteristics();
219             // parameters.setParameter(ParameterTypes.A, new Acceleration(1, METER_PER_SECOND_2));
220             // parameters.setParameter(ParameterTypes.B, new Acceleration(1.5, METER_PER_SECOND_2));
221             // parameters.setParameter(ParameterTypes.S0, new Length(2, METER));
222             // parameters.setParameter(ParameterTypes.T, new Duration(1, SECOND));
223             // parameters.setParameter(ParameterTypes.A, new Acceleration(1, METER_PER_SECOND_2));
224             // parameters.setParameter(AbstractIDM.DELTA, 1d);
225             // drivingCharacteristics =
226             // new LaneBasedBehavioralCharacteristics(new IDMPlusOld(new Acceleration(1, METER_PER_SECOND_2),
227             // new Acceleration(1.5, METER_PER_SECOND_2), new Length(2, METER), new Duration(1, SECOND), 1d),
228             // laneChangeModel);
229             LaneBasedGtu collisionCar = new LaneBasedGtu("LaneChangeBlockingCarAt" + pos, gtuType, vehicleLength,
230                     new Length(2, METER), new Speed(150, KM_PER_HOUR), vehicleLength.times(0.5), this.network);
231             strategicalPlanner = new LaneBasedStrategicalRoutePlanner(
232                     new LaneBasedCfLcTacticalPlanner(new IdmPlusOld(), laneChangeModel, collisionCar), collisionCar);
233             collisionCar.setParameters(parameters);
234             collisionCar.init(strategicalPlanner, new LanePosition(lanes[1], new Length(pos, METER)),
235                     new Speed(100, KM_PER_HOUR));
236             preferredLaneGTUs.clear();
237             HeadwayGtuSimple collisionHWGTU = new HeadwayGtuSimple(collisionCar.getId(), collisionCar.getType(),
238                     new Length(pos - reference.getSI(), LengthUnit.SI), collisionCar.getLength(), collisionCar.getWidth(),
239                     collisionCar.getSpeed(), collisionCar.getAcceleration(), null);
240             preferredLaneGTUs.add(collisionHWGTU);
241             laneChangeModelResult = new Egoistic().computeLaneChangeAndAcceleration(car, sameLaneGTUs, preferredLaneGTUs,
242                     nonPreferredLaneGTUs, new Speed(100, KM_PER_HOUR), new Acceleration(0.3, METER_PER_SECOND_2),
243                     new Acceleration(0.1, METER_PER_SECOND_2), new Acceleration(-0.3, METER_PER_SECOND_2));
244             // System.out.println(laneChangeModelResult.toString());
245             assertEquals(null, laneChangeModelResult.getLaneChangeDirection(),
246                     "Vehicle cannot to change to the right lane because that would result in an immediate collision");
247         }
248         for (double pos = 0; pos < 180; pos += 5) // beyond 180m, a GTU gets a plan beyond the 200m long network
249         {
250             parameters = new ParameterSet();
251             parameters.setParameter(ParameterTypes.A, new Acceleration(1, METER_PER_SECOND_2));
252             parameters.setParameter(ParameterTypes.B, new Acceleration(1.5, METER_PER_SECOND_2));
253             parameters.setParameter(ParameterTypes.S0, new Length(2, METER));
254             parameters.setParameter(ParameterTypes.T, new Duration(1, SECOND));
255             parameters.setParameter(ParameterTypes.A, new Acceleration(1, METER_PER_SECOND_2));
256             parameters.setDefaultParameter(ParameterTypes.LOOKAHEAD);
257             parameters.setDefaultParameter(ParameterTypes.LOOKBACKOLD);
258             parameters.setParameter(AbstractIdm.DELTA, 1d);
259             // drivingCharacteristics =
260             // new LaneBasedBehavioralCharacteristics(new IDMPlusOld(new Acceleration(1, METER_PER_SECOND_2),
261             // new Acceleration(1.5, METER_PER_SECOND_2), new Length(2, METER), new Duration(1, SECOND), 1d),
262             // laneChangeModel);
263             LaneBasedGtu otherCar = new LaneBasedGtu("OtherCarAt" + pos, gtuType, vehicleLength, new Length(2, METER),
264                     new Speed(150, KM_PER_HOUR), vehicleLength.times(0.5), this.network);
265             strategicalPlanner = new LaneBasedStrategicalRoutePlanner(
266                     new LaneBasedCfLcTacticalPlanner(new IdmPlusOld(), laneChangeModel, otherCar), otherCar);
267             otherCar.setParameters(parameters);
268             otherCar.init(strategicalPlanner, new LanePosition(lanes[1], new Length(pos, METER)), new Speed(100, KM_PER_HOUR));
269             preferredLaneGTUs.clear();
270             HeadwayGtuSimple collisionHWGTU = new HeadwayGtuSimple(otherCar.getId(), otherCar.getType(),
271                     new Length(pos - car.position(lanes[0], car.getReference()).getSI(), LengthUnit.SI), otherCar.getLength(),
272                     otherCar.getWidth(), otherCar.getSpeed(), otherCar.getAcceleration(), null);
273             preferredLaneGTUs.add(collisionHWGTU);
274             laneChangeModelResult = new Egoistic().computeLaneChangeAndAcceleration(car, sameLaneGTUs, preferredLaneGTUs,
275                     nonPreferredLaneGTUs, new Speed(100, KM_PER_HOUR), new Acceleration(0.3, METER_PER_SECOND_2),
276                     new Acceleration(0.1, METER_PER_SECOND_2), new Acceleration(-0.3, METER_PER_SECOND_2));
277             // System.out.println(String.format("pos=%5fm Egoistic: %s", pos, laneChangeModelResult.toString()));
278             laneChangeModelResult = new Altruistic().computeLaneChangeAndAcceleration(car, sameLaneGTUs, preferredLaneGTUs,
279                     nonPreferredLaneGTUs, new Speed(100, KM_PER_HOUR), new Acceleration(0.3, METER_PER_SECOND_2),
280                     new Acceleration(0.1, METER_PER_SECOND_2), new Acceleration(-0.3, METER_PER_SECOND_2));
281             // System.out.println(String.format("pos=%5fm Altruistic: %s", pos, laneChangeModelResult.toString()));
282             // assertEquals(
283             // "Vehicle cannot to change to the right lane because that would result in an immediate collision",
284             // null, laneChangeModelResult.getLaneChange());
285         }
286     }
287 
288     // TODO test/prove the expected differences between Egoistic and Altruistic
289     // TODO prove that the most restrictive car in the other lane determines what happens
290     // TODO test merge into overtaking lane
291 
292     @Override
293     public void constructModel() throws SimRuntimeException
294     {
295         // DO NOTHING
296     }
297 
298     @Override
299     public final RoadNetwork getNetwork()
300     {
301         return this.network;
302     }
303 
304 }