1 package org.opentrafficsim.road.gtu.perception.mental.channel;
2
3 import java.util.Iterator;
4 import java.util.LinkedHashMap;
5 import java.util.LinkedHashSet;
6 import java.util.Map;
7 import java.util.Map.Entry;
8 import java.util.Optional;
9 import java.util.Set;
10 import java.util.SortedSet;
11 import java.util.TreeSet;
12 import java.util.UUID;
13 import java.util.function.Function;
14
15 import org.djunits.unit.SpeedUnit;
16 import org.djunits.value.vdouble.scalar.Duration;
17 import org.djunits.value.vdouble.scalar.Length;
18 import org.djunits.value.vdouble.scalar.Speed;
19 import org.djutils.exceptions.Throw;
20 import org.djutils.exceptions.Try;
21 import org.djutils.immutablecollections.ImmutableSet;
22 import org.opentrafficsim.base.DistancedObject;
23 import org.opentrafficsim.base.OtsRuntimeException;
24 import org.opentrafficsim.base.parameters.ParameterException;
25 import org.opentrafficsim.base.parameters.ParameterTypeDouble;
26 import org.opentrafficsim.base.parameters.ParameterTypeDuration;
27 import org.opentrafficsim.base.parameters.ParameterTypeLength;
28 import org.opentrafficsim.base.parameters.ParameterTypes;
29 import org.opentrafficsim.base.parameters.Parameters;
30 import org.opentrafficsim.base.parameters.constraint.DualBound;
31 import org.opentrafficsim.base.parameters.constraint.NumericConstraint;
32 import org.opentrafficsim.core.network.Link;
33 import org.opentrafficsim.core.network.Node;
34 import org.opentrafficsim.road.gtu.LaneBasedGtu;
35 import org.opentrafficsim.road.gtu.perception.LanePerception;
36 import org.opentrafficsim.road.gtu.perception.PerceptionCollectable;
37 import org.opentrafficsim.road.gtu.perception.RelativeLane;
38 import org.opentrafficsim.road.gtu.perception.categories.IntersectionPerception;
39 import org.opentrafficsim.road.gtu.perception.categories.neighbors.PerceivedGtuType;
40 import org.opentrafficsim.road.gtu.perception.mental.AbstractTask;
41 import org.opentrafficsim.road.gtu.perception.object.PerceivedGtu;
42 import org.opentrafficsim.road.network.conflict.Conflict;
43 import org.opentrafficsim.road.network.speed.SpeedLimit;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 public class ChannelTaskIntersection extends AbstractTask implements ChannelTask
78 {
79
80
81 public static final ParameterTypeLength LOOKAHEAD = ParameterTypes.LOOKAHEAD;
82
83
84 public static final ParameterTypeDouble TD_B = new ParameterTypeDouble("td_b",
85 "Maximum task demand due to ego distance to intersection", 0.3 / (0.3 + 0.1), DualBound.UNITINTERVAL)
86 {
87 @Override
88 public void check(final Double value, final Parameters params) throws ParameterException
89 {
90 Throw.when(params.contains(TD_C) && params.getParameter(TD_C) + value >= 1.0, ParameterException.class,
91 "Values for td_b and td_c should sum to a value below 1.0");
92 }
93 };
94
95
96 public static final ParameterTypeLength BETA_YL = new ParameterTypeLength("beta_yl",
97 "Exponential decay of conflict task by ego distance", Length.ofSI(25.12), NumericConstraint.POSITIVEZERO);
98
99
100 public static final ParameterTypeDouble TD_C =
101 new ParameterTypeDouble("td_c", "Maximum task demand due to time-to-conflict-point of conflicting vehicle",
102 0.1 / (0.3 + 0.1), DualBound.UNITINTERVAL)
103 {
104 @Override
105 public void check(final Double value, final Parameters params) throws ParameterException
106 {
107 Throw.when(params.contains(TD_B) && params.getParameter(TD_B) + value >= 1.0, ParameterException.class,
108 "Values for td_b and td_c should sum to a value below 1.0");
109 }
110 };
111
112
113 public static final ParameterTypeDuration BETA_CON =
114 new ParameterTypeDuration("beta_con", "Exponential decay of conflict task from time-to-conflict-point",
115 Duration.ofSI(12.13), NumericConstraint.POSITIVEZERO);
116
117
118 private static final Speed GHOST_SPEED = new Speed(50.0, SpeedUnit.KM_PER_HOUR);
119
120
121
122
123 public static final Function<LanePerception, Set<ChannelTask>> SUPPLIER = (perception) ->
124 {
125 Set<ChannelTask> tasksOut = new LinkedHashSet<>();
126 ChannelMental channelMental =
127 (perception.getMental().isPresent() && perception.getMental().get() instanceof ChannelMental m) ? m : null;
128 Set<SortedSet<DistancedObject<Conflict>>> groups = findConflictGroups(perception);
129 IntersectionTaskGroup intersectionTaskGroup = new IntersectionTaskGroup();
130 if (!groups.isEmpty())
131 {
132 DistancedObject<Conflict> first = null;
133 for (SortedSet<DistancedObject<Conflict>> group : groups)
134 {
135 for (DistancedObject<Conflict> conflict : group)
136 {
137 if (!conflict.object().getConflictType().isSplit()
138 && (first == null || first.distance().gt(conflict.distance())))
139 {
140 first = conflict;
141 }
142 }
143 }
144
145
146 tasksOut.add(new ChannelTaskIntersection(perception.getGtu(), first, new TreeSet<>(), intersectionTaskGroup));
147
148
149 for (SortedSet<DistancedObject<Conflict>> group : groups)
150 {
151 splitCarFollowing(tasksOut, group, channelMental);
152 if (!group.isEmpty())
153 {
154 tasksOut.add(new ChannelTaskIntersection(perception.getGtu(), first, group, intersectionTaskGroup));
155
156 if (channelMental != null)
157 {
158 group.forEach((c) -> channelMental.mapToChannel(c.object(), group.first().object()));
159 }
160 }
161 }
162 }
163 return tasksOut;
164 };
165
166
167 private final LaneBasedGtu gtu;
168
169
170 private final DistancedObject<Conflict> first;
171
172
173 private final SortedSet<DistancedObject<Conflict>> conflicts;
174
175
176 private final IntersectionTaskGroup intersectionTaskGroup;
177
178
179 private Double conflictingTaskDemand;
180
181
182
183
184
185
186
187
188 protected ChannelTaskIntersection(final LaneBasedGtu gtu, final DistancedObject<Conflict> first,
189 final SortedSet<DistancedObject<Conflict>> conflicts, final IntersectionTaskGroup intersectionTaskGroup)
190 {
191 super(getId(conflicts));
192 this.gtu = gtu;
193 this.first = first;
194 this.conflicts = conflicts;
195 this.intersectionTaskGroup = intersectionTaskGroup;
196 intersectionTaskGroup.addTask(this);
197 }
198
199
200
201
202
203
204 private static String getId(final SortedSet<DistancedObject<Conflict>> conflicts)
205 {
206 if (conflicts.isEmpty())
207 {
208 return UUID.randomUUID().toString();
209 }
210 return conflicts.first().object().getFullId();
211 }
212
213 @Override
214 public Object getChannel()
215 {
216 return this.conflicts.isEmpty() ? FRONT : this.conflicts.first().object();
217 }
218
219 @Override
220 public double calculateTaskDemand(final LanePerception perception)
221 {
222 Length betaYl = Try.assign(() -> this.gtu.getParameters().getParameter(BETA_YL), "Parameter Beta_yl not present.");
223 double tdB = Try.assign(() -> this.gtu.getParameters().getParameter(TD_B), "Parameter TD_B not present.");
224 double egoDistance = this.first.distance().si < 0.0 ? 0.0 : this.first.distance().si;
225 return this.intersectionTaskGroup.getWeightedFactor(this) * tdB * Math.exp(-egoDistance / betaYl.si)
226 + getConflictingTaskDemand();
227 }
228
229
230
231
232
233 private double getWeight()
234 {
235 return this.conflicts.isEmpty() ? 0.0 : 1.0 + getConflictingTaskDemand();
236 }
237
238
239
240
241
242 private double getConflictingTaskDemand()
243 {
244 if (this.conflictingTaskDemand == null)
245 {
246 Duration conflictingTimeToConflict = Duration.POSITIVE_INFINITY;
247 Length x0 = this.gtu.getParameters().getOptionalParameter(LOOKAHEAD)
248 .orElseThrow(() -> new OtsRuntimeException("Parameter Lookahead not present."));
249 for (DistancedObject<Conflict> conflict : this.conflicts)
250 {
251 if (conflict.distance().ge0())
252 {
253 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> conflictingGtus =
254 conflict.object().getOtherConflict().getUpstreamGtus(this.gtu, PerceivedGtuType.WRAP, x0);
255 if (conflictingGtus.isEmpty())
256 {
257 Optional<SpeedLimit> speedLimitLane = conflict.object().getOtherConflict().getLane().getSpeedLimit();
258 Speed speedLimit = speedLimitLane.isPresent() ? speedLimitLane.get().speed() : GHOST_SPEED;
259 conflictingTimeToConflict = Duration.min(conflictingTimeToConflict, x0.divide(speedLimit));
260 }
261 else
262 {
263 PerceivedGtu conflictingGtu = conflictingGtus.first();
264 conflictingTimeToConflict =
265 Duration.min(conflictingTimeToConflict, conflictingGtu.getKinematics().getOverlap().isParallel()
266 ? Duration.ZERO : conflictingGtu.getDistance().divide(conflictingGtu.getSpeed()));
267 }
268 }
269 }
270 double tdC = this.gtu.getParameters().getOptionalParameter(TD_C)
271 .orElseThrow(() -> new OtsRuntimeException("Parameter TD_C not present."));
272 Duration betaCon = this.gtu.getParameters().getOptionalParameter(BETA_CON)
273 .orElseThrow(() -> new OtsRuntimeException("Parameter Beta_con not present."));
274 this.conflictingTaskDemand = tdC * Math.exp(-conflictingTimeToConflict.si / betaCon.si);
275 }
276 return this.conflictingTaskDemand;
277 }
278
279
280
281
282
283
284 private static Set<SortedSet<DistancedObject<Conflict>>> findConflictGroups(final LanePerception perception)
285 {
286 IntersectionPerception intersection =
287 Try.assign(() -> perception.getPerceptionCategory(IntersectionPerception.class), "No intersection perception.");
288 Iterator<DistancedObject<Conflict>> conflicts =
289 intersection.getConflicts(RelativeLane.CURRENT).underlyingWithDistance();
290
291
292 Map<SortedSet<DistancedObject<Conflict>>, Set<Node>> groups = new LinkedHashMap<>();
293 Length x0 = perception.getGtu().getParameters().getOptionalParameter(LOOKAHEAD)
294 .orElseThrow(() -> new OtsRuntimeException("No x0 parameter."));
295 while (conflicts.hasNext())
296 {
297 DistancedObject<Conflict> conflict = conflicts.next();
298 Set<Node> nodes = getUpstreamNodes(conflict.object().getOtherConflict(), x0);
299
300 Entry<SortedSet<DistancedObject<Conflict>>, Set<Node>> group = null;
301 Iterator<Entry<SortedSet<DistancedObject<Conflict>>, Set<Node>>> groupIterator = groups.entrySet().iterator();
302 while (groupIterator.hasNext())
303 {
304 Entry<SortedSet<DistancedObject<Conflict>>, Set<Node>> entry = groupIterator.next();
305 if (entry.getValue().stream().anyMatch(nodes::contains))
306 {
307
308 if (group == null)
309 {
310 entry.getKey().add(conflict);
311 entry.getValue().addAll(nodes);
312 group = entry;
313
314 }
315 else
316 {
317
318 group.getKey().addAll(entry.getKey());
319 group.getValue().addAll(entry.getValue());
320 groupIterator.remove();
321 }
322 }
323 }
324 if (group == null)
325 {
326
327 SortedSet<DistancedObject<Conflict>> key = new TreeSet<>();
328 key.add(conflict);
329 groups.put(key, nodes);
330 }
331 }
332 return groups.keySet();
333 }
334
335
336
337
338
339
340
341 private static Set<Node> getUpstreamNodes(final Conflict conflict, final Length x0)
342 {
343 Set<Node> nodes = new LinkedHashSet<>();
344 Link link = conflict.getLane().getLink();
345 Length distance = link.getLength().times(conflict.getLane().fraction(conflict.getLongitudinalPosition()) - 1.0);
346 appendUpstreamNodes(link, distance, x0, nodes);
347 return nodes;
348 }
349
350
351
352
353
354
355
356
357 private static void appendUpstreamNodes(final Link link, final Length distance, final Length x0, final Set<Node> nodes)
358 {
359 Length nextDistance = distance.plus(link.getLength());
360 if (nextDistance.le(x0))
361 {
362 Node start = link.getStartNode();
363 ImmutableSet<Link> links = start.getLinks();
364 Set<Link> upstreamLinks = new LinkedHashSet<>();
365 for (Link next : links)
366 {
367 if (!next.equals(link))
368 {
369 if (next.getStartNode().equals(start))
370 {
371
372 nodes.add(start);
373 return;
374 }
375 upstreamLinks.add(next);
376 }
377 }
378 nodes.add(start);
379 for (Link upstreamLink : upstreamLinks)
380 {
381 appendUpstreamNodes(upstreamLink, nextDistance, x0, nodes);
382 }
383 }
384 }
385
386
387
388
389
390
391
392 private static void splitCarFollowing(final Set<ChannelTask> tasks, final SortedSet<DistancedObject<Conflict>> group,
393 final ChannelMental channelMental)
394 {
395 Iterator<DistancedObject<Conflict>> iterator = group.iterator();
396 while (iterator.hasNext())
397 {
398 DistancedObject<Conflict> conflict = iterator.next();
399 if (conflict.object().getConflictType().isSplit())
400 {
401 iterator.remove();
402 tasks.add(new ChannelTaskCarFollowing((p) ->
403 {
404
405 Conflict otherconflict = conflict.object().getOtherConflict();
406 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> conflictingGtus =
407 otherconflict.getDownstreamGtus(p.getGtu(), PerceivedGtuType.WRAP, otherconflict.getLength());
408 if (conflictingGtus.isEmpty())
409 {
410 return null;
411 }
412 DistancedObject<LaneBasedGtu> leader = conflictingGtus.underlyingWithDistance().next();
413 return new DistancedObject<LaneBasedGtu>(leader.object(), conflict.distance().plus(leader.distance()));
414 }));
415
416 if (channelMental != null)
417 {
418 channelMental.mapToChannel(conflict.object(), FRONT);
419 }
420 }
421 }
422 }
423
424
425
426
427 private static final class IntersectionTaskGroup
428 {
429
430
431 private final Map<ChannelTaskIntersection, Double> weights = new LinkedHashMap<>();
432
433
434 private double totalWeight = 0.0;
435
436
437
438
439
440 public void addTask(final ChannelTaskIntersection task)
441 {
442 double weight = task.getWeight();
443 this.weights.put(task, weight);
444 this.totalWeight += weight;
445 }
446
447
448
449
450
451
452 public double getWeightedFactor(final ChannelTaskIntersection channelTaskIntersection)
453 {
454 if (channelTaskIntersection.conflicts.isEmpty())
455 {
456 return this.totalWeight == 0.0 ? 1.0 : 0.0;
457 }
458 return this.totalWeight == 0.0 ? 0.0 : (this.weights.get(channelTaskIntersection) / this.totalWeight);
459 }
460
461 }
462
463 }