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 }