View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical.toledo;
2   
3   import java.io.Serializable;
4   import java.util.Iterator;
5   import java.util.Random;
6   
7   import org.djunits.unit.AccelerationUnit;
8   import org.djunits.unit.LengthUnit;
9   import org.djunits.unit.LinearDensityUnit;
10  import org.djunits.value.vdouble.scalar.Acceleration;
11  import org.djunits.value.vdouble.scalar.Length;
12  import org.djunits.value.vdouble.scalar.LinearDensity;
13  import org.djunits.value.vdouble.scalar.Speed;
14  import org.djunits.value.vdouble.scalar.Time;
15  import org.djutils.exceptions.Try;
16  import org.opentrafficsim.base.parameters.ParameterException;
17  import org.opentrafficsim.base.parameters.ParameterTypeLength;
18  import org.opentrafficsim.base.parameters.ParameterTypes;
19  import org.opentrafficsim.base.parameters.Parameters;
20  import org.opentrafficsim.core.geometry.OTSGeometryException;
21  import org.opentrafficsim.core.gtu.GTUException;
22  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
23  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
24  import org.opentrafficsim.core.network.LateralDirectionality;
25  import org.opentrafficsim.core.network.NetworkException;
26  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
27  import org.opentrafficsim.road.gtu.lane.perception.CategoricalLanePerception;
28  import org.opentrafficsim.road.gtu.lane.perception.InfrastructureLaneChangeInfo;
29  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
30  import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
31  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
32  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.DirectNeighborsPerception;
33  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.HeadwayGtuType;
34  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
35  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
36  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneChange;
37  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneOperationalPlanBuilder;
38  import org.opentrafficsim.road.gtu.lane.tactical.AbstractLaneBasedTacticalPlanner;
39  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
40  import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
41  import org.opentrafficsim.road.network.speed.SpeedLimitProspect;
42  
43  import nl.tudelft.simulation.language.d3.DirectedPoint;
44  
45  /**
46   * Implementation of the model of Toledo (2003).<br>
47   * <br>
48   * Tomer Toledo (2003) "Integrated Driving Behavior Modeling", Massachusetts Institute of Technology.
49   * <p>
50   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
51   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
52   * <p>
53   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jun 21, 2016 <br>
54   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
55   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
56   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
57   */
58  
59  public class Toledo extends AbstractLaneBasedTacticalPlanner
60  {
61  
62      /** */
63      private static final long serialVersionUID = 20160711L;
64  
65      /** Look ahead parameter type. */
66      protected static final ParameterTypeLength LOOKAHEAD = ParameterTypes.LOOKAHEAD;
67  
68      /** Defines light vs heavy vehicles. */
69      static final Length MAX_LIGHT_VEHICLE_LENGTH = new Length(9.14, LengthUnit.METER);
70  
71      /** Distance defining tail gating. */
72      static final Length TAILGATE_LENGTH = new Length(10, LengthUnit.METER);
73  
74      /** Density for tail gating (Level Of Service (LOS) A, B or C). */
75      static final LinearDensity LOS_DENSITY = new LinearDensity(16, LinearDensityUnit.PER_KILOMETER);
76  
77      /** Random number generator. */
78      public static final Random RANDOM = new Random();
79  
80      /** Lane change status. */
81      private final LaneChange laneChange;
82  
83      /**
84       * Constructor.
85       * @param carFollowingModel CarFollowingModel; Car-following model.
86       * @param gtu LaneBasedGTU; GTU
87       */
88      public Toledo(final CarFollowingModel carFollowingModel, final LaneBasedGTU gtu)
89      {
90          super(carFollowingModel, gtu, new CategoricalLanePerception(gtu));
91          this.laneChange = Try.assign(() -> new LaneChange(gtu), "Parameter LCDUR is required.", GTUException.class);
92          getPerception().addPerceptionCategory(new ToledoPerception(getPerception()));
93          getPerception().addPerceptionCategory(new DirectNeighborsPerception(getPerception(), HeadwayGtuType.WRAP));
94      }
95  
96      /** {@inheritDoc} */
97      @Override
98      public final OperationalPlan generateOperationalPlan(final Time startTime, final DirectedPoint locationAtStartTime)
99              throws OperationalPlanException, GTUException, NetworkException, ParameterException
100     {
101 
102         // obtain objects to get info
103         LanePerception perception = getPerception();
104         SpeedLimitProspect slp =
105                 perception.getPerceptionCategory(ToledoPerception.class).getSpeedLimitProspect(RelativeLane.CURRENT);
106         SpeedLimitInfo sli = slp.getSpeedLimitInfo(Length.ZERO);
107         Parameters params = getGtu().getParameters();
108 
109         Acceleration acceleration = null;
110 
111         // if (this.laneChangeDirectionality == null)
112         LateralDirectionality initiatedLaneChange;
113         NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
114         if (!this.laneChange.isChangingLane())
115         {
116             // not changing lane
117 
118             // 3rd layer of model: Target gap model
119             // TODO vehicle not ahead and not backwards but completely adjacent
120             GapInfo gapFwdL = getGapInfo(params, perception, Gap.FORWARD, RelativeLane.LEFT);
121             GapInfo gapAdjL = getGapInfo(params, perception, Gap.ADJACENT, RelativeLane.LEFT);
122             GapInfo gapBckL = getGapInfo(params, perception, Gap.BACKWARD, RelativeLane.LEFT);
123             GapInfo gapFwdR = getGapInfo(params, perception, Gap.FORWARD, RelativeLane.RIGHT);
124             GapInfo gapAdjR = getGapInfo(params, perception, Gap.ADJACENT, RelativeLane.RIGHT);
125             GapInfo gapBckR = getGapInfo(params, perception, Gap.BACKWARD, RelativeLane.RIGHT);
126             double emuTgL =
127                     Math.log(Math.exp(gapFwdL.getUtility()) + Math.exp(gapAdjL.getUtility()) + Math.exp(gapBckL.getUtility()));
128             double emuTgR =
129                     Math.log(Math.exp(gapFwdR.getUtility()) + Math.exp(gapAdjR.getUtility()) + Math.exp(gapBckR.getUtility()));
130 
131             // 2nd layer of model: Gap-acceptance model
132             // gap acceptance random terms (variable over time, equal for left and right)
133             double eLead = RANDOM.nextGaussian() * Math.pow(params.getParameter(ToledoLaneChangeParameters.SIGMA_LEAD), 2);
134             double eLag = RANDOM.nextGaussian() * Math.pow(params.getParameter(ToledoLaneChangeParameters.SIGMA_LAG), 2);
135             GapAcceptanceInfo gapAcceptL =
136                     getGapAcceptanceInfo(getGtu(), params, perception, emuTgL, eLead, eLag, RelativeLane.LEFT);
137             GapAcceptanceInfo gapAcceptR =
138                     getGapAcceptanceInfo(getGtu(), params, perception, emuTgR, eLead, eLag, RelativeLane.RIGHT);
139 
140             // 1st layer of model: Target lane model
141             double vL = laneUtility(getGtu(), params, perception, gapAcceptL.getEmu(), sli, RelativeLane.LEFT);
142             double vC = laneUtility(getGtu(), params, perception, 0, sli, RelativeLane.CURRENT);
143             double vR = laneUtility(getGtu(), params, perception, gapAcceptR.getEmu(), sli, RelativeLane.RIGHT);
144 
145             // change lane?
146             if (vL > vR && vL > vC && gapAcceptL.isAcceptable())
147             {
148                 initiatedLaneChange = LateralDirectionality.LEFT;
149             }
150             else if (vR > vL && vR > vC && gapAcceptR.isAcceptable())
151             {
152                 initiatedLaneChange = LateralDirectionality.RIGHT;
153             }
154             else
155             {
156                 initiatedLaneChange = LateralDirectionality.NONE;
157             }
158 
159             // accelerate for gap selection
160             if (initiatedLaneChange.isNone())
161             {
162                 if ((vC > vR && vC > vL)
163                         || (!neighbors.getLeaders(RelativeLane.CURRENT).isEmpty() && neighbors.getLeaders(RelativeLane.CURRENT)
164                                 .first().getDistance().lt(getCarFollowingModel().desiredHeadway(params, getGtu().getSpeed()))))
165                 {
166                     acceleration = getCarFollowingModel().followingAcceleration(params, getGtu().getSpeed(), sli,
167                             neighbors.getLeaders(RelativeLane.CURRENT));
168                 }
169                 else
170                 {
171                     GapInfo gapAdj;
172                     GapInfo gapFwd;
173                     GapInfo gapBck;
174                     if (vL > vR && vL > vC)
175                     {
176                         gapAdj = gapAdjL;
177                         gapFwd = gapFwdL;
178                         gapBck = gapBckL;
179                     }
180                     else
181                     {
182                         gapAdj = gapAdjR;
183                         gapFwd = gapFwdR;
184                         gapBck = gapBckR;
185                     }
186                     if (gapAdj.getUtility() > gapFwd.getUtility() && gapAdj.getUtility() > gapBck.getUtility())
187                     {
188                         // adjacent gap selected
189                         double eadj = Toledo.RANDOM.nextGaussian() * params.getParameter(ToledoLaneChangeParameters.SIGMA_ADJ)
190                                 * params.getParameter(ToledoLaneChangeParameters.SIGMA_ADJ);
191                         acceleration =
192                                 new Acceleration(
193                                         params.getParameter(ToledoLaneChangeParameters.C_ADJ_ACC)
194                                                 * (params.getParameter(ToledoLaneChangeParameters.BETA_DP)
195                                                         * gapAdj.getLength().si - gapAdj.getDistance().si)
196                                                 + eadj,
197                                         AccelerationUnit.SI);
198                     }
199                     else if (gapFwd.getUtility() > gapAdj.getUtility() && gapFwd.getUtility() > gapBck.getUtility())
200                     {
201                         // forward gap selected
202                         Length desiredPosition = new Length(
203                                 gapFwd.getDistance().si
204                                         + params.getParameter(ToledoLaneChangeParameters.BETA_DP) * gapFwd.getLength().si,
205                                 LengthUnit.SI);
206                         double deltaV = gapFwd.getSpeed().si > getGtu().getSpeed().si
207                                 ? params.getParameter(ToledoLaneChangeParameters.LAMBDA_FWD_POS)
208                                         * (gapFwd.getSpeed().si - getGtu().getSpeed().si)
209                                 : params.getParameter(ToledoLaneChangeParameters.LAMBDA_FWD_NEG)
210                                         * (getGtu().getSpeed().si - gapFwd.getSpeed().si);
211                         double efwd = Toledo.RANDOM.nextGaussian() * params.getParameter(ToledoLaneChangeParameters.SIGMA_FWD)
212                                 * params.getParameter(ToledoLaneChangeParameters.SIGMA_FWD);
213                         acceleration = new Acceleration(params.getParameter(ToledoLaneChangeParameters.C_FWD_ACC)
214                                 * Math.pow(desiredPosition.si, params.getParameter(ToledoLaneChangeParameters.BETA_FWD))
215                                 * Math.exp(deltaV) + efwd, AccelerationUnit.SI);
216                     }
217                     else
218                     {
219                         // backward gap selected
220                         Length desiredPosition = new Length(
221                                 gapBck.getDistance().si
222                                         + (1 - params.getParameter(ToledoLaneChangeParameters.BETA_DP)) * gapBck.getLength().si,
223                                 LengthUnit.SI);
224                         double deltaV = gapBck.getSpeed().si > getGtu().getSpeed().si
225                                 ? params.getParameter(ToledoLaneChangeParameters.LAMBDA_BCK_POS)
226                                         * (gapBck.getSpeed().si - getGtu().getSpeed().si)
227                                 : params.getParameter(ToledoLaneChangeParameters.LAMBDA_BCK_NEG)
228                                         * (getGtu().getSpeed().si - gapBck.getSpeed().si);
229                         double ebck = Toledo.RANDOM.nextGaussian() * params.getParameter(ToledoLaneChangeParameters.SIGMA_BCK)
230                                 * params.getParameter(ToledoLaneChangeParameters.SIGMA_BCK);
231                         acceleration = new Acceleration(params.getParameter(ToledoLaneChangeParameters.C_BCK_ACC)
232                                 * Math.pow(desiredPosition.si, params.getParameter(ToledoLaneChangeParameters.BETA_BCK))
233                                 * Math.exp(deltaV) + ebck, AccelerationUnit.SI);
234                     }
235                 }
236             }
237         }
238         else
239         {
240             initiatedLaneChange = LateralDirectionality.NONE;
241         }
242 
243         if (initiatedLaneChange.isLeft())
244         {
245             // changing left
246             getCarFollowingModel().followingAcceleration(params, getGtu().getSpeed(), sli,
247                     neighbors.getLeaders(RelativeLane.LEFT));
248         }
249         else if (initiatedLaneChange.isRight())
250         {
251             // changing right
252             getCarFollowingModel().followingAcceleration(params, getGtu().getSpeed(), sli,
253                     neighbors.getLeaders(RelativeLane.RIGHT));
254         }
255 
256         if (acceleration == null)
257         {
258             throw new Error("Acceleration from toledo model is null.");
259         }
260 
261         // operational plan
262         if (initiatedLaneChange.isNone())
263         {
264             try
265             {
266                 return LaneOperationalPlanBuilder.buildAccelerationPlan(getGtu(), startTime, getGtu().getSpeed(), acceleration,
267                         params.getParameter(ToledoLaneChangeParameters.DT), false);
268             }
269             catch (OTSGeometryException exception)
270             {
271                 throw new OperationalPlanException(exception);
272             }
273         }
274 
275         try
276         {
277             OperationalPlan plan = LaneOperationalPlanBuilder.buildAccelerationLaneChangePlan(getGtu(), initiatedLaneChange,
278                     getGtu().getLocation(), startTime, getGtu().getSpeed(), acceleration,
279                     params.getParameter(ToledoLaneChangeParameters.DT), this.laneChange);
280             return plan;
281         }
282         catch (OTSGeometryException exception)
283         {
284             throw new OperationalPlanException(exception);
285         }
286     }
287 
288     /**
289      * Returns info regarding a gap.
290      * @param params Parameters; parameters
291      * @param perception LanePerception; perception
292      * @param gap Gap; gap
293      * @param lane RelativeLane; lane
294      * @return utility of gap
295      * @throws ParameterException if parameter is not given
296      * @throws OperationalPlanException perception exception
297      */
298     private GapInfo getGapInfo(final Parameters params, final LanePerception perception, final Gap gap, final RelativeLane lane)
299             throws ParameterException, OperationalPlanException
300     {
301 
302         // capture no leaders/follower cases for forward and backward gaps
303         NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
304         if (gap.equals(Gap.FORWARD) && (neighbors.getLeaders(lane) == null || neighbors.getLeaders(lane).isEmpty()))
305         {
306             // no leaders
307             return new GapInfo(Double.NEGATIVE_INFINITY, null, null, null);
308         }
309         if (gap.equals(Gap.BACKWARD) && (neighbors.getFollowers(lane) == null || neighbors.getFollowers(lane).isEmpty()))
310         {
311             // no followers
312             return new GapInfo(Double.NEGATIVE_INFINITY, null, null, null);
313         }
314 
315         double constant; // utility function: constant
316         double alpha; // utility function: alpha parameter
317         Length distanceToGap; // utility function: distance to gap
318         int deltaFrontVehicle; // utility function: dummy whether the gap is limited by vehicle in front
319         Length putativeLength; // acceleration model: gap length, not affected by vehicle in front
320         Length putativeDistance; // acceleration model: distance to gap, different from 'distanceToGap' in utility function
321         Speed putativeSpeed; // acceleration model: speed of putative follower or leader
322         Length leaderDist; // leader of (effective) gap
323         Speed leaderSpeed;
324         Length followerDist; // follower of (effective) gap
325         Speed followerSpeed;
326 
327         if (gap.equals(Gap.FORWARD))
328         {
329             constant = params.getParameter(ToledoLaneChangeParameters.C_FWD_TG);
330             alpha = 0;
331             PerceptionCollectable<HeadwayGTU, LaneBasedGTU> itAble = neighbors.getLeaders(lane);
332             HeadwayGTU second = null;
333             if (!itAble.isEmpty())
334             {
335                 Iterator<HeadwayGTU> it = itAble.iterator();
336                 it.next();
337                 second = it.next();
338             }
339             if (second != null)
340             {
341                 // two leaders
342                 Iterator<HeadwayGTU> it = neighbors.getLeaders(lane).iterator();
343                 it.next();
344                 HeadwayGTU leader = it.next();
345                 leaderDist = leader.getDistance();
346                 leaderSpeed = leader.getSpeed();
347                 putativeLength = leaderDist.minus(neighbors.getLeaders(lane).first().getDistance())
348                         .minus(neighbors.getLeaders(lane).first().getLength());
349             }
350             else
351             {
352                 // TODO infinite -> some limited space, speed also
353                 // one leader
354                 leaderDist = Length.POSITIVE_INFINITY;
355                 leaderSpeed = Speed.POSITIVE_INFINITY;
356                 putativeLength = leaderDist;
357             }
358             // distance to nose, so add length
359             followerDist =
360                     neighbors.getLeaders(lane).first().getDistance().plus(neighbors.getLeaders(lane).first().getLength());
361             followerSpeed = neighbors.getLeaders(lane).first().getSpeed();
362             distanceToGap = followerDist; // same as distance to nose of first leader
363             putativeDistance = distanceToGap;
364             putativeSpeed = followerSpeed;
365         }
366         else if (gap.equals(Gap.ADJACENT))
367         {
368             constant = 0;
369             alpha = params.getParameter(ToledoLaneChangeParameters.ALPHA_ADJ);
370             distanceToGap = Length.ZERO;
371             if (neighbors.getLeaders(lane) != null && !neighbors.getLeaders(lane).isEmpty())
372             {
373                 leaderDist = neighbors.getLeaders(lane).first().getDistance();
374                 leaderSpeed = neighbors.getLeaders(lane).first().getSpeed();
375             }
376             else
377             {
378                 leaderDist = Length.POS_MAXVALUE;
379                 leaderSpeed = Speed.POS_MAXVALUE;
380             }
381             if (neighbors.getFollowers(lane) != null && !neighbors.getFollowers(lane).isEmpty())
382             {
383                 // plus own vehicle length for distance from nose of own vehicle (and then whole negative)
384                 followerDist = neighbors.getFollowers(lane).first().getDistance().plus(getGtu().getLength());
385                 followerSpeed = neighbors.getFollowers(lane).first().getSpeed();
386                 putativeDistance = followerDist;
387             }
388             else
389             {
390                 followerDist = Length.NEG_MAXVALUE;
391                 followerSpeed = Speed.NEG_MAXVALUE;
392                 putativeDistance = Length.POS_MAXVALUE;
393             }
394             putativeSpeed = null;
395             putativeLength = leaderDist.plus(followerDist).plus(getGtu().getLength());
396         }
397         else
398         {
399             constant = params.getParameter(ToledoLaneChangeParameters.C_BCK_TG);
400             alpha = params.getParameter(ToledoLaneChangeParameters.ALPHA_BCK);
401             deltaFrontVehicle = 0;
402             PerceptionCollectable<HeadwayGTU, LaneBasedGTU> itAble = neighbors.getFollowers(lane);
403             HeadwayGTU second = null;
404             if (!itAble.isEmpty())
405             {
406                 Iterator<HeadwayGTU> it = itAble.iterator();
407                 it.next();
408                 second = it.next();
409             }
410             if (second != null)
411             {
412                 // two followers
413                 Iterator<HeadwayGTU> it = neighbors.getFollowers(lane).iterator();
414                 it.next();
415                 HeadwayGTU follower = it.next();
416                 followerDist = follower.getDistance();
417                 followerSpeed = follower.getSpeed();
418                 putativeLength = followerDist.minus(neighbors.getFollowers(lane).first().getDistance())
419                         .minus(neighbors.getFollowers(lane).first().getLength());
420             }
421             else
422             {
423                 // one follower
424                 followerDist = Length.NEGATIVE_INFINITY;
425                 followerSpeed = Speed.NEGATIVE_INFINITY;
426                 putativeLength = followerDist;
427             }
428             // add vehicle length to get distance to tail of 1st follower (and then whole negative)
429             leaderDist =
430                     neighbors.getFollowers(lane).first().getDistance().plus(neighbors.getLeaders(lane).first().getLength());
431             leaderSpeed = neighbors.getFollowers(lane).first().getSpeed();
432             distanceToGap = neighbors.getFollowers(lane).first().getDistance().plus(getGtu().getLength()); // own nose to nose
433             putativeDistance = distanceToGap.plus(neighbors.getFollowers(lane).first().getLength());
434             putativeSpeed = leaderSpeed;
435         }
436 
437         // limit by leader in current lane
438         if (!gap.equals(Gap.BACKWARD) && !neighbors.getLeaders(RelativeLane.CURRENT).isEmpty()
439                 && neighbors.getLeaders(RelativeLane.CURRENT).first().getDistance().lt(leaderDist))
440         {
441             leaderDist = neighbors.getLeaders(RelativeLane.CURRENT).first().getDistance();
442             leaderSpeed = neighbors.getLeaders(RelativeLane.CURRENT).first().getSpeed();
443             deltaFrontVehicle = 1;
444         }
445         else
446         {
447             deltaFrontVehicle = 0;
448         }
449 
450         // calculate relative gap speed and effective gap
451         Speed dV = followerSpeed.minus(leaderSpeed);
452         Length effectiveGap = leaderDist.minus(followerDist);
453         // {@formatter:off}
454         // calculate utility 
455         double util = constant 
456                 + params.getParameter(ToledoLaneChangeParameters.BETA_DTG) * distanceToGap.si
457                 + params.getParameter(ToledoLaneChangeParameters.BETA_EG) * effectiveGap.si
458                 + params.getParameter(ToledoLaneChangeParameters.BETA_FV) * deltaFrontVehicle
459                 + params.getParameter(ToledoLaneChangeParameters.BETA_RGS) * dV.si
460                 + alpha * params.getParameter(ToledoLaneChangeParameters.ERROR_TERM);
461         // {@formatter:on}
462         return new GapInfo(util, putativeLength, putativeDistance, putativeSpeed);
463 
464     }
465 
466     /**
467      * Returns info regarding gap-acceptance.
468      * @param gtu LaneBasedGTU; GTU
469      * @param params Parameters; parameters
470      * @param perception LanePerception; perception
471      * @param emuTg double; emu from target gap model
472      * @param eLead double; lead error
473      * @param eLag double; lag error
474      * @param lane RelativeLane; lane to evaluate
475      * @return info regarding gap-acceptance
476      * @throws ParameterException if parameter is not defined
477      * @throws OperationalPlanException perception exception
478      */
479     private GapAcceptanceInfo getGapAcceptanceInfo(final LaneBasedGTU gtu, final Parameters params,
480             final LanePerception perception, final double emuTg, final double eLead, final double eLag, final RelativeLane lane)
481             throws ParameterException, OperationalPlanException
482     {
483 
484         // get lead and lag utility and acceptance
485         double vLead;
486         double vLag;
487         boolean acceptLead;
488         boolean acceptLag;
489 
490         NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
491         if (neighbors.getLeaders(lane) != null && !neighbors.getLeaders(lane).isEmpty())
492         {
493             Speed dVLead = neighbors.getLeaders(lane).first().getSpeed().minus(gtu.getSpeed());
494             Length sLead = neighbors.getLeaders(lane).first().getDistance();
495             // critical gap
496             Length gLead =
497                     new Length(
498                             Math.exp(params.getParameter(ToledoLaneChangeParameters.C_LEAD)
499                                     + params.getParameter(ToledoLaneChangeParameters.BETA_POS_LEAD)
500                                             * Speed.max(dVLead, Speed.ZERO).si
501                                     + params.getParameter(ToledoLaneChangeParameters.BETA_NEG_LEAD)
502                                             * Speed.min(dVLead, Speed.ZERO).si
503                                     + params.getParameter(ToledoLaneChangeParameters.BETA_EMU_LEAD) * emuTg
504                                     + params.getParameter(ToledoLaneChangeParameters.ALPHA_TL_LEAD)
505                                             * params.getParameter(ToledoLaneChangeParameters.ERROR_TERM)
506                                     + eLead),
507                             LengthUnit.SI);
508             vLead = Math.log(gLead.si) - (params.getParameter(ToledoLaneChangeParameters.C_LEAD)
509                     + params.getParameter(ToledoLaneChangeParameters.BETA_POS_LEAD) * Speed.max(dVLead, Speed.ZERO).si
510                     + params.getParameter(ToledoLaneChangeParameters.BETA_NEG_LEAD) * Speed.min(dVLead, Speed.ZERO).si
511                     + params.getParameter(ToledoLaneChangeParameters.BETA_EMU_LEAD) * emuTg);
512             acceptLead = gLead.lt(sLead);
513         }
514         else
515         {
516             vLead = 0;
517             acceptLead = false;
518         }
519 
520         if (neighbors.getFollowers(lane) != null && !neighbors.getFollowers(lane).isEmpty())
521         {
522             Speed dVLag = neighbors.getFollowers(lane).first().getSpeed().minus(gtu.getSpeed());
523             Length sLag = neighbors.getFollowers(lane).first().getDistance();
524             // critical gap
525             Length gLag =
526                     new Length(
527                             Math.exp(params.getParameter(ToledoLaneChangeParameters.C_LAG)
528                                     + params.getParameter(ToledoLaneChangeParameters.BETA_POS_LAG)
529                                             * Speed.max(dVLag, Speed.ZERO).si
530                                     + params.getParameter(ToledoLaneChangeParameters.BETA_EMU_LAG) * emuTg
531                                     + params.getParameter(ToledoLaneChangeParameters.ALPHA_TL_LAG)
532                                             * params.getParameter(ToledoLaneChangeParameters.ERROR_TERM)
533                                     + eLag),
534                             LengthUnit.SI);
535             vLag = Math.log(gLag.si) - (params.getParameter(ToledoLaneChangeParameters.C_LAG)
536                     + params.getParameter(ToledoLaneChangeParameters.BETA_POS_LAG) * Speed.max(dVLag, Speed.ZERO).si
537                     + params.getParameter(ToledoLaneChangeParameters.BETA_EMU_LAG) * emuTg);
538             acceptLag = gLag.lt(sLag);
539         }
540         else
541         {
542             vLag = 0;
543             acceptLag = false;
544         }
545 
546         // gap acceptance emu
547         double vRatLead = vLead / params.getParameter(ToledoLaneChangeParameters.SIGMA_LEAD);
548         double vRatLag = vLag / params.getParameter(ToledoLaneChangeParameters.SIGMA_LAG);
549         double emuGa = vLead * cumNormDist(vRatLead)
550                 + params.getParameter(ToledoLaneChangeParameters.SIGMA_LEAD) * normDist(vRatLead) + vLag * cumNormDist(vRatLag)
551                 + params.getParameter(ToledoLaneChangeParameters.SIGMA_LAG) * normDist(vRatLag);
552 
553         // return info
554         return new GapAcceptanceInfo(emuGa, acceptLead && acceptLag);
555 
556     }
557 
558     /**
559      * Returns the utility of a lane.
560      * @param gtu LaneBasedGTU; GTU
561      * @param params Parameters; parameters
562      * @param perception LanePerception; perception
563      * @param emuGa double; emu from gap acceptance model
564      * @param sli SpeedLimitInfo; speed limit info
565      * @param lane RelativeLane; lane to evaluate
566      * @return utility of lane
567      * @throws ParameterException if parameter is not defined
568      * @throws OperationalPlanException perception exception
569      */
570     private double laneUtility(final LaneBasedGTU gtu, final Parameters params, final LanePerception perception,
571             final double emuGa, final SpeedLimitInfo sli, final RelativeLane lane)
572             throws ParameterException, OperationalPlanException
573     {
574 
575         ToledoPerception toledo = perception.getPerceptionCategory(ToledoPerception.class);
576         if (!perception.getLaneStructure().getExtendedCrossSection().contains(lane))
577         {
578             return 0.0;
579         }
580 
581         // get infrastructure info
582         boolean takeNextOffRamp = false;
583         for (InfrastructureLaneChangeInfoToledo info : toledo.getInfrastructureLaneChangeInfo(RelativeLane.CURRENT))
584         {
585             if (info.getSplitNumber() == 1)
586             {
587                 takeNextOffRamp = true;
588             }
589         }
590         int deltaNextExit = takeNextOffRamp ? 1 : 0;
591 
592         Length dExit;
593         if (!toledo.getInfrastructureLaneChangeInfo(RelativeLane.CURRENT).isEmpty())
594         {
595             dExit = toledo.getInfrastructureLaneChangeInfo(RelativeLane.CURRENT).first().getRemainingDistance();
596         }
597         else
598         {
599             dExit = Length.POSITIVE_INFINITY;
600         }
601 
602         int[] delta = new int[3];
603         int deltaAdd = 0;
604         if (!toledo.getInfrastructureLaneChangeInfo(lane).isEmpty())
605         {
606             InfrastructureLaneChangeInfo ilciLef = toledo.getInfrastructureLaneChangeInfo(lane).first();
607             if (ilciLef.getRequiredNumberOfLaneChanges() > 1 && ilciLef.getRequiredNumberOfLaneChanges() < 5)
608             {
609                 deltaAdd = ilciLef.getRequiredNumberOfLaneChanges() - 2;
610                 delta[deltaAdd] = 1;
611             }
612         }
613 
614         // heavy neighbor
615         NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
616         Length leaderLength = !lane.isCurrent() && !neighbors.getFirstLeaders(lane.getLateralDirectionality()).isEmpty()
617                 ? neighbors.getFirstLeaders(lane.getLateralDirectionality()).first().getLength() : Length.ZERO;
618         Length followerLength = !lane.isCurrent() && !neighbors.getFirstFollowers(lane.getLateralDirectionality()).isEmpty()
619                 ? neighbors.getFirstFollowers(lane.getLateralDirectionality()).first().getDistance() : Length.ZERO;
620         int deltaHeavy = leaderLength.gt(MAX_LIGHT_VEHICLE_LENGTH) || followerLength.gt(MAX_LIGHT_VEHICLE_LENGTH) ? 1 : 0;
621 
622         // get density
623         LinearDensity d = getDensityInLane(gtu, perception, lane);
624 
625         // tail gating
626         int deltaTailgate = 0;
627         if (lane.equals(RelativeLane.CURRENT) && !neighbors.getFollowers(RelativeLane.CURRENT).isEmpty()
628                 && neighbors.getFollowers(RelativeLane.CURRENT).first().getDistance().le(TAILGATE_LENGTH))
629         {
630             LinearDensity dL = getDensityInLane(gtu, perception, RelativeLane.LEFT);
631             LinearDensity dR = getDensityInLane(gtu, perception, RelativeLane.RIGHT);
632             LinearDensity dRoad = new LinearDensity((dL.si + d.si + dR.si) / 3, LinearDensityUnit.SI);
633             if (dRoad.le(LOS_DENSITY))
634             {
635                 deltaTailgate = 1;
636             }
637         }
638 
639         // right most
640         // TODO definition of 'right most lane' does not account for ramps and weaving sections
641         int deltaRightMost = 0;
642         if (lane.equals(RelativeLane.CURRENT) && !toledo.getCrossSection().contains(RelativeLane.RIGHT))
643         {
644             deltaRightMost = 1;
645         }
646         else if (lane.equals(RelativeLane.RIGHT) && toledo.getCrossSection().contains(RelativeLane.RIGHT)
647                 && !toledo.getCrossSection().contains(RelativeLane.SECOND_RIGHT))
648         {
649             deltaRightMost = 1;
650         }
651 
652         // current lane traffic
653         Speed vFront;
654         Length sFront;
655         if (lane.equals(RelativeLane.CURRENT))
656         {
657             if (!neighbors.getLeaders(RelativeLane.CURRENT).isEmpty())
658             {
659                 vFront = neighbors.getLeaders(RelativeLane.CURRENT).first().getSpeed();
660                 sFront = neighbors.getLeaders(RelativeLane.CURRENT).first().getDistance();
661             }
662             else
663             {
664                 vFront = getCarFollowingModel().desiredSpeed(params, sli);
665                 sFront = getCarFollowingModel().desiredHeadway(params, gtu.getSpeed());
666             }
667         }
668         else
669         {
670             vFront = Speed.ZERO;
671             sFront = Length.ZERO;
672         }
673 
674         // target lane utility
675         double constant = 0;
676         double error = 0;
677         if (lane.equals(RelativeLane.CURRENT))
678         {
679             constant = params.getParameter(ToledoLaneChangeParameters.C_CL);
680             error = params.getParameter(ToledoLaneChangeParameters.ALPHA_CL)
681                     * params.getParameter(ToledoLaneChangeParameters.ERROR_TERM);
682         }
683         else if (lane.equals(RelativeLane.RIGHT))
684         {
685             constant = params.getParameter(ToledoLaneChangeParameters.C_RL);
686             error = params.getParameter(ToledoLaneChangeParameters.ALPHA_RL)
687                     * params.getParameter(ToledoLaneChangeParameters.ERROR_TERM);
688         }
689         // {@formatter:off}
690         return constant
691                 + params.getParameter(ToledoLaneChangeParameters.BETA_RIGHT_MOST) * deltaRightMost // 0 for LEFT
692                 + params.getParameter(ToledoLaneChangeParameters.BETA_VFRONT) * vFront.si // 0 for LEFT/RIGHT
693                 + params.getParameter(ToledoLaneChangeParameters.BETA_SFRONT) * sFront.si // 0 for LEFT/RIGHT
694                 + params.getParameter(ToledoLaneChangeParameters.BETA_DENSITY) * d.getInUnit(LinearDensityUnit.PER_KILOMETER)
695                 + params.getParameter(ToledoLaneChangeParameters.BETA_HEAVY_NEIGHBOUR) * deltaHeavy
696                 + params.getParameter(ToledoLaneChangeParameters.BETA_TAILGATE) * deltaTailgate
697                 + Math.pow(dExit.getInUnit(LengthUnit.KILOMETER), params.getParameter(ToledoLaneChangeParameters.THETA_MLC)) 
698                 * (params.getParameter(ToledoLaneChangeParameters.BETA1) * delta[0] 
699                         + params.getParameter(ToledoLaneChangeParameters.BETA2) * delta[1] 
700                         + params.getParameter(ToledoLaneChangeParameters.BETA3) * delta[2])
701                 + params.getParameter(ToledoLaneChangeParameters.BETA_NEXT_EXIT) * deltaNextExit
702                 + params.getParameter(ToledoLaneChangeParameters.BETA_ADD) * deltaAdd
703                 + params.getParameter(ToledoLaneChangeParameters.BETA_EMU_GA) * emuGa // 0 for CURRENT (given correct input)
704                 + error; // 0 for LEFT
705         // {@formatter:on}
706     }
707 
708     /**
709      * Returns the density in the given lane based on the following and leading vehicles.
710      * @param gtu LaneBasedGTU; subject GTU
711      * @param perception LanePerception; perception
712      * @param lane RelativeLane; lane to get density of
713      * @return density in the given lane based on the following and leading vehicles
714      * @throws OperationalPlanException perception exception
715      */
716     private LinearDensity getDensityInLane(final LaneBasedGTU gtu, final LanePerception perception, final RelativeLane lane)
717             throws OperationalPlanException
718     {
719         int nVehicles = 0;
720         NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
721         Length up = Length.ZERO;
722         Length down = Length.ZERO;
723         for (HeadwayGTU neighbor : neighbors.getFollowers(lane))
724         {
725             nVehicles++;
726             down = neighbor.getDistance();
727         }
728         for (HeadwayGTU neighbor : neighbors.getLeaders(lane))
729         {
730             nVehicles++;
731             up = neighbor.getDistance();
732         }
733         if (nVehicles > 0)
734         {
735             return new LinearDensity(nVehicles / up.plus(down).getInUnit(LengthUnit.KILOMETER),
736                     LinearDensityUnit.PER_KILOMETER);
737         }
738         return LinearDensity.ZERO;
739     }
740 
741     /**
742      * Returns the cumulative density function (CDF) at given value of the standard normal distribution.
743      * @param x double; value
744      * @return cumulative density function (CDF) at given value of the standard normal distribution
745      */
746     private static double cumNormDist(final double x)
747     {
748         return .5 * (1 + erf(x / Math.sqrt(2)));
749     }
750 
751     /**
752      * Error function approximation using Horner's method.
753      * @param x double; value
754      * @return error function approximation
755      */
756     private static double erf(final double x)
757     {
758         double t = 1.0 / (1.0 + 0.5 * Math.abs(x));
759         // use Horner's method
760         // {@formatter:off}
761         double tau = t * Math.exp(-x * x - 1.26551223 + t * (1.00002368 + t * (0.37409196 
762                 + t * (0.09678418 + t * (0.18628806 + t * (0.27886807 + t * (-1.13520398 
763                         + t * (1.48851587 + t * (-0.82215223 + t * (0.17087277))))))))));
764         // {@formatter:on}
765         return x >= 0 ? 1 - tau : tau - 1;
766     }
767 
768     /**
769      * Returns the probability density function (PDF) at given value of the standard normal distribution.
770      * @param x double; value
771      * @return probability density function (PDF) at given value of the standard normal distribution
772      */
773     private static double normDist(final double x)
774     {
775         return Math.exp(-x * x / 2) / Math.sqrt(2 * Math.PI);
776     }
777 
778     /**
779      * Gap indicator in adjacent lane.
780      * <p>
781      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
782      * <br>
783      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
784      * <p>
785      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 11, 2016 <br>
786      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
787      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
788      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
789      */
790     private enum Gap
791     {
792         /** Gap in front of leader in adjacent lane. */
793         FORWARD,
794 
795         /** Gap between follower and leader in adjacent lane. */
796         ADJACENT,
797 
798         /** Gap behind follower in adjacent lane. */
799         BACKWARD;
800     }
801 
802     /**
803      * Contains info regarding an adjacent gap.
804      * <p>
805      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
806      * <br>
807      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
808      * <p>
809      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 11, 2016 <br>
810      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
811      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
812      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
813      */
814     private class GapInfo implements Serializable
815     {
816 
817         /** */
818         private static final long serialVersionUID = 20160811L;
819 
820         /** Utility of the gap. */
821         private final double utility;
822 
823         /** Length of the gap. */
824         private final Length length;
825 
826         /** Distance towards the gap. */
827         private final Length distance;
828 
829         /** Speed of the vehicle in front or behind the gap, always the closest. */
830         private final Speed speed;
831 
832         /**
833          * @param utility double; utility of gap
834          * @param length Length; length of the gap
835          * @param distance Length; distance towards the gap
836          * @param speed Speed; speed of the vehicle in front or behind the gap, always the closest
837          */
838         GapInfo(final double utility, final Length length, final Length distance, final Speed speed)
839         {
840             this.utility = utility;
841             this.length = length;
842             this.distance = distance;
843             this.speed = speed;
844         }
845 
846         /**
847          * @return utility
848          */
849         public final double getUtility()
850         {
851             return this.utility;
852         }
853 
854         /**
855          * @return length
856          */
857         public final Length getLength()
858         {
859             return this.length;
860         }
861 
862         /**
863          * @return distance
864          */
865         public final Length getDistance()
866         {
867             return this.distance;
868         }
869 
870         /**
871          * @return speed
872          */
873         public final Speed getSpeed()
874         {
875             return this.speed;
876         }
877 
878         /** {@inheritDoc} */
879         @Override
880         public String toString()
881         {
882             return "GapAcceptanceInfo [utility = " + this.utility + ", length = " + this.length + ", distance = "
883                     + this.distance + ", speed = " + this.speed + "]";
884         }
885 
886     }
887 
888     /**
889      * Contains info regarding gap-acceptance.
890      * <p>
891      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
892      * <br>
893      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
894      * <p>
895      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 11, 2016 <br>
896      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
897      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
898      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
899      */
900     private class GapAcceptanceInfo implements Serializable
901     {
902 
903         /** */
904         private static final long serialVersionUID = 20160811L;
905 
906         /** Emu value of gap-acceptance. */
907         private final double emu;
908 
909         /** Whether gap is acceptable. */
910         private final boolean acceptable;
911 
912         /**
913          * @param emu double; emu
914          * @param acceptable boolean; whether gap is acceptable
915          */
916         GapAcceptanceInfo(final double emu, final boolean acceptable)
917         {
918             this.emu = emu;
919             this.acceptable = acceptable;
920         }
921 
922         /**
923          * @return emu
924          */
925         public final double getEmu()
926         {
927             return this.emu;
928         }
929 
930         /**
931          * @return acceptable
932          */
933         public final boolean isAcceptable()
934         {
935             return this.acceptable;
936         }
937 
938         /** {@inheritDoc} */
939         @Override
940         public String toString()
941         {
942             return "GapAcceptanceInfo [emu = " + this.emu + ", acceptable = " + this.acceptable + "]";
943         }
944 
945     }
946 
947     /** {@inheritDoc} */
948     @Override
949     public final String toString()
950     {
951         return "Toledo tactical planner.";
952     }
953 
954 }