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
241Q = Q - diag(diag(Q));
242%SolverCTMC.printInfGen(Q,stateSpace)
243%%
244arvRates = zeros(size(stateSpaceHashed,1),nstateful,nclasses);
245depRates = zeros(size(stateSpaceHashed,1),nstateful,nclasses);
246for a=1:A
247 % active
248 node_a = sync{a}.active{1}.node;
249 class_a = sync{a}.active{1}.class;
250 event_a = sync{a}.active{1}.event;
251 % passive
252 node_p = sync{a}.passive{1}.node;
253 class_p = sync{a}.passive{1}.class;
254 if event_a == EventType.DEP
255 node_a_sf = sn.nodeToStateful(node_a);
256 node_p_sf = sn.nodeToStateful(node_p);
257 for s=1:size(stateSpaceHashed,1)
258 depRates(s,node_a_sf,class_a) = depRates(s,node_a_sf,class_a) + sum(Dfilt{a}(s,:));
259 arvRates(s,node_p_sf,class_p) = arvRates(s,node_p_sf,class_p) + sum(Dfilt{a}(s,:));
260 end
261 end
262end
263zero_row = find(sum(Q,2)==0);
264zero_col = find(sum(Q,1)==0);
265
266%%
267% in case the last column of Q represent a state for a transient class, it
268% is possible that no transitions go back to it, although it is valid for
269% the system to be initialized in that state. So we need to fill-in the
270% zeros at the end.
271Q(:,end+1:end+(size(Q,1)-size(Q,2)))=0;
272Q(zero_row,zero_row) = -eye(length(zero_row)); % can this be replaced by []?
273Q(zero_col,zero_col) = -eye(length(zero_col));
274for a=1:A
275 Dfilt{a}(:,end+1:end+(size(Dfilt{a},1)-size(Dfilt{a},2)))=0;
276end
277
278if options.verbose == VerboseLevel.DEBUG || GlobalConstants.Verbose == VerboseLevel.DEBUG
279 SolverCTMC.printInfGen(Q,stateSpace);
280end
281Q = ctmc_makeinfgen(Q);
282%% now remove immediate transitions
283% we first determine states in stateful nodes where there is an immediate
284% job in the node
285
286if options.config.hide_immediate % if want to remove immediate transitions
287 imm = [];
288 for ind = 1:sn.nnodes
289 % Only consider non-station stateful nodes that are not Caches
290 % Cache nodes need their immediate transitions to compute hit/miss rates
291 if sn.isstateful(ind) && ~sn.isstation(ind) && sn.nodetype(ind) ~= NodeType.Cache
292 isf = sn.nodeToStateful(ind);
293 imm_st = find(sum(sn.space{isf}(:,1:nclasses),2)>0);
294 imm = [imm; find(arrayfun(@(a) any(a==imm_st),stateSpaceHashed(:,isf)))];
295 end
296 end
297 imm = unique(imm);
298 nonimm = setdiff(1:size(Q,1),imm);
299 stateSpace(imm,:) = [];
300 stateSpaceAggr(imm,:) = [];
301 % full(Q)
302 [Q,~,Q12,~,Q22] = ctmc_stochcomp(Q, nonimm);
303 % full(Q)
304 for a=1:A
305 % stochastic complement for action a
306 Q21a = Dfilt{a}(imm,nonimm);
307 Ta = (-Q22) \ Q21a;
308 Ta = Q12*Ta;
309 Dfilt{a} = Dfilt{a}(nonimm,nonimm)+Ta;
310 end
311
312 depRates(imm,:,:) = [];
313 arvRates(imm,:,:) = [];
314 % recompute arvRates and depRates
315 % arvRates = zeros(size(stateSpace,1),nstateful,nclasses);
316 % depRates = zeros(size(stateSpace,1),nstateful,nclasses);
317 % for a=1:A
318 % % active
319 % node_a = sync{a}.active{1}.node;
320 % class_a = sync{a}.active{1}.class;
321 % event_a = sync{a}.active{1}.event;
322 % % passive
323 % node_p = sync{a}.passive{1}.node;
324 % class_p = sync{a}.passive{1}.class;
325 % if event_a == EventType.DEP
326 % node_a_sf = sn.nodeToStateful(node_a);
327 % node_p_sf = sn.nodeToStateful(node_p);
328 % for s=1:size(stateSpace,1)
329 % depRates(s,node_a_sf,class_a) = depRates(s,node_a_sf,class_a) + sum(Dfilt{a}(s,:));
330 % arvRates(s,node_p_sf,class_p) = arvRates(s,node_p_sf,class_p) + sum(Dfilt{a}(s,:));
331 % end
332 % end
333 % end
334end
335%SolverCTMC.printInfGen(Q,stateSpace)
336%
337% Draft SPN:
338% if ~isempty(Adj) && ~isempty(ST)
339% imm = [];
340% for s=1:size(SSh, 1)
341% % check for immediate transitions
342% enabled_t = find(Adj_t(s,:));
343% imm_t = [];
344% for t=1:length(enabled_t)
345% if sn.varsparam{Adj_t(s,enabled_t(t))}.timingstrategies(Adj_m(s,enabled_t(t)))
346% imm_t = [imm_t, enabled_t(t)];
347% end
348% end
349% % Immediate transitions exists, the marking needs to be removed.
350% if ~isempty(imm_t)
351% imm = [imm, s];
352% non_imm = setdiff(enabled_t,imm_t);
353% inM = find(Adj_t(:,s));
354% for in=1:length(inM)
355% depRates(inM(in),:,:) = depRates(inM(in),:,:) + depRates(s,:,:);
356% for nim=1:length(non_imm)
357% Q(inM(in), non_imm(nim)) = Q(inM(in), non_imm(nim)) + Q(inM(in), s) + Q(s, non_imm(nim));
358% if Adj_t(inM(in), non_imm(nim))==0
359% Adj_t(inM(in), non_imm(nim)) = Adj_t(inM(in), s);
360% Adj_m(inM(in), non_imm(nim)) = Adj_m(inM(in), s);
361% end
362% arvRates(non_imm(nim),:,:) = arvRates(non_imm(nim),:,:) + arvRates(s,:,:);
363% end
364% totalWeight = 0;
365% for im=1:length(imm_t)
366% weight = sn.varsparam{Adj_t(s,imm_t(im))}.firingweights(Adj_m(s,imm_t(im)));
367% totalWeight = totalWeight + weight;
368% if Adj_t(inM(in), imm_t(im))==0
369% Adj_t(inM(in), imm_t(im)) = Adj_t(inM(in), s);
370% Adj_m(inM(in), imm_t(im)) = Adj_m(inM(in), s);
371% end
372% end
373% for im=1:length(imm_t)
374% weight = sn.varsparam{Adj_t(s,imm_t(im))}.firingweights(Adj_m(s,imm_t(im)));
375% Q(inM(in), imm_t(im)) = Q(inM(in), imm_t(im)) + Q(inM(in), s) * (weight/totalWeight);
376% end
377% end
378% Adj_t(s,:) = 0;
379% Adj_m(s,:) = 0;
380% Adj_t(:,s) = 0;
381% Adj_m(:,s) = 0;
382% end
383% end
384% Q(imm,:) = [];
385% SS(imm,:) = [];
386% SSq(imm,:) = [];
387% arvRates(imm,:,:) = [];
388% depRates(imm,:,:) = [];
389% Q(:,imm) = [];
390% end
391%%
392%Q = ctmc_makeinfgen(Q);
393end
Definition mmt.m:92