LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_ssa_nrm_OM2R2.m
1function [pi, SSq, depRates, sn] = solver_ssa_nrm_OM2R2(sn, options)
2% SOLVER_SSA_NRM Steady‑state analysis via Gibson & Bruck's Next‑Reaction
3% Method (SSA) as modified in Anderson's, THE JOURNAL OF CHEMICAL PHYSICS
4% 127, 214107, 2007.
5
6% ---------------------------------------------------------------------
7% Parameters & shorthands
8% ---------------------------------------------------------------------
9samples = options.samples;
10R = sn.nclasses;
11I = sn.nnodes;
12state = sn.state;
13% ---------------------------------------------------------------------
14% Stoichiometry & reaction mapping (self‑loops included) ----------------
15% ---------------------------------------------------------------------
16S = zeros(0, I*R); % will transpose at the end
17fromIdx = [];
18toIdx = [];
19rateIR = [];
20
21% This is currently M^2*R^2, it may be lowered to M*R decoupling the
22% routing from the departure rate, but then the caching for the rates in
23% each state becomes less efficient as a given state in M*R can yield
24% multiple outgoing rates depending on the routing target. Also, this
25% would still require a M^2*R^2 loop at the end to compute arvRates.
26k = 0;
27for ind = 1:I
28 for r = 1:R
29 for jnd = 1:I
30 for s = 1:R
31 if sn.rtnodes((ind-1)*R+r, (jnd-1)*R+s) == 0, continue; end
32 k = k + 1;
33 fromIdx(k) = (ind-1)*R + r;
34 toIdx(k) = (jnd-1)*R + s;
35 rateIR(k,:) = [ind, r];
36 probIR(k) = sn.rtnodes((ind-1)*R+r, (jnd-1)*R+s);
37
38 % build stoichiometry row
39 Srow = zeros(1, I*R);
40 if fromIdx(k) ~= toIdx(k)
41 Srow(fromIdx(k)) = -1;
42 Srow(toIdx(k)) = 1;
43 else % mark self-loops
44 Srow(fromIdx(k)) = -Inf;
45 end
46 S(k,:) = Srow;
47 end
48 end
49 end
50end
51S = S.'; % states × reactions
52
53% ---------------------------------------------------------------------
54% Initial state vector --------------------------------------------------
55% ---------------------------------------------------------------------
56nvec0 = zeros(I*R,1); % initial state (aggregate state)
57for ind=1:I
58 if sn.isstateful(ind)
59 [~,nir] = State.toMarginalAggr(sn, ind, state{sn.nodeToStateful(ind)});
60 for r = 1:R
61 if isinf(nir(r))
62 if sn.nodetype(ind) == NodeType.Source
63 nir(r) = 1;
64 else
65 nir(r) = GlobalConstants.MaxInt;
66 end
67 end
68 nvec0((ind-1)*R + r,1) = nir(r);
69 end
70 end
71end
72
73mi = zeros(I,1);
74rates = zeros(I,R);
75for ind=1:I
76 if sn.isstation(ind)
77 for r=1:R
78 ist = sn.nodeToStation(ind);
79 rir = sn.rates(ist,r);
80 if ~isnan(rir)
81 rates(ind,r) = rir;
82 end
83 mi(ind,1) = sn.nservers(ist);
84 end
85 else
86 for r=1:R
87 rates(ind,r) = GlobalConstants.Immediate;
88 mi(ind,1) = GlobalConstants.MaxInt;
89 end
90 end
91 mi(isinf(mi)) = GlobalConstants.MaxInt;
92end
93
94% Propensity function ---------------------------------------------------
95epstol = GlobalConstants.Zero;
96a = {};
97for k=1:length(fromIdx)
98 if sn.isstation(rateIR(k,1))
99 switch sn.sched(sn.nodeToStation(rateIR(k,1)))
100 case SchedStrategy.EXT
101 a{k} = @(X) rates(rateIR(k,1), rateIR(k,2)) * ...
102 probIR(k);
103 case SchedStrategy.INF
104 a{k} = @(X) rates(rateIR(k,1), rateIR(k,2)) * ...
105 probIR(k) * X(fromIdx(k));
106 case SchedStrategy.PS
107 if R == 1 % single class
108 a{k} = @(X) rates(rateIR(k,1), rateIR(k,2)) * ...
109 probIR(k) * ...
110 min( mi(rateIR(k,1)), X(fromIdx(k)));
111 else
112 a{k} = @(X) rates(rateIR(k,1), rateIR(k,2)) * ...
113 probIR(k) * ...
114 ( X(fromIdx(k)) ./ ...
115 (epstol+sum( X(((rateIR(k,1)-1)*R + 1):((rateIR(k,1)-1)*R + R)) ) )) * ...
116 min( mi(rateIR(k,1)), ...
117 (epstol+sum( X(((rateIR(k,1)-1)*R + 1):((rateIR(k,1)-1)*R + R)) ) ));
118 end
119 end
120 else
121 a{k} = @(X) rates(rateIR(k,1), rateIR(k,2)) * ...
122 probIR(k) * min(1, X(fromIdx(k)));
123 end
124end
125
126% Propensity functions dependencies -----------------------------------
127D = cell(1,size(S,2));
128for k=1:size(D,2)
129 J = find(S(:,k))'; % set of state variables affected by reaction k
130 vecd = [];
131 for j=1:length(J)
132 % (ind-1)*R + r
133 pos = J(j);
134 r = mod(pos-1, R) + 1;
135 ind = ((pos-r)/R) + 1;
136 vecd(end+1:end+R) = ((ind-1)*R + 1) : (ind*R);
137 end
138 % vecd now contains all state variables affected by the firing of
139 % reaction k. We now find the propensity functions that depend
140 % on those variables
141 if ~isempty(vecd)
142 vecd = unique(vecd);
143 vecs = [];
144 for j=1:length(vecd)
145 vecs = [vecs,find(S(vecd(j),:)<=-1)];
146 end
147 D{k} = unique(vecs);
148 else
149 D{k} = [];
150 end
151end
152% Having accounted for them in D, we can now remove self-loops markings
153S(isinf(S))=0;
154% ---------------------------------------------------------------------
155% Run SSA/NRM -----------------------------------------------------------
156% ---------------------------------------------------------------------
157if false %snIsClosedModel(sn)
158 % mixed-radix hashing
159 reactCache = containers.Map('KeyType','uint64','ValueType','any');
160 njobs = sn.njobs;
161 mixedradix = [cumprod(repmat(1+njobs,1,I))];
162 mixedradix = [1,mixedradix(1:end-1)];
163 hashfun = @(v) uint64(mixedradix*v(:));
164else
165 % buffer size unbounded so use string
166 reactCache = containers.Map('KeyType','char','ValueType','any');
167 hashfun = @(v) mat2str(v(:)');
168end
169[t, X] = next_reaction_method(S, D, a, nvec0, samples, options, reactCache, hashfun);
170
171% ---------------------------------------------------------------------
172% Empirical state probabilities ----------------------------------------
173% ---------------------------------------------------------------------
174dt = diff(t);
175[SSq, ~, ic] = unique(X(:,1:end-1).', 'rows', 'stable');
176timeAccum = accumarray(ic, dt);
177pi = timeAccum / sum(timeAccum);
178
179% ---------------------------------------------------------------------
180% Per‑state arrival / departure rates (self‑loops counted) -------------
181% ---------------------------------------------------------------------
182numStates = size(SSq,1);
183arvRates = zeros(numStates, I*R);
184depRates = zeros(numStates, I*R);
185
186for k = 1:numStates
187 a_state = reactCache(hashfun(SSq(k,:)'));
188
189 for ia = 1:length(fromIdx)
190 depRates(k, fromIdx(ia)) = depRates(k, fromIdx(ia)) + a_state(ia);
191 %arvRates(k, toIdx(ia)) = arvRates(k, toIdx(ia)) + a_state(ia);
192 end
193end
194end % solver_ssa_nrm
195
196% ======================================================================
197% Next-Reaction Method core --------------------------------------------
198% ======================================================================
199function [t, nvecout, kfire] = next_reaction_method(S, D, a, nvec0, samples, options, reactcache, hashfun)
200numReactions = size(S,2);
201rand_pool_size = 1e7;
202
203% initialise Gillespie clocks -----------------------------------------
204t = 0;
205Ak = zeros(1,size(S,2));
206for k=1:size(S,2)
207 Ak(k) = a{k}(nvec0);
208end
209nvec = nvec0;
210key = hashfun(nvec);
211reactcache(key) = Ak; % cache first state's propensities
212Pk = -log(rand(1,numReactions));
213Tk = zeros(1,numReactions);
214
215tau = (Pk - Tk) ./ Ak;
216
217% logs -----------------------------------------------------------------
218tout = zeros(samples,1);
219nvecsim = zeros(length(nvec0),samples);
220kfires = zeros(samples,1);
221
222n = 1;
223while n <= samples
224 [dt, kfire] = min(tau);
225 kfires(n) = kfire; % record reaction that fired
226 if isinf(dt), line_error(mfilename,'Deadlock. Quitting nrm method.'); end
227
228 t = t + dt;
229 % update aggregate state
230 nvec = nvec + S(:,kfire); % zero change for self-loops
231 Tk = Tk + Ak * dt;
232
233 % update rates for all reactions dependent on the last fired one
234 for k=D{kfire}
235 Ak(k) = a{k}(nvec);
236 end
237
238 key = hashfun(nvec);
239 if ~isKey(reactcache,key)
240 reactcache(key) = Ak; % store rates of new state
241 end
242
243 % maintain random number pool
244 n_mod = mod(n,rand_pool_size);
245 if n_mod == 1
246 rand_pool = rand(1+min(rand_pool_size, samples-n),1);
247 end
248
249 % update clocks
250 Pk(kfire) = Pk(kfire) - log(rand_pool(n_mod));
251 tau = (Pk - Tk) ./ Ak;
252 tau(Ak==0) = inf;
253
254 % update measures
255 tout(n) = t;
256 nvecsim(:,n) = nvec;
257
258 % do not count immediate events
259 n = n + 1;
260 print_progress(options, n);
261end
262% Print newline after progress counter
263if isfield(options,'verbose') && options.verbose
264 line_printf('\n');
265end
266
267t = [0; tout];
268nvecout = [nvec0, nvecsim];
269
270 function print_progress(opt, samples_collected)
271 if ~isfield(opt,'verbose') || ~opt.verbose, return; end
272 if samples_collected == 1e3
273 line_printf('\nSSA samples: %8d', samples_collected);
274 elseif opt.verbose == 2
275 if samples_collected == 0
276 line_printf('\nSSA samples: %9d', samples_collected);
277 else
278 line_printf('\b\b\b\b\b\b\b\b\b%9d', samples_collected);
279 end
280 elseif mod(samples_collected,1e3)==0 || opt.verbose == 2
281 line_printf('\b\b\b\b\b\b\b\b\b%9d', samples_collected);
282 end
283 end
284end % next_reaction_method