View Javadoc
1   package org.opentrafficsim.road.gtu.perception.mental.channel;
2   
3   import java.util.Collection;
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.function.Function;
11  
12  import org.djunits.value.vdouble.scalar.Duration;
13  import org.djutils.exceptions.Throw;
14  import org.djutils.immutablecollections.Immutable;
15  import org.djutils.immutablecollections.ImmutableLinkedHashSet;
16  import org.djutils.immutablecollections.ImmutableSet;
17  import org.opentrafficsim.base.logger.Logger;
18  import org.opentrafficsim.base.parameters.ParameterException;
19  import org.opentrafficsim.base.parameters.ParameterTypeDouble;
20  import org.opentrafficsim.base.parameters.ParameterTypeDuration;
21  import org.opentrafficsim.base.parameters.Parameters;
22  import org.opentrafficsim.base.parameters.constraint.DualBound;
23  import org.opentrafficsim.base.parameters.constraint.NumericConstraint;
24  import org.opentrafficsim.road.gtu.perception.LanePerception;
25  import org.opentrafficsim.road.gtu.perception.mental.BehavioralAdaptation;
26  import org.opentrafficsim.road.gtu.perception.mental.FactorEstimation;
27  import org.opentrafficsim.road.gtu.perception.mental.Fuller;
28  import org.opentrafficsim.road.gtu.perception.mental.Task;
29  
30  /**
31   * Fuller implementation with perception channels. This is based on a set of task suppliers, which may either provide static
32   * tasks (always the same) or a dynamic set of tasks (e.g. per conflicting road present). When relevant, task suppliers need to
33   * map objects to channel keys when they are invoked to return the currently applicable channel tasks. For example mapping a
34   * single conflict to a common key that refers to a channel based on a group of conflicts. In this way the correct perception
35   * delay can be found when only knowing the single conflict, without knowing how it was grouped or what then defines the key.
36   * <p>
37   * Copyright (c) 2024-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
38   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
39   * </p>
40   * @author Wouter Schakel
41   */
42  public class ChannelFuller extends Fuller implements ChannelMental
43  {
44  
45      /** Task capability in nominal task capability units, i.e. mean is 1. */
46      public static final ParameterTypeDouble TC = Fuller.TC;
47  
48      /** Task saturation. */
49      public static final ParameterTypeDouble TS = Fuller.TS;
50  
51      /** Over-estimation parameter type. Negative values reflect under-estimation. */
52      public static final ParameterTypeDouble OVER_EST = Fuller.OVER_EST;
53  
54      /** Erroneous estimation factor on distance and speed difference. */
55      public static final ParameterTypeDouble EST_FACTOR = FactorEstimation.EST_FACTOR;
56  
57      /** Level of attention, which is the maximum in the steady state of the Attention Matrix. */
58      public static final ParameterTypeDouble ATT =
59              new ParameterTypeDouble("ATT", "Attention (maximum of all channels).", 0.0, DualBound.UNITINTERVAL);
60  
61      /** Minimum perception delay. */
62      public static final ParameterTypeDuration TAU_MIN = new ParameterTypeDuration("tau_min", "Minimum perception delay",
63              Duration.ofSI(0.32), NumericConstraint.POSITIVEZERO)
64      {
65          /** {@inheritDoc} */
66          @Override
67          public void check(final Duration value, final Parameters params) throws ParameterException
68          {
69              Throw.when(params.contains(TAU_MAX) && params.getParameter(TAU_MAX).lt(value), ParameterException.class,
70                      "Value of tau_max less smaller than tau_min.");
71  
72          }
73      };
74  
75      /** Maximum perception delay. */
76      public static final ParameterTypeDuration TAU_MAX = new ParameterTypeDuration("tau_max", "Maximum perception delay",
77              Duration.ofSI(0.32 + 0.87), NumericConstraint.POSITIVE)
78      {
79          /** {@inheritDoc} */
80          @Override
81          public void check(final Duration value, final Parameters params) throws ParameterException
82          {
83              Throw.when(params.contains(TAU_MIN) && params.getParameter(TAU_MIN).gt(value), ParameterException.class,
84                      "Value of tau_min is greater than tau_max.");
85          }
86      };
87  
88      /** Task suppliers. */
89      private Set<Function<LanePerception, Set<ChannelTask>>> taskSuppliers = new LinkedHashSet<>();
90  
91      /** Set of tasks as derived from suppliers. */
92      private Set<ChannelTask> tasks;
93  
94      /** Map of tasks as derived from suppliers. */
95      private Map<String, ChannelTask> taskMap;
96  
97      /** Mappings from object to channel. */
98      private Map<Object, Object> channelMapping = new LinkedHashMap<>();
99  
100     /** Stored perception delay per channel. */
101     private Map<Object, Duration> perceptionDelay = new LinkedHashMap<>();
102 
103     /** Stored level of attention per channel. */
104     private Map<Object, Double> attention = new LinkedHashMap<>();
105 
106     /**
107      * Constructor.
108      * @param taskSuppliers task suppliers.
109      * @param behavioralAdapatations behavioral adaptations.
110      */
111     public ChannelFuller(final Collection<Function<LanePerception, Set<ChannelTask>>> taskSuppliers,
112             final Set<BehavioralAdaptation> behavioralAdapatations)
113     {
114         super(behavioralAdapatations);
115         this.taskSuppliers.addAll(taskSuppliers);
116     }
117 
118     @Override
119     protected double getTotalTaskDemand(final LanePerception perception) throws ParameterException
120     {
121         // Clear mappings
122         this.channelMapping.clear();
123 
124         // Gather all channels and their maximum task demand
125         Map<Object, Double> channelTaskDemand = new LinkedHashMap<>();
126         Set<ChannelTask> gatheredTasks = new LinkedHashSet<>();
127         Map<String, ChannelTask> gatheredTaskMap = new LinkedHashMap<>();
128         for (Function<LanePerception, Set<ChannelTask>> taskFunction : this.taskSuppliers)
129         {
130             for (ChannelTask task : taskFunction.apply(perception)) // if applicable will (re)map objects to channel keys
131             {
132                 double td = task.getTaskDemand(perception);
133                 if (td >= 1.0)
134                 {
135                     td = 0.999;
136                     Logger.ots().warn("Task {} produced task demand that is greater than, or equal to, 1.0.", task.getId());
137                 }
138                 channelTaskDemand.merge(task.getChannel(), td, Math::max); // map to max value
139                 gatheredTasks.add(task);
140                 gatheredTaskMap.put(task.getId(), task);
141             }
142         }
143         this.tasks = gatheredTasks;
144         this.taskMap = gatheredTaskMap;
145 
146         // Apply attention matrix and couple channel to indices
147         double[] tdArray = new double[channelTaskDemand.size()];
148         int index = 0;
149         double sumTaskDemand = 0.0;
150         Map<Object, Integer> channelIndex = new LinkedHashMap<>();
151         for (Entry<Object, Double> entry : channelTaskDemand.entrySet())
152         {
153             channelIndex.put(entry.getKey(), index);
154             double td = entry.getValue();
155             tdArray[index] = td;
156             sumTaskDemand += td;
157             index++;
158         }
159         AttentionMatrix matrix = new AttentionMatrix(tdArray);
160 
161         // Determine attention and perception delay per channel
162         double maxAttention = 0.0;
163         this.perceptionDelay.clear();
164         this.attention.clear();
165         Parameters parameters = perception.getGtu().getParameters();
166         Duration tauMin = parameters.getParameter(TAU_MIN);
167         Duration tauMax = parameters.getParameter(TAU_MAX);
168         double tc = parameters.getParameter(TC);
169         for (Entry<Object, Integer> entry : channelIndex.entrySet())
170         {
171             index = entry.getValue();
172             this.perceptionDelay.put(entry.getKey(),
173                     Duration.interpolate(tauMin, tauMax, matrix.getDeterioration(index)).divide(tc));
174             double att = matrix.getAttention(index);
175             maxAttention = Double.max(maxAttention, att);
176             this.attention.put(entry.getKey(), att);
177         }
178 
179         // Results
180         double ts = sumTaskDemand / tc;
181         parameters.setClaimedParameter(EST_FACTOR, Math.pow(Math.max(ts, 1.0), parameters.getParameter(OVER_EST)), this);
182         parameters.setClaimedParameter(ATT, maxAttention, this);
183         return sumTaskDemand;
184 
185         // super sets task saturation
186         // super applies behavioral adaptations
187     }
188 
189     @Override
190     public ImmutableSet<ChannelTask> getTasks()
191     {
192         return new ImmutableLinkedHashSet<ChannelTask>(this.tasks, Immutable.WRAP);
193     }
194 
195     @Override
196     public Optional<Task> getTask(final String taskId)
197     {
198         return Optional.ofNullable(this.taskMap.get(taskId));
199     }
200 
201     /**
202      * Add task supplier.
203      * @param taskSupplier task supplier to add
204      */
205     public void addTaskSupplier(final Function<LanePerception, Set<ChannelTask>> taskSupplier)
206     {
207         this.taskSuppliers.add(taskSupplier);
208     }
209 
210     /**
211      * Remove task supplier.
212      * @param taskSupplier task supplier to remove
213      */
214     public void removeTaskSupplier(final Function<LanePerception, Set<ChannelTask>> taskSupplier)
215     {
216         this.taskSuppliers.remove(taskSupplier);
217     }
218 
219     @Override
220     public Duration getPerceptionDelay(final Object obj)
221     {
222         return this.perceptionDelay.get(getChannel(obj));
223     }
224 
225     @Override
226     public double getAttention(final Object obj)
227     {
228         return this.attention.get(getChannel(obj));
229     }
230 
231     @Override
232     public void mapToChannel(final Object obj, final Object channel)
233     {
234         this.channelMapping.put(obj, channel);
235     }
236 
237     /**
238      * Returns the relevant channel key for the object. This is a channel key mapped to the object, or the object itself if
239      * there is no such mapping (in which case the object should itself directly be a channel key).
240      * @param obj object.
241      * @return relevant channel key for the object.
242      */
243     private Object getChannel(final Object obj)
244     {
245         if (this.channelMapping.containsKey(obj))
246         {
247             return this.channelMapping.get(obj);
248         }
249         Throw.when(!this.perceptionDelay.containsKey(obj), IllegalArgumentException.class, "Channel %s is not present.", obj);
250         return obj;
251     }
252 
253     /**
254      * Returns the current channels.
255      * @return set of channels
256      */
257     public Set<Object> getChannels()
258     {
259         return new LinkedHashSet<>(this.attention.keySet());
260     }
261 
262 }