1 package org.opentrafficsim.road.gtu.tactical.util.lmrs;
2
3 import java.util.Optional;
4
5 import org.djunits.value.vdouble.scalar.Acceleration;
6 import org.djunits.value.vdouble.scalar.Duration;
7 import org.djunits.value.vdouble.scalar.Length;
8 import org.djunits.value.vdouble.scalar.Speed;
9 import org.opentrafficsim.base.TimeStampedObject;
10 import org.opentrafficsim.base.parameters.ParameterException;
11 import org.opentrafficsim.base.parameters.ParameterTypeAcceleration;
12 import org.opentrafficsim.base.parameters.ParameterTypeDuration;
13 import org.opentrafficsim.base.parameters.ParameterTypes;
14 import org.opentrafficsim.base.parameters.Parameters;
15 import org.opentrafficsim.core.gtu.GtuException;
16 import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
17 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
18 import org.opentrafficsim.core.network.LateralDirectionality;
19 import org.opentrafficsim.core.network.NetworkException;
20 import org.opentrafficsim.road.gtu.LaneBasedGtu;
21 import org.opentrafficsim.road.gtu.operational.SimpleOperationalPlan;
22 import org.opentrafficsim.road.gtu.perception.PerceptionCollectable;
23 import org.opentrafficsim.road.gtu.perception.RelativeLane;
24 import org.opentrafficsim.road.gtu.perception.categories.InfrastructurePerception;
25 import org.opentrafficsim.road.gtu.perception.categories.IntersectionPerception;
26 import org.opentrafficsim.road.gtu.perception.categories.neighbors.NeighborsPerception;
27 import org.opentrafficsim.road.gtu.perception.object.PerceivedConflict;
28 import org.opentrafficsim.road.gtu.perception.object.PerceivedGtu;
29 import org.opentrafficsim.road.gtu.perception.object.PerceivedTrafficLight;
30 import org.opentrafficsim.road.gtu.tactical.Synchronizable;
31 import org.opentrafficsim.road.gtu.tactical.TacticalContext;
32 import org.opentrafficsim.road.gtu.tactical.TacticalContextEgo;
33 import org.opentrafficsim.road.gtu.tactical.lmrs.AbstractIncentivesTacticalPlanner;
34 import org.opentrafficsim.road.gtu.tactical.util.CarFollowingUtil;
35 import org.opentrafficsim.road.gtu.tactical.util.ConflictUtil;
36 import org.opentrafficsim.road.gtu.tactical.util.ConflictUtil.ConflictPlans;
37 import org.opentrafficsim.road.gtu.tactical.util.TrafficLightUtil;
38 import org.opentrafficsim.road.network.conflict.Conflict;
39
40
41
42
43
44
45
46
47
48
49 public final class LmrsUtil implements LmrsParameters
50 {
51
52
53 public static final ParameterTypeDuration DT = ParameterTypes.DT;
54
55
56 public static final ParameterTypeDuration TMIN = ParameterTypes.TMIN;
57
58
59 public static final ParameterTypeDuration T = ParameterTypes.T;
60
61
62 public static final ParameterTypeDuration TMAX = ParameterTypes.TMAX;
63
64
65 public static final ParameterTypeDuration TAU = ParameterTypes.TAU;
66
67
68 public static final ParameterTypeAcceleration BCRIT = ParameterTypes.BCRIT;
69
70
71 private static final Object PARAMETER_KEY = new Object()
72 {
73 @Override
74 public String toString()
75 {
76 return "LmrsUtil.PARAMETER_KEY";
77 }
78 };
79
80
81 public static final Object T_KEY = new Object()
82 {
83 @Override
84 public String toString()
85 {
86 return "LmrsUtil.T_KEY";
87 }
88 };
89
90
91
92
93 private LmrsUtil()
94 {
95
96 }
97
98
99
100
101
102
103
104
105
106
107
108
109 @SuppressWarnings("checkstyle:methodlength")
110 public static SimpleOperationalPlan determinePlan(final TacticalContextEgo context, final LmrsData lmrsData,
111 final AbstractIncentivesTacticalPlanner incentives) throws GtuException, NetworkException, ParameterException
112 {
113 lmrsData.initStep();
114
115
116 NeighborsPerception neighbors = context.getPerception().getPerceptionCategory(NeighborsPerception.class);
117 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
118
119
120 Acceleration a;
121 if (lmrsData.isHumanLongitudinalControl())
122 {
123 lmrsData.getTailgating().tailgate(context);
124 if (!leaders.isEmpty() && lmrsData.isNewLeader(leaders.first()))
125 {
126 initHeadwayRelaxation(context.getParameters(), leaders.first());
127 }
128 a = context.getCarFollowingAcceleration();
129 }
130 else
131 {
132 a = Acceleration.POS_MAXVALUE;
133 }
134 lmrsData.setDesiredSpeed(context.getDesiredSpeed());
135
136
137 Desire desire = getLaneChangeDesire(context, incentives);
138
139
140 LateralDirectionality initiatedOrContinuedLaneChange;
141 TurnIndicatorStatus turnIndicatorStatus = null;
142 double dFree = context.getParameters().getParameter(DFREE);
143 initiatedOrContinuedLaneChange = LateralDirectionality.NONE;
144 if (desire.leftIsLargerOrEqual() && desire.left() >= dFree)
145 {
146 if (acceptLaneChange(context, desire.left(), LateralDirectionality.LEFT, lmrsData.getGapAcceptance()))
147 {
148
149 initiatedOrContinuedLaneChange = LateralDirectionality.LEFT;
150 turnIndicatorStatus = TurnIndicatorStatus.LEFT;
151 context.getParameters().setClaimedParameter(DLC, desire.left(), PARAMETER_KEY);
152 setDesiredHeadway(context.getParameters(), desire.left(), false);
153 leaders = neighbors.getLeaders(RelativeLane.LEFT);
154 if (!leaders.isEmpty())
155 {
156
157
158 lmrsData.isNewLeader(leaders.first());
159 }
160 a = Acceleration.min(a,
161 context.getCarFollowingModel().followingAcceleration(context.getParameters(), context.getSpeed(),
162 context.getSpeedLimits(), context.getMaximumSpeed(), neighbors.getLeaders(RelativeLane.LEFT)));
163 }
164 }
165 else if (!desire.leftIsLargerOrEqual() && desire.right() >= dFree)
166 {
167 if (acceptLaneChange(context, desire.right(), LateralDirectionality.RIGHT, lmrsData.getGapAcceptance()))
168 {
169
170 initiatedOrContinuedLaneChange = LateralDirectionality.RIGHT;
171 turnIndicatorStatus = TurnIndicatorStatus.RIGHT;
172 context.getParameters().setClaimedParameter(DLC, desire.right(), PARAMETER_KEY);
173 setDesiredHeadway(context.getParameters(), desire.right(), false);
174 leaders = neighbors.getLeaders(RelativeLane.RIGHT);
175 if (!leaders.isEmpty())
176 {
177
178 lmrsData.isNewLeader(leaders.first());
179 }
180 a = Acceleration.min(a,
181 context.getCarFollowingModel().followingAcceleration(context.getParameters(), context.getSpeed(),
182 context.getSpeedLimits(), context.getMaximumSpeed(), neighbors.getLeaders(RelativeLane.RIGHT)));
183 }
184 }
185
186 context.getParameters().setClaimedParameter(DLEFT, desire.left(), PARAMETER_KEY);
187 context.getParameters().setClaimedParameter(DRIGHT, desire.right(), PARAMETER_KEY);
188 if (initiatedOrContinuedLaneChange.isNone())
189 {
190
191 Acceleration aSync;
192
193
194 double dSync = context.getParameters().getParameter(DSYNC);
195 lmrsData.setSynchronizationState(Synchronizable.State.NONE);
196 if (desire.leftIsLargerOrEqual() && desire.left() >= dSync)
197 {
198 if (desire.left() >= context.getParameters().getParameter(DCOOP))
199 {
200
201 turnIndicatorStatus = TurnIndicatorStatus.LEFT;
202 lmrsData.setSynchronizationState(Synchronizable.State.INDICATING);
203 }
204 else
205 {
206 lmrsData.setSynchronizationState(Synchronizable.State.SYNCHRONIZING);
207 }
208 aSync = lmrsData.getSynchronization().synchronize(context, desire.left(), LateralDirectionality.LEFT, lmrsData,
209 initiatedOrContinuedLaneChange);
210 a = applyAcceleration(a, aSync);
211 }
212 else if (!desire.leftIsLargerOrEqual() && desire.right() >= dSync)
213 {
214 if (desire.right() >= context.getParameters().getParameter(DCOOP))
215 {
216
217 turnIndicatorStatus = TurnIndicatorStatus.RIGHT;
218 lmrsData.setSynchronizationState(Synchronizable.State.INDICATING);
219 }
220 else
221 {
222 lmrsData.setSynchronizationState(Synchronizable.State.SYNCHRONIZING);
223 }
224 aSync = lmrsData.getSynchronization().synchronize(context, desire.right(), LateralDirectionality.RIGHT,
225 lmrsData, initiatedOrContinuedLaneChange);
226 a = applyAcceleration(a, aSync);
227 }
228
229
230 aSync = lmrsData.getCooperation().cooperate(context, LateralDirectionality.LEFT, lmrsData, desire);
231 a = applyAcceleration(a, aSync);
232 aSync = lmrsData.getCooperation().cooperate(context, LateralDirectionality.RIGHT, lmrsData, desire);
233 a = applyAcceleration(a, aSync);
234
235
236 exponentialHeadwayRelaxation(context.getParameters());
237 }
238
239 SimpleOperationalPlan simplePlan =
240 new SimpleOperationalPlan(a, context.getParameters().getParameter(DT), initiatedOrContinuedLaneChange);
241
242
243 if (turnIndicatorStatus != null)
244 {
245 context.addIntent(turnIndicatorStatus, Length.ZERO);
246 }
247 else
248 {
249
250 TimeStampedObject<TurnIndicatorStatus> lingeringLaneChangeIndicator = lmrsData.getInitiatedLaneChange();
251 if (lingeringLaneChangeIndicator != null)
252 {
253 if (lingeringLaneChangeIndicator.timestamp().si > context.getTime().si
254 - context.getParameters().getParameter(ParameterTypes.LCDUR).si)
255 {
256 context.addIntent(lingeringLaneChangeIndicator.object(), Length.ZERO);
257 }
258 else
259 {
260 lmrsData.setInitiatedLaneChange(null);
261 }
262 }
263 }
264 if (simplePlan.isLaneChange())
265 {
266 turnIndicatorStatus =
267 simplePlan.getLaneChangeDirection().isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT;
268 lmrsData.setInitiatedLaneChange(new TimeStampedObject<TurnIndicatorStatus>(turnIndicatorStatus, context.getTime()));
269 }
270 return simplePlan;
271
272 }
273
274
275
276
277
278
279
280 private static Acceleration applyAcceleration(final Acceleration a, final Acceleration aNew)
281 {
282 if (a.si < aNew.si)
283 {
284 return a;
285 }
286 return aNew;
287 }
288
289
290
291
292
293
294
295 private static void initHeadwayRelaxation(final Parameters params, final PerceivedGtu leader) throws ParameterException
296 {
297 Optional<Double> dlc = leader.getBehavior().getParameters().getOptionalParameter(DLC);
298 if (dlc.isPresent())
299 {
300 setDesiredHeadway(params, dlc.get(), false);
301 }
302
303 }
304
305
306
307
308
309
310 private static void exponentialHeadwayRelaxation(final Parameters params) throws ParameterException
311 {
312 double ratio = params.getParameter(DT).si / params.getParameter(TAU).si;
313 params.setClaimedParameter(T,
314 Duration.interpolate(params.getParameter(T), params.getParameter(TMAX), ratio <= 1.0 ? ratio : 1.0), T_KEY);
315 }
316
317
318
319
320
321
322
323
324
325
326
327
328
329 public static Desire getLaneChangeDesire(final TacticalContextEgo context,
330 final AbstractIncentivesTacticalPlanner incentives) throws ParameterException, GtuException
331 {
332 double dSync = context.getParameters().getParameter(DSYNC);
333 double dCoop = context.getParameters().getParameter(DCOOP);
334
335 Desire mandatoryDesire = incentives.getMandatoryDesire(context);
336 Desire voluntaryDesire = incentives.getVoluntaryDesire(context);
337 double thetaA = context.getParameters().getParameter(LAMBDA_V);
338 double leftThetaV = getThetaV(mandatoryDesire.left(), voluntaryDesire.left(), dSync, dCoop);
339 double rightThetaV = getThetaV(mandatoryDesire.right(), voluntaryDesire.right(), dSync, dCoop);
340 return new Desire(mandatoryDesire.left() + thetaA * leftThetaV * voluntaryDesire.left(),
341 mandatoryDesire.right() + thetaA * rightThetaV * voluntaryDesire.right());
342 }
343
344
345
346
347
348
349
350
351
352 private static double getThetaV(final double mandatoryDesire, final double voluntaryDesire, final double dSync,
353 final double dCoop)
354 {
355 double leftThetaV = 0;
356 double dLeftMandatoryAbs = Math.abs(mandatoryDesire);
357
358 if (dLeftMandatoryAbs <= dSync || mandatoryDesire * voluntaryDesire >= 0)
359 {
360
361 leftThetaV = 1;
362 }
363 else if (dSync < dLeftMandatoryAbs && dLeftMandatoryAbs < dCoop && mandatoryDesire * voluntaryDesire < 0)
364 {
365
366 leftThetaV = (dCoop - dLeftMandatoryAbs) / (dCoop - dSync);
367 }
368 return leftThetaV;
369 }
370
371
372
373
374
375
376
377
378
379
380
381 static boolean acceptLaneChange(final TacticalContextEgo context, final double desire, final LateralDirectionality lat,
382 final GapAcceptance gapAcceptance) throws ParameterException, OperationalPlanException
383 {
384
385 InfrastructurePerception infra = context.getPerception().getPerceptionCategory(InfrastructurePerception.class);
386 if (infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat).si <= 0.0)
387 {
388 return false;
389 }
390
391
392 double consideredDesire = context.getLaneChangeDirection().equals(lat) ? 1.0 : desire;
393 if (!gapAcceptance.acceptGap(context, consideredDesire, lat))
394 {
395 return false;
396 }
397
398
399 Optional<IntersectionPerception> intersection =
400 context.getPerception().getPerceptionCategoryOptional(IntersectionPerception.class);
401 if (intersection.isPresent())
402 {
403 RelativeLane lane = new RelativeLane(lat, 1);
404
405
406
407
408
409
410
411
412
413
414
415
416
417 PerceptionCollectable<PerceivedConflict, Conflict> conflicts = intersection.get().getConflicts(lane);
418 try
419 {
420 Acceleration a = ConflictUtil.approachConflicts(context, new ConflictPlans(), lane, Length.ZERO, false);
421 if (a.lt(context.getParameters().getParameter(ParameterTypes.BCRIT).neg()))
422 {
423 return false;
424 }
425
426
427 for (PerceivedConflict conflict : conflicts)
428 {
429 if (conflict.isMerge() && conflict.getDistance().si < 10.0)
430 {
431 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> down = conflict.getDownstreamConflictingGtus();
432 if (!down.isEmpty() && down.first().getKinematics().getOverlap().isParallel())
433 {
434 return false;
435 }
436 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> up = conflict.getUpstreamConflictingGtus();
437 if (!up.isEmpty() && up.first().getKinematics().getOverlap().isParallel())
438 {
439 return false;
440 }
441 }
442 }
443 }
444 catch (GtuException exception)
445 {
446 throw new OperationalPlanException(exception);
447 }
448 conflicts = intersection.get().getConflicts(RelativeLane.CURRENT);
449 for (PerceivedConflict conflict : conflicts)
450 {
451 if (conflict.getLane().getLink().equals(conflict.getConflictingLink()))
452 {
453 if (conflict.isMerge() && conflict.getDistance().le0()
454 && conflict.getDistance().neg().gt(conflict.getLength()))
455 {
456 return false;
457 }
458 else if (conflict.isSplit() && conflict.getDistance().le0()
459 && conflict.getDistance().neg().lt(context.getLength()))
460 {
461 return false;
462 }
463 }
464 }
465
466
467 Iterable<PerceivedTrafficLight> trafficLights = intersection.get().getTrafficLights(lane);
468 for (PerceivedTrafficLight trafficLight : trafficLights)
469 {
470 if (trafficLight.getTrafficLightColor().isRedOrYellow())
471 {
472 Acceleration a = TrafficLightUtil.respondToTrafficLight(context, trafficLight);
473 if (a.lt(context.getParameters().getParameter(ParameterTypes.BCRIT).neg()))
474 {
475 return false;
476 }
477 }
478 }
479 }
480
481
482 RelativeLane lane = new RelativeLane(lat, 2);
483 Acceleration b = context.getParameters().getParameter(ParameterTypes.B).neg();
484 for (PerceivedGtu leader : context.getPerception().getPerceptionCategory(NeighborsPerception.class).getLeaders(lane))
485 {
486 if (leader.getManeuver().isChangingLane(lat.flip()) && CarFollowingUtil.followSingleLeader(context, leader).lt(b))
487 {
488 return false;
489 }
490 }
491
492 return true;
493
494 }
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544 static void setDesiredHeadway(final Parameters params, final double desire, final boolean resettable)
545 throws ParameterException
546 {
547 double limitedDesire = desire < 0 ? 0 : desire > 1 ? 1 : desire;
548 double tDes = limitedDesire * params.getParameter(TMIN).si + (1 - limitedDesire) * params.getParameter(TMAX).si;
549 double tSi = params.getParameter(T).si;
550 Duration t = Duration.ofSI(tDes < tSi ? tDes : tSi);
551 if (resettable)
552 {
553 params.setParameterResettable(T, t);
554 }
555 else
556 {
557 params.setClaimedParameter(T, t, T_KEY);
558 }
559 }
560
561
562
563
564
565
566 static void resetDesiredHeadway(final Parameters params) throws ParameterException
567 {
568 params.resetParameter(T);
569 }
570
571
572
573
574
575
576
577
578
579
580 public static Acceleration singleAcceleration(final TacticalContext context, final Length distance, final Speed leaderSpeed,
581 final double desire) throws ParameterException
582 {
583
584 setDesiredHeadway(context.getParameters(), desire, true);
585
586 Acceleration a = CarFollowingUtil.followSingleLeader(context, distance, leaderSpeed);
587
588 resetDesiredHeadway(context.getParameters());
589 return a;
590 }
591
592 }