1 package org.opentrafficsim.road.gtu.tactical.util.lmrs;
2
3 import java.util.SortedSet;
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.NamedConstants;
10 import org.opentrafficsim.base.parameters.ParameterException;
11 import org.opentrafficsim.base.parameters.ParameterTypes;
12 import org.opentrafficsim.base.parameters.Parameters;
13 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
14 import org.opentrafficsim.core.network.LateralDirectionality;
15 import org.opentrafficsim.road.gtu.LaneBasedGtu;
16 import org.opentrafficsim.road.gtu.perception.LanePerception;
17 import org.opentrafficsim.road.gtu.perception.PerceptionCollectable;
18 import org.opentrafficsim.road.gtu.perception.RelativeLane;
19 import org.opentrafficsim.road.gtu.perception.categories.InfrastructurePerception;
20 import org.opentrafficsim.road.gtu.perception.categories.neighbors.NeighborsPerception;
21 import org.opentrafficsim.road.gtu.perception.object.PerceivedGtu;
22 import org.opentrafficsim.road.gtu.tactical.TacticalContextEgo;
23 import org.opentrafficsim.road.gtu.tactical.util.CarFollowingUtil;
24 import org.opentrafficsim.road.network.LaneChangeInfo;
25
26
27
28
29
30
31
32
33
34
35
36 public interface Synchronization extends NamedConstants, LmrsParameters
37 {
38
39
40 Synchronization PASSIVE = new Synchronization()
41 {
42 @Override
43 public Acceleration synchronize(final TacticalContextEgo context, final double desire, final LateralDirectionality lat,
44 final LmrsData lmrsData, final LateralDirectionality initiatedLaneChange)
45 throws ParameterException, OperationalPlanException
46 {
47 Acceleration a = Acceleration.POS_MAXVALUE;
48 double dCoop = context.getParameters().getParameter(DCOOP);
49 RelativeLane relativeLane = new RelativeLane(lat, 1);
50
51 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> set =
52 context.getPerception().getPerceptionCategory(NeighborsPerception.class).getLeaders(relativeLane);
53 PerceivedGtu leader = null;
54 if (set != null)
55 {
56 if (desire >= dCoop && !set.isEmpty())
57 {
58
59 leader = set.first();
60 }
61 else
62 {
63
64 for (PerceivedGtu gtu : set)
65 {
66 if (gtu.getSpeed().gt0())
67 {
68 leader = gtu;
69 break;
70 }
71 }
72 }
73 }
74 if (leader != null)
75 {
76 Length headway = leader.getDistance();
77 Acceleration aSingle = LmrsUtil.singleAcceleration(context, headway, leader.getSpeed(), desire);
78 a = Acceleration.min(a, aSingle);
79 a = Synchronization.gentleUrgency(a, desire, context.getParameters());
80 }
81
82
83 Length xMerge = Synchronization.getMergeDistance(context.getPerception(), lat).minus(context.getLength());
84 if (xMerge.gt0())
85 {
86 Acceleration aMerge = LmrsUtil.singleAcceleration(context, xMerge, Speed.ZERO, desire);
87 a = Acceleration.max(a, aMerge);
88 }
89 return a;
90 }
91
92 @Override
93 public String name()
94 {
95 return "PASSIVE";
96 }
97 };
98
99
100
101
102
103
104
105 Synchronization ALIGN_GAP = new Synchronization()
106 {
107 @Override
108 public Acceleration synchronize(final TacticalContextEgo context, final double desire, final LateralDirectionality lat,
109 final LmrsData lmrsData, final LateralDirectionality initiatedLaneChange)
110 throws ParameterException, OperationalPlanException
111 {
112 Acceleration a = Acceleration.POS_MAXVALUE;
113 RelativeLane relativeLane = new RelativeLane(lat, 1);
114 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> leaders =
115 context.getPerception().getPerceptionCategory(NeighborsPerception.class).getLeaders(relativeLane);
116 if (!leaders.isEmpty())
117 {
118 PerceivedGtu leader = leaders.first();
119 Length gap = leader.getDistance();
120
121 LmrsUtil.setDesiredHeadway(context.getParameters(), desire, true);
122 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> followers =
123 context.getPerception().getPerceptionCategory(NeighborsPerception.class).getFollowers(relativeLane);
124 if (!followers.isEmpty())
125 {
126 PerceivedGtu follower = followers.first();
127 Length netGap = leader.getDistance().plus(follower.getDistance()).times(0.5);
128 gap = Length.max(gap, leader.getDistance().minus(netGap)
129 .plus(context.getCarFollowingModel().desiredHeadway(context.getParameters(), context.getSpeed())));
130 }
131 a = CarFollowingUtil.followSingleLeader(context, gap, leader.getSpeed());
132 LmrsUtil.resetDesiredHeadway(context.getParameters());
133
134 a = Synchronization.gentleUrgency(a, desire, context.getParameters());
135 }
136
137
138 Length xMerge = Synchronization.getMergeDistance(context.getPerception(), lat);
139 if (xMerge.gt0())
140 {
141 Acceleration aMerge = LmrsUtil.singleAcceleration(context, xMerge, Speed.ZERO, desire);
142 a = Acceleration.max(a, aMerge);
143 }
144 return a;
145 }
146
147 @Override
148 public String name()
149 {
150 return "ALIGN_GAP";
151 }
152 };
153
154
155 Synchronization PASSIVE_MOVING = new Synchronization()
156 {
157 @Override
158 public Acceleration synchronize(final TacticalContextEgo context, final double desire, final LateralDirectionality lat,
159 final LmrsData lmrsData, final LateralDirectionality initiatedLaneChange)
160 throws ParameterException, OperationalPlanException
161 {
162 double dCoop = context.getParameters().getParameter(DCOOP);
163 if (desire < dCoop && context.getSpeed().si < context.getParameters().getParameter(ParameterTypes.LOOKAHEAD).si
164 / context.getParameters().getParameter(ParameterTypes.T0).si)
165 {
166 return Acceleration.POS_MAXVALUE;
167 }
168 return PASSIVE.synchronize(context, desire, lat, lmrsData, initiatedLaneChange);
169 }
170
171 @Override
172 public String name()
173 {
174 return "PASSIVE_MOVING";
175 }
176 };
177
178
179 Synchronization ACTIVE = new Synchronization()
180 {
181 @Override
182 public Acceleration synchronize(final TacticalContextEgo context, final double desire, final LateralDirectionality lat,
183 final LmrsData lmrsData, final LateralDirectionality initiatedLaneChange)
184 throws ParameterException, OperationalPlanException
185 {
186
187
188
189 Acceleration b = context.getParameters().getParameter(ParameterTypes.B);
190 Duration tMin = context.getParameters().getParameter(ParameterTypes.TMIN);
191 Duration tMax = context.getParameters().getParameter(ParameterTypes.TMAX);
192 Speed vCong = context.getParameters().getParameter(ParameterTypes.VCONG);
193 Length x0 = context.getParameters().getParameter(ParameterTypes.LOOKAHEAD);
194 Duration t0 = context.getParameters().getParameter(ParameterTypes.T0);
195 Duration lc = context.getParameters().getParameter(ParameterTypes.LCDUR);
196 Speed tagSpeed = x0.divide(t0);
197 double dCoop = context.getParameters().getParameter(DCOOP);
198 Length dx = context.getPerception().getGtu().getFront().dx();
199
200
201 InfrastructurePerception infra = context.getPerception().getPerceptionCategory(InfrastructurePerception.class);
202 SortedSet<LaneChangeInfo> info = infra.getLegalLaneChangeInfo(RelativeLane.CURRENT);
203
204
205 Length xMerge = Synchronization.getMergeDistance(context.getPerception(), lat);
206 int nCur = 0;
207 Length xCur = Length.POSITIVE_INFINITY;
208 for (LaneChangeInfo lcInfo : info)
209 {
210 int nCurTmp = lcInfo.numberOfLaneChanges();
211
212 Length xCurTmp = lcInfo.remainingDistance().minus(context.getLength().times(2.0 * nCurTmp)).minus(dx);
213 if (xCurTmp.lt(xCur))
214 {
215 nCur = nCurTmp;
216 xCur = xCurTmp;
217 }
218 }
219
220
221
222 Length xMergeSync = xCur.minus(Length.ofSI(.5 * context.getSpeed().si * context.getSpeed().si / b.si));
223 xMergeSync = Length.min(xMerge, xMergeSync);
224
225
226 NeighborsPerception neighbors = context.getPerception().getPerceptionCategory(NeighborsPerception.class);
227 RelativeLane lane = new RelativeLane(lat, 1);
228 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> leaders = neighbors.getLeaders(lane);
229 PerceivedGtu syncVehicle = lmrsData.getSyncVehicle(leaders);
230 if (syncVehicle != null && ((syncVehicle.getSpeed().lt(vCong) && syncVehicle.getDistance().lt(xMergeSync))
231 || syncVehicle.getDistance().gt(xCur)))
232 {
233 syncVehicle = null;
234 }
235
236
237 if (leaders != null && syncVehicle == null)
238 {
239 Length maxDistance = Length.min(x0, xCur);
240 for (PerceivedGtu leader : leaders)
241 {
242 if (leader.getDistance().lt(maxDistance))
243 {
244 if ((leader.getDistance().gt(xMergeSync) || leader.getSpeed().gt(vCong))
245 && Synchronization.tagAlongAcceleration(context, leader, tagSpeed, desire).gt(b.neg()))
246 {
247 syncVehicle = leader;
248 break;
249 }
250 }
251 else
252 {
253 break;
254 }
255 }
256 }
257
258
259 PerceivedGtu up;
260 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> followers = neighbors.getFollowers(lane);
261 PerceivedGtu follower = followers == null || followers.isEmpty() ? null
262 : followers.first().moved(
263 followers.first().getDistance().plus(context.getLength()).plus(followers.first().getLength()).neg(),
264 followers.first().getSpeed(), followers.first().getAcceleration());
265 boolean upOk;
266 if (syncVehicle == null)
267 {
268 up = null;
269 upOk = false;
270 }
271 else
272 {
273 up = Synchronization.getFollower(syncVehicle, leaders, follower, context.getLength());
274 upOk = up == null ? false : Synchronization.tagAlongAcceleration(context, up, tagSpeed, desire).gt(b.neg());
275 }
276 while (syncVehicle != null
277 && up != null && (upOk || (!Synchronization.canBeAhead(context, up, xCur, nCur, tagSpeed, dCoop, b, tMin,
278 tMax, x0, t0, lc, desire) && desire > dCoop))
279 && (up.getDistance().gt(xMergeSync) || up.getSpeed().gt(vCong)))
280 {
281 if (up.equals(follower))
282 {
283
284 syncVehicle = null;
285 up = null;
286 break;
287 }
288 syncVehicle = up;
289 up = Synchronization.getFollower(syncVehicle, leaders, follower, context.getLength());
290 upOk = up == null ? false : Synchronization.tagAlongAcceleration(context, up, tagSpeed, desire).gt(b.neg());
291 }
292 lmrsData.setSyncVehicle(syncVehicle);
293
294
295 Acceleration a = Acceleration.POS_MAXVALUE;
296 if (syncVehicle != null)
297 {
298 a = Synchronization.gentleUrgency(Synchronization.tagAlongAcceleration(context, syncVehicle, tagSpeed, desire),
299 desire, context.getParameters());
300 }
301 else if (nCur > 0 && (follower != null || (leaders != null && !leaders.isEmpty())))
302 {
303
304 if (follower != null && !Synchronization.canBeAhead(context, follower, xCur, nCur, tagSpeed, dCoop, b, tMin,
305 tMax, x0, t0, lc, desire))
306 {
307
308 double c = Synchronization.requiredBufferSpace(context.getSpeed(), nCur, x0, t0, lc, dCoop).si;
309 double t = (xCur.si - follower.getDistance().si - c) / follower.getSpeed().si;
310 double xGap = context.getSpeed().si * (tMin.si + desire * (tMax.si - tMin.si));
311 Acceleration acc = Acceleration.ofSI(2 * (xCur.si - c - context.getSpeed().si * t - xGap) / (t * t));
312 if (follower.getSpeed().eq0() || acc.si < -context.getSpeed().si / t || t < 0)
313 {
314
315
316 a = Synchronization.stopForEnd(context, xCur, xMerge);
317 }
318 else
319 {
320 a = Synchronization.gentleUrgency(acc, desire, context.getParameters());
321 }
322 }
323 else if (!LmrsUtil.acceptLaneChange(context, desire, lat, lmrsData.getGapAcceptance()))
324 {
325 a = Synchronization.stopForEnd(context, xCur, xMerge);
326
327 if (leaders != null && !leaders.isEmpty())
328 {
329 double c = Synchronization.requiredBufferSpace(context.getSpeed(), nCur, x0, t0, lc, dCoop).si;
330 double t = (xCur.si - leaders.first().getDistance().si - c) / leaders.first().getSpeed().si;
331 double xGap = context.getSpeed().si * (tMin.si + desire * (tMax.si - tMin.si));
332 Acceleration acc = Acceleration.ofSI(2 * (xCur.si - c - context.getSpeed().si * t - xGap) / (t * t));
333 if (!(leaders.first().getSpeed().eq0() || acc.si < -context.getSpeed().si / t || t < 0))
334 {
335 a = Acceleration.max(a, acc);
336 }
337 }
338 }
339 }
340
341
342 if (nCur > 1)
343 {
344 if (xMerge.gt0())
345 {
346
347 Speed vMerge = xCur.lt(xMerge) ? Speed.ZERO
348 : xCur.minus(xMerge).divide(t0.times((1 - dCoop) * (nCur - 1)).plus(lc));
349 vMerge = Speed.max(vMerge, x0.divide(t0));
350 a = Acceleration.min(a, CarFollowingUtil.approachTargetSpeed(context, xMerge, vMerge));
351 }
352 else
353 {
354
355 Length c = Synchronization.requiredBufferSpace(context.getSpeed(), nCur, x0, t0, lc, dCoop);
356 if (xCur.lt(c))
357 {
358 a = Acceleration.min(a, b.neg());
359 }
360 }
361 }
362 return a;
363 }
364
365 @Override
366 public String name()
367 {
368 return "ACTIVE";
369 }
370 };
371
372
373
374
375
376
377
378
379 static Length getMergeDistance(final LanePerception perception, final LateralDirectionality lat)
380 throws OperationalPlanException
381 {
382 InfrastructurePerception infra = perception.getPerceptionCategory(InfrastructurePerception.class);
383 Length dx = perception.getGtu().getFront().dx();
384 Length xMergeRef = infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, lat);
385 if (xMergeRef.gt0() && xMergeRef.lt(dx))
386 {
387 return Length.ZERO;
388 }
389 Length xMerge = xMergeRef.minus(dx);
390 return xMerge.lt0() ? xMerge.neg() : Length.ZERO;
391 }
392
393
394
395
396
397
398
399
400
401
402
403
404 Acceleration synchronize(TacticalContextEgo context, double desire, LateralDirectionality lat, LmrsData lmrsData,
405 LateralDirectionality initiatedLaneChange) throws ParameterException, OperationalPlanException;
406
407
408
409
410
411
412
413
414
415
416 static Acceleration gentleUrgency(final Acceleration a, final double desire, final Parameters params)
417 throws ParameterException
418 {
419 Acceleration b = params.getParameter(ParameterTypes.B);
420 if (a.si > -b.si)
421 {
422 return a;
423 }
424 double dCoop = params.getParameter(DCOOP);
425 if (desire < dCoop)
426 {
427 return b.neg();
428 }
429 Acceleration bCrit = params.getParameter(ParameterTypes.BCRIT);
430 double f = (desire - dCoop) / (1.0 - dCoop);
431 Acceleration lim = Acceleration.interpolate(b.neg(), bCrit.neg(), f);
432 return Acceleration.max(a, lim);
433 }
434
435
436
437
438
439
440
441
442
443 static PerceivedGtu getFollower(final PerceivedGtu gtu, final PerceptionCollectable<PerceivedGtu, LaneBasedGtu> leaders,
444 final PerceivedGtu follower, final Length ownLength)
445 {
446 PerceivedGtu last = null;
447 for (PerceivedGtu leader : leaders)
448 {
449 if (leader.equals(gtu))
450 {
451 return last == null ? follower : last;
452 }
453 last = leader;
454 }
455 return null;
456 }
457
458
459
460
461
462
463
464
465
466
467 @SuppressWarnings("checkstyle:parameternumber")
468 static Acceleration tagAlongAcceleration(final TacticalContextEgo context, final PerceivedGtu leader, final Speed tagSpeed,
469 final double desire) throws ParameterException
470 {
471 double dCoop = context.getParameters().getParameter(DCOOP);
472 double tagV = context.getSpeed().lt(tagSpeed) ? 1.0 - context.getSpeed().si / tagSpeed.si : 0.0;
473 double tagD = desire <= dCoop ? 1.0 : 1.0 - (desire - dCoop) / (1.0 - dCoop);
474 double tagExtent = tagV < tagD ? tagV : tagD;
475
476
477
478
479
480
481
482
483
484
485
486 Length headwayAdjustment = context.getParameters().getParameter(ParameterTypes.S0)
487 .plus(Length.min(context.getLength(), leader.getLength()).times(0.5)).times(tagExtent);
488 Acceleration a =
489 LmrsUtil.singleAcceleration(context, leader.getDistance().plus(headwayAdjustment), leader.getSpeed(), desire);
490 return a;
491 }
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511 static boolean canBeAhead(final TacticalContextEgo context, final PerceivedGtu adjacentVehicle, final Length xCur,
512 final int nCur, final Speed tagSpeed, final double dCoop, final Acceleration b, final Duration tMin,
513 final Duration tMax, final Length x0, final Duration t0, final Duration lc, final double desire)
514 throws ParameterException
515 {
516
517
518 boolean tmp = LmrsUtil.singleAcceleration(adjacentVehicle,
519 adjacentVehicle.getDistance().neg().minus(adjacentVehicle.getLength()).minus(context.getLength()),
520 context.getSpeed(), desire).gt(b.neg());
521 if (adjacentVehicle.getDistance().lt(context.getLength().neg())
522 && ((desire > dCoop && tmp) || (context.getSpeed().lt(tagSpeed) && adjacentVehicle.getSpeed().lt(tagSpeed))))
523 {
524 return true;
525 }
526
527
528
529
530
531
532
533
534
535
536
537
538
539 Length c = Synchronization.requiredBufferSpace(context.getSpeed(), nCur, x0, t0, lc, dCoop);
540 double t = (xCur.si - c.si) / context.getSpeed().si;
541 double xGap = adjacentVehicle.getSpeed().si * (tMin.si + desire * (tMax.si - tMin.si));
542 return 0.0 < t && t < (xCur.si - adjacentVehicle.getDistance().si - context.getLength().si
543 - adjacentVehicle.getLength().si - c.si - xGap) / adjacentVehicle.getSpeed().si;
544 }
545
546
547
548
549
550
551
552
553
554
555
556 static Length requiredBufferSpace(final Speed speed, final int nCur, final Length x0, final Duration t0, final Duration lc,
557 final double dCoop)
558 {
559 Length xCrit = speed.times(t0);
560 xCrit = Length.max(xCrit, x0);
561 return speed.times(lc).plus(xCrit.times((nCur - 1.0) * (1.0 - dCoop)));
562 }
563
564
565
566
567
568
569
570
571
572 static Acceleration stopForEnd(final TacticalContextEgo context, final Length xCur, final Length xMerge)
573 throws ParameterException
574 {
575 if (xCur.lt0())
576 {
577
578 return Acceleration.max(context.getParameters().getParameter(ParameterTypes.BCRIT).neg(),
579 CarFollowingUtil.stop(context, xMerge));
580 }
581 LmrsUtil.setDesiredHeadway(context.getParameters(), 1.0, true);
582 Acceleration a = CarFollowingUtil.stop(context, xCur);
583 if (a.lt0())
584 {
585
586 a = Acceleration.min(a, context.getParameters().getParameter(ParameterTypes.B).neg());
587
588 if (xMerge.gt0())
589 {
590 a = Acceleration.max(a, CarFollowingUtil.stop(context, xMerge));
591 }
592 }
593 else
594 {
595 a = Acceleration.POSITIVE_INFINITY;
596 }
597 LmrsUtil.resetDesiredHeadway(context.getParameters());
598 return a;
599 }
600
601
602
603
604
605
606
607 static PerceivedGtu getTargetLeader(final PerceivedGtu gtu, final SortedSet<PerceivedGtu> leaders)
608 {
609 for (PerceivedGtu leader : leaders)
610 {
611 if (leader.getDistance().gt(gtu.getDistance()))
612 {
613 return leader;
614 }
615 }
616 return null;
617 }
618
619 }