View Javadoc
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   * Stochastic Distraction Model by Manuel Lindorfer.
38   * <p>
39   * Copyright (c) 2013-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
40   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
41   * </p>
42   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
43   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
44   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
45   */
46  public class StochasticDistractionModel implements EventListener
47  {
48  
49      /** Whether to allow multi-tasking. */
50      private final boolean allowMultiTasking;
51  
52      /** List of distractions. */
53      private final List<Distraction> distractions;
54  
55      /** Network. */
56      private final RoadNetwork network;
57  
58      /** GTU types. */
59      private final Set<GtuType> gtuTypes;
60  
61      /** Set of distracted GTUs. */
62      private final Set<String> distractedGTUs = new LinkedHashSet<>();
63  
64      /** Queue of distractions per GTU. */
65      private final Map<String, Queue<Distraction>> distractionQueues = new LinkedHashMap<>();
66  
67      /** Task per GTU id and distraction id. */
68      private final MultiKeyMap<Task> tasks = new MultiKeyMap<>(String.class, String.class, Task.class);
69  
70      /** Task per GTU id and distraction id. */
71      private final MultiKeyMap<Function<LanePerception, Set<ChannelTask>>> taskSuppliers =
72              new MultiKeyMap<>(String.class, String.class, Function.class);
73  
74      /**
75       * Constructor. This model will react to GTU's being created in simulation and apply distractions.
76       * @param allowMultiTasking whether to allow multi-tasking
77       * @param distractions list of distractions
78       * @param network network
79       * @param gtuTypes GTU types to which the distractions apply
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       * Start a distraction.
97       * @param gtu gtu to start the distraction on
98       * @param distraction distraction
99       * @param scheduleNext whether to schedule the next distraction (not if starting from queue)
100      * @throws SimRuntimeException on time error
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                 // start the distraction now
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                 // stop the distraction
147                 this.network.getSimulator().scheduleEventRel(distraction.nextDuration(),
148                         () -> stopDistraction(gtu, distraction.getId()));
149             }
150             else
151             {
152                 return; // this vehicle (currently) does not use Fuller model
153             }
154         }
155         else
156         {
157             // need to queue distraction
158             this.distractionQueues.computeIfAbsent(gtuId, (id) -> new LinkedList<>()).add(distraction);
159         }
160         if (scheduleNext)
161         {
162             // schedule next distraction
163             this.network.getSimulator().scheduleEventRel(distraction.nextInterArrival(),
164                     () -> startDistraction(gtu, distraction, true));
165         }
166     }
167 
168     /**
169      * Stops a distraction task.
170      * @param gtu gtu to stop the task for
171      * @param distractionId distraction id
172      * @throws SimRuntimeException on time error
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             // start next distraction if any in queue
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             // this vehicle (currently) does not use Fuller model
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             // The GTU is not initialized yet, so we can't obtain the tactical planner
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 }