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