LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
reachableSpaceGenerator.m
1function [SSq,SSh,sn] = reachableSpaceGenerator(sn,options)
2% [SSQ,SSH,QN] = REACHABLESPACEGENERATOR(QN,OPTIONS)
3%
4% This differs from spaceGenerator as it is restricted to states reachable
5% from the initial state.
6
7% Copyright (c) 2012-2026, Imperial College London
8% All rights reserved.
9
10% By default the jobs are all initialized in the first valid state
11
12%nstations = sn.nstations;
13nstateful = sn.nstateful;
14%init_nserver = sn.nservers; % restore Inf at delay nodes
15R = sn.nclasses;
16N = sn.njobs';
17sync = sn.sync;
18csmask = sn.csmask;
19stack = {sn.state'};
20SSq = [];
21A = length(sn.sync);
22sync = sn.sync;
23isSimulation = false;
24local = sn.nnodes+1;
25
26for act=1:A
27 node_a{act} = sync{act}.active{1}.node;
28 node_p{act} = sync{act}.passive{1}.node;
29 class_a{act} = sync{act}.active{1}.class;
30 class_p{act} = sync{act}.passive{1}.class;
31 event_a{act} = sync{act}.active{1}.event;
32 event_p{act} = sync{act}.passive{1}.event;
33 outprob_a{act} = [];
34 outprob_p{act} = [];
35end
36
37space = cell(1,nstateful);
38for i=1:nstateful
39 space{i} = sn.state{i};
40end
41SSh = ones(1,nstateful); % initial state
42it = 0;
43%ih = 1;
44stack_index = 1;
45maxstatesz = zeros(1,nstateful);
46% Q = sparse([]);
47% Dfilt = cell(1,A);
48% for a=1:A
49% Dfilt{a} = sparse([]);
50% end
51while 1
52 % Cooperative wall-clock budget checkpoint (options.timeout): a partial
53 % reachable space would give silently wrong results, so abort the solve.
54 if lineTimeoutExceeded(options)
55 line_error(mfilename,'Reachable state space generation exceeded the wall-clock time budget (options.timeout=%gs).', options.timeout);
56 end
57 if isempty(stack)
58 %Q = ctmc_makeinfgen(Q);
59 SSq = []; % initial state
60 for i=1:size(SSh,1)
61 colctr = 1;
62 for j=1:nstateful
63 SSq(i,colctr:(colctr+size(space{j},2)-1)) = space{j}(SSh(i,j),:);
64 colctr = colctr+size(space{j},2);
65 end
66 end
67 % for a=1:A
68 % if (size(Dfilt{a},1) < size(SSq,1)) || (size(Dfilt{a},2) < size(SSq,1))
69 % Dfilt{a}(size(SSq,1),size(SSq,1)) = 0;
70 % end
71 % end
72 sn.space = space;
73 return
74 end
75 % pop state
76 stateCell = stack{end};
77 state = cell2mat(stateCell);
78 ih = stack_index(end);
79 stack_index(end)=[];
80 stack(end)=[];
81 statelen = cellfun(@length, stateCell);
82 newStateCell = cell(1,A);
83 %samples_collected
84 ctr = 1;
85 enabled_sync = {}; % row is action label, col1=rate, col2=new state
86 %enabled_new_state = {};
87 enabled_rates = [];
88 for act=1:A
89 outprob_a{act} = [];
90 outprob_p{act} = [];
91 update_cond_a = true; %((node_a{act} == last_node_a || node_a{act} == last_node_p));
92 newStateCell{act} = stateCell;
93 if update_cond_a || isempty(outprob_a{act})
94 isf = sn.nodeToStateful(node_a{act});
95 [newStateCell{act}{sn.nodeToStateful(node_a{act})}, rate_a{act}, outprob_a{act}] = State.afterEvent(sn, node_a{act}, stateCell{isf}, event_a{act}, class_a{act}, isSimulation);
96 if size(newStateCell{act}{sn.nodeToStateful(node_a{act})},1) == 0
97 outprob_a{act} = [];
98 end
99 end
100
101 if isempty(newStateCell{act}{sn.nodeToStateful(node_a{act})}) || isempty(rate_a{act}) % state not found
102 continue
103 end
104
105 for ia=1:size(newStateCell{act}{sn.nodeToStateful(node_a{act})},1) % for all possible new states of the active agent
106 if isempty(newStateCell{act}{sn.nodeToStateful(node_a{act})})
107 continue
108 end
109
110 if newStateCell{act}{sn.nodeToStateful(node_a{act})}(ia,:) == -1 % hash not found
111 continue
112 end
113 %update_cond_p = ((node_p{act} == last_node_a || node_p{act} == last_node_p)) || isempty(outprob_p{act});
114 update_cond = true; %update_cond_a || update_cond_p;
115 if rate_a{act}(ia)>0
116 if node_p{act} ~= local
117 if node_p{act} == node_a{act} %self-loop
118 if update_cond
119 % since self-loop sare multiple instantaneous
120 % state transitions, we save the intermediate
121 % states in the sn.space structure
122 i = node_p{act};
123 nci = newStateCell{act}{sn.nodeToStateful(node_a{act})};
124 for j=1:size(nci,1)
125 if length(nci(j,:)) > size(space{i},2)
126 space{i} = [zeros(size(space{i},1),length(nci(j,:))-size(space{i},2)),space{i}];
127 elseif length(nci(j,:)) < size(space{i},2)
128 nci = [zeros(1, size(space{i},2)-length(nci(j,:))),nci(j,:)];
129 end
130 if matchrow(space{i},nci(j,:))==-1
131 space{i}(end+1,:) = nci(j,:);
132 end
133 end
134 [newStateCell{act}{sn.nodeToStateful(node_p{act})}, ~, outprob_p{act}] = State.afterEvent(sn, node_p{act}, newStateCell{act}{sn.nodeToStateful(node_a{act})}, event_p{act}, class_p{act}, isSimulation);
135 outprob_a{act} = outprob_p{act};
136 end
137 else % departure from active
138 if update_cond
139 [newStateCell{act}{sn.nodeToStateful(node_p{act})}, ~, outprob_p{act}] = State.afterEvent(sn, node_p{act}, stateCell{sn.nodeToStateful(node_p{act})}, event_p{act}, class_p{act}, isSimulation);
140 end
141 end
142 if ~isempty(newStateCell{act}{sn.nodeToStateful(node_p{act})})
143 if sn.isstatedep(node_a{act},3)
144 prob_sync_p{act} = sync{act}.passive{1}.prob(stateCell, newStateCell{act}); %state-dependent
145 else
146 prob_sync_p{act} = sync{act}.passive{1}.prob;
147 end
148 else
149 prob_sync_p{act} = 0;
150 end
151 end
152 if ~isempty(newStateCell{act}{sn.nodeToStateful(node_a{act})})
153 if node_p{act} == local
154 prob_sync_p{act} = 1; %outprob_a{act}; % was 1
155 end
156 if ~isnan(rate_a{act})
157 if all(~cellfun(@isempty,newStateCell{act}))
158 if event_a{act} == EventType.DEP
159 node_a_sf{act} = sn.nodeToStateful(node_a{act});
160 node_p_sf{act} = sn.nodeToStateful(node_p{act});
161 end
162 % simulate also self-loops as we need to log them
163 if node_p{act} < local && ~csmask(class_a{act}, class_p{act}) && sn.nodetype(node_p{act})~=NodeType.Source && (rate_a{act}(ia) * prob_sync_p{act} >0)
164 line_error(mfilename,sprintf('Error: state-dependent routing at node %d (%s) violates the class switching mask (node %d -> node %d, class %d -> class %d).', node_a{act}, sn.nodenames{node_a{act}}, node_a{act}, node_p{act}, class_a{act}, class_p{act}));
165 end
166 enabled_rates(ctr) = rate_a{act}(ia) * prob_sync_p{act};
167 enabled_sync{ctr} = act;
168 ctr = ctr + 1; % keep
169 end
170 end
171 end
172 end
173 end
174 end
175
176 for firing_ctr=1:length(enabled_rates)
177 firing_rate = enabled_rates(firing_ctr);
178 last_node_a = node_a{enabled_sync{firing_ctr}};
179 last_node_p = node_p{enabled_sync{firing_ctr}};
180 act = enabled_sync{firing_ctr};
181 netstates = newStateCell{act};
182 if enabled_rates(firing_ctr)>0 && ~any(cellfun(@isempty,netstates))
183 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
184 nvec = cellfun(@(c) size(c,1)-1, netstates);
185 n = pprod(nvec);
186 while n>=0
187 newstate = [];
188 newstatec = {};
189 firingprob = 1;
190 for i=1:size(netstates,2)
191 if i==last_node_a
192 firingprob = firingprob*outprob_a{enabled_sync{firing_ctr}}(1+n(i));
193 end
194 if i==last_node_p
195 firingprob = firingprob*outprob_p{enabled_sync{firing_ctr}}(1+n(i));
196 end
197 maxstatesz(i) = max(maxstatesz(i),length(netstates{i}(1+n(i),:)));
198 newstate = [newstate,zeros(1,maxstatesz(i)-length(netstates{i}(1+n(i),:))),netstates{i}(1+n(i),:)];
199 newstatec{i} = [zeros(1,maxstatesz(i)-length(netstates{i}(1+n(i),:))),netstates{i}(1+n(i),:)];
200 end
201 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
202 if firingprob>0
203 hashednewstate = zeros(1,nstateful);
204 for i=1:nstateful
205 if length(newstatec{i}) > size(space{i},2)
206 hashednewstate(i) = matchrow([zeros(size(space{i},1),length(newstatec{i})-size(space{i},2)),space{i}], newstatec{i});
207 else
208 hashednewstate(i) = matchrow(space{i}, newstatec{i});
209 end
210 end
211 jh = matchrow(SSh,hashednewstate);
212 if jh == -1 || (length(newstate)~=length(state) || any(newstate~=state))
213 % store and update jh index
214 if jh == -1
215 for i=1:nstateful
216 if hashednewstate(i) == -1
217 nci = newstatec{i};
218 if length(nci) > size(space{i},2)
219 space{i} = [zeros(size(space{i},1),length(nci)-size(space{i},2)),space{i}];
220 elseif length(nci) < size(space{i},2)
221 nci = [zeros(1, size(space{i},2)-length(nci)),nci];
222 end
223 space{i}(end+1,:) = nci;
224 hashednewstate(i) = size(space{i},1);
225 end
226 end
227 SSh = [SSh; hashednewstate];
228 jh = size(SSh,1);
229 stack{end+1} = newstatec;
230 stack_index(end+1) = jh;
231 end
232 end
233 % for some reason the Q generation is buggy so we
234 % disabled it
235 % if (size(Q,1)< ih) || (size(Q,2)< jh)
236 % Q(ih,jh) = firing_rate;
237 % for a=1:A
238 % Dfilt{act}(ih,jh) = 0;
239 % end
240 % Dfilt{act}(ih,jh) = firing_rate;
241 % else
242 % Q(ih,jh) = Q(ih,jh) + firing_rate;
243 % for a=1:A
244 % if (size(Dfilt{act},1) < ih) || (size(Dfilt{act},2) < jh)
245 % Dfilt{act}(ih,jh) = 0;
246 % end
247 % end
248 % Dfilt{act}(ih,jh) = Dfilt{act}(ih,jh) + firing_rate;
249 % end
250 end
251 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
252 n = pprod(n,nvec);
253 end
254 end
255 end
256 % explore SPN global synchronizations: a Transition's ENABLE moves tokens
257 % into the mode's servers and its FIRE completes them, applying the PRE
258 % consumption and POST production on the places. These live in sn.gsync and
259 % not in sn.sync, so without this block a net whose dynamics are entirely
260 % Petri-net firings never leaves its initial marking.
261 if isfield(sn,'gsync') && ~isempty(sn.gsync)
262 for g=1:length(sn.gsync)
263 gind = sn.gsync{g}.active{1}.node;
264 isf_t = sn.nodeToStateful(gind);
265 [outglspace, outrate, outprob] = State.afterGlobalEvent(sn, gind, stateCell, sn.gsync{g}, false);
266 if isempty(outrate)
267 continue
268 end
269 for io=1:length(outrate)
270 if outrate(io) <= 0
271 continue
272 end
273 prob_io = 1;
274 if ~isempty(outprob) && io <= length(outprob)
275 prob_io = outprob(io);
276 end
277 if prob_io <= 0
278 continue
279 end
280 if size(outglspace{isf_t},1) < io
281 continue
282 end
283 gStates = stateCell;
284 gStates{isf_t} = outglspace{isf_t}(io,:);
285 if sn.gsync{g}.active{1}.event == EventType.FIRE
286 for i=1:nstateful
287 if i ~= isf_t && ~isequal(stateCell{i}, outglspace{i})
288 gStates{i} = outglspace{i};
289 end
290 end
291 end
292
293 % A firing that consumed tokens its input place did not hold is
294 % not enabled and must never be emitted. afterGlobalEvent gates
295 % the completion on the place's enabling degree for exactly this
296 % reason, so this is an invariant guard, not the fix. The lattice
297 % generator has an implicit backstop -- an unrepresentable marking
298 % is absent from sn.space, matchrow misses and solver_ctmc drops
299 % the arc -- but enumerating reachable states has none, so a
300 % regression in the FIRE handler would silently mint the state
301 % and surface only as a mean token count above the population.
302 % Failing here names the event instead.
303 if i_hasNegativePlace(sn, gStates)
304 line_error(mfilename, sprintf(['%s at %s produced a negative place: ' ...
305 'a firing completed without its input place holding the tokens ' ...
306 'to pay for it. The FIRE handler in afterGlobalEvent should have ' ...
307 'gated this completion on the enabling degree.'], ...
308 EventType.toText(sn.gsync{g}.active{1}.event), sn.nodenames{gind}));
309 end
310
311 newstatec = cell(1,nstateful);
312 for i=1:nstateful
313 maxstatesz(i) = max(maxstatesz(i),length(gStates{i}));
314 newstatec{i} = [zeros(1,maxstatesz(i)-length(gStates{i})),gStates{i}];
315 end
316 hashednewstate = zeros(1,nstateful);
317 for i=1:nstateful
318 if length(newstatec{i}) > size(space{i},2)
319 hashednewstate(i) = matchrow([zeros(size(space{i},1),length(newstatec{i})-size(space{i},2)),space{i}], newstatec{i});
320 else
321 hashednewstate(i) = matchrow(space{i}, newstatec{i});
322 end
323 end
324 jh = matchrow(SSh,hashednewstate);
325 if jh == -1
326 for i=1:nstateful
327 if hashednewstate(i) == -1
328 nci = newstatec{i};
329 if length(nci) > size(space{i},2)
330 space{i} = [zeros(size(space{i},1),length(nci)-size(space{i},2)),space{i}];
331 elseif length(nci) < size(space{i},2)
332 nci = [zeros(1, size(space{i},2)-length(nci)),nci];
333 end
334 space{i}(end+1,:) = nci;
335 hashednewstate(i) = size(space{i},1);
336 end
337 end
338 SSh = [SSh; hashednewstate]; %#ok<AGROW>
339 stack{end+1} = newstatec; %#ok<AGROW>
340 stack_index(end+1) = size(SSh,1); %#ok<AGROW>
341 end
342 end
343 end
344 end
345 % explore fork firing synchronizations (FJ-augmented structs only):
346 % each firing consumes the parent held at the Fork and emits one
347 % sibling per branch atomically (multi-node successor states)
348 if isfield(sn,'fjsync') && ~isempty(sn.fjsync)
349 for k=1:length(sn.fjsync)
350 [fjStates, ~, fjprob] = State.afterFJEvent(sn, sn.fjsync{k}, stateCell, false);
351 for io=1:length(fjStates)
352 if fjprob(io) > 0
353 newstatec = cell(1,nstateful);
354 for i=1:nstateful
355 maxstatesz(i) = max(maxstatesz(i),length(fjStates{io}{i}));
356 newstatec{i} = [zeros(1,maxstatesz(i)-length(fjStates{io}{i})),fjStates{io}{i}];
357 end
358 hashednewstate = zeros(1,nstateful);
359 for i=1:nstateful
360 if length(newstatec{i}) > size(space{i},2)
361 hashednewstate(i) = matchrow([zeros(size(space{i},1),length(newstatec{i})-size(space{i},2)),space{i}], newstatec{i});
362 else
363 hashednewstate(i) = matchrow(space{i}, newstatec{i});
364 end
365 end
366 jh = matchrow(SSh,hashednewstate);
367 if jh == -1
368 for i=1:nstateful
369 if hashednewstate(i) == -1
370 nci = newstatec{i};
371 if length(nci) > size(space{i},2)
372 space{i} = [zeros(size(space{i},1),length(nci)-size(space{i},2)),space{i}];
373 elseif length(nci) < size(space{i},2)
374 nci = [zeros(1, size(space{i},2)-length(nci)),nci];
375 end
376 space{i}(end+1,:) = nci;
377 hashednewstate(i) = size(space{i},1);
378 end
379 end
380 SSh = [SSh; hashednewstate]; %#ok<AGROW>
381 stack{end+1} = newstatec; %#ok<AGROW>
382 stack_index(end+1) = size(SSh,1); %#ok<AGROW>
383 end
384 end
385 end
386 end
387 end
388 it = it + 1;
389end
390end
391
392function neg = i_hasNegativePlace(sn, stateCell)
393% True when any place in the successor holds a negative number of tokens, which
394% means the firing that produced it consumed tokens the place did not have.
395neg = false;
396for ind = 1:sn.nnodes
397 if ~sn.isstateful(ind) || sn.nodetype(ind) ~= NodeType.Place
398 continue
399 end
400 isf = sn.nodeToStateful(ind);
401 if isf > numel(stateCell) || isempty(stateCell{isf})
402 continue
403 end
404 if any(stateCell{isf} < 0)
405 neg = true;
406 return
407 end
408end
409end
Definition fjtag.m:157
Definition Station.m:245