LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
sn_refresh_visits.m
1%{ @file sn_refresh_visits.m
2 % @brief Solves traffic equations to compute visit ratios
3 %
4 % @author LINE Development Team
5 % Copyright (c) 2012-2026, Imperial College London
6 % All rights reserved.
7%}
8
9%{
10 % @brief Solves traffic equations to compute visit ratios
11 %
12 % @details
13 % This function solves the traffic equations to compute the average number
14 % of visits to nodes and stations for each chain in the network.
15 %
16 % @par Syntax:
17 % @code
18 % [visits, nodevisits, sn] = sn_refresh_visits(sn, chains, rt, rtnodes)
19 % @endcode
20 %
21 % @par Parameters:
22 % <table>
23 % <tr><th>Name<th>Description
24 % <tr><td>sn<td>Network structure
25 % <tr><td>chains<td>Chain definitions
26 % <tr><td>rt<td>Station routing matrix
27 % <tr><td>rtnodes<td>Node routing matrix
28 % </table>
29 %
30 % @par Returns:
31 % <table>
32 % <tr><th>Name<th>Description
33 % <tr><td>visits<td>Cell array of visit ratios at stations per chain
34 % <tr><td>nodevisits<td>Cell array of visit ratios at nodes per chain
35 % <tr><td>sn<td>Updated network structure with visit fields populated
36 % </table>
37%}
38function [visits, nodevisits, sn] = sn_refresh_visits(sn, chains, rt, rtnodes)
39
40I = sn.nnodes;
41M = sn.nstateful;
42K = sn.nclasses;
43refstat = sn.refstat;
44nchains = size(chains,1);
45
46%% obtain chain characteristics
47inchain = sn.inchain;
48for c=1:nchains
49 if sum(refstat(inchain{c}) == refstat(inchain{c}(1))) ~= length(inchain{c})
50 refstat(inchain{c}) = refstat(inchain{c}(1));
51 % line_error(mfilename,sprintf('Classes in chain %d have different reference stations. Chain %d classes: %s', c, c, int2str(inchain{c})));
52 end
53end
54
55%% generate station visits
56visits = cell(nchains,1); % visits{c}(i,j) is the number of visits that a chain-c job pays at node i in class j
57for c=1:nchains
58 cols = zeros(1,M*length(inchain{c}));
59 for ist=1:M
60 nIC = length(inchain{c});
61 for ik=1:nIC
62 cols(1,(ist-1)*nIC+ik) = (ist-1)*K+inchain{c}(ik);
63 end
64 end
65
66 Pchain = rt(cols,cols); % routing probability of the chain
67
68 % Handle NaN values in routing matrix (e.g., from Cache class switching)
69 % For visits calculation, replace NaN with equal probabilities
70 for row = 1:size(Pchain,1)
71 nan_cols = isnan(Pchain(row,:));
72 if any(nan_cols)
73 % Get the non-NaN sum for this row
74 non_nan_sum = sum(Pchain(row, ~nan_cols));
75 % Distribute remaining probability equally among NaN entries
76 remaining_prob = max(0, 1 - non_nan_sum);
77 n_nan = sum(nan_cols);
78 if n_nan > 0 && remaining_prob > 0
79 Pchain(row, nan_cols) = remaining_prob / n_nan;
80 else
81 Pchain(row, nan_cols) = 0;
82 end
83 end
84 end
85
86 visited = sum(Pchain,2) > 0;
87
88 % Normalize routing matrix for Fork-containing models
89 % Fork nodes have row sums > 1 (sending to all branches with prob 1 each)
90 % which causes dtmc_solve_reducible to fail. Normalize to make stochastic.
91 % Record original row sums to correct visit ratios after DTMC solve.
92 row_sums = ones(size(Pchain,1), 1);
93 if any(sn.nodetype == NodeType.Fork)
94 for row = 1:size(Pchain,1)
95 rs = sum(Pchain(row,:));
96 row_sums(row) = rs;
97 if rs > GlobalConstants.FineTol
98 Pchain(row,:) = Pchain(row,:) / rs;
99 end
100 end
101 end
102
103 % Use dtmc_solve as primary, fallback to dtmc_solve_reducible for chains with transient states
104 Pchain_visited = Pchain(visited,visited);
105 % see _kb/04-networkstruct.md (api/sn/*.m derived-field helpers) for rationale
106 try
107 alpha_visited = dtmc_solve(Pchain_visited);
108 if all(alpha_visited == 0) || any(isnan(alpha_visited))
109 [alpha_visited, ~, ~, ~, ~] = dtmc_solve_reducible(Pchain_visited, [], struct('tol', GlobalConstants.FineTol));
110 end
111 catch
112 [alpha_visited, ~, ~, ~, ~] = dtmc_solve_reducible(Pchain_visited, [], struct('tol', GlobalConstants.FineTol));
113 end
114 alpha = zeros(1,M*K); alpha(visited) = alpha_visited;
115 if max(alpha)>=1-GlobalConstants.FineTol
116 %disabled because a self-looping customer is an absorbing chain
117 %line_error(mfilename,'One chain has an absorbing state.');
118 end
119
120 % SPN-based fork correction: population-preserving SPN analysis proves
121 % that all visited entries have uniform visit ratios in fork-join models.
122 % This replaces the transitive closure correction.
123 if any(sn.nodetype == NodeType.Fork) && any(row_sums > 1 + GlobalConstants.FineTol)
124 for idx = 1:length(alpha)
125 if alpha(idx) > GlobalConstants.FineTol
126 alpha(idx) = 1;
127 end
128 end
129 end
130
131 visits{c} = zeros(M,K);
132 for ist=1:M
133 for k=1:length(inchain{c})
134 visits{c}(ist,inchain{c}(k)) = alpha((ist-1)*length(inchain{c})+k);
135 end
136 end
137 normSum = sum(visits{c}(sn.stationToStateful(refstat(inchain{c}(1))),inchain{c}));
138 if normSum > GlobalConstants.FineTol
139 visits{c} = visits{c} / normSum;
140 end
141 visits{c} = abs(visits{c});
142end
143
144%% generate node visits
145nodevisits = cell(1,nchains);
146for c=1:nchains
147 nodes_cols = zeros(1,I*length(inchain{c}));
148 for ind=1:I
149 nIC = length(inchain{c});
150 for ik=1:nIC
151 nodes_cols(1,(ind-1)*nIC+ik) = (ind-1)*K+inchain{c}(ik);
152 end
153 end
154 nodes_Pchain = rtnodes(nodes_cols, nodes_cols); % routing probability of the chain
155
156 % Handle NaN values in routing matrix (e.g., from Cache class switching)
157 % For visits calculation, replace NaN with equal probabilities
158 for row = 1:size(nodes_Pchain,1)
159 nan_cols = isnan(nodes_Pchain(row,:));
160 if any(nan_cols)
161 % Get the non-NaN sum for this row
162 non_nan_sum = sum(nodes_Pchain(row, ~nan_cols));
163 % Distribute remaining probability equally among NaN entries
164 remaining_prob = max(0, 1 - non_nan_sum);
165 n_nan = sum(nan_cols);
166 if n_nan > 0 && remaining_prob > 0
167 nodes_Pchain(row, nan_cols) = remaining_prob / n_nan;
168 else
169 nodes_Pchain(row, nan_cols) = 0;
170 end
171 end
172 end
173
174 nodes_visited = sum(nodes_Pchain,2) > 0;
175
176 % Normalize routing matrix for Fork-containing models
177 % Record original row sums to correct visit ratios after DTMC solve.
178 nodes_row_sums = ones(size(nodes_Pchain,1), 1);
179 if any(sn.nodetype == NodeType.Fork)
180 for row = 1:size(nodes_Pchain,1)
181 rs = sum(nodes_Pchain(row,:));
182 nodes_row_sums(row) = rs;
183 if rs > GlobalConstants.FineTol
184 nodes_Pchain(row,:) = nodes_Pchain(row,:) / rs;
185 end
186 end
187 end
188
189 % Use dtmc_solve as primary, fallback to dtmc_solve_reducible for chains with transient states
190 nodes_Pchain_visited = nodes_Pchain(nodes_visited,nodes_visited);
191 % Guard the primary solve against a reducible-chain throw, as above.
192 try
193 nodes_alpha_visited = dtmc_solve(nodes_Pchain_visited);
194 if all(nodes_alpha_visited == 0) || any(isnan(nodes_alpha_visited))
195 [nodes_alpha_visited, ~, ~, ~, ~] = dtmc_solve_reducible(nodes_Pchain_visited, [], struct('tol', GlobalConstants.FineTol));
196 end
197 catch
198 [nodes_alpha_visited, ~, ~, ~, ~] = dtmc_solve_reducible(nodes_Pchain_visited, [], struct('tol', GlobalConstants.FineTol));
199 end
200 nodes_alpha = zeros(1,I*K); nodes_alpha(nodes_visited) = nodes_alpha_visited;
201
202 % SPN-based fork correction for node visits: population-preserving SPN
203 % analysis gives visit=1 for stations/Fork nodes and visit=n_sources
204 % for Join nodes (one per incoming branch).
205 if any(sn.nodetype == NodeType.Fork) && any(nodes_row_sums > 1 + GlobalConstants.FineTol)
206 nIC = length(inchain{c});
207 for idx = 1:length(nodes_alpha)
208 if nodes_alpha(idx) > GlobalConstants.FineTol
209 nd = floor((idx-1) / nIC) + 1;
210 if sn.nodetype(nd) == NodeType.Join
211 r = inchain{c}(mod(idx-1, nIC) + 1);
212 col = (nd-1)*K + r;
213 n_sources = sum(sn.rtnodes(:, col) > GlobalConstants.FineTol);
214 nodes_alpha(idx) = n_sources;
215 else
216 nodes_alpha(idx) = 1;
217 end
218 end
219 end
220 end
221
222 nodevisits{c} = zeros(I,K);
223 for ind=1:I
224 for k=1:length(inchain{c})
225 nodevisits{c}(ind,inchain{c}(k)) = nodes_alpha((ind-1)*length(inchain{c})+k);
226 end
227 end
228 nodeNormSum = sum(nodevisits{c}(sn.statefulToNode(refstat(inchain{c}(1))),inchain{c}));
229 if nodeNormSum > GlobalConstants.FineTol
230 nodevisits{c} = nodevisits{c} / nodeNormSum;
231 end
232 nodevisits{c}(nodevisits{c}<0) = 0; % remove small numerical perturbations
233end
234
235for c=1:nchains
236 nodevisits{c}(isnan(nodevisits{c})) = 0;
237end
238
239%% save results in sn
240sn.visits = visits;
241sn.nodevisits = nodevisits;
242sn.inchain = inchain;
243end
Definition fjtag.m:161