package org.opentrafficsim.road.gtu.following;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import nl.tudelft.simulation.dsol.SimRuntimeException;
import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
import org.djunits.unit.LengthUnit;
import org.djunits.unit.TimeUnit;
import org.djunits.unit.UNITS;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.DoubleScalar;
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.core.dsol.OTSModelInterface;
import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
import org.opentrafficsim.core.gtu.GTUDirectionality;
import org.opentrafficsim.core.gtu.GTUType;
import org.opentrafficsim.core.network.OTSNetwork;
import org.opentrafficsim.road.car.CarTest;
import org.opentrafficsim.road.car.LaneBasedIndividualCar;
import org.opentrafficsim.road.gtu.lane.driver.LaneBasedDrivingCharacteristics;
import org.opentrafficsim.road.gtu.lane.perception.LanePerceptionFull;
import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedGTUFollowingTacticalPlanner;
import org.opentrafficsim.road.gtu.lane.tactical.following.AbstractGTUFollowingModel;
import org.opentrafficsim.road.gtu.lane.tactical.following.AccelerationStep;
import org.opentrafficsim.road.gtu.lane.tactical.following.DualAccelerationStep;
import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModel;
import org.opentrafficsim.road.gtu.lane.tactical.following.HeadwayGTU;
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.lanechangemobil.AbstractLaneChangeModel;
import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Egoistic;
import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlanner;
import org.opentrafficsim.road.network.lane.DirectedLanePosition;
import org.opentrafficsim.road.network.lane.Lane;
import org.opentrafficsim.road.network.lane.LaneType;
import org.opentrafficsim.simulationengine.SimpleSimulator;
/**
* Test the methods that the classes that implement GTUFollowingModel have in common.
*
* Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $,
* initial version 27 feb. 2015
* @author Peter Knoppers
*/
public class GTUFollowingModelTest implements OTSModelInterface, UNITS
{
/** */
private static final long serialVersionUID = 20150227L;
/** network. */
private OTSNetwork network = new OTSNetwork("network");
/**
* Test that the methods required by the GTUFollowingModel interface.
* @param gtuFollowingModel GTUFollowingModel
* @throws Exception when something goes wrong (should not happen)
*/
private void gtuFollowingModelTests(GTUFollowingModel gtuFollowingModel) throws Exception
{
Acceleration maxSafeDeceleration = gtuFollowingModel.getMaximumSafeDeceleration();
assertNotNull("maximumSafeDeceleration must return non-null value", maxSafeDeceleration);
assertTrue("value of maximuSafeDeceleration must be positive", 0 < maxSafeDeceleration.getSI());
assertTrue("value of maximumSafeDeceleration must be less than g", maxSafeDeceleration.getSI() < 10);
Time.Rel stepSize = gtuFollowingModel.getStepSize();
assertNotNull("stepSize must return non-null value", stepSize);
assertTrue("stepSize must be > 0", 0 < stepSize.getSI());
String name = gtuFollowingModel.getName();
// System.out.println("GTUFollowingModel " + name);
assertNotNull("getName must return non-null value", name);
assertTrue("getName result must not be the empty string", name.length() > 0);
String longName = gtuFollowingModel.getLongName();
assertNotNull("getLongName must return non-null value", longName);
assertTrue("getLongName result must not be the empty string", longName.length() > 0);
Speed speed = Speed.ZERO;
Length.Rel precision = new Length.Rel(0.5, METER);
Speed maxSpeed = new Speed(200, KM_PER_HOUR);
Speed speedLimit = new Speed(100, KM_PER_HOUR);
Length.Rel maxHeadway = new Length.Rel(250.0, LengthUnit.METER);
Length.Rel minimumHeadway =
gtuFollowingModel.minimumHeadway(speed, speed, precision, maxHeadway, speedLimit, maxSpeed);
assertNotNull("minimum headway at speed 0 should be non null", minimumHeadway);
assertTrue("minimum headway at speed 0 hould have value >= 0", 0 <= minimumHeadway.getSI());
// System.out.println("minimum headway at speed " + speed + " is " + minimumHeadway);
speed = new Speed(50, KM_PER_HOUR);
minimumHeadway = gtuFollowingModel.minimumHeadway(speed, speed, precision, maxHeadway, speedLimit, maxSpeed);
assertNotNull("minimum headway at speed 0 should be non null", minimumHeadway);
assertTrue("minimum headway at speed 0 hould have value >= 0", 0 <= minimumHeadway.getSI());
// System.out.println("minimum headway at speed " + speed + " is " + minimumHeadway);
SimpleSimulator simulator =
new SimpleSimulator(new Time.Abs(0, SECOND), new Time.Rel(0, SECOND), new Time.Rel(1800, SECOND), this);
GTUType carType = GTUType.makeGTUType("Car");
LaneType laneType = new LaneType("CarLane");
laneType.addCompatibility(carType);
Lane lane = CarTest.makeLane(laneType);
Length.Rel initialPosition = new Length.Rel(1234.567, METER);
Length.Rel length = new Length.Rel(5.0, METER);
Length.Rel width = new Length.Rel(2.0, METER);
Set initialLongitudinalPositions = new LinkedHashSet<>(1);
initialLongitudinalPositions.add(new DirectedLanePosition(lane, initialPosition, GTUDirectionality.DIR_PLUS));
AbstractLaneChangeModel laneChangeModel = new Egoistic();
LaneBasedDrivingCharacteristics drivingCharacteristics =
new LaneBasedDrivingCharacteristics(gtuFollowingModel, laneChangeModel);
maxHeadway = drivingCharacteristics.getForwardHeadwayDistance();
LaneBasedStrategicalPlanner strategicalPlanner =
new LaneBasedStrategicalRoutePlanner(drivingCharacteristics, new LaneBasedGTUFollowingTacticalPlanner());
LaneBasedIndividualCar gtu =
new LaneBasedIndividualCar("12345", carType, initialLongitudinalPositions, speed, length, width, maxSpeed,
simulator, strategicalPlanner, new LanePerceptionFull(), this.network);
Length.Rel longerHeadway = minimumHeadway.plus(precision);
Acceleration longerHeadwayAcceleration =
gtuFollowingModel.computeAcceleration(speed, maxSpeed, speed, longerHeadway, speedLimit);
// System.out.println("acceleration at headway " + longerHeadway + " is " + longerHeadwayAcceleration);
assertTrue("deceleration with longer headway than minimum should be >= -maximumSafeDeceleration",
-maxSafeDeceleration.getSI() <= longerHeadwayAcceleration.getSI());
Length.Rel shorterHeadway = minimumHeadway.minus(precision);
Acceleration shorterHeadwayAcceleration =
gtuFollowingModel.computeAcceleration(speed, maxSpeed, speed, shorterHeadway, speedLimit);
// System.out.println("acceleration at headway " + shorterHeadway + " is " + shorterHeadwayAcceleration);
gtuFollowingModel.computeAcceleration(speed, maxSpeed, speed, shorterHeadway, speedLimit);
assertTrue("deceleration with longer headway than minimum should be <= -maximumSafeDeceleration",
-maxSafeDeceleration.getSI() >= shorterHeadwayAcceleration.getSI());
AccelerationStep noLeader = gtuFollowingModel.computeAccelerationStepWithNoLeader(gtu, maxHeadway, speedLimit);
// System.out.println("noLeader is " + noLeader);
assertNotNull("result of computeAccelerationWithNoLeader is not null", noLeader);
assertEquals("result of computeAccelerationWithNoLeader is valid for " + stepSize, stepSize.getSI(), noLeader
.getValidUntil().getSI(), 0.001);
assertTrue("acceleration of stationary gtu with no leader should be > 0", 0 < noLeader.getAcceleration()
.getSI());
precision = Length.Rel.ZERO;
try
{
gtuFollowingModel.minimumHeadway(speed, speed, precision, maxHeadway, speedLimit, maxSpeed);
fail("precision 0 should have thrown an Error");
}
catch (Error e)
{
// Ignore
}
precision = new Length.Rel(-1, LengthUnit.SI);
try
{
gtuFollowingModel.minimumHeadway(speed, speed, precision, maxHeadway, speedLimit, maxSpeed);
fail("precision -1 should have thrown an Error");
}
catch (Error e)
{
// Ignore
}
Length.Rel headway50m = new Length.Rel(50, METER);
Set initialLongitudinalPositions50 = new LinkedHashSet<>(1);
initialLongitudinalPositions50.add(new DirectedLanePosition(lane, initialPosition.plus(headway50m),
GTUDirectionality.DIR_PLUS));
LaneBasedIndividualCar gtu50m =
new LaneBasedIndividualCar("100050", carType, initialLongitudinalPositions50, speed, length, width,
maxSpeed, simulator, strategicalPlanner, new LanePerceptionFull(), this.network);
HeadwayGTU hwgtu50m =
new HeadwayGTU(gtu50m.getId(), gtu50m.getVelocity(), headway50m.getSI(), gtu50m.getGTUType());
Collection otherGTUs = new ArrayList();
DualAccelerationStep asEmpty =
gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
// System.out.println("asEmpty: [" + asEmpty[0] + ", " + asEmpty[1] + "]");
Time.Abs expectedValidUntil = new Time.Abs(stepSize.getSI(), TimeUnit.SI);
checkAccelerationStep("Empty collection", asEmpty, noLeader.getAcceleration(), noLeader.getAcceleration(),
expectedValidUntil);
otherGTUs.add(new HeadwayGTU(gtu.getId(), gtu.getVelocity(), java.lang.Double.NaN, gtu.getGTUType()));
// If otherGTUs only contains the reference GTU, the result should be exactly the same
asEmpty = gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
checkAccelerationStep("Empty collection", asEmpty, noLeader.getAcceleration(), noLeader.getAcceleration(),
expectedValidUntil);
otherGTUs.clear();
otherGTUs.add(hwgtu50m);
DualAccelerationStep as50m =
gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
// System.out.println("as50m: [" + as50m[0] + ", " + as50m[1] + "]");
AccelerationStep a50 =
gtuFollowingModel.computeAccelerationStep(gtu50m, gtu50m.getVelocity(), headway50m, maxHeadway, speedLimit);
checkAccelerationStep("leader at " + headway50m, as50m, a50.getAcceleration(), noLeader.getAcceleration(),
expectedValidUntil);
Map initialLongitudinalPositions100 = new HashMap<>();
Length.Rel headway100m = new Length.Rel(100, METER);
initialLongitudinalPositions100.put(lane, initialPosition.plus(headway100m));
LaneBasedIndividualCar gtu100m =
new LaneBasedIndividualCar("100100", carType, initialLongitudinalPositions50, speed, length, width,
maxSpeed, simulator, strategicalPlanner, new LanePerceptionFull(), this.network);
HeadwayGTU hwgtu100m =
new HeadwayGTU(gtu100m.getId(), gtu100m.getVelocity(), headway100m.getSI(), gtu100m.getGTUType());
otherGTUs.add(hwgtu100m);
DualAccelerationStep as50and100m =
gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
checkAccelerationStep("leader at " + headway50m + " and at " + headway100m, as50and100m, a50.getAcceleration(),
noLeader.getAcceleration(), expectedValidUntil);
otherGTUs.add(new HeadwayGTU(gtu.getId(), gtu.getVelocity(), 0, gtu.getGTUType()));
as50and100m = gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
checkAccelerationStep("follower at 0, leader at " + headway50m + " and at " + headway100m, as50and100m,
a50.getAcceleration(), noLeader.getAcceleration(), expectedValidUntil);
otherGTUs.add(new HeadwayGTU(gtu.getId(), gtu.getVelocity(), java.lang.Double.NaN, gtu.getGTUType()));
as50and100m = gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
checkAccelerationStep("follower at NaN, leader at " + headway50m + " and at " + headway100m, as50and100m,
a50.getAcceleration(), noLeader.getAcceleration(), expectedValidUntil);
otherGTUs.clear();
otherGTUs.add(hwgtu100m);
DualAccelerationStep as100m =
gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
AccelerationStep a100 =
gtuFollowingModel.computeAccelerationStep(gtu, gtu100m.getVelocity(), headway100m, maxHeadway, speedLimit);
// System.out.println("a100: " + a100);
// System.out.println("as100m: [" + as100m[0] + ", " + as100m[1] + "]");
checkAccelerationStep("leader at " + headway100m, as100m, a100.getAcceleration(), noLeader.getAcceleration(),
expectedValidUntil);
// Add an overlapping GTU. Immediate collision situation should return TOODANGEROUS
Map initialLongitudinalPositionsOverlapping = new HashMap<>();
initialLongitudinalPositionsOverlapping.put(lane, initialPosition.plus(new Length.Rel(1, METER)));
LaneBasedIndividualCar gtu1m =
new LaneBasedIndividualCar("100100", carType, initialLongitudinalPositions50, speed, length, width,
maxSpeed, simulator, strategicalPlanner, new LanePerceptionFull(), this.network);
HeadwayGTU hwgtu1m =
new HeadwayGTU(gtu1m.getId(), gtu1m.getVelocity(), java.lang.Double.NaN, gtu1m.getGTUType());
otherGTUs.add(hwgtu1m);
DualAccelerationStep as1m =
gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
AccelerationStep a1 = AbstractGTUFollowingModel.TOODANGEROUS.getLeaderAccelerationStep();
// System.out.println("a1: " + a1);
// System.out.println("as1m: [" + as1m[0] + ", " + as1m[1] + "]");
checkAccelerationStep("leader overlapping ", as1m, a1.getAcceleration(), a1.getAcceleration(),
a1.getValidUntil());
otherGTUs.clear();
otherGTUs.add(hwgtu100m);
// Follower at 75m
Length.Rel headwayMinus75m = new Length.Rel(-75, METER);
Set initialLongitudinalPositionsMinus75 = new LinkedHashSet<>(1);
initialLongitudinalPositionsMinus75.add(new DirectedLanePosition(lane, initialPosition.plus(headwayMinus75m),
GTUDirectionality.DIR_PLUS));
LaneBasedIndividualCar gtuMinus75m =
new LaneBasedIndividualCar("100075", carType, initialLongitudinalPositionsMinus75, speed, length, width,
maxSpeed, simulator, strategicalPlanner, new LanePerceptionFull(), this.network);
HeadwayGTU hwgtuMinus75m =
new HeadwayGTU(gtuMinus75m.getId(), gtuMinus75m.getVelocity(), headwayMinus75m.getSI(),
gtuMinus75m.getGTUType());
otherGTUs.add(hwgtuMinus75m);
DualAccelerationStep asMinus75And100m =
gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
AccelerationStep a75 =
gtuFollowingModel.computeAccelerationStep(gtuMinus75m, gtu.getVelocity(),
new Length.Rel(Math.abs(headwayMinus75m.getSI()), LengthUnit.SI), maxHeadway, speedLimit);
checkAccelerationStep("leader at " + headway100m + " and follower at " + headwayMinus75m, asMinus75And100m,
a100.getAcceleration(), a75.getAcceleration(), expectedValidUntil);
// Another follower at 200m
Length.Rel headwayMinus200m = new Length.Rel(-200, METER);
Set initialLongitudinalPositionsMinus200 = new LinkedHashSet<>(1);
initialLongitudinalPositionsMinus200.add(new DirectedLanePosition(lane, initialPosition.plus(headwayMinus200m),
GTUDirectionality.DIR_PLUS));
LaneBasedIndividualCar gtuMinus200m =
new LaneBasedIndividualCar("100200", carType, initialLongitudinalPositionsMinus200, speed, length, width,
maxSpeed, simulator, strategicalPlanner, new LanePerceptionFull(), this.network);
HeadwayGTU hwgtuMinus200m =
new HeadwayGTU(gtuMinus200m.getId(), gtuMinus200m.getVelocity(), headwayMinus200m.getSI(),
gtuMinus200m.getGTUType());
otherGTUs.add(hwgtuMinus200m);
DualAccelerationStep asMinus200Minus75And100m =
gtuFollowingModel.computeDualAccelerationStep(gtu, otherGTUs, maxHeadway, speedLimit);
// The extra follower at -200 should not make a difference
checkAccelerationStep("leader at " + headway100m + " and follower at " + headwayMinus75m,
asMinus200Minus75And100m, a100.getAcceleration(), a75.getAcceleration(), expectedValidUntil);
}
/**
* Verify a result of computeAcceleration.
* @param description String; description of the result to check
* @param as AccelerationStep[2]; the result to verify
* @param a0 Acceleration; the expected acceleration in as[0]
* @param a1 Acceleration; the expected acceleration in as[1]
* @param validUntil Time.Abs; the expected validUntil value in both entries of as
*/
private void checkAccelerationStep(final String description, final DualAccelerationStep as, final Acceleration a0,
final Acceleration a1, final Time.Abs validUntil)
{
assertEquals(description + ": a leader should be " + a0, a0.getSI(), as.getLeaderAcceleration().getSI(), 0.001);
assertEquals(description + ": a leader should be " + a0, a0.getSI(), as.getLeaderAccelerationStep()
.getAcceleration().getSI(), 0.001);
assertEquals(description + ": a leader should be valid until " + validUntil, validUntil.getSI(), as
.getLeaderValidUntil().getSI(), 0.001);
assertEquals(description + ": a leader should be valid until " + validUntil, validUntil.getSI(), as
.getLeaderAccelerationStep().getValidUntil().getSI(), 0.001);
assertEquals(description + ": a follower should be " + a1, a1.getSI(), as.getFollowerAcceleration().getSI(),
0.001);
assertEquals(description + ": a follower should be " + a1, a1.getSI(), as.getFollowerAccelerationStep()
.getAcceleration().getSI(), 0.001);
assertEquals(description + ": a follower should be valid until " + validUntil, validUntil.getSI(), as
.getFollowerValidUntil().getSI(), 0.001);
assertEquals(description + ": a follower should be valid until " + validUntil, validUntil.getSI(), as
.getFollowerValidUntil().getSI(), 0.001);
}
/**
* Test IDM
* @throws Exception when something goes wrong (should not happen)
*/
@Test
public void testIDM() throws Exception
{
gtuFollowingModelTests(new IDM());
}
/**
* Test IDMPlus
* @throws Exception when something goes wrong (should not happen)
*/
@Test
public void testIDMPlus() throws Exception
{
gtuFollowingModelTests(new IDMPlus());
}
/** {@inheritDoc} */
@Override
public void constructModel(
SimulatorInterface, DoubleScalar.Rel, OTSSimTimeDouble> simulator)
throws SimRuntimeException
{
// Do nothing.
}
/** {@inheritDoc} */
@Override
public SimulatorInterface, DoubleScalar.Rel, OTSSimTimeDouble> getSimulator()
{
return null;
}
}