LMRS.java
package org.opentrafficsim.road.gtu.lane.tactical.lmrs;
import java.util.HashSet;
import java.util.Set;
import nl.tudelft.simulation.language.d3.DirectedPoint;
import org.djunits.unit.AccelerationUnit;
import org.djunits.unit.SpeedUnit;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Speed;
import org.djunits.value.vdouble.scalar.Time;
import org.opentrafficsim.core.gtu.GTU;
import org.opentrafficsim.core.gtu.GTUException;
import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
import org.opentrafficsim.road.gtu.lane.perception.HeadwayGTU;
import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
import org.opentrafficsim.road.gtu.lane.tactical.AbstractLaneBasedTacticalPlanner;
import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
/**
* Implementation of the LMRS (Lane change Model with Relaxation and Synchronization). See Schakel, W.J., Knoop, V.L., and Van
* Arem, B. (2012), <a href="http://victorknoop.eu/research/papers/TRB2012_LMRS_reviewed.pdf">LMRS: Integrated Lane Change Model
* with Relaxation and Synchronization</a>, Transportation Research Records: Journal of the Transportation Research Board, No.
* 2316, pp. 47-57. Note in the official versions of TRB and TRR some errors appeared due to the typesetting of the papers (not
* in the preprint provided here). A list of errata for the official versions is found <a
* href="http://victorknoop.eu/research/papers/Erratum_LMRS.pdf">here</a>.
* <p>
* Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
* <p>
* @version $Revision$, $LastChangedDate$, by $Author$, initial version Apr 13, 2016 <br>
* @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
*/
public class LMRS extends AbstractLMRS
{
/** Serialization id. */
private static final long serialVersionUID = 20160300L;
/**
* Constructor setting the car-following model.
* @param carFollowingModel Car-following model.
*/
public LMRS(final CarFollowingModel carFollowingModel)
{
super(carFollowingModel);
}
/** {@inheritDoc} */
@Override
public final Set<MandatoryIncentive> getDefaultMandatoryIncentives()
{
HashSet<MandatoryIncentive> set = new HashSet<>();
set.add(new IncentiveRoute());
return set;
}
/** {@inheritDoc} */
@Override
public final Set<VoluntaryIncentive> getDefaultVoluntaryIncentives()
{
HashSet<VoluntaryIncentive> set = new HashSet<>();
set.add(new IncentiveSpeedWithCourtesy());
set.add(new IncentiveKeep());
return set;
}
/** {@inheritDoc} */
@Override
public final OperationalPlan generateOperationalPlan(final GTU gtu, final Time startTime,
final DirectedPoint locationAtStartTime) throws OperationalPlanException, GTUException, NetworkException,
ParameterException
{
// TODO: Remove this when other todo's are done, it is used as a placeholder where some acceleration needs to be
// determined.
Acceleration dummy = new Acceleration(0, AccelerationUnit.SI);
// Obtain objects to get info
LaneBasedGTU gtuLane = (LaneBasedGTU) gtu;
LanePerception perception = (LanePerception) gtu.getPerception();
CarFollowingModel cfm = ((AbstractLaneBasedTacticalPlanner) gtuLane.getTacticalPlanner()).getCarFollowingModel();
// TODO: Throw ParameterException
BehavioralCharacteristics bc = gtuLane.getBehavioralCharacteristics();
Acceleration b = bc.getParameter(ParameterTypes.B);
double dFree = bc.getParameter(DFREE);
double dSync = bc.getParameter(DSYNC);
double dCoop = bc.getParameter(DCOOP);
// Determine tactical plan
if (Math.random() > .5)
{ // TODO: if changing lane (rand to disable annoying warnings)
/*
* During a lane change, both leaders are followed.
*/
Acceleration a = dummy;
a = minOf(a, dummy);
/*
* Operational plan.
*/
// TODO: Build the operational plan using minimum acceleration
// LaneOperationalPlanBuilder
return null;
}
// Relaxation
exponentialHeadwayRelaxation(bc);
/*
* Determine desire Mandatory is deduced as the maximum of a set of mandatory incentives, while voluntary desires are
* added. Depending on the level of mandatory lane change desire, voluntary desire may be included partially. If both
* are positive or negative, voluntary desire is fully included. Otherwise, voluntary desire is less considered within
* the range dSync < |mandatory| < dCoop. The absolute value is used as large negative mandatory desire may also
* dominate voluntary desire.
*/
// Mandatory desire
double dLeftMandatory = Double.NEGATIVE_INFINITY;
double dRightMandatory = Double.NEGATIVE_INFINITY;
for (MandatoryIncentive incentive : getMandatoryIncentives())
{
Desire d = incentive.determineDesire(gtuLane, perception);
dLeftMandatory = d.getLeft() > dLeftMandatory ? d.getLeft() : dLeftMandatory;
dRightMandatory = d.getRight() > dRightMandatory ? d.getRight() : dRightMandatory;
}
Desire mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
// Voluntary desire
double dLeftVoluntary = 0;
double dRightVoluntary = 0;
for (VoluntaryIncentive incentive : getVoluntaryIncentives())
{
Desire d = incentive.determineDesire(gtuLane, perception, mandatoryDesire);
dLeftVoluntary += d.getLeft();
dRightVoluntary += d.getRight();
}
// Total desire
double thetaLeft = 0;
if (dLeftMandatory <= dSync || dLeftMandatory * dLeftVoluntary >= 0)
{
// low mandatory desire, or same sign
thetaLeft = 1;
}
else if (dSync < dLeftMandatory && dLeftMandatory < dCoop && dLeftMandatory * dLeftVoluntary < 0)
{
// linear from 1 at dSync to 0 at dCoop
thetaLeft = (dCoop - Math.abs(dLeftMandatory)) / (dCoop - dSync);
}
double thetaRight = 0;
if (dRightMandatory <= dSync || dRightMandatory * dRightVoluntary >= 0)
{
// low mandatory desire, or same sign
thetaRight = 1;
}
else if (dSync < dRightMandatory && dRightMandatory < dCoop && dRightMandatory * dRightVoluntary < 0)
{
// linear from 1 at dSync to 0 at dCoop
thetaRight = (dCoop - Math.abs(dRightMandatory)) / (dCoop - dSync);
}
Desire totalDesire =
new Desire(dLeftMandatory + thetaLeft * dLeftVoluntary, dRightMandatory + thetaRight * dRightVoluntary);
/*
* Gap acceptance The adjacent gap is accepted if acceleration is safe for the potential follower and for this driver.
*/
// TODO: get neighboring vehicles, use their (perceived) car-following model with altered headway
Acceleration aFollow = dummy;
Acceleration aSelf = dummy;
boolean leftAllowed = true; // TODO: get from perception / infrastructure, with relaxation
boolean acceptLeft =
aSelf.getSI() >= -b.si * totalDesire.getLeft() && aFollow.getSI() >= -b.si * totalDesire.getLeft()
&& leftAllowed;
aFollow = dummy;
aSelf = dummy;
boolean rightAllowed = true; // TODO: get from perception / infrastructure, with relaxation
boolean acceptRight =
aSelf.getSI() >= -b.si * totalDesire.getRight() && aFollow.getSI() >= -b.si * totalDesire.getRight()
&& rightAllowed;
/*
* Lane change decision A lane change is initiated for the largest desire if this is above the threshold and the gap is
* accepted. Otherwise, the indicator may be switched on.
*/
// TODO: switch on indicators (and off)?
boolean changeLeft = false;
boolean changeRight = false;
if (totalDesire.leftIsLargerOrEqual() && totalDesire.getLeft() >= dFree && acceptLeft)
{
// change left
changeLeft = true;
}
else if (!totalDesire.leftIsLargerOrEqual() && totalDesire.getRight() >= dFree && acceptRight)
{
// change right
changeRight = true;
}
else if (totalDesire.leftIsLargerOrEqual() && totalDesire.getLeft() >= dCoop)
{
// switch on left indicator
}
else if (!totalDesire.leftIsLargerOrEqual() && totalDesire.getRight() >= dCoop)
{
// switch on right indicator
}
/*
* Acceleration Acceleration is determined by the leader, and possibly adjacent vehicles for synchronization and
* cooperation.
*/
// follow leader
Acceleration a = dummy;
// synchronize
// TODO: get neighboring vehicles, use car-following model with altered headway
if (totalDesire.leftIsLargerOrEqual() && totalDesire.getLeft() >= dSync)
{
// sync left
a = minOf(a, limitDeceleration(dummy, b));
}
else if (!totalDesire.leftIsLargerOrEqual() && totalDesire.getRight() >= dSync)
{
// sync right
a = minOf(a, limitDeceleration(dummy, b));
}
// cooperate
// TODO: get neighboring vehicles, their indicators, use car-following model with altered headway
if (Math.random() > 0)
{
// cooperate left
a = minOf(a, limitDeceleration(dummy, b));
}
if (Math.random() > 0)
{
// cooperate right
a = minOf(a, limitDeceleration(dummy, b));
}
/*
* Operational plan.
*/
// TODO: Build the operational plan using minimum acceleration and including a possible lane change using
// a
// changeLeft/changeRight
// LaneOperationalPlanBuilder
return null;
}
protected Acceleration calculateAcceleration(final LaneBasedGTU follower, final HeadwayGTU leader, final double d)
{
// TODO: adjust desired headway based on desire
// set T
Acceleration a = calculateAcceleration(follower, leader);
// reset T
return a;
}
protected Acceleration calculateAcceleration(final LaneBasedGTU follower, final HeadwayGTU leader)
{
// TODO: speed limit
// TODO: follower != self
Speed speedLimit = new Speed(0, SpeedUnit.SI);
return Acceleration.ZERO;
}
/** {@inheritDoc} */
@Override
public final String toString()
{
String mandatory;
try
{
mandatory = "mandatoryIncentives=" + getMandatoryIncentives() + ", ";
}
catch (OperationalPlanException ope)
{
// thrown if no mandatory incentives
mandatory = "mandatoryIncentives=[]";
}
String voluntary;
if (!getVoluntaryIncentives().isEmpty())
{
voluntary = "voluntaryIncentives=" + getVoluntaryIncentives();
}
else
{
voluntary = "voluntaryIncentives=[]";
}
return "LMRS [" + mandatory + voluntary + "]";
}
}