1function [isErg, info] = isRoutingErgodic(self,
P)
2% [ISERG, INFO] = ISROUTINGERGODIC()
3% [ISERG, INFO] = ISROUTINGERGODIC(
P)
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.
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.
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().
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
27% Copyright (c) 2012-2026, Imperial College London
31info.absorbingStations = {};
32info.transientStations = {};
34info.isReducible =
false;
36% Get the routing matrix
if not provided
37if nargin < 2 || isempty(
P)
38 P = self.getLinkedRoutingMatrix();
41 % No routing defined yet
46K = self.getNumberOfClasses;
47I = self.getNumberOfNodes;
48nodeNames = self.getNodeNames;
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);
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);
63 % Matrix is larger than expected (includes ClassSwitch nodes)
64 adjMatrix = adjMatrix + (P{r,s}(1:I, 1:I) > 0);
69adjMatrix = adjMatrix > 0; % Convert to logical
71% Find strongly connected components
72[scc, isrec] = stronglyconncomp(adjMatrix);
74info.numSCCs = numSCCs;
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();
81 % Check if this station has any outgoing transitions to other nodes
82 outgoing = adjMatrix(idx, :);
83 outgoing(idx) = 0; % Exclude self-loop
86 % No outgoing transitions except possibly self-loop
87 % Check if there's routing defined
for this node
92 [nRows, ~] = size(
P{r,s});
93 if idx <= nRows && any(
P{r,s}(idx,:) > 0)
105 % This station has routing but only to itself - it's absorbing
106 info.absorbingStations{end+1} = nodeNames{idx};
111% Identify transient stations (stations in transient SCCs)
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;
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)
132% Check
for Sink node (which
is legitimately absorbing in open networks)
133sinkIdx = self.getIndexSinkNode();
134sourceIdx = self.getIndexSourceNode();
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});
142% Count recurrent SCCs (excluding Source/Sink nodes)
143recurrentSCCs = find(isrec);
144numRecurrentStations = 0;
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;
158% Determine ergodicity
159hasAbsorbingStations = ~isempty(info.absorbingStations);
160hasMultipleRecurrentSCCs = sum(isrec) > 1;
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
166 info.isReducible = true;
169 info.isReducible = false;