View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception.mental.channel;
2   
3   import org.djutils.exceptions.Throw;
4   
5   import Jama.Matrix;
6   
7   /**
8    * This class describes attention over channels, based on task demand per channel. Transition probabilities are based on demand
9    * per channel, where drivers are assumed to keep perceiving the same channel by the demand of that channel alone. When total
10   * demand is above 1, this means that the probability of switching to another channel is reduced. All transition probabilities
11   * together result in an overall steady-state, which describes what fraction of time is spent on what channel.
12   * <p>
13   * Copyright (c) 2024-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
14   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
15   * </p>
16   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
17   */
18  public class AttentionMatrix
19  {
20  
21      /** Mental task demand, i.e. desired fraction of time for perception, per channel. */
22      private final double[] demand;
23  
24      /** Attention, i.e. fraction of time, per channel. */
25      private double[] attention;
26  
27      /** Anticipation reliance per channel. */
28      private double[] anticipationReliance;
29  
30      /**
31       * Constructor which pre-calculates attention distribution assuming drivers are serial mono-taskers. The probability of
32       * staying on a task is the task demand of the task, while the probability of switching is the complement. The task that
33       * will then be switched to is selected by weighting them by their task demand. This creates a transition matrix in a Markov
34       * chain. The steady-state of this Markov chain is the attention distribution.
35       * @param demand level of mental task demand per channel.
36       * @throws IllegalArgumentException when a demand value is below 0 or larger than or equal to 1
37       */
38      public AttentionMatrix(final double[] demand)
39      {
40          int n = demand.length;
41          this.demand = new double[n];
42          System.arraycopy(demand, 0, this.demand, 0, n);
43          this.attention = new double[n];
44          this.anticipationReliance = new double[n];
45  
46          double demandSum = 0.0;
47          for (int i = 0; i < n; i++)
48          {
49              Throw.when(demand[i] < 0.0, IllegalArgumentException.class, "Demand must be >= 0");
50              Throw.when(demand[i] >= 1.0, IllegalArgumentException.class, "Demand must be < 1");
51              demandSum += demand[i];
52          }
53          if (demandSum == 0.0)
54          {
55              return;
56          }
57          if (demandSum <= 1.0)
58          {
59              this.attention = this.demand;
60              return;
61          }
62  
63          /*
64           * matrix is the transition matrix in a Markov chain describing the probability of the next perception glance to be
65           * towards channel j, given previous channel i.
66           */
67          Matrix matrix = new Matrix(n, n);
68          for (int i = 0; i < n; i++)
69          {
70              for (int j = 0; j < n; j++)
71              {
72                  /*
73                   * As we need a left-eigenvector (v*P = 1*v, v=eigenvector, P=matrix), we pre-transpose the data setting at (j,
74                   * i). Note that left-eigenvectors are the transposed right-eigenvectors of matrix'. We do not care for how it
75                   * is transposed.
76                   */
77                  if (i == j)
78                  {
79                      // probability to keep perceiving the same channel is the demand of the channel
80                      matrix.set(j, i, demand[i]);
81                  }
82                  else if (demandSum > demand[i])
83                  {
84                      /*
85                       * The probability of a switch to another channel is 1 - TD(i). The relative probabilities of the other
86                       * channels to be switched to, is proportional to the demand in these channels TD(j). These are normalized
87                       * by the total sum of demand minus the demand of the channel we switch from (i.e. the sum of demand of the
88                       * other channels), and scaled by the probability to switch 1 - TD(i).
89                       */
90                      matrix.set(j, i, (1 - demand[i]) * demand[j] / (demandSum - demand[i]));
91                  }
92                  else
93                  {
94                      // there is only one channel with task demand, do not switch
95                      matrix.set(j, i, 0.0);
96                  }
97              }
98          }
99  
100         /*
101          * We use Jama to find the eigenvector of the transition matrix pertaining to the eigenvalue 1. Each Markov transition
102          * matrix has an eigenvalue 1, and the pertaining eigenvector is the steady-state. This steady state is the distribution
103          * of attention (in time) over the channels.
104          */
105         var ed = matrix.eig();
106         double[] eigenValues = ed.getRealEigenvalues();
107         // find the eigenvalue closest to 1 (these values are not highly exact)
108         int eigenIndex = 0;
109         double dMin = 1.0;
110         for (int i = 0; i < n; i++)
111         {
112             double di = Math.abs(eigenValues[i] - 1.0);
113             if (di < dMin)
114             {
115                 dMin = di;
116                 eigenIndex = i;
117             }
118         }
119         // obtain the eigenvector pertaining to the eigenvalue of 1
120         double[][] v = ed.getV().getArray();
121         double sumEigenVector = 0.0;
122         for (int i = 0; i < n; i++)
123         {
124             this.attention[i] = v[i][eigenIndex];
125             sumEigenVector += this.attention[i];
126         }
127         // normalize so it sums to 1
128         for (int i = 0; i < n; i++)
129         {
130             this.attention[i] = this.attention[i] / sumEigenVector;
131         }
132 
133         /*
134          * Anticipation reliance per channel is the difference between the steady state (actual proportion of time we perceive a
135          * channel) and the desired proportion of time to perceive a channel.
136          */
137         for (int i = 0; i < n; i++)
138         {
139             this.anticipationReliance[i] = this.demand[i] - this.attention[i];
140         }
141     }
142 
143     /**
144      * Returns the fraction of time that is spent on channel <i>i</i>.
145      * @param i index of channel.
146      * @return fraction of time that is spent on channel <i>i</i>.
147      */
148     public double getAttention(final int i)
149     {
150         return this.attention[i];
151     }
152 
153     /**
154      * Returns the level of anticipation reliance for channel <i>i</i>. This is the fraction of time that is reduced from
155      * perceiving channel <i>i</i>, relative to the desired fraction of time to perceive channel <i>i</i>.
156      * @param i index of channel.
157      * @return level of anticipation reliance for channel <i>i</i>.
158      */
159     public double getAnticipationReliance(final int i)
160     {
161         return this.anticipationReliance[i];
162     }
163 
164     /**
165      * Returns the deterioration of channel <i>i</i>. This is the anticipation reliance for channel <i>i</i>, divided by the
166      * desired level of attention for channel <i>i</i>. This value is an indication of perception delay for the channel.
167      * <p>
168      * If demand for the channel is 0, this method returns 1.
169      * @param i index of channel.
170      * @return fraction of anticipation reliance over desired attention for channel <i>i</i>.
171      */
172     public double getDeterioration(final int i)
173     {
174         return this.demand[i] == 0.0 ? 1.0 : this.anticipationReliance[i] / this.demand[i];
175     }
176 
177 }