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