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