LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_ssa_nrm_OMR.m
1function [pi, outspace, depRates, sn] = solver_ssa_nrm(sn, options)
2% SOLVER_SSA_NRM Steady‑state analysis via the Next‑Reaction Method (SSA)
3%
4% [PI, SSQ, ARVRATES, DEPRATES, SN] = SOLVER_SSA_NRM(SN, OPTIONS)
5% runs a stochastic simulation of the queueing network described in SN
6% for OPTIONS.samples reaction firings using Gibson & Bruck's
7% Next‑Reaction Method. During the run it:
8% • observes every distinct global state visited and the time spent in it;
9% • accumulates the per‑state propensities of **all** enabled reactions,
10% including *self‑loops* (service completions routed back to the same
11% queue).
12%
13% Outputs
14% PI – 1×S vector of empirical steady‑state probabilities
15% (sojourn‑time fractions) for the S unique states;
16% SSQ – S×(M·R) matrix listing those states row‑by‑row in the
17% flattened (station, class) order;
18% DEPRATES – S×(M·R) matrix of total departure rates from each queue
19% in the corresponding state;
20% SN – (Possibly updated) network structure.
21%
22% See also NEXT_REACTION_METHOD.
23
24% ---------------------------------------------------------------------
25% Parameters & shorthands
26% ---------------------------------------------------------------------
27samples = options.samples;
28R = sn.nclasses;
29I = sn.nnodes;
30state = sn.state;
31% ---------------------------------------------------------------------
32% Stoichiometry & reaction mapping (self‑loops included) ----------------
33% ---------------------------------------------------------------------
34S = zeros(0, I*R); % will transpose at the end
35fromIdx = [];
36toIdx = [];
37fromIR = [];
38
39% this is currently M^2*R^2, it can be lowered to M*R decoupling the
40% routing
41k = 0;
42for ind = 1:I
43 for r = 1:R
44 k = k + 1;
45 fromIR(k,:) = [ind, r];
46 fromIdx(k) = (ind-1)*R + r;
47 probIR{k} = [];
48 toIdx{k} = [];
49 Srow = zeros(1, I*R); % build stoichiometry row
50 if sn.isslc(r)
51 Srow(fromIdx(k)) = -Inf;
52 else
53 Srow(fromIdx(k)) = -1;
54 for jnd = 1:I
55 for s = 1:R
56 if sn.rtnodes((ind-1)*R+r, (jnd-1)*R+s) > 0
57 toIdx{k}(end+1) = (jnd-1)*R + s;
58 p = sn.rtnodes((ind-1)*R+r, (jnd-1)*R+s);
59 probIR{k}(end+1) = p;
60 Srow((jnd-1)*R + s) = Srow((jnd-1)*R + s) + p;
61 end
62 end
63 end
64 end
65 S(k,:) = Srow;
66 end
67end
68S = S.'; % states × reactions
69
70% ---------------------------------------------------------------------
71% Initial state vector --------------------------------------------------
72% ---------------------------------------------------------------------
73nvec0 = zeros(I*R,1); % initial state (aggregate state)
74for ind=1:I
75 if sn.isstateful(ind)
76 [~,nir] = State.toMarginalAggr(sn, ind, state{sn.nodeToStateful(ind)});
77 for r = 1:R
78 if isinf(nir(r))
79 if sn.nodetype(ind) == NodeType.Source
80 nir(r) = 1;
81 else
82 line_error(mfilename, 'Infinite population error.');
83 end
84 end
85 nvec0((ind-1)*R + r,1) = nir(r);
86 end
87 end
88end
89
90mi = zeros(I,1);
91rates = zeros(I,R);
92for ind=1:I
93 if sn.isstation(ind)
94 for r=1:R
95 ist = sn.nodeToStation(ind);
96 muir = sn.rates(ist,r);
97 if ~isnan(muir)
98 rates(ind,r) = muir;
99 end
100 mi(ind,1) = sn.nservers(ist);
101 end
102 else
103 for r=1:R
104 rates(ind,r) = GlobalConstants.Immediate;
105 mi(ind,1) = GlobalConstants.MaxInt;
106 end
107 end
108 mi(isinf(mi)) = GlobalConstants.MaxInt;
109end
110
111% Propensity function ---------------------------------------------------
112epstol = GlobalConstants.Zero;
113a = {};
114for j=1:length(fromIdx)
115 if sn.isstation(fromIR(j,1))
116 switch sn.sched(sn.nodeToStation(fromIR(j,1)))
117 case SchedStrategy.EXT
118 a{j} = @(X) rates(fromIR(j,1), fromIR(j,2));
119 case SchedStrategy.INF
120 a{j} = @(X) rates(fromIR(j,1), fromIR(j,2)) * X(fromIdx(j));
121 case SchedStrategy.PS
122 if R == 1 % single class
123 a{j} = @(X) rates(fromIR(j,1), fromIR(j,2)) * min( mi(fromIR(j,1)), X(fromIdx(j)));
124 else
125 a{j} = @(X) rates(fromIR(j,1), fromIR(j,2)) * ( X(fromIdx(j)) ./ ...
126 (epstol+sum( X(((fromIR(j,1)-1)*R + 1):((fromIR(j,1)-1)*R + R)) ) )) * ...
127 min( mi(fromIR(j,1)), ...
128 (epstol+sum( X(((fromIR(j,1)-1)*R + 1):((fromIR(j,1)-1)*R + R)) ) ));
129 end
130 end
131 else
132 a{j} = @(X) rates(fromIR(j,1), fromIR(j,2)) * min(1, X(fromIdx(j)));
133 end
134end
135
136% Propensity functions dependencies -----------------------------------
137D = cell(1,size(S,2));
138for k=1:size(D,2)
139 J = find(S(:,k))'; % set of state variables affected by reaction k
140 vecd = [];
141 for j=1:length(J)
142 % (ind-1)*R + r
143 pos = J(j);
144 r = mod(pos-1, R) + 1;
145 ind = ((pos-r)/R) + 1;
146 vecd(end+1:end+R) = ((ind-1)*R + 1) : (ind*R);
147 end
148 % vecd now contains all state variables affected by the firing of
149 % reaction k. We now find the propensity functions that depend
150 % on those variables
151 if ~isempty(vecd)
152 vecd = unique(vecd);
153 vecs = [];
154 for j=1:length(vecd)
155 vecs = [vecs,find(S(vecd(j),:)<0)];
156 end
157 D{k} = unique(vecs);
158 else
159 D{k} = [];
160 end
161end
162
163% Having accounted for them in D, we can now remove self-loops markings
164S(isinf(S))=0;
165% ---------------------------------------------------------------------
166% Run SSA/NRM -----------------------------------------------------------
167% ---------------------------------------------------------------------
168if false %snIsClosedModel(sn)
169 % mixed-radix hashing
170 reactCache = containers.Map('KeyType','uint64','ValueType','any');
171 njobs = sn.njobs;
172 mixedradix = [cumprod(repmat(1+njobs,1,I))];
173 mixedradix = [1,mixedradix(1:end-1)];
174 hashfun = @(v) uint64(mixedradix*v(:));
175else
176 % buffer size unbounded so use string
177 reactCache = containers.Map('KeyType','char','ValueType','any');
178 hashfun = @(v) mat2str(v(:)');
179end
180[t, nvecsim, ~, ~] = next_reaction_method(S, D, a, nvec0, samples, options, reactCache, hashfun);
181
182% ---------------------------------------------------------------------
183% Empirical state probabilities ----------------------------------------
184% ---------------------------------------------------------------------
185dt = diff(t);
186[outspace, ~, ic] = unique(nvecsim(:,1:end-1).', 'rows', 'stable');
187timeAccum = accumarray(ic, dt);
188pi = timeAccum / sum(timeAccum);
189
190% ---------------------------------------------------------------------
191% Per‑state arrival / departure rates (self‑loops counted) -------------
192% ---------------------------------------------------------------------
193numStates = size(outspace,1);
194depRates = zeros(numStates, I*R);
195
196for st = 1:numStates
197 a_state = reactCache(hashfun(outspace(st,:)'));
198 for j = 1:length(fromIdx)
199 depRates(st, fromIdx(j)) = depRates(st, fromIdx(j)) + a_state(j);
200 end
201end
202end % solver_ssa_nrm
203
204% ======================================================================
205% Next-Reaction Method core --------------------------------------------
206% ======================================================================
207function [t, nvec, kfires, rfires] = next_reaction_method(S, D, a, nvec0, samples, options, reactcache, hashfun)
208numReactions = size(S,2);
209rand_pool_size = 1e7;
210
211% when a reaction fires, this matrix helps selecting the probability that a
212% particular routing or phase is selected as a result ------------------
213P = S; P(P<0)=P(P<0)+1';
214fromIdx = cell(numReactions,1);
215toIdx = cell(numReactions,1);
216cdfVec = cell(numReactions,1);
217for r=1:numReactions
218 nnzP(r) = nnz(P(:,r));
219 if nnzP(r)>1
220 fromIdx{r} = find(S(:,r)<0);
221 toIdx{r} = find(P(:,r));
222 cdfVec{r} = cumsum(P(toIdx{r},r));
223 end
224end
225
226% initialise Gillespie clocks ------------------------------------------
227t = 0;
228for k=1:size(S,2)
229 Ak(k) = a{k}(nvec0);
230end
231nvec = nvec0;
232key = hashfun(nvec);
233reactcache(key) = Ak; % cache first state's propensities
234Pk = -log(rand(1,numReactions));
235Tk = zeros(1,numReactions);
236
237tau = (Pk - Tk) ./ Ak;
238
239% logs -----------------------------------------------------------------
240tout = zeros(samples,1);
241nvecout = zeros(length(nvec0),samples);
242kfires = zeros(samples,1);
243rfires = zeros(samples,1);
244
245n = 1;
246while n <= samples
247 [dt, kfire] = min(tau);
248 kfires(n) = kfire;
249 if isinf(dt), line_error(mfilename,'Deadlock. Quitting nrm method.'); end
250
251 t = t + dt;
252
253 % update aggregate state
254 if nnzP(kfire)>1
255 r = 1+find(rand>=cdfVec{kfire},1);
256 if isempty(r)
257 r = 1;
258 end
259 rfires(n) = r;
260 nvec(fromIdx{kfire}) = nvec(fromIdx{kfire}) - 1;
261 nvec(toIdx{kfire}(r)) = nvec(toIdx{kfire}(r)) + 1;
262 else
263 nvec = nvec + S(:,kfire); % zero change for self-loops
264 end
265
266 Tk = Tk + Ak * dt;
267
268 % update rates for all reactions dependent on the last fired reaction
269 for k=D{kfire}
270 Ak(k) = a{k}(nvec);
271 end
272
273 key = hashfun(nvec);
274 if ~isKey(reactcache,key)
275 reactcache(key) = Ak; % store propensities of new state
276 end
277
278 % maintain random number pool
279 n_mod = mod(n,rand_pool_size);
280 if n_mod == 1
281 rand_pool = rand(1+min(rand_pool_size, samples-n),1);
282 end
283
284 % update clocks
285 Pk(kfire) = Pk(kfire) - log(rand_pool(n_mod));
286 tau = (Pk - Tk) ./ Ak;
287 tau(Ak==0) = inf;
288
289 % update measures
290 tout(n) = t;
291 nvecout(:,n) = nvec;
292
293 % do not count immediate events
294 n = n + 1;
295 print_progress(options, n);
296end
297% Print newline after progress counter
298if isfield(options,'verbose') && options.verbose
299 line_printf('\n');
300end
301
302t = [0; tout];
303nvec = [nvec0, nvecout];
304
305 function print_progress(opt, samples_collected)
306 if ~isfield(opt,'verbose') || ~opt.verbose, return; end
307 if samples_collected == 1e3
308 line_printf('\nSSA samples: %8d', samples_collected);
309 elseif opt.verbose == 2
310 if samples_collected == 0
311 line_printf('\nSSA samples: %9d', samples_collected);
312 else
313 line_printf('\b\b\b\b\b\b\b\b\b%9d', samples_collected);
314 end
315 elseif mod(samples_collected,1e3)==0 || opt.verbose == 2
316 line_printf('\b\b\b\b\b\b\b\b\b%9d', samples_collected);
317 end
318 end
319end % next_reaction_method