package org.opentrafficsim.road.gtu.lane.changing;
import static org.junit.Assert.assertEquals;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingException;
import org.djunits.unit.DurationUnit;
import org.djunits.unit.LengthUnit;
import org.djunits.unit.util.UNITS;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Direction;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.djunits.value.vdouble.scalar.Time;
import org.junit.Test;
import org.opentrafficsim.base.parameters.ParameterSet;
import org.opentrafficsim.base.parameters.ParameterTypes;
import org.opentrafficsim.core.dsol.AbstractOTSModel;
import org.opentrafficsim.core.dsol.OTSSimulator;
import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
import org.opentrafficsim.core.geometry.OTSGeometryException;
import org.opentrafficsim.core.geometry.OTSLine3D;
import org.opentrafficsim.core.geometry.OTSPoint3D;
import org.opentrafficsim.core.gtu.GTUDirectionality;
import org.opentrafficsim.core.gtu.GTUType;
import org.opentrafficsim.core.gtu.RelativePosition;
import org.opentrafficsim.core.network.LateralDirectionality;
import org.opentrafficsim.core.network.LinkType;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.road.DefaultTestParameters;
import org.opentrafficsim.road.gtu.lane.LaneBasedIndividualGTU;
import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTUSimple;
import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedCFLCTacticalPlanner;
import org.opentrafficsim.road.gtu.lane.tactical.following.AbstractIDM;
import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusOld;
import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.AbstractLaneChangeModel;
import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Altruistic;
import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Egoistic;
import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneMovementStep;
import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlanner;
import org.opentrafficsim.road.network.OTSRoadNetwork;
import org.opentrafficsim.road.network.lane.CrossSectionLink;
import org.opentrafficsim.road.network.lane.DirectedLanePosition;
import org.opentrafficsim.road.network.lane.Lane;
import org.opentrafficsim.road.network.lane.LaneType;
import org.opentrafficsim.road.network.lane.OTSRoadNode;
import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
import nl.tudelft.simulation.dsol.SimRuntimeException;
/**
* Test some very basic properties of lane change models.
*
* Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* $LastChangedDate: 2015-09-16 19:20:07 +0200 (Wed, 16 Sep 2015) $, @version $Revision: 1405 $, by $Author: averbraeck $,
* initial version 14 nov. 2014
* @author Peter Knoppers
*/
public class LaneChangeModelTest extends AbstractOTSModel implements UNITS
{
/** */
private static final long serialVersionUID = 20150313;
/** The network. */
private OTSRoadNetwork network;
/**
*/
public LaneChangeModelTest()
{
super(new OTSSimulator("LaneChangeModelTest"));
this.network = new OTSRoadNetwork("lane change model test network", true, getSimulator());
}
/**
* Create a Link.
* @param network OTSRoadNetwork; the network
* @param name String; name of the new Link
* @param from OTSRoadNode; start node of the new Link
* @param to OTSRoadNode; end node of the new Link
* @param width Length; the width of the new Link
* @param simulator OTSSimulatorInterface; the simulator
* @return Link
* @throws OTSGeometryException when coordinates cannot be calculated
* @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
* or the end node of the link are not registered in the network
*/
private static CrossSectionLink makeLink(final OTSRoadNetwork network, final String name, final OTSRoadNode from,
final OTSRoadNode to, final Length width, final OTSSimulatorInterface simulator)
throws OTSGeometryException, NetworkException
{
// TODO create a LinkAnimation if the simulator is compatible with that.
// FIXME The current LinkAnimation is too bad to use...
OTSPoint3D[] coordinates = new OTSPoint3D[] { new OTSPoint3D(from.getPoint().x, from.getPoint().y, 0),
new OTSPoint3D(to.getPoint().x, to.getPoint().y, 0) };
OTSLine3D line = new OTSLine3D(coordinates);
CrossSectionLink link = new CrossSectionLink(network, name, from, to, network.getLinkType(LinkType.DEFAULTS.ROAD), line,
LaneKeepingPolicy.KEEPRIGHT);
return link;
}
/**
* Create one Lane.
* @param link Link; the link that owns the new Lane
* @param id String; the id of the lane, has to be unique within the link
* @param laneType LaneType<String>; the type of the new Lane
* @param latPos Length; the lateral position of the new Lane with respect to the design line of the link
* @param width Length; the width of the new Lane
* @return Lane
* @throws NamingException on ???
* @throws NetworkException on ??
* @throws OTSGeometryException when center line or contour of a link or lane cannot be generated
*/
private static Lane makeLane(final CrossSectionLink link, final String id, final LaneType laneType, final Length latPos,
final Length width) throws NamingException, NetworkException, OTSGeometryException
{
Map speedMap = new LinkedHashMap<>();
speedMap.put(link.getNetwork().getGtuType(GTUType.DEFAULTS.VEHICLE), new Speed(100, KM_PER_HOUR));
// XXX Decide what type of overtaking conditions we want in this test
Lane result = new Lane(link, id, latPos, latPos, width, width, laneType, speedMap);
return result;
}
/**
* Create a simple straight road with the specified number of Lanes.
* @param network OTSRoadNetwork; the network
* @param name String; name of the Link
* @param from OTSRoadNode; starting node of the new Lane
* @param to OTSRoadNode; ending node of the new Lane
* @param laneType LaneType<String>; the type of GTU that can use the lanes
* @param laneCount int; number of lanes in the road
* @param simulator OTSSimulatorInterface; the simulator
* @return Lane<String, String>[]; array containing the new Lanes
* @throws Exception when something goes wrong (should not happen)
*/
public static Lane[] makeMultiLane(final OTSRoadNetwork network, final String name, final OTSRoadNode from,
final OTSRoadNode to, final LaneType laneType, final int laneCount, final OTSSimulatorInterface simulator)
throws Exception
{
Length width = new Length(laneCount * 4.0, METER);
final CrossSectionLink link = makeLink(network, name, from, to, width, simulator);
Lane[] result = new Lane[laneCount];
width = new Length(4.0, METER);
for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
{
// successive lanes have a more negative offset => more to the RIGHT
Length latPos = new Length((-0.5 - laneIndex) * width.getSI(), METER);
result[laneIndex] = makeLane(link, "lane." + laneIndex, laneType, latPos, width);
}
return result;
}
/**
* Test that a vehicle in the left lane changes to the right lane if that is empty, or there is enough room.
* @throws Exception when something goes wrong (should not happen)
*/
@Test
public final void changeRight() throws Exception
{
GTUType gtuType = this.network.getGtuType(GTUType.DEFAULTS.CAR);
LaneType laneType = this.network.getLaneType(LaneType.DEFAULTS.TWO_WAY_LANE);
int laneCount = 2;
this.simulator.initialize(Time.ZERO, Duration.ZERO, new Duration(3600.0, DurationUnit.SECOND), this);
Lane[] lanes = makeMultiLane(this.network, "Road with two lanes",
new OTSRoadNode(this.network, "From", new OTSPoint3D(0, 0, 0), Direction.ZERO),
new OTSRoadNode(this.network, "To", new OTSPoint3D(200, 0, 0), Direction.ZERO), laneType, laneCount,
this.simulator);
// Let's see if adjacent lanes are accessible
// lanes: | 0 : 1 : 2 | in case of three lanes
assertEquals("Leftmost lane should not have accessible adjacent lanes on the LEFT side", 0,
lanes[0].accessibleAdjacentLanesLegal(LateralDirectionality.LEFT, gtuType, GTUDirectionality.DIR_PLUS).size());
assertEquals("Leftmost lane should have one accessible adjacent lane on the RIGHT side", 1,
lanes[0].accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT, gtuType, GTUDirectionality.DIR_PLUS).size());
assertEquals("Rightmost lane should have one accessible adjacent lane on the LEFT side", 1,
lanes[1].accessibleAdjacentLanesLegal(LateralDirectionality.LEFT, gtuType, GTUDirectionality.DIR_PLUS).size());
assertEquals("Rightmost lane should not have accessible adjacent lanes on the RIGHT side", 0,
lanes[1].accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT, gtuType, GTUDirectionality.DIR_PLUS).size());
Set initialLongitudinalPositions = new LinkedHashSet<>(1);
initialLongitudinalPositions
.add(new DirectedLanePosition(lanes[1], new Length(100, METER), GTUDirectionality.DIR_PLUS));
AbstractLaneChangeModel laneChangeModel = new Egoistic();
ParameterSet parameters = DefaultTestParameters.create();
// LaneBasedBehavioralCharacteristics drivingCharacteristics =
// new LaneBasedBehavioralCharacteristics(new IDMPlusOld(new Acceleration(1, METER_PER_SECOND_2), new Acceleration(
// 1.5, METER_PER_SECOND_2), new Length(2, METER), new Duration(1, SECOND), 1d), laneChangeModel);
LaneBasedIndividualGTU car = new LaneBasedIndividualGTU("ReferenceCar", gtuType, new Length(4, METER),
new Length(2, METER), new Speed(150, KM_PER_HOUR), Length.instantiateSI(2.0), this.simulator, this.network);
LaneBasedStrategicalPlanner strategicalPlanner = new LaneBasedStrategicalRoutePlanner(
new LaneBasedCFLCTacticalPlanner(new IDMPlusOld(), laneChangeModel, car), car);
car.setParameters(parameters);
car.init(strategicalPlanner, initialLongitudinalPositions, new Speed(100, KM_PER_HOUR));
car.getTacticalPlanner().getPerception().perceive();
Collection sameLaneGTUs = new LinkedHashSet<>();
sameLaneGTUs.add(new HeadwayGTUSimple(car.getId(), car.getGTUType(), Length.ZERO, Length.ZERO, car.getLength(),
car.getSpeed(), car.getAcceleration(), null));
Collection preferredLaneGTUs = new LinkedHashSet<>();
Collection nonPreferredLaneGTUs = new LinkedHashSet<>();
LaneMovementStep laneChangeModelResult = laneChangeModel.computeLaneChangeAndAcceleration(car, sameLaneGTUs,
preferredLaneGTUs, nonPreferredLaneGTUs, new Speed(100, KM_PER_HOUR), new Acceleration(0.3, METER_PER_SECOND_2),
new Acceleration(0.1, METER_PER_SECOND_2), new Acceleration(-0.3, METER_PER_SECOND_2));
// System.out.println(laneChangeModelResult.toString());
assertEquals("Vehicle want to change to the right lane", LateralDirectionality.RIGHT,
laneChangeModelResult.getLaneChangeDirection());
Length rear = car.position(lanes[0], car.getRear());
Length front = car.position(lanes[0], car.getFront());
Length reference = car.position(lanes[0], RelativePosition.REFERENCE_POSITION);
// System.out.println("rear: " + rear);
// System.out.println("front: " + front);
// System.out.println("reference: " + reference);
Length vehicleLength = front.minus(rear);
Length collisionStart = reference.minus(vehicleLength);
Length collisionEnd = reference.plus(vehicleLength);
for (double pos = collisionStart.getSI() + 0.01; pos < collisionEnd.getSI() - 0.01; pos += 0.1)
{
Set otherLongitudinalPositions = new LinkedHashSet<>(1);
otherLongitudinalPositions
.add(new DirectedLanePosition(lanes[1], new Length(pos, METER), GTUDirectionality.DIR_PLUS));
parameters = DefaultTestParameters.create();
// parameters = new BehavioralCharacteristics();
// parameters.setParameter(ParameterTypes.A, new Acceleration(1, METER_PER_SECOND_2));
// parameters.setParameter(ParameterTypes.B, new Acceleration(1.5, METER_PER_SECOND_2));
// parameters.setParameter(ParameterTypes.S0, new Length(2, METER));
// parameters.setParameter(ParameterTypes.T, new Duration(1, SECOND));
// parameters.setParameter(ParameterTypes.A, new Acceleration(1, METER_PER_SECOND_2));
// parameters.setParameter(AbstractIDM.DELTA, 1d);
// drivingCharacteristics =
// new LaneBasedBehavioralCharacteristics(new IDMPlusOld(new Acceleration(1, METER_PER_SECOND_2),
// new Acceleration(1.5, METER_PER_SECOND_2), new Length(2, METER), new Duration(1, SECOND), 1d),
// laneChangeModel);
LaneBasedIndividualGTU collisionCar =
new LaneBasedIndividualGTU("LaneChangeBlockingCarAt" + pos, gtuType, vehicleLength, new Length(2, METER),
new Speed(150, KM_PER_HOUR), vehicleLength.times(0.5), this.simulator, this.network);
strategicalPlanner = new LaneBasedStrategicalRoutePlanner(
new LaneBasedCFLCTacticalPlanner(new IDMPlusOld(), laneChangeModel, collisionCar), collisionCar);
collisionCar.setParameters(parameters);
collisionCar.init(strategicalPlanner, otherLongitudinalPositions, new Speed(100, KM_PER_HOUR));
preferredLaneGTUs.clear();
HeadwayGTUSimple collisionHWGTU = new HeadwayGTUSimple(collisionCar.getId(), collisionCar.getGTUType(),
new Length(pos - reference.getSI(), LengthUnit.SI), collisionCar.getLength(), collisionCar.getWidth(),
collisionCar.getSpeed(), collisionCar.getAcceleration(), null);
preferredLaneGTUs.add(collisionHWGTU);
laneChangeModelResult = new Egoistic().computeLaneChangeAndAcceleration(car, sameLaneGTUs, preferredLaneGTUs,
nonPreferredLaneGTUs, new Speed(100, KM_PER_HOUR), new Acceleration(0.3, METER_PER_SECOND_2),
new Acceleration(0.1, METER_PER_SECOND_2), new Acceleration(-0.3, METER_PER_SECOND_2));
// System.out.println(laneChangeModelResult.toString());
assertEquals("Vehicle cannot to change to the right lane because that would result in an immediate collision", null,
laneChangeModelResult.getLaneChangeDirection());
}
for (double pos = 0; pos < 180; pos += 5) // beyond 180m, a GTU gets a plan beyond the 200m long network
{
Set otherLongitudinalPositions = new LinkedHashSet<>(1);
otherLongitudinalPositions
.add(new DirectedLanePosition(lanes[1], new Length(pos, METER), GTUDirectionality.DIR_PLUS));
parameters = new ParameterSet();
parameters.setParameter(ParameterTypes.A, new Acceleration(1, METER_PER_SECOND_2));
parameters.setParameter(ParameterTypes.B, new Acceleration(1.5, METER_PER_SECOND_2));
parameters.setParameter(ParameterTypes.S0, new Length(2, METER));
parameters.setParameter(ParameterTypes.T, new Duration(1, SECOND));
parameters.setParameter(ParameterTypes.A, new Acceleration(1, METER_PER_SECOND_2));
parameters.setDefaultParameter(ParameterTypes.LOOKAHEAD);
parameters.setDefaultParameter(ParameterTypes.LOOKBACKOLD);
parameters.setParameter(AbstractIDM.DELTA, 1d);
// drivingCharacteristics =
// new LaneBasedBehavioralCharacteristics(new IDMPlusOld(new Acceleration(1, METER_PER_SECOND_2),
// new Acceleration(1.5, METER_PER_SECOND_2), new Length(2, METER), new Duration(1, SECOND), 1d),
// laneChangeModel);
LaneBasedIndividualGTU otherCar =
new LaneBasedIndividualGTU("OtherCarAt" + pos, gtuType, vehicleLength, new Length(2, METER),
new Speed(150, KM_PER_HOUR), vehicleLength.times(0.5), this.simulator, this.network);
strategicalPlanner = new LaneBasedStrategicalRoutePlanner(
new LaneBasedCFLCTacticalPlanner(new IDMPlusOld(), laneChangeModel, otherCar), otherCar);
otherCar.setParameters(parameters);
otherCar.init(strategicalPlanner, otherLongitudinalPositions, new Speed(100, KM_PER_HOUR));
preferredLaneGTUs.clear();
HeadwayGTUSimple collisionHWGTU = new HeadwayGTUSimple(otherCar.getId(), otherCar.getGTUType(),
new Length(pos - car.position(lanes[0], car.getReference()).getSI(), LengthUnit.SI), otherCar.getLength(),
otherCar.getWidth(), otherCar.getSpeed(), otherCar.getAcceleration(), null);
preferredLaneGTUs.add(collisionHWGTU);
laneChangeModelResult = new Egoistic().computeLaneChangeAndAcceleration(car, sameLaneGTUs, preferredLaneGTUs,
nonPreferredLaneGTUs, new Speed(100, KM_PER_HOUR), new Acceleration(0.3, METER_PER_SECOND_2),
new Acceleration(0.1, METER_PER_SECOND_2), new Acceleration(-0.3, METER_PER_SECOND_2));
// System.out.println(String.format("pos=%5fm Egoistic: %s", pos, laneChangeModelResult.toString()));
laneChangeModelResult = new Altruistic().computeLaneChangeAndAcceleration(car, sameLaneGTUs, preferredLaneGTUs,
nonPreferredLaneGTUs, new Speed(100, KM_PER_HOUR), new Acceleration(0.3, METER_PER_SECOND_2),
new Acceleration(0.1, METER_PER_SECOND_2), new Acceleration(-0.3, METER_PER_SECOND_2));
// System.out.println(String.format("pos=%5fm Altruistic: %s", pos, laneChangeModelResult.toString()));
// assertEquals(
// "Vehicle cannot to change to the right lane because that would result in an immediate collision",
// null, laneChangeModelResult.getLaneChange());
}
}
// TODO test/prove the expected differences between Egoistic and Altruistic
// TODO prove that the most restrictive car in the other lane determines what happens
// TODO test merge into overtaking lane
/** {@inheritDoc} */
@Override
public void constructModel() throws SimRuntimeException
{
// DO NOTHING
}
/** {@inheritDoc} */
@Override
public final OTSRoadNetwork getNetwork()
{
return this.network;
}
/** {@inheritDoc} */
@Override
public Serializable getSourceId()
{
return "LaneChangeModelTest.Model";
}
}