1 package org.opentrafficsim.road.gtu.lane.tactical.util.lmrs;
2
3 import java.util.Iterator;
4 import java.util.LinkedHashSet;
5 import java.util.Map;
6 import java.util.SortedSet;
7
8 import org.djunits.value.vdouble.scalar.Acceleration;
9 import org.djunits.value.vdouble.scalar.Duration;
10 import org.djunits.value.vdouble.scalar.Length;
11 import org.djunits.value.vdouble.scalar.Speed;
12 import org.djunits.value.vdouble.scalar.Time;
13 import org.djutils.exceptions.Try;
14 import org.opentrafficsim.base.parameters.ParameterException;
15 import org.opentrafficsim.base.parameters.ParameterTypeAcceleration;
16 import org.opentrafficsim.base.parameters.ParameterTypeDuration;
17 import org.opentrafficsim.base.parameters.ParameterTypes;
18 import org.opentrafficsim.base.parameters.Parameters;
19 import org.opentrafficsim.core.gtu.GTUException;
20 import org.opentrafficsim.core.gtu.TurnIndicatorIntent;
21 import org.opentrafficsim.core.gtu.perception.EgoPerception;
22 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
23 import org.opentrafficsim.core.network.LateralDirectionality;
24 import org.opentrafficsim.core.network.NetworkException;
25 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
26 import org.opentrafficsim.road.gtu.lane.perception.InfrastructureLaneChangeInfo;
27 import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
28 import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
29 import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
30 import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
31 import org.opentrafficsim.road.gtu.lane.perception.categories.IntersectionPerception;
32 import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
33 import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayConflict;
34 import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
35 import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayTrafficLight;
36 import org.opentrafficsim.road.gtu.lane.plan.operational.LaneChange;
37 import org.opentrafficsim.road.gtu.lane.plan.operational.SimpleOperationalPlan;
38 import org.opentrafficsim.road.gtu.lane.tactical.Synchronizable;
39 import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
40 import org.opentrafficsim.road.gtu.lane.tactical.util.CarFollowingUtil;
41 import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
42 import org.opentrafficsim.road.network.speed.SpeedLimitProspect;
43
44
45
46
47
48
49
50
51
52
53
54 public final class LmrsUtil implements LmrsParameters
55 {
56
57
58 public static final ParameterTypeDuration DT = ParameterTypes.DT;
59
60
61 public static final ParameterTypeDuration TMIN = ParameterTypes.TMIN;
62
63
64 public static final ParameterTypeDuration T = ParameterTypes.T;
65
66
67 public static final ParameterTypeDuration TMAX = ParameterTypes.TMAX;
68
69
70 public static final ParameterTypeDuration TAU = ParameterTypes.TAU;
71
72
73 public static final ParameterTypeAcceleration BCRIT = ParameterTypes.BCRIT;
74
75
76
77
78 private LmrsUtil()
79 {
80
81 }
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 @SuppressWarnings({ "checkstyle:parameternumber", "checkstyle:methodlength" })
100 public static SimpleOperationalPlan determinePlan(final LaneBasedGTU gtu, final Time startTime,
101 final CarFollowingModel carFollowingModel, final LaneChange laneChange, final LmrsData lmrsData,
102 final LanePerception perception, final LinkedHashSet<MandatoryIncentive> mandatoryIncentives,
103 final LinkedHashSet<VoluntaryIncentive> voluntaryIncentives)
104 throws GTUException, NetworkException, ParameterException, OperationalPlanException
105 {
106
107
108
109
110 InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
111 SpeedLimitProspect slp = infra.getSpeedLimitProspect(RelativeLane.CURRENT);
112 SpeedLimitInfo sli = slp.getSpeedLimitInfo(Length.ZERO);
113 Parameters params = gtu.getParameters();
114 EgoPerception ego = perception.getPerceptionCategory(EgoPerception.class);
115 Speed speed = ego.getSpeed();
116 NeighborsPerception neighbors = perception.getPerceptionCategory(NeighborsPerception.class);
117 PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
118
119
120 lmrsData.getTailGating().tailgate(perception, params);
121 if (!leaders.isEmpty() && lmrsData.isNewLeader(leaders.first()))
122 {
123 initHeadwayRelaxation(params, leaders.first());
124 }
125 Acceleration a = gtu.getCarFollowingAcceleration();
126
127
128 LateralDirectionality initiatedLaneChange;
129 TurnIndicatorIntent turnIndicatorStatus = TurnIndicatorIntent.NONE;
130 if (laneChange.isChangingLane())
131 {
132 RelativeLane secondLane = laneChange.getSecondLane(gtu);
133 initiatedLaneChange = LateralDirectionality.NONE;
134 PerceptionCollectable<HeadwayGTU, LaneBasedGTU> secondLeaders = neighbors.getLeaders(secondLane);
135 Acceleration aSecond = carFollowingModel.followingAcceleration(params, speed, sli, secondLeaders);
136 if (!secondLeaders.isEmpty() && lmrsData.isNewLeader(secondLeaders.first()))
137 {
138 initHeadwayRelaxation(params, secondLeaders.first());
139 }
140 a = Acceleration.min(a, aSecond);
141 }
142 else
143 {
144
145
146 Desire desire = getLaneChangeDesire(params, perception, carFollowingModel, mandatoryIncentives, voluntaryIncentives,
147 lmrsData.desireMap);
148
149
150 double dFree = params.getParameter(DFREE);
151 initiatedLaneChange = LateralDirectionality.NONE;
152 turnIndicatorStatus = TurnIndicatorIntent.NONE;
153 if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dFree)
154 {
155 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getLeft(), speed, a,
156 LateralDirectionality.LEFT, lmrsData.getGapAcceptance()))
157 {
158
159 initiatedLaneChange = LateralDirectionality.LEFT;
160 turnIndicatorStatus = TurnIndicatorIntent.LEFT;
161 params.setParameter(DLC, desire.getLeft());
162 setDesiredHeadway(params, desire.getLeft());
163 leaders = neighbors.getLeaders(RelativeLane.LEFT);
164 if (!leaders.isEmpty())
165 {
166
167
168 lmrsData.isNewLeader(leaders.first());
169 }
170 a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
171 neighbors.getLeaders(RelativeLane.LEFT)));
172 }
173 }
174 else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dFree)
175 {
176 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getRight(), speed, a,
177 LateralDirectionality.RIGHT, lmrsData.getGapAcceptance()))
178 {
179
180 initiatedLaneChange = LateralDirectionality.RIGHT;
181 turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
182 params.setParameter(DLC, desire.getRight());
183 setDesiredHeadway(params, desire.getRight());
184 leaders = neighbors.getLeaders(RelativeLane.RIGHT);
185 if (!leaders.isEmpty())
186 {
187
188
189 lmrsData.isNewLeader(leaders.first());
190 }
191 a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
192 neighbors.getLeaders(RelativeLane.RIGHT)));
193 }
194 }
195 if (!initiatedLaneChange.isNone())
196 {
197 SortedSet<InfrastructureLaneChangeInfo> set = infra.getInfrastructureLaneChangeInfo(RelativeLane.CURRENT);
198 if (!set.isEmpty())
199 {
200 Length boundary = null;
201 for (InfrastructureLaneChangeInfo info : set)
202 {
203 int n = info.getRequiredNumberOfLaneChanges();
204 if (n > 1)
205 {
206 Length thisBoundary = info.getRemainingDistance()
207 .minus(Synchronization.requiredBufferSpace(speed, info.getRequiredNumberOfLaneChanges(),
208 params.getParameter(ParameterTypes.LOOKAHEAD),
209 params.getParameter(ParameterTypes.T0), params.getParameter(ParameterTypes.LCDUR),
210 params.getParameter(DCOOP)));
211 if (thisBoundary.le0())
212 {
213 thisBoundary = info.getRemainingDistance().divideBy(info.getRequiredNumberOfLaneChanges());
214 }
215 boundary = boundary == null || thisBoundary.si < boundary.si ? thisBoundary : boundary;
216 }
217 }
218 laneChange.setBoundary(boundary);
219 }
220 params.setParameter(DLEFT, 0.0);
221 params.setParameter(DRIGHT, 0.0);
222 }
223 else
224 {
225 params.setParameter(DLEFT, desire.getLeft());
226 params.setParameter(DRIGHT, desire.getRight());
227 }
228
229
230 Acceleration aSync;
231
232
233 double dSync = params.getParameter(DSYNC);
234 if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dSync)
235 {
236 Synchronizable.State state;
237 if (desire.getLeft() >= params.getParameter(DCOOP))
238 {
239
240 turnIndicatorStatus = TurnIndicatorIntent.LEFT;
241 state = Synchronizable.State.INDICATING;
242 }
243 else
244 {
245 state = Synchronizable.State.SYNCHRONIZING;
246 }
247 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getLeft(),
248 LateralDirectionality.LEFT, lmrsData);
249 a = applyAcceleration(a, aSync, lmrsData, state);
250 }
251 else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dSync)
252 {
253 Synchronizable.State state;
254 if (desire.getRight() >= params.getParameter(DCOOP))
255 {
256
257 turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
258 state = Synchronizable.State.INDICATING;
259 }
260 else
261 {
262 state = Synchronizable.State.SYNCHRONIZING;
263 }
264 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getRight(),
265 LateralDirectionality.RIGHT, lmrsData);
266 a = applyAcceleration(a, aSync, lmrsData, state);
267 }
268 else
269 {
270 lmrsData.synchronizationState = Synchronizable.State.NONE;
271 }
272
273
274 aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.LEFT,
275 desire);
276 a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);
277 aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.RIGHT,
278 desire);
279 a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);
280
281
282 exponentialHeadwayRelaxation(params);
283
284 }
285 lmrsData.finalizeStep();
286
287 SimpleOperationalPlan simplePlan = new SimpleOperationalPlan(a, params.getParameter(DT), initiatedLaneChange);
288 if (turnIndicatorStatus.isLeft())
289 {
290 simplePlan.setIndicatorIntentLeft();
291 }
292 else if (turnIndicatorStatus.isRight())
293 {
294 simplePlan.setIndicatorIntentRight();
295 }
296 return simplePlan;
297
298 }
299
300
301
302
303
304
305
306
307
308 private static Acceleration applyAcceleration(final Acceleration a, final Acceleration aNew, final LmrsData lmrsData,
309 final Synchronizable.State state)
310 {
311 if (a.si < aNew.si)
312 {
313 return a;
314 }
315 lmrsData.synchronizationState = state;
316 return aNew;
317 }
318
319
320
321
322
323
324
325 private static void initHeadwayRelaxation(final Parameters params, final HeadwayGTU leader) throws ParameterException
326 {
327 Double dlc = leader.getParameters().getParameterOrNull(DLC);
328 if (dlc != null)
329 {
330 setDesiredHeadway(params, dlc);
331 }
332
333 }
334
335
336
337
338
339
340 private static void exponentialHeadwayRelaxation(final Parameters params) throws ParameterException
341 {
342 double ratio = params.getParameter(DT).si / params.getParameter(TAU).si;
343 params.setParameter(T,
344 Duration.interpolate(params.getParameter(T), params.getParameter(TMAX), ratio <= 1.0 ? ratio : 1.0));
345 }
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364 public static Desire getLaneChangeDesire(final Parameters parameters, final LanePerception perception,
365 final CarFollowingModel carFollowingModel, final LinkedHashSet<MandatoryIncentive> mandatoryIncentives,
366 final LinkedHashSet<VoluntaryIncentive> voluntaryIncentives,
367 final Map<Class<? extends Incentive>, Desire> desireMap)
368 throws ParameterException, GTUException, OperationalPlanException
369 {
370
371 double dSync = parameters.getParameter(DSYNC);
372 double dCoop = parameters.getParameter(DCOOP);
373
374
375 double dLeftMandatory = 0.0;
376 double dRightMandatory = 0.0;
377 Desire mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
378 for (MandatoryIncentive incentive : mandatoryIncentives)
379 {
380 Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire);
381 desireMap.put(incentive.getClass(), d);
382 dLeftMandatory = Math.abs(d.getLeft()) > Math.abs(dLeftMandatory) ? d.getLeft() : dLeftMandatory;
383 dRightMandatory = Math.abs(d.getRight()) > Math.abs(dRightMandatory) ? d.getRight() : dRightMandatory;
384 mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
385 }
386
387
388 double dLeftVoluntary = 0;
389 double dRightVoluntary = 0;
390 Desire voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
391 for (VoluntaryIncentive incentive : voluntaryIncentives)
392 {
393 Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire, voluntaryDesire);
394 desireMap.put(incentive.getClass(), d);
395 dLeftVoluntary += d.getLeft();
396 dRightVoluntary += d.getRight();
397 voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
398 }
399
400
401 double thetaLeft = 0;
402 double dLeftMandatoryAbs = Math.abs(dLeftMandatory);
403 double dRightMandatoryAbs = Math.abs(dRightMandatory);
404 if (dLeftMandatoryAbs <= dSync || dLeftMandatory * dLeftVoluntary >= 0)
405 {
406
407 thetaLeft = 1;
408 }
409 else if (dSync < dLeftMandatoryAbs && dLeftMandatoryAbs < dCoop && dLeftMandatory * dLeftVoluntary < 0)
410 {
411
412 thetaLeft = (dCoop - dLeftMandatoryAbs) / (dCoop - dSync);
413 }
414 double thetaRight = 0;
415 if (dRightMandatoryAbs <= dSync || dRightMandatory * dRightVoluntary >= 0)
416 {
417
418 thetaRight = 1;
419 }
420 else if (dSync < dRightMandatoryAbs && dRightMandatoryAbs < dCoop && dRightMandatory * dRightVoluntary < 0)
421 {
422
423 thetaRight = (dCoop - dRightMandatoryAbs) / (dCoop - dSync);
424 }
425 return new Desire(dLeftMandatory + thetaLeft * dLeftVoluntary, dRightMandatory + thetaRight * dRightVoluntary);
426
427 }
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444 static boolean acceptLaneChange(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
445 final CarFollowingModel cfm, final double desire, final Speed ownSpeed, final Acceleration ownAcceleration,
446 final LateralDirectionality lat, final GapAcceptance gapAcceptance)
447 throws ParameterException, OperationalPlanException
448 {
449
450
451 boolean beyond = Try.assign(() -> perception.getGtu().laneChangeAllowed(), "Cannot obtain GTU.");
452 if (!beyond)
453 {
454 return false;
455 }
456
457
458 InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
459 if (infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat).si <= 0.0)
460 {
461 return false;
462 }
463
464
465 IntersectionPerception intersection = perception.getPerceptionCategoryOrNull(IntersectionPerception.class);
466 if (intersection != null)
467 {
468
469 if ((lat.isLeft() && intersection.isAlongsideConflictLeft())
470 || (lat.isRight() && intersection.isAlongsideConflictRight()))
471 {
472 return false;
473 }
474 if (quickIntersectionScan(params, sli, cfm, ownSpeed, lat, intersection).lt(params.getParameter(BCRIT).neg()))
475 {
476 return false;
477 }
478 }
479
480
481 return gapAcceptance.acceptGap(perception, params, sli, cfm, desire, ownSpeed, ownAcceleration, lat);
482 }
483
484
485
486
487
488
489
490
491
492
493
494
495 private static Acceleration quickIntersectionScan(final Parameters params, final SpeedLimitInfo sli,
496 final CarFollowingModel cfm, final Speed ownSpeed, final LateralDirectionality lat,
497 final IntersectionPerception intersection) throws ParameterException
498 {
499 Acceleration a = Acceleration.POSITIVE_INFINITY;
500 if (intersection != null)
501 {
502 RelativeLane lane = lat.isRight() ? RelativeLane.RIGHT : RelativeLane.LEFT;
503 Iterable<HeadwayConflict> iterable = intersection.getConflicts(lane);
504 if (iterable != null)
505 {
506 Iterator<HeadwayConflict> conflicts = iterable.iterator();
507 if (conflicts.hasNext())
508 {
509 a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
510 conflicts.next().getDistance(), Speed.ZERO));
511 }
512 Iterator<HeadwayTrafficLight> trafficLights = intersection.getTrafficLights(lane).iterator();
513 if (trafficLights.hasNext())
514 {
515 HeadwayTrafficLight trafficLight = trafficLights.next();
516 if (trafficLight.getTrafficLightColor().isRedOrYellow())
517 {
518 a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
519 trafficLight.getDistance(), Speed.ZERO));
520 }
521 }
522 }
523 }
524 return a;
525 }
526
527
528
529
530
531
532
533 static void setDesiredHeadway(final Parameters params, final double desire) throws ParameterException
534 {
535 double limitedDesire = desire < 0 ? 0 : desire > 1 ? 1 : desire;
536 double tDes = limitedDesire * params.getParameter(TMIN).si + (1 - limitedDesire) * params.getParameter(TMAX).si;
537 double t = params.getParameter(T).si;
538 params.setParameterResettable(T, Duration.createSI(tDes < t ? tDes : t));
539 }
540
541
542
543
544
545
546 static void resetDesiredHeadway(final Parameters params) throws ParameterException
547 {
548 params.resetParameter(T);
549 }
550
551
552
553
554
555
556
557
558
559
560
561
562
563 public static Acceleration singleAcceleration(final Length distance, final Speed followerSpeed, final Speed leaderSpeed,
564 final double desire, final Parameters params, final SpeedLimitInfo sli, final CarFollowingModel cfm)
565 throws ParameterException
566 {
567
568 setDesiredHeadway(params, desire);
569
570 Acceleration a = CarFollowingUtil.followSingleLeader(cfm, params, followerSpeed, sli, distance, leaderSpeed);
571
572 resetDesiredHeadway(params);
573 return a;
574 }
575
576 }