1 package org.opentrafficsim.road.gtu.lane.perception.mental.sdm;
2
3 import java.util.LinkedHashMap;
4 import java.util.LinkedHashSet;
5 import java.util.LinkedList;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Optional;
9 import java.util.Queue;
10 import java.util.Set;
11 import java.util.function.Function;
12
13 import org.djutils.event.Event;
14 import org.djutils.event.EventListener;
15 import org.djutils.exceptions.Throw;
16 import org.djutils.multikeymap.MultiKeyMap;
17 import org.opentrafficsim.base.OtsRuntimeException;
18 import org.opentrafficsim.base.logger.Logger;
19 import org.opentrafficsim.core.gtu.Gtu;
20 import org.opentrafficsim.core.gtu.GtuType;
21 import org.opentrafficsim.core.network.Network;
22 import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
23 import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
24 import org.opentrafficsim.road.gtu.lane.perception.mental.Fuller;
25 import org.opentrafficsim.road.gtu.lane.perception.mental.Mental;
26 import org.opentrafficsim.road.gtu.lane.perception.mental.SumFuller;
27 import org.opentrafficsim.road.gtu.lane.perception.mental.Task;
28 import org.opentrafficsim.road.gtu.lane.perception.mental.ar.ArTaskConstant;
29 import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelFuller;
30 import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTask;
31 import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTaskConstant;
32 import org.opentrafficsim.road.network.RoadNetwork;
33
34 import nl.tudelft.simulation.dsol.SimRuntimeException;
35
36
37
38
39
40
41
42
43
44
45
46 public class StochasticDistractionModel implements EventListener
47 {
48
49
50 private final boolean allowMultiTasking;
51
52
53 private final List<Distraction> distractions;
54
55
56 private final RoadNetwork network;
57
58
59 private final Set<GtuType> gtuTypes;
60
61
62 private final Set<String> distractedGTUs = new LinkedHashSet<>();
63
64
65 private final Map<String, Queue<Distraction>> distractionQueues = new LinkedHashMap<>();
66
67
68 private final MultiKeyMap<Task> tasks = new MultiKeyMap<>(String.class, String.class, Task.class);
69
70
71 private final MultiKeyMap<Function<LanePerception, Set<ChannelTask>>> taskSuppliers =
72 new MultiKeyMap<>(String.class, String.class, Function.class);
73
74
75
76
77
78
79
80
81 public StochasticDistractionModel(final boolean allowMultiTasking, final List<Distraction> distractions,
82 final RoadNetwork network, final Set<GtuType> gtuTypes)
83 {
84 Throw.whenNull(distractions, "distractions");
85 Throw.whenNull(network, "network");
86 Throw.whenNull(gtuTypes, "gtuTypes");
87 this.allowMultiTasking = allowMultiTasking;
88 this.distractions = distractions;
89 this.network = network;
90 this.gtuTypes = gtuTypes;
91 network.addListener(this, Network.GTU_ADD_EVENT);
92 network.addListener(this, Network.GTU_REMOVE_EVENT);
93 }
94
95
96
97
98
99
100
101
102 @SuppressWarnings("unchecked")
103 public void startDistraction(final LaneBasedGtu gtu, final Distraction distraction, final boolean scheduleNext)
104 throws SimRuntimeException
105 {
106 if (gtu.isDestroyed())
107 {
108 return;
109 }
110 String gtuId = gtu.getId();
111 if (this.allowMultiTasking || !this.distractedGTUs.contains(gtuId))
112 {
113 Optional<Mental> mental = gtu.getTacticalPlanner().getPerception().getMental();
114 if (mental.isEmpty())
115 {
116 return;
117 }
118 if (mental.get() instanceof Fuller fuller)
119 {
120
121 if (!this.allowMultiTasking)
122 {
123 this.distractedGTUs.add(gtuId);
124 }
125 if (mental.get() instanceof SumFuller sumFuller)
126 {
127 Task task = new ArTaskConstant(distraction.getId(), distraction.getTaskDemand());
128 ((SumFuller<Task>) sumFuller).addTask(task);
129 this.tasks.put(task, gtuId, distraction.getId());
130 }
131 else if (mental.get() instanceof ChannelFuller channelFuller)
132 {
133 boolean internal = !DefaultDistraction.EXTERNAL_DISTRACTION.getId().equals(distraction.getId());
134 ChannelTask task = new ChannelTaskConstant(distraction.getId(),
135 internal ? ChannelTask.IN_VEHICLE : ChannelTask.FRONT, distraction.getTaskDemand());
136 Set<ChannelTask> set = Set.of(task);
137 Function<LanePerception, Set<ChannelTask>> taskSupplier = (perception) -> set;
138 channelFuller.addTaskSupplier(taskSupplier);
139 this.taskSuppliers.put(taskSupplier, gtuId, distraction.getId());
140 }
141 else
142 {
143 Logger.ots().warn("Fuller implementation {} not supported by {}", fuller.getClass().getName(),
144 getClass().getName());
145 }
146
147 this.network.getSimulator().scheduleEventRel(distraction.nextDuration(),
148 () -> stopDistraction(gtu, distraction.getId()));
149 }
150 else
151 {
152 return;
153 }
154 }
155 else
156 {
157
158 this.distractionQueues.computeIfAbsent(gtuId, (id) -> new LinkedList<>()).add(distraction);
159 }
160 if (scheduleNext)
161 {
162
163 this.network.getSimulator().scheduleEventRel(distraction.nextInterArrival(),
164 () -> startDistraction(gtu, distraction, true));
165 }
166 }
167
168
169
170
171
172
173
174 @SuppressWarnings("unchecked")
175 public void stopDistraction(final LaneBasedGtu gtu, final String distractionId) throws SimRuntimeException
176 {
177 if (gtu.isDestroyed())
178 {
179 return;
180 }
181 boolean isFuller = false;
182 String gtuId = gtu.getId();
183 Optional<Mental> mental = gtu.getTacticalPlanner().getPerception().getMental();
184 if (mental.isPresent() && mental.get() instanceof SumFuller sumFuller)
185 {
186 ((SumFuller<Task>) sumFuller).removeTask((Task) this.tasks.clear(gtuId, distractionId));
187 isFuller = true;
188 }
189 else if (mental.isPresent() && mental.get() instanceof ChannelFuller channelFuller)
190 {
191 channelFuller.removeTaskSupplier(
192 (Function<LanePerception, Set<ChannelTask>>) this.taskSuppliers.clear(gtuId, distractionId));
193 isFuller = true;
194 }
195 else
196 {
197 Logger.ots().warn("Disabling distraction " + distractionId + " on GTU " + gtuId
198 + ", but it (no longer) has a tactical planner with mental module that can do this.");
199 }
200 if (isFuller)
201 {
202
203 if (!this.allowMultiTasking)
204 {
205 this.distractedGTUs.remove(gtuId);
206 if (this.distractionQueues.containsKey(gtuId))
207 {
208 Queue<Distraction> queue = this.distractionQueues.get(gtuId);
209 Distraction distraction = queue.poll();
210 startDistraction(gtu, distraction, false);
211 if (queue.isEmpty())
212 {
213 this.distractionQueues.remove(gtuId);
214 }
215 }
216 }
217 }
218 else
219 {
220
221 this.distractedGTUs.remove(gtuId);
222 this.distractionQueues.remove(gtuId);
223 }
224 }
225
226 @Override
227 public void notify(final Event event)
228 {
229 if (event.getType().equals(Network.GTU_ADD_EVENT))
230 {
231
232 String gtuId = (String) event.getContent();
233 Gtu gtu = this.network.getGTU(gtuId).orElseThrow(
234 () -> new OtsRuntimeException("Distraction event for GTU " + gtuId + " which is not in the network."));
235 if (gtu instanceof LaneBasedGtu && this.gtuTypes.contains(gtu.getType()))
236 {
237 gtu.addListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT);
238 }
239 }
240 else if (event.getType().equals(LaneBasedGtu.LANEBASED_MOVE_EVENT))
241 {
242 String gtuId = (String) ((Object[]) event.getContent())[0];
243 LaneBasedGtu gtu = (LaneBasedGtu) this.network.getGTU(gtuId).get();
244 Optional<Mental> mental = gtu.getTacticalPlanner().getPerception().getMental();
245 if (mental.isPresent() && mental.get() instanceof Fuller)
246 {
247 for (Distraction distraction : this.distractions)
248 {
249 if (distraction.nextExposure())
250 {
251 this.network.getSimulator().scheduleEventRel(distraction.nextInterArrival(),
252 () -> startDistraction(gtu, distraction, true));
253 }
254 }
255 }
256 gtu.removeListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT);
257 }
258 else if (event.getType().equals(Network.GTU_REMOVE_EVENT))
259 {
260 String gtuId = (String) event.getContent();
261 if (!this.allowMultiTasking)
262 {
263 this.distractedGTUs.remove(gtuId);
264 }
265 this.distractionQueues.remove(gtuId);
266 }
267 }
268
269 }