LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
isRoutingErgodic.m
1function [isErg, info] = isRoutingErgodic(self, P)
2% [ISERG, INFO] = ISROUTINGERGODIC()
3% [ISERG, INFO] = ISROUTINGERGODIC(P)
4%
5% Check if the queueing network routing matrix is ergodic (irreducible).
6% This checks only the routing structure, not the full CTMC state space.
7% A routing is ergodic if all stations communicate, meaning the routing
8% matrix does not create absorbing states or disconnected components.
9%
10% Note: This is a necessary but not sufficient condition for the full
11% Markov chain to be ergodic. State-dependent routing, finite buffers,
12% or other factors may still cause reducibility in the actual CTMC.
13%
14% Input:
15% P: (optional) routing matrix cell array. If not provided, it will be
16% retrieved via getLinkedRoutingMatrix(). Passing P directly avoids
17% calling getStruct() which can have side effects during link().
18%
19% Output:
20% ISERG: true if the routing is ergodic, false otherwise
21% INFO: struct with details about non-ergodicity:
22% - absorbingStations: cell array of station names that are absorbing
23% - transientStations: cell array of station names that are transient
24% - numSCCs: number of strongly connected components
25% - isReducible: true if the routing creates a reducible structure
26%
27% Copyright (c) 2012-2026, Imperial College London
28% All rights reserved.
29
30info = struct();
31info.absorbingStations = {};
32info.transientStations = {};
33info.numSCCs = 1;
34info.isReducible = false;
35
36% Get the routing matrix if not provided
37if nargin < 2 || isempty(P)
38 P = self.getLinkedRoutingMatrix();
39end
40if isempty(P)
41 % No routing defined yet
42 isErg = true;
43 return;
44end
45
46K = self.getNumberOfClasses;
47I = self.getNumberOfNodes;
48nodeNames = self.getNodeNames;
49
50% Build an aggregate adjacency matrix across all classes
51% A transition exists from node i to node j if there's any class
52% that can route from i to j
53adjMatrix = zeros(I, I);
54for r = 1:K
55 for s = 1:K
56 if ~isempty(P{r,s})
57 % Handle routing matrices that may be smaller than I x I
58 % (e.g., when user specifies routing only for stations)
59 [nRows, nCols] = size(P{r,s});
60 if nRows <= I && nCols <= I
61 adjMatrix(1:nRows, 1:nCols) = adjMatrix(1:nRows, 1:nCols) + (P{r,s} > 0);
62 else
63 % Matrix is larger than expected (includes ClassSwitch nodes)
64 adjMatrix = adjMatrix + (P{r,s}(1:I, 1:I) > 0);
65 end
66 end
67 end
68end
69adjMatrix = adjMatrix > 0; % Convert to logical
70
71% Find strongly connected components
72[scc, isrec] = stronglyconncomp(adjMatrix);
73numSCCs = max(scc);
74info.numSCCs = numSCCs;
75
76% Check for absorbing stations (stations with only self-loops or no outgoing edges)
77% An absorbing station is one where once entered, jobs never leave
78stationIdxs = self.getStationIndexes();
79
80for idx = stationIdxs
81 % Check if this station has any outgoing transitions to other nodes
82 outgoing = adjMatrix(idx, :);
83 outgoing(idx) = 0; % Exclude self-loop
84
85 if sum(outgoing) == 0
86 % No outgoing transitions except possibly self-loop
87 % Check if there's routing defined for this node
88 hasRouting = false;
89 for r = 1:K
90 for s = 1:K
91 if ~isempty(P{r,s})
92 [nRows, ~] = size(P{r,s});
93 if idx <= nRows && any(P{r,s}(idx,:) > 0)
94 hasRouting = true;
95 break;
96 end
97 end
98 end
99 if hasRouting
100 break;
101 end
102 end
103
104 if hasRouting
105 % This station has routing but only to itself - it's absorbing
106 info.absorbingStations{end+1} = nodeNames{idx};
107 end
108 end
109end
110
111% Identify transient stations (stations in transient SCCs)
112for i = 1:numSCCs
113 if ~isrec(i)
114 % This SCC is transient
115 sccNodes = find(scc == i);
116 for nodeIdx = sccNodes
117 if ismember(nodeIdx, stationIdxs)
118 nodeName = nodeNames{nodeIdx};
119 if ~ismember(nodeName, info.absorbingStations)
120 info.transientStations{end+1} = nodeName;
121 end
122 end
123 end
124 end
125end
126
127% The network is ergodic if:
128% 1. There are no absorbing stations (other than Sink for open networks)
129% 2. There is only one recurrent SCC that contains all stations
130% (excluding Source/Sink for open networks)
131
132% Check for Sink node (which is legitimately absorbing in open networks)
133sinkIdx = self.getIndexSinkNode();
134sourceIdx = self.getIndexSourceNode();
135
136% Remove Sink from absorbing list if present (it's expected to be absorbing)
137if ~isempty(sinkIdx) && sinkIdx > 0
138 sinkName = nodeNames{sinkIdx};
139 info.absorbingStations = setdiff(info.absorbingStations, {sinkName});
140end
141
142% Count recurrent SCCs (excluding Source/Sink nodes)
143recurrentSCCs = find(isrec);
144numRecurrentStations = 0;
145for i = recurrentSCCs
146 sccNodes = find(scc == i);
147 for nodeIdx = sccNodes
148 if ismember(nodeIdx, stationIdxs)
149 % Exclude source and sink
150 if (isempty(sourceIdx) || nodeIdx ~= sourceIdx) && ...
151 (isempty(sinkIdx) || nodeIdx ~= sinkIdx)
152 numRecurrentStations = numRecurrentStations + 1;
153 end
154 end
155 end
156end
157
158% Determine ergodicity
159hasAbsorbingStations = ~isempty(info.absorbingStations);
160hasMultipleRecurrentSCCs = sum(isrec) > 1;
161
162% For closed networks: should have exactly 1 recurrent SCC containing all stations
163% For open networks: Sink is allowed to be absorbing
164if hasAbsorbingStations || hasMultipleRecurrentSCCs
165 isErg = false;
166 info.isReducible = true;
167else
168 isErg = true;
169 info.isReducible = false;
170end
171
172end