LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
ctmc_solve_reducible_blkdecomp.m
1function [pi,pis,pi0,scc,isrec] = ctmc_solve_reducible_blkdecomp(Q,pi0,options)
2% [PI,PIS,PI0,SCC,ISREC] = CTMC_SOLVE_REDUCIBLE_BLKDECOMP(Q, PI0, OPTIONS)
3%
4% Compute limiting distribution for a CTMC with reducible generator Q
5% using direct block decomposition on the generator matrix.
6%
7% Algorithm:
8% 1. Decompose states into transient and recurrent classes via SCC
9% 2. For transient states: solve n * Q_tt = -p0_t for expected sojourn
10% 3. Compute hitting probabilities: h = n * Q_ta + p0_r
11% 4. For each recurrent class: solve pi_c * Q_cc = 0, scale by hitting prob
12%
13% Input:
14% Q: infinitesimal generator matrix
15% pi0: initial distribution vector, set to [] if not available
16% options: struct where options.tol sets the tolerance
17%
18% Output:
19% pi: limiting distribution (1 x N)
20% - For an ergodic CTMC, this is the unique limiting distribution.
21% - For a reducible CTMC:
22% - if there is a single transient SCC then this is the limiting
23% distribution when starting uniformly within it.
24% - otherwise pi is the weighted average of pis rows.
25% pis: limiting distribution given initialization in a single SCC (numSCC x N)
26% pi0: starting distribution for each row of pis (numSCC x N)
27% scc: mapping of each state of Q to its SCC index
28% isrec: element i is true if SCC i is recurrent
29
30N = size(Q, 1);
31
32if nargin < 3
33 options = struct('tol', 1e-12);
34end
35
36if nargin < 2
37 pin = [];
38else
39 pin = pi0;
40end
41
42% Ensure valid generator
43Q = ctmc_makeinfgen(Q);
44
45% Build adjacency from off-diagonal positive entries
46Adj = Q;
47Adj(1:N+1:end) = 0;
48[scc, isrec] = stronglyconncomp(Adj > 0);
49numSCC = max(scc);
50
51% Irreducible case: use standard solver
52if numSCC == 1
53 pi = ctmc_solve(Q, struct('force',true));
54 pis = pi;
55 pi0 = [];
56 return
57end
58
59% Build SCC index sets
60scc_idx = cell(1, numSCC);
61for i = 1:numSCC
62 scc_idx{i} = find(scc == i);
63end
64
65% Classify SCCs
66trans_scc_ids = find(~isrec);
67rec_scc_ids = find(isrec);
68
69% Gather ordered state indices
70trans_states = [];
71for i = trans_scc_ids
72 trans_states = [trans_states, scc_idx{i}];
73end
74trans_states = sort(trans_states);
75
76rec_states = [];
77for i = rec_scc_ids
78 rec_states = [rec_states, scc_idx{i}];
79end
80rec_states = sort(rec_states);
81
82nt = length(trans_states);
83nr = length(rec_states);
84
85% Extract Q sub-blocks (only when transient states exist)
86if nt > 0
87 Q_tt = Q(trans_states, trans_states);
88 Q_ta = Q(trans_states, rec_states);
89end
90
91% Compute per-SCC limiting distributions
92pis = zeros(numSCC, N);
93pi0 = zeros(numSCC, N);
94
95for s = 1:numSCC
96 % Starting distribution: uniform within SCC s
97 p0 = zeros(1, N);
98 p0(scc_idx{s}) = 1 / length(scc_idx{s});
99 pi0(s, :) = p0;
100
101 % Compute absorption probabilities into recurrent states
102 hit = zeros(1, nr);
103 if nt > 0
104 p0_t = p0(trans_states);
105 if any(abs(p0_t) > 0)
106 % Solve n * Q_tt = -p0_t for expected sojourn in transient states
107 % Q_tt is non-singular (Hurwitz) for transient states.
108 % Above the dispatch threshold the transient block is what the direct
109 % factorization cannot hold; it remains the fallback.
110 sojourn = [];
111 if nt > 6000
112 [xg,gflag] = ctmc_gmres(Q_tt', (-p0_t(:)));
113 if gflag == 0
114 sojourn = xg';
115 end
116 end
117 if isempty(sojourn)
118 sojourn = (-p0_t) / Q_tt;
119 end
120 hit = sojourn * Q_ta;
121 end
122 end
123
124 % Add initial mass already in recurrent states
125 hit = hit + p0(rec_states);
126
127 % Solve steady-state per recurrent class, scaled by hitting probability
128 for c = rec_scc_ids
129 idx_c = scc_idx{c};
130 [~, loc] = ismember(idx_c, rec_states);
131 reachprob = sum(hit(loc));
132 if reachprob < 1e-15
133 continue
134 end
135 if length(idx_c) == 1
136 % Absorbing state: hitting probability IS the final probability
137 pis(s, idx_c) = reachprob;
138 else
139 % Solve pi_c * Q_cc = 0 within this recurrent class
140 pi_c = ctmc_solve(Q(idx_c, idx_c), struct('force',true));
141 pis(s, idx_c) = pi_c * reachprob;
142 end
143 end
144end
145
146% Compute initial SCC probabilities for weighted average
147if isempty(pin)
148 pinl = ones(1, numSCC);
149 % Zero out SCCs containing states with zero column sums (no incoming)
150 col_sums = sum(abs(Q), 1);
151 for j = find(col_sums < 1e-12)
152 pinl(scc(j)) = 0;
153 end
154 if sum(pinl) > 0
155 pinl = pinl / sum(pinl);
156 else
157 pinl = ones(1, numSCC) / numSCC;
158 end
159else
160 pinl = zeros(1, numSCC);
161 for i = 1:numSCC
162 pinl(i) = sum(pin(scc_idx{i}));
163 end
164end
165
166% Weighted average over starting SCCs
167pi = zeros(1, N);
168for i = 1:numSCC
169 if pinl(i) > 0
170 pi = pi + pis(i, :) * pinl(i);
171 end
172end
173
174% Special case: single transient SCC without explicit initial distribution
175if isscalar(trans_scc_ids) && isempty(pin)
176 pi = pis(trans_scc_ids, :);
177end
178
179% Normalize
180total = sum(pi);
181if total > 0
182 pi = pi / total;
183end
184
185end
Definition Station.m:245