LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_ctmc.m
1function [Q,stateSpace,stateSpaceAggr,Dfilt,arvRates,depRates,sn]=solver_ctmc(sn,options)
2% [Q,SS,SSQ,DFILT,ARVRATES,DEPRATES,QN]=SOLVER_CTMC(QN,OPTIONS)
3%
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7%% generate state space
8%nnodes = sn.nnodes;
9nstateful = sn.nstateful;
10nclasses = sn.nclasses;
11sync = sn.sync;
12A = length(sync);
13csmask = sn.csmask;
14
15line_debug('CTMC solver starting: nstateful=%d, nclasses=%d, sync_events=%d', nstateful, nclasses, A);
16
17if ~isfield(options.config, 'hide_immediate')
18 options.config.hide_immediate = true;
19end
20
21if ~isfield(options.config, 'state_space_gen')
22 options.config.state_space_gen = 'default';
23end
24
25%% generate state spaces, detailed and aggregate
26switch options.config.state_space_gen
27 case 'reachable' % does not handle open models yet (no cutoff)
28 line_debug('Using reachable state space generation, calling ctmc_ssg_reachability');
29 [stateSpace, stateSpaceAggr, stateSpaceHashed,~,sn] = ctmc_ssg_reachability(sn,options);
30 case {'default','full'}
31 line_debug('Using full state space generation, calling ctmc_ssg');
32 [stateSpace, stateSpaceAggr, stateSpaceHashed,~,sn] = ctmc_ssg(sn,options);
33end
34
35line_debug('State space generated: %d states', size(stateSpaceHashed,1));
36
37%%
38Q = sparse(eye(size(stateSpaceHashed,1))); % the diagonal elements will be removed later
39Dfilt = cell(1,A);
40for a=1:A
41 Dfilt{a} = 0*Q;
42end
43local = sn.nnodes+1; % passive action
44
45% SPN code
46% Adj_t = zeros(size(SSh,1),size(SSh,1));
47% Adj_m = zeros(size(SSh,1),size(SSh,1));
48% if ~isempty(Adj) && ~isempty(ST)
49% edges = adj_to_mat(Adj);
50% end
51
52%% for all synchronizations
53for a=1:A
54 stateCell = cell(nstateful,1);
55 %sn.sync{a}.active{1}.print
56 for s=1:size(stateSpaceHashed,1)
57 %[a,s]
58 state = stateSpaceHashed(s,:);
59 % SPN code
60 % ustate = stateSpace(s,:);
61 % state_pn = [];
62 % for st=1:length(ustate)
63 % if ~isempty(sn.varsparam{st}) && isfield(sn.varsparam{st}, 'nodeToPlace')
64 % state_pn(sn.varsparam{st}.nodeToPlace) = ustate(st);
65 % end
66 % end
67
68 % update state cell array and SSq
69 for ind = 1:sn.nnodes
70 if sn.isstateful(ind)
71 isf = sn.nodeToStateful(ind);
72 stateCell{isf} = sn.space{isf}(state(isf),:);
73 % if sn.isstation(ind)
74 % ist = sn.nodeToStation(ind);
75 % [~,nir] = State.toMarginal(sn,ind,stateCell{isf});
76 % end
77 end
78 end
79 node_a = sync{a}.active{1}.node;
80 state_a = state(sn.nodeToStateful(node_a));
81 class_a = sync{a}.active{1}.class;
82 event_a = sync{a}.active{1}.event;
83 [new_state_a, rate_a] = State.afterEventHashed( sn, node_a, state_a, event_a, class_a);
84 % SPN code:
85 %[new_state_a, rate_a,~,trans_a, modes_a] = State.afterEventHashed( qn, node_a, state_a, event_a, class_a);
86
87 %% debugging block
88 % if true%options.verbose == 2
89 % line_printf('---\n');
90 % sync{a}.active{1}.print,
91 % end
92 %%
93 if new_state_a == -1 % hash not found
94 continue
95 end
96 for ia=1:length(new_state_a)
97 if rate_a(ia)>0
98 % SPN code:
99 %if rate_a(ia)>0 || modes_a(ia) > 0
100 node_p = sync{a}.passive{1}.node;
101 if node_p ~= local
102 % Skip if the active transition hash was not found
103 if new_state_a(ia) == -1
104 continue
105 end
106 state_p = state(sn.nodeToStateful(node_p));
107 class_p = sync{a}.passive{1}.class;
108 event_p = sync{a}.passive{1}.event;
109
110 % SPN code:
111 % enabled = 0;
112 % if ia <= length(trans_a)
113 % % check if other input places of the transition contains as many token as the multiplicity of the input arcs
114 % tr = trans_a(ia);
115 % mode = modes_a(ia);
116 % bmatrix = sn.varsparam{tr}.back(:,mode);
117 % inmatrix = sn.varsparam{tr}.inh(:,mode);
118 % enabled = all(state_pn >= bmatrix' & ~any(inmatrix'>0 & inmatrix' <= state_pn));
119 % end
120
121 %prob_sync_p = sync{a}.passive{1}.prob(state_a, state_p)
122 %if prob_sync_p > 0
123 %% debugging block
124 %if options.verbose == 2
125 % line_printf('---\n');
126 % sync{a}.active{1}.print,
127 % sync{a}.passive{1}.print
128 %end
129 %%
130 if node_p == node_a %self-loop
131 [new_state_p, ~, outprob_p] = State.afterEventHashed( sn, node_p, new_state_a(ia), event_p, class_p);
132 else % departure
133 [new_state_p, ~, outprob_p] = State.afterEventHashed( sn, node_p, state_p, event_p, class_p);
134 end
135 % SPN code:
136 % if node_p == node_a %self-loop
137 % [new_state_p, ~, outprob_p, trans_p, modes_p] = State.afterEventHashed( qn, node_p, new_state_a(ia), event_p, class_p);
138 % else % departure
139 % [new_state_p, ~, outprob_p, trans_p, modes_p] = State.afterEventHashed( qn, node_p, state_p, event_p, class_p);
140 % end
141 for ip=1:size(new_state_p,1)
142 if node_p ~= local
143 if new_state_p ~= -1
144 if sn.isstatedep(node_a,3)
145 newStateCell = stateCell;
146 newStateCell{sn.nodeToStateful(node_a)} = sn.space{sn.nodeToStateful(node_a)}(new_state_a(ia),:);
147 newStateCell{sn.nodeToStateful(node_p)} = sn.space{sn.nodeToStateful(node_p)}(new_state_p(ip),:);
148 prob_sync_p = sync{a}.passive{1}.prob(stateCell, newStateCell) * outprob_p(ip); %state-dependent
149 else
150 prob_sync_p = sync{a}.passive{1}.prob * outprob_p(ip);
151 end
152 else
153 prob_sync_p = 0;
154 end
155 end
156 if ~isempty(new_state_a(ia))
157 if node_p == local % local action
158 new_state = state;
159 new_state(sn.nodeToStateful(node_a)) = new_state_a(ia);
160 prob_sync_p = outprob_p(ip);
161 elseif ~isempty(new_state_p)
162 new_state = state;
163 new_state(sn.nodeToStateful(node_a)) = new_state_a(ia);
164 new_state(sn.nodeToStateful(node_p)) = new_state_p(ip);
165 end
166 % SPN code:
167 % if enabled
168 % ns = find(ismember(SSh(:,[sn.nodeToStateful(node_a),sn.nodeToStateful(node_p)]),[new_state_a(ia),new_state_p(ip)],'rows'));
169 % for ins=1:length(ns)
170 % if ns(ins) > 0 && ~isempty(trans_p)
171 % tr = trans_p(ip);
172 % mode = modes_p(ip);
173 % bmatrix = sn.varsparam{tr}.back(:,mode);
174 % fmatrix = sn.varsparam{tr}.forw(:,mode);
175 % cmatrix = fmatrix - bmatrix;
176 % if isequal(state_pn + cmatrix',SS(ns(ins),3:end))
177 % [ex_a,seq_a] = ST.search(state_pn');
178 % [ex_p,seq_p] = ST.search(SS(ns(ins),3:end)');
179 % if ex_a && ex_p && edges(seq_a, seq_p)
180 % Adj_m(s, ns(ins)) = modes_p(ip);
181 % Adj_t(s, ns(ins)) = trans_p(ip);
182 % % s,ns(ins)
183 % if ~isnan(rate_a(ia))
184 % if node_p < local && ~csmask(class_a, class_p) && rate_a(ia) * prob_sync_p >0 && (sn.nodetype(node_p)~=NodeType.Source)
185 % error('Error: state-dependent routing at node %d (%s) violates the class switching mask (node %d -> node %d, class %d -> class %d).', node_a, sn.nodenames{node_a}, node_a, node_p, class_a, class_p);
186 % end
187 % if size(Dfilt{a}) >= [s,ns(ins)] % check needed as D{a} is a sparse matrix
188 % Dfilt{a}(s,ns(ins)) = Dfilt{a}(s,ns(ins)) + rate_a(ia) * prob_sync_p;
189 % else
190 % Dfilt{a}(s,ns(ins)) = rate_a(ia) * prob_sync_p;
191 % end
192 % end
193 % end
194 % end
195 % end
196 % end
197 % else
198
199 ns = matchrow(stateSpaceHashed, new_state);
200 if ns>0
201 if ~isnan(rate_a)
202 if node_p < local && ~csmask(class_a, class_p) && rate_a(ia) * prob_sync_p >0 && (sn.nodetype(node_p)~=NodeType.Source)
203 line_error(mfilename,sprintf('Error: state-dependent routing at node %d (%s) violates the class switching mask (node %s -> node %s, class %s -> class %s).', node_a, sn.nodenames{node_a}, sn.nodenames{node_a}, sn.nodenames{node_p}, sn.classnames{class_a}, sn.classnames{class_p}));
204 end
205 if size(Dfilt{a}) >= [s,ns] % check needed as D{a} is a sparse matrix
206 Dfilt{a}(s,ns) = Dfilt{a}(s,ns) + rate_a(ia) * prob_sync_p;
207 else
208 Dfilt{a}(s,ns) = rate_a(ia) * prob_sync_p;
209 end
210 end
211 end
212 % SPN code:
213 % end
214 end
215 end
216 else % node_p == local
217 if ~isempty(new_state_a(ia))
218 new_state = state;
219 new_state(sn.nodeToStateful(node_a)) = new_state_a(ia);
220 prob_sync_p = 1;
221 ns = matchrow(stateSpaceHashed, new_state);
222 if ns>0
223 if ~isnan(rate_a)
224 if size(Dfilt{a}) >= [s,ns] % needed for sparse matrix
225 Dfilt{a}(s,ns) = Dfilt{a}(s,ns) + rate_a(ia) * prob_sync_p;
226 else
227 Dfilt{a}(s,ns) = rate_a(ia) * prob_sync_p;
228 end
229 end
230 end
231 end
232 end
233 end
234 end
235 end
236end
237
238for a=1:A
239 Q = Q + Dfilt{a};
240end
241
242%% for all global synchronizations (SPN support)
243if isfield(sn, 'gsync') && ~isempty(sn.gsync)
244 gsyncEvents = sn.gsync;
245 G = length(gsyncEvents);
246
247 % Track FIRE completion rates for arvRates/depRates
248 Dfilt_gsync_comp = cell(1, G);
249 for g = 1:G
250 Dfilt_gsync_comp{g} = sparse(size(stateSpaceHashed,1), size(stateSpaceHashed,1));
251 end
252
253 for g = 1:G
254 gind = gsyncEvents{g}.active{1}.node;
255 isf_transition = sn.nodeToStateful(gind);
256 nmodes_g = sn.nodeparam{gind}.nmodes;
257
258 for s = 1:size(stateSpaceHashed, 1)
259 state = stateSpaceHashed(s, :);
260
261 % Build glspace cell array from hashed state
262 glspace = cell(nstateful, 1);
263 for isf = 1:nstateful
264 glspace{isf} = sn.space{isf}(state(isf), :);
265 end
266
267 % Process event (both ENABLE and FIRE)
268 [outglspace, outrate, outprob] = State.afterGlobalEvent(sn, gind, glspace, gsyncEvents{g}, false);
269
270 if isempty(outrate)
271 continue;
272 end
273
274 for io = 1:length(outrate)
275 if outrate(io) == 0
276 continue;
277 end
278
279 % Build new hashed state
280 new_state = state;
281
282 % Hash transition's new state
283 if size(outglspace{isf_transition}, 1) < io
284 continue;
285 end
286 trans_state = outglspace{isf_transition}(io, :);
287 hash_t = matchrow(sn.space{isf_transition}, trans_state);
288 if hash_t <= 0, continue; end
289 new_state(isf_transition) = hash_t;
290
291 is_comp = false;
292 % For FIRE events: detect completion and update Places
293 if gsyncEvents{g}.active{1}.event == EventType.FIRE
294 orig_buf = glspace{isf_transition}(1:nmodes_g);
295 new_buf = trans_state(1:nmodes_g);
296 is_comp = any(new_buf > orig_buf);
297
298 if is_comp
299 for isf = 1:nstateful
300 if isf ~= isf_transition && ~isequal(glspace{isf}, outglspace{isf})
301 hash_p = matchrow(sn.space{isf}, outglspace{isf});
302 if hash_p <= 0, continue; end
303 new_state(isf) = hash_p;
304 end
305 end
306 end
307 end
308 % For ENABLE events: only Transition state changes, Places unchanged
309
310 ns = matchrow(stateSpaceHashed, new_state);
311 if ns > 0
312 prob_val = 1;
313 if ~isempty(outprob) && io <= length(outprob)
314 prob_val = outprob(io);
315 end
316 rate_val = outrate(io) * prob_val;
317 Q(s, ns) = Q(s, ns) + rate_val;
318 if is_comp
319 Dfilt_gsync_comp{g}(s, ns) = Dfilt_gsync_comp{g}(s, ns) + rate_val;
320 end
321 end
322 end
323 end
324 end
325end
326
327Q = Q - diag(diag(Q));
328%SolverCTMC.printInfGen(Q,stateSpace)
329%%
330arvRates = zeros(size(stateSpaceHashed,1),nstateful,nclasses);
331depRates = zeros(size(stateSpaceHashed,1),nstateful,nclasses);
332for a=1:A
333 % active
334 node_a = sync{a}.active{1}.node;
335 class_a = sync{a}.active{1}.class;
336 event_a = sync{a}.active{1}.event;
337 % passive
338 node_p = sync{a}.passive{1}.node;
339 class_p = sync{a}.passive{1}.class;
340 if event_a == EventType.DEP
341 node_a_sf = sn.nodeToStateful(node_a);
342 node_p_sf = sn.nodeToStateful(node_p);
343 for s=1:size(stateSpaceHashed,1)
344 depRates(s,node_a_sf,class_a) = depRates(s,node_a_sf,class_a) + sum(Dfilt{a}(s,:));
345 arvRates(s,node_p_sf,class_p) = arvRates(s,node_p_sf,class_p) + sum(Dfilt{a}(s,:));
346 end
347 end
348end
349
350%% Compute arrival/departure rates for gsync FIRE completion events
351if isfield(sn, 'gsync') && ~isempty(sn.gsync)
352 gsyncEvents = sn.gsync;
353 G = length(gsyncEvents);
354 for g = 1:G
355 if gsyncEvents{g}.active{1}.event == EventType.FIRE
356 for j = 1:length(gsyncEvents{g}.passive)
357 pev = gsyncEvents{g}.passive{j};
358 [pev_node, pev_class] = ind2sub([sn.nnodes, nclasses], pev.node);
359 if pev_node > sn.nnodes || ~sn.isstateful(pev_node)
360 continue;
361 end
362 pev_isf = sn.nodeToStateful(pev_node);
363 if pev.event == EventType.PRE
364 for s = 1:size(stateSpaceHashed, 1)
365 depRates(s, pev_isf, pev_class) = depRates(s, pev_isf, pev_class) + sum(Dfilt_gsync_comp{g}(s,:));
366 end
367 elseif pev.event == EventType.POST
368 for s = 1:size(stateSpaceHashed, 1)
369 arvRates(s, pev_isf, pev_class) = arvRates(s, pev_isf, pev_class) + sum(Dfilt_gsync_comp{g}(s,:));
370 end
371 end
372 end
373 end
374 end
375end
376
377zero_row = find(sum(Q,2)==0);
378zero_col = find(sum(Q,1)==0);
379
380%%
381% in case the last column of Q represent a state for a transient class, it
382% is possible that no transitions go back to it, although it is valid for
383% the system to be initialized in that state. So we need to fill-in the
384% zeros at the end.
385Q(:,end+1:end+(size(Q,1)-size(Q,2)))=0;
386Q(zero_row,zero_row) = -eye(length(zero_row)); % can this be replaced by []?
387Q(zero_col,zero_col) = -eye(length(zero_col));
388for a=1:A
389 Dfilt{a}(:,end+1:end+(size(Dfilt{a},1)-size(Dfilt{a},2)))=0;
390end
391
392if options.verbose == VerboseLevel.DEBUG || GlobalConstants.Verbose == VerboseLevel.DEBUG
393 SolverCTMC.printInfGen(Q,stateSpace);
394end
395Q = ctmc_makeinfgen(Q);
396%% now remove immediate transitions
397% we first determine states in stateful nodes where there is an immediate
398% job in the node
399
400if options.config.hide_immediate % if want to remove immediate transitions
401 imm = [];
402 for ind = 1:sn.nnodes
403 % Only consider non-station stateful nodes that are not Caches
404 % Cache nodes need their immediate transitions to compute hit/miss rates
405 if sn.isstateful(ind) && ~sn.isstation(ind) && sn.nodetype(ind) ~= NodeType.Cache && sn.nodetype(ind) ~= NodeType.Transition
406 isf = sn.nodeToStateful(ind);
407 imm_st = find(sum(sn.space{isf}(:,1:nclasses),2)>0);
408 imm = [imm; find(arrayfun(@(a) any(a==imm_st),stateSpaceHashed(:,isf)))];
409 end
410 end
411 % Transition immediate states: states where any ENABLE event would change the state
412 if isfield(sn, 'gsync') && ~isempty(sn.gsync)
413 gsyncEvents_sc = sn.gsync;
414 for s = 1:size(stateSpaceHashed, 1)
415 if any(s == imm)
416 continue; % already marked
417 end
418 state_sc = stateSpaceHashed(s, :);
419 glspace_sc = cell(nstateful, 1);
420 for isf = 1:nstateful
421 glspace_sc{isf} = sn.space{isf}(state_sc(isf), :);
422 end
423 for g = 1:length(gsyncEvents_sc)
424 if gsyncEvents_sc{g}.active{1}.event == EventType.ENABLE
425 gind_sc = gsyncEvents_sc{g}.active{1}.node;
426 [~, outrate_sc, ~] = State.afterGlobalEvent(sn, gind_sc, glspace_sc, gsyncEvents_sc{g}, false);
427 if ~isempty(outrate_sc)
428 imm = [imm; s]; %#ok<AGROW>
429 break;
430 end
431 end
432 end
433 end
434 end
435 imm = unique(imm);
436 nonimm = setdiff(1:size(Q,1),imm);
437 stateSpace(imm,:) = [];
438 stateSpaceAggr(imm,:) = [];
439 % full(Q)
440 [Q,~,Q12,~,Q22] = ctmc_stochcomp(Q, nonimm);
441 % full(Q)
442 for a=1:A
443 % stochastic complement for action a
444 Q21a = Dfilt{a}(imm,nonimm);
445 Ta = (-Q22) \ Q21a;
446 Ta = Q12*Ta;
447 Dfilt{a} = Dfilt{a}(nonimm,nonimm)+Ta;
448 end
449
450 depRates(imm,:,:) = [];
451 arvRates(imm,:,:) = [];
452 % recompute arvRates and depRates
453 % arvRates = zeros(size(stateSpace,1),nstateful,nclasses);
454 % depRates = zeros(size(stateSpace,1),nstateful,nclasses);
455 % for a=1:A
456 % % active
457 % node_a = sync{a}.active{1}.node;
458 % class_a = sync{a}.active{1}.class;
459 % event_a = sync{a}.active{1}.event;
460 % % passive
461 % node_p = sync{a}.passive{1}.node;
462 % class_p = sync{a}.passive{1}.class;
463 % if event_a == EventType.DEP
464 % node_a_sf = sn.nodeToStateful(node_a);
465 % node_p_sf = sn.nodeToStateful(node_p);
466 % for s=1:size(stateSpace,1)
467 % depRates(s,node_a_sf,class_a) = depRates(s,node_a_sf,class_a) + sum(Dfilt{a}(s,:));
468 % arvRates(s,node_p_sf,class_p) = arvRates(s,node_p_sf,class_p) + sum(Dfilt{a}(s,:));
469 % end
470 % end
471 % end
472end
473%SolverCTMC.printInfGen(Q,stateSpace)
474%
475% Draft SPN:
476% if ~isempty(Adj) && ~isempty(ST)
477% imm = [];
478% for s=1:size(SSh, 1)
479% % check for immediate transitions
480% enabled_t = find(Adj_t(s,:));
481% imm_t = [];
482% for t=1:length(enabled_t)
483% if sn.varsparam{Adj_t(s,enabled_t(t))}.timingstrategies(Adj_m(s,enabled_t(t)))
484% imm_t = [imm_t, enabled_t(t)];
485% end
486% end
487% % Immediate transitions exists, the marking needs to be removed.
488% if ~isempty(imm_t)
489% imm = [imm, s];
490% non_imm = setdiff(enabled_t,imm_t);
491% inM = find(Adj_t(:,s));
492% for in=1:length(inM)
493% depRates(inM(in),:,:) = depRates(inM(in),:,:) + depRates(s,:,:);
494% for nim=1:length(non_imm)
495% Q(inM(in), non_imm(nim)) = Q(inM(in), non_imm(nim)) + Q(inM(in), s) + Q(s, non_imm(nim));
496% if Adj_t(inM(in), non_imm(nim))==0
497% Adj_t(inM(in), non_imm(nim)) = Adj_t(inM(in), s);
498% Adj_m(inM(in), non_imm(nim)) = Adj_m(inM(in), s);
499% end
500% arvRates(non_imm(nim),:,:) = arvRates(non_imm(nim),:,:) + arvRates(s,:,:);
501% end
502% totalWeight = 0;
503% for im=1:length(imm_t)
504% weight = sn.varsparam{Adj_t(s,imm_t(im))}.firingweights(Adj_m(s,imm_t(im)));
505% totalWeight = totalWeight + weight;
506% if Adj_t(inM(in), imm_t(im))==0
507% Adj_t(inM(in), imm_t(im)) = Adj_t(inM(in), s);
508% Adj_m(inM(in), imm_t(im)) = Adj_m(inM(in), s);
509% end
510% end
511% for im=1:length(imm_t)
512% weight = sn.varsparam{Adj_t(s,imm_t(im))}.firingweights(Adj_m(s,imm_t(im)));
513% Q(inM(in), imm_t(im)) = Q(inM(in), imm_t(im)) + Q(inM(in), s) * (weight/totalWeight);
514% end
515% end
516% Adj_t(s,:) = 0;
517% Adj_m(s,:) = 0;
518% Adj_t(:,s) = 0;
519% Adj_m(:,s) = 0;
520% end
521% end
522% Q(imm,:) = [];
523% SS(imm,:) = [];
524% SSq(imm,:) = [];
525% arvRates(imm,:,:) = [];
526% depRates(imm,:,:) = [];
527% Q(:,imm) = [];
528% end
529%%
530%Q = ctmc_makeinfgen(Q);
531end