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 Acceleration a;
121 if (lmrsData.isHumanLongitudinalControl())
122 {
123 lmrsData.getTailGating().tailgate(perception, params);
124 if (!leaders.isEmpty() && lmrsData.isNewLeader(leaders.first()))
125 {
126 initHeadwayRelaxation(params, leaders.first());
127 }
128 a = gtu.getCarFollowingAcceleration();
129 }
130 else
131 {
132 a = Acceleration.POS_MAXVALUE;
133 }
134
135
136 LateralDirectionality initiatedLaneChange;
137 TurnIndicatorIntent turnIndicatorStatus = TurnIndicatorIntent.NONE;
138 if (laneChange.isChangingLane())
139 {
140 RelativeLane secondLane = laneChange.getSecondLane(gtu);
141 initiatedLaneChange = LateralDirectionality.NONE;
142 PerceptionCollectable<HeadwayGTU, LaneBasedGTU> secondLeaders = neighbors.getLeaders(secondLane);
143 Acceleration aSecond = carFollowingModel.followingAcceleration(params, speed, sli, secondLeaders);
144 if (!secondLeaders.isEmpty() && lmrsData.isNewLeader(secondLeaders.first()))
145 {
146 initHeadwayRelaxation(params, secondLeaders.first());
147 }
148 a = Acceleration.min(a, aSecond);
149 }
150 else
151 {
152
153
154 Desire desire = getLaneChangeDesire(params, perception, carFollowingModel, mandatoryIncentives, voluntaryIncentives,
155 lmrsData.getDesireMap());
156
157
158 double dFree = params.getParameter(DFREE);
159 initiatedLaneChange = LateralDirectionality.NONE;
160 turnIndicatorStatus = TurnIndicatorIntent.NONE;
161 if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dFree)
162 {
163 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getLeft(), speed, a,
164 LateralDirectionality.LEFT, lmrsData.getGapAcceptance()))
165 {
166
167 initiatedLaneChange = LateralDirectionality.LEFT;
168 turnIndicatorStatus = TurnIndicatorIntent.LEFT;
169 params.setParameter(DLC, desire.getLeft());
170 setDesiredHeadway(params, desire.getLeft());
171 leaders = neighbors.getLeaders(RelativeLane.LEFT);
172 if (!leaders.isEmpty())
173 {
174
175
176 lmrsData.isNewLeader(leaders.first());
177 }
178 a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
179 neighbors.getLeaders(RelativeLane.LEFT)));
180 }
181 }
182 else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dFree)
183 {
184 if (acceptLaneChange(perception, params, sli, carFollowingModel, desire.getRight(), speed, a,
185 LateralDirectionality.RIGHT, lmrsData.getGapAcceptance()))
186 {
187
188 initiatedLaneChange = LateralDirectionality.RIGHT;
189 turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
190 params.setParameter(DLC, desire.getRight());
191 setDesiredHeadway(params, desire.getRight());
192 leaders = neighbors.getLeaders(RelativeLane.RIGHT);
193 if (!leaders.isEmpty())
194 {
195
196
197 lmrsData.isNewLeader(leaders.first());
198 }
199 a = Acceleration.min(a, carFollowingModel.followingAcceleration(params, speed, sli,
200 neighbors.getLeaders(RelativeLane.RIGHT)));
201 }
202 }
203 if (!initiatedLaneChange.isNone())
204 {
205 SortedSet<InfrastructureLaneChangeInfo> set = infra.getInfrastructureLaneChangeInfo(RelativeLane.CURRENT);
206 if (!set.isEmpty())
207 {
208 Length boundary = null;
209 for (InfrastructureLaneChangeInfo info : set)
210 {
211 int n = info.getRequiredNumberOfLaneChanges();
212 if (n > 1)
213 {
214 Length thisBoundary = info.getRemainingDistance()
215 .minus(Synchronization.requiredBufferSpace(speed, info.getRequiredNumberOfLaneChanges(),
216 params.getParameter(ParameterTypes.LOOKAHEAD),
217 params.getParameter(ParameterTypes.T0), params.getParameter(ParameterTypes.LCDUR),
218 params.getParameter(DCOOP)));
219 if (thisBoundary.le0())
220 {
221 thisBoundary = info.getRemainingDistance().divideBy(info.getRequiredNumberOfLaneChanges());
222 }
223 boundary = boundary == null || thisBoundary.si < boundary.si ? thisBoundary : boundary;
224 }
225 }
226 laneChange.setBoundary(boundary);
227 }
228 params.setParameter(DLEFT, 0.0);
229 params.setParameter(DRIGHT, 0.0);
230 }
231 else
232 {
233 params.setParameter(DLEFT, desire.getLeft());
234 params.setParameter(DRIGHT, desire.getRight());
235 }
236
237
238 Acceleration aSync;
239
240
241 double dSync = params.getParameter(DSYNC);
242 if (desire.leftIsLargerOrEqual() && desire.getLeft() >= dSync)
243 {
244 Synchronizable.State state;
245 if (desire.getLeft() >= params.getParameter(DCOOP))
246 {
247
248 turnIndicatorStatus = TurnIndicatorIntent.LEFT;
249 state = Synchronizable.State.INDICATING;
250 }
251 else
252 {
253 state = Synchronizable.State.SYNCHRONIZING;
254 }
255 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getLeft(),
256 LateralDirectionality.LEFT, lmrsData);
257 a = applyAcceleration(a, aSync, lmrsData, state);
258 }
259 else if (!desire.leftIsLargerOrEqual() && desire.getRight() >= dSync)
260 {
261 Synchronizable.State state;
262 if (desire.getRight() >= params.getParameter(DCOOP))
263 {
264
265 turnIndicatorStatus = TurnIndicatorIntent.RIGHT;
266 state = Synchronizable.State.INDICATING;
267 }
268 else
269 {
270 state = Synchronizable.State.SYNCHRONIZING;
271 }
272 aSync = lmrsData.getSynchronization().synchronize(perception, params, sli, carFollowingModel, desire.getRight(),
273 LateralDirectionality.RIGHT, lmrsData);
274 a = applyAcceleration(a, aSync, lmrsData, state);
275 }
276 else
277 {
278 lmrsData.setSynchronizationState(Synchronizable.State.NONE);
279 }
280
281
282 aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.LEFT,
283 desire);
284 a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);
285 aSync = lmrsData.getCooperation().cooperate(perception, params, sli, carFollowingModel, LateralDirectionality.RIGHT,
286 desire);
287 a = applyAcceleration(a, aSync, lmrsData, Synchronizable.State.COOPERATING);
288
289
290 exponentialHeadwayRelaxation(params);
291
292 }
293 lmrsData.finalizeStep();
294
295 SimpleOperationalPlan simplePlan = new SimpleOperationalPlan(a, params.getParameter(DT), initiatedLaneChange);
296 if (turnIndicatorStatus.isLeft())
297 {
298 simplePlan.setIndicatorIntentLeft();
299 }
300 else if (turnIndicatorStatus.isRight())
301 {
302 simplePlan.setIndicatorIntentRight();
303 }
304 return simplePlan;
305
306 }
307
308
309
310
311
312
313
314
315
316 private static Acceleration applyAcceleration(final Acceleration a, final Acceleration aNew, final LmrsData lmrsData,
317 final Synchronizable.State state)
318 {
319 if (a.si < aNew.si)
320 {
321 return a;
322 }
323 lmrsData.setSynchronizationState(state);
324 return aNew;
325 }
326
327
328
329
330
331
332
333 private static void initHeadwayRelaxation(final Parameters params, final HeadwayGTU leader) throws ParameterException
334 {
335 Double dlc = leader.getParameters().getParameterOrNull(DLC);
336 if (dlc != null)
337 {
338 setDesiredHeadway(params, dlc);
339 }
340
341 }
342
343
344
345
346
347
348 private static void exponentialHeadwayRelaxation(final Parameters params) throws ParameterException
349 {
350 double ratio = params.getParameter(DT).si / params.getParameter(TAU).si;
351 params.setParameter(T,
352 Duration.interpolate(params.getParameter(T), params.getParameter(TMAX), ratio <= 1.0 ? ratio : 1.0));
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372 public static Desire getLaneChangeDesire(final Parameters parameters, final LanePerception perception,
373 final CarFollowingModel carFollowingModel, final LinkedHashSet<MandatoryIncentive> mandatoryIncentives,
374 final LinkedHashSet<VoluntaryIncentive> voluntaryIncentives,
375 final Map<Class<? extends Incentive>, Desire> desireMap)
376 throws ParameterException, GTUException, OperationalPlanException
377 {
378
379 double dSync = parameters.getParameter(DSYNC);
380 double dCoop = parameters.getParameter(DCOOP);
381
382
383 double dLeftMandatory = 0.0;
384 double dRightMandatory = 0.0;
385 Desire mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
386 for (MandatoryIncentive incentive : mandatoryIncentives)
387 {
388 Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire);
389 desireMap.put(incentive.getClass(), d);
390 dLeftMandatory = Math.abs(d.getLeft()) > Math.abs(dLeftMandatory) ? d.getLeft() : dLeftMandatory;
391 dRightMandatory = Math.abs(d.getRight()) > Math.abs(dRightMandatory) ? d.getRight() : dRightMandatory;
392 mandatoryDesire = new Desire(dLeftMandatory, dRightMandatory);
393 }
394
395
396 double dLeftVoluntary = 0;
397 double dRightVoluntary = 0;
398 Desire voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
399 for (VoluntaryIncentive incentive : voluntaryIncentives)
400 {
401 Desire d = incentive.determineDesire(parameters, perception, carFollowingModel, mandatoryDesire, voluntaryDesire);
402 desireMap.put(incentive.getClass(), d);
403 dLeftVoluntary += d.getLeft();
404 dRightVoluntary += d.getRight();
405 voluntaryDesire = new Desire(dLeftVoluntary, dRightVoluntary);
406 }
407
408
409 double thetaLeft = 0;
410 double dLeftMandatoryAbs = Math.abs(dLeftMandatory);
411 double dRightMandatoryAbs = Math.abs(dRightMandatory);
412 if (dLeftMandatoryAbs <= dSync || dLeftMandatory * dLeftVoluntary >= 0)
413 {
414
415 thetaLeft = 1;
416 }
417 else if (dSync < dLeftMandatoryAbs && dLeftMandatoryAbs < dCoop && dLeftMandatory * dLeftVoluntary < 0)
418 {
419
420 thetaLeft = (dCoop - dLeftMandatoryAbs) / (dCoop - dSync);
421 }
422 double thetaRight = 0;
423 if (dRightMandatoryAbs <= dSync || dRightMandatory * dRightVoluntary >= 0)
424 {
425
426 thetaRight = 1;
427 }
428 else if (dSync < dRightMandatoryAbs && dRightMandatoryAbs < dCoop && dRightMandatory * dRightVoluntary < 0)
429 {
430
431 thetaRight = (dCoop - dRightMandatoryAbs) / (dCoop - dSync);
432 }
433 return new Desire(dLeftMandatory + thetaLeft * dLeftVoluntary, dRightMandatory + thetaRight * dRightVoluntary);
434
435 }
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452 static boolean acceptLaneChange(final LanePerception perception, final Parameters params, final SpeedLimitInfo sli,
453 final CarFollowingModel cfm, final double desire, final Speed ownSpeed, final Acceleration ownAcceleration,
454 final LateralDirectionality lat, final GapAcceptance gapAcceptance)
455 throws ParameterException, OperationalPlanException
456 {
457
458
459 boolean beyond = Try.assign(() -> perception.getGtu().laneChangeAllowed(), "Cannot obtain GTU.");
460 if (!beyond)
461 {
462 return false;
463 }
464
465
466 InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
467 if (infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat).si <= 0.0)
468 {
469 return false;
470 }
471
472
473 IntersectionPerception intersection = perception.getPerceptionCategoryOrNull(IntersectionPerception.class);
474 if (intersection != null)
475 {
476
477 if ((lat.isLeft() && intersection.isAlongsideConflictLeft())
478 || (lat.isRight() && intersection.isAlongsideConflictRight()))
479 {
480 return false;
481 }
482 if (quickIntersectionScan(params, sli, cfm, ownSpeed, lat, intersection).lt(params.getParameter(BCRIT).neg()))
483 {
484 return false;
485 }
486 }
487
488
489 return gapAcceptance.acceptGap(perception, params, sli, cfm, desire, ownSpeed, ownAcceleration, lat);
490 }
491
492
493
494
495
496
497
498
499
500
501
502
503 private static Acceleration quickIntersectionScan(final Parameters params, final SpeedLimitInfo sli,
504 final CarFollowingModel cfm, final Speed ownSpeed, final LateralDirectionality lat,
505 final IntersectionPerception intersection) throws ParameterException
506 {
507 Acceleration a = Acceleration.POSITIVE_INFINITY;
508 if (intersection != null)
509 {
510 RelativeLane lane = lat.isRight() ? RelativeLane.RIGHT : RelativeLane.LEFT;
511 Iterable<HeadwayConflict> iterable = intersection.getConflicts(lane);
512 if (iterable != null)
513 {
514 Iterator<HeadwayConflict> conflicts = iterable.iterator();
515 if (conflicts.hasNext())
516 {
517 a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
518 conflicts.next().getDistance(), Speed.ZERO));
519 }
520 Iterator<HeadwayTrafficLight> trafficLights = intersection.getTrafficLights(lane).iterator();
521 if (trafficLights.hasNext())
522 {
523 HeadwayTrafficLight trafficLight = trafficLights.next();
524 if (trafficLight.getTrafficLightColor().isRedOrYellow())
525 {
526 a = Acceleration.min(a, CarFollowingUtil.followSingleLeader(cfm, params, ownSpeed, sli,
527 trafficLight.getDistance(), Speed.ZERO));
528 }
529 }
530 }
531 }
532 return a;
533 }
534
535
536
537
538
539
540
541 static void setDesiredHeadway(final Parameters params, final double desire) throws ParameterException
542 {
543 double limitedDesire = desire < 0 ? 0 : desire > 1 ? 1 : desire;
544 double tDes = limitedDesire * params.getParameter(TMIN).si + (1 - limitedDesire) * params.getParameter(TMAX).si;
545 double t = params.getParameter(T).si;
546 params.setParameterResettable(T, Duration.createSI(tDes < t ? tDes : t));
547 }
548
549
550
551
552
553
554 static void resetDesiredHeadway(final Parameters params) throws ParameterException
555 {
556 params.resetParameter(T);
557 }
558
559
560
561
562
563
564
565
566
567
568
569
570
571 public static Acceleration singleAcceleration(final Length distance, final Speed followerSpeed, final Speed leaderSpeed,
572 final double desire, final Parameters params, final SpeedLimitInfo sli, final CarFollowingModel cfm)
573 throws ParameterException
574 {
575
576 setDesiredHeadway(params, desire);
577
578 Acceleration a = CarFollowingUtil.followSingleLeader(cfm, params, followerSpeed, sli, distance, leaderSpeed);
579
580 resetDesiredHeadway(params);
581 return a;
582 }
583
584 }