1function [outglspace, outrate, outprob] = afterGlobalEvent(sn, ind, glspace, glevent, isSimulation)
2% [OUTGLSPACE, OUTRATE, OUTPROB] = AFTERGLOBALEVENT(QN, IND, GLSPACE, GLEVENT, ISSIMULATION)
4% Copyright (c) 2012-2026, Imperial College London
7outspace = []; % state space of ind node
8outrate = []; % rate of synchronization
9outprob = []; % probability of synchronization
10outglspace = glspace; %
new global state after synchronization
13%phasessz = sn.phasessz;
14%phaseshift = sn.phaseshift;
15if sn.nodetype(ind) == NodeType.Transition % same isa(glevent,
'ModeEvent')
17 isf = sn.nodeToStateful(ind);
18 inspace = glspace{isf};
19 V = sum(sn.nvars(ind,:));
20 % in
this case service
is always immediate so sum(K)=1
21 space_var = inspace(:,(end-V+1):end); % local state variables
22 if sn.nodetype(ind) == NodeType.Transition
23 fK = sn.nodeparam{ind}.firingphases;
24 nmodes = sn.nodeparam{ind}.nmodes;
25 % Handle NaN firingphases (non-phase-type distributions like Pareto)
27 fK = zeros(1, nmodes);
29 if iscell(sn.nodeparam{ind}.firingproc) && ~isempty(sn.nodeparam{ind}.firingproc{m})
30 fK(m) = size(sn.nodeparam{ind}.firingproc{m}{1}, 1);
36 fKs = [0,cumsum(fK,2)];
37 mode = glevent.active{1}.mode;
38 % Transition state format: [idle_counts(nmodes), phase_counts(sum(fK)), fired_counts(nmodes)]
39 space_buf = inspace(:,1:nmodes); % idle servers count put in buf
40 space_srv = inspace(:,(nmodes+1):(nmodes+sum(fK))); % enabled servers
' phases
41 % Handle both state formats: with and without fired component
42 expected_len_with_fired = 2*nmodes + sum(fK);
43 expected_len_without_fired = nmodes + sum(fK);
44 if size(inspace, 2) >= expected_len_with_fired
45 space_fired = inspace(:,(nmodes+sum(fK)+1):(2*nmodes+sum(fK))); % servers that just fired
46 elseif size(inspace, 2) == expected_len_without_fired
47 % Legacy format without fired component - initialize to zeros
48 space_fired = zeros(size(inspace,1), nmodes);
50 line_error(mfilename, 'Unexpected state vector length
for Transition node
');
53 switch glevent.active{1}.event
55 enabling_m = sn.nodeparam{ind}.enabling{mode}; % enabling requirement for mode m
56 ep_space = zeros(sn.nnodes,R);
57 for j=1:length(glevent.passive)
58 ep_linidx = glevent.passive{j}.node; % linear index into (nnodes x nclasses) matrix
59 % Decode linear index to (node, class) - the passive node is a linear index from find() on enabling matrix
60 [ep_ind, ~] = ind2sub([sn.nnodes, R], ep_linidx);
61 if ep_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(ep_ind)) || sn.nodeToStateful(ep_ind) <= 0
62 continue; % skip non-stateful nodes
64 ep_isf = sn.nodeToStateful(ep_ind);
67 ep_space_buf = glspace{ep_isf};
68 ep_space_srv = zeros(1,R);
70 [~,ep_space(ep_ind,1:R)] = State.toMarginalAggr(sn,ep_ind, glspace{ep_isf},K,Ks,ep_space_buf,ep_space_srv,ep_space_var);
73 if any(ep_space(:) < enabling_m(:))
74 % check if any servers need to be disabled
75 new_space_buf = space_buf;
76 new_space_srv = space_srv;
77 % Cap nmodeservers to MaxInt for state (SSA needs finite states)
78 nmodeservers_m = sn.nodeparam{ind}.nmodeservers(m);
79 if isinf(nmodeservers_m)
80 nmodeservers_m = GlobalConstants.MaxInt();
82 new_space_buf(m) = nmodeservers_m;
83 new_space_srv((fKs(m)+1):(fKs(m)+fK(m))) = 0;
84 new_state = [new_space_buf, new_space_srv, space_fired, space_var];
85 old_state = [space_buf, space_srv, space_fired, space_var];
86 if ~isequal(new_state, old_state)
87 % State changes - return the disable action
89 outrate = GlobalConstants.Immediate;
92 % Already disabled, no state change
98 % find enabling degree, i.e., running servers
100 while all(ep_space >= en_degree_m * enabling_m)
101 en_degree_m = en_degree_m + 1;
103 % Cap nmodeservers to MaxInt for enabling degree calculation
104 nmodeservers_m = sn.nodeparam{ind}.nmodeservers(m);
105 if isinf(nmodeservers_m)
106 nmodeservers_m = GlobalConstants.MaxInt();
108 en_degree_m = min(en_degree_m - 1, nmodeservers_m);
109 running_m = sum(space_srv((fKs(m)+1):(fKs(m)+fK(m))));
110 if running_m == en_degree_m
111 % all running as expected, do nothing
115 elseif running_m < en_degree_m
116 % fewer expected, start en_degree_m - running_m
117 space_buf(m) = space_buf(m) - (en_degree_m - running_m);
118 pentry = sn.nodeparam{ind}.firingpie{mode};
119 nadd = en_degree_m - running_m;
120 combs = sortrows(multichoose(fK(m), nadd),'descend
');
122 for i = 1:size(combs,1)
124 space_srv_k = space_srv;
126 space_srv_k(fKs(m)+k) = space_srv_k(fKs(m)+k) + comb(k);
128 outspace = [outspace; space_buf, space_srv_k, space_fired, space_var]; %#ok<AGROW>
129 outrate = [outrate; GlobalConstants.Immediate]; %#ok<AGROW>
131 % compute multinomial coefficient
132 % multinomial probability: n! / (k1! * k2! * ...) * p1^k1 * p2^k2 * ...
133 logprob = factln(nadd);
136 logprob = logprob + comb(k)*log(pentry(k)) - factln(comb(k));
137 elseif (pentry(k)==0 && comb(k)==0)
139 else % (pentry(k)==0 && comb(k)>0)
140 logprob = -Inf; % set zero outprob
143 outprob = [outprob; exp(logprob)]; %#ok<AGROW>
145 %outprob = outprob / sum(outprob);
146 else %running_m > en_degree_m
147 % more than expected, stop running_m - en_degree_m
148 % servers chosen uniformly at random across phases.
149 % We enumerate how many servers to stop per phase (comb),
150 % where 0 <= comb(k) <= srv_vec(k) and sum(comb) = ndiff.
151 % Probability of each comb follows the hypergeometric
152 % mixture: prod_k C(srv_vec(k), comb(k)) / C(running_m, ndiff).
153 ndiff = running_m - en_degree_m; % number of servers to stop
154 srv_vec = space_srv((fKs(m)+1):(fKs(m)+fK(m)));
156 % Enumerate all (comb) of length n with sum == ndiff and
157 % comb(k) <= srv_vec(k). multichoose gives sum==ndiff with
158 % entries >= 0 but without the per-phase cap, so filter.
159 all_combs = multichoose(n, ndiff);
160 valid = all(bsxfun(@le, all_combs, srv_vec(:)'), 2);
161 all_combs = all_combs(valid, :);
162 % Compute weights via multivariate hypergeometric
163 logW = zeros(size(all_combs,1),1);
164 for i = 1:size(all_combs,1)
166 logW(i) = logW(i) + factln(srv_vec(k)) ...
167 - factln(all_combs(i,k)) ...
168 - factln(srv_vec(k) - all_combs(i,k));
171 W = exp(logW - max(logW));
173 for i = 1:size(all_combs,1)
174 comb = all_combs(i,:);
175 space_srv_reduced = space_srv;
176 space_srv_reduced((fKs(m)+1):(fKs(m)+fK(m))) = srv_vec - comb;
177 space_buf_reduced = space_buf;
178 space_buf_reduced(m) = space_buf_reduced(m) + ndiff; % return stopped servers to idle pool
179 outspace = [outspace; space_buf_reduced, space_srv_reduced, space_fired, space_var]; %
#ok<AGROW>
180 outrate = [outrate; GlobalConstants.Immediate]; %#ok<AGROW>
181 outprob = [outprob; W(i)]; %#ok<AGROW>
185 % update
new state space
186 outglspace{isf} = outspace;
188 %% Update transition servers
189 fK = sn.nodeparam{ind}.firingphases;
190 % Handle NaN firingphases (non-phase-type distributions like Pareto)
192 fK = zeros(1, nmodes);
194 if iscell(sn.nodeparam{ind}.firingproc) && ~isempty(sn.nodeparam{ind}.firingproc{m})
195 fK(m) = size(sn.nodeparam{ind}.firingproc{m}{1}, 1);
201 fKs = [0,cumsum(fK,2)];
202 mode = glevent.active{1}.mode;
203 % find transition server counts
204 [~,nim,~,kim] = State.toMarginal(sn,ind,inspace,fK,fKs,space_buf,space_srv,space_var);
205 % state of transition mode
206 enabling_m = sn.nodeparam{ind}.enabling{mode}; % enabling requirement
for mode m
207 firing_m = sn.nodeparam{ind}.firing{mode}; % firing requirement
for mode m
208 % find enabling degree, i.e., running servers
209 ep_space = zeros(sn.nnodes,R);
210 for j=1:length(glevent.passive)
211 ep_linidx = glevent.passive{j}.node; % linear index into (nnodes x nclasses) matrix
212 % Decode linear index to (node,
class) - the passive node
is a linear index from find() on enabling/firing matrix
213 [ep_ind, ~] = ind2sub([sn.nnodes, R], ep_linidx);
214 if ep_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(ep_ind)) || sn.nodeToStateful(ep_ind) <= 0
215 continue; % skip non-stateful
nodes
217 ep_isf = sn.nodeToStateful(ep_ind);
219 Ks = [0,cumsum(K,2)];
220 ep_space_buf = outglspace{ep_isf}; %
this equals glspace at
this point
221 ep_space_srv = zeros(1,R);
223 [~,ep_space(ep_ind,1:R)] = State.toMarginalAggr(sn,ep_ind, glspace{ep_isf},K,Ks,ep_space_buf,ep_space_srv,ep_space_var);
226 while all(ep_space >= en_degree_m * enabling_m)
227 en_degree_m = en_degree_m + 1;
229 %en_degree_m = min(en_degree_m - 1, sn.nodeparam{ind}.nmodeservers(m));
230 en_degree_m = min(en_degree_m - 1, nim(mode)); % the actual enabling degree depends on servers actually in execution, though
this should coincide
232 % Get D0 and D1 matrices
for phase transitions and completions
233 D0 = sn.nodeparam{ind}.firingproc{mode}{1}; % Internal phase transitions
234 D1 = sn.nodeparam{ind}.firingproc{mode}{2}; % Completions/firings
236 % Track whether
this is a completion (with place updates) or just phase transition
237 completion_start_idx = []; % indices in outspace that are completions
239 % First, add phase transitions (D0) - these only change internal state
240 for k = 1:fK(mode) % source phase
241 if kim(:,mode,k) > 0 % only if there are servers in phase k
242 for j = 1:fK(mode) % destination phase
243 if k ~= j && D0(k,j) > 0 % off-diagonal positive rates
244 rate_kj = D0(k,j) * kim(:,mode,k);
245 space_buf_kj = space_buf;
246 space_srv_kj = space_srv;
247 % Move server from phase k to phase j
248 space_srv_kj(fKs(mode)+k) = space_srv_kj(fKs(mode)+k) - 1;
249 space_srv_kj(fKs(mode)+j) = space_srv_kj(fKs(mode)+j) + 1;
250 space_fired_kj = space_fired; % No firing, just phase transition
251 outspace = [outspace; space_buf_kj, space_srv_kj, space_fired_kj, space_var]; %#ok<AGROW>
252 outrate = [outrate; rate_kj]; %#ok<AGROW>
253 outprob = [outprob; 1.0]; %#ok<AGROW>
259 % Then, add completions (D1) - these update places
260 % Track which outcomes are completions (vs phase transitions)
261 is_completion =
false(size(outspace,1), 1); % mark existing as phase transitions
262 for k = 1:fK(mode) % source phase
263 if kim(:,mode,k) > 0 % only if there are servers in phase k
264 rate_kd = sum(D1(k,:)) * kim(:,mode,k);
266 space_buf_kd = space_buf;
267 space_srv_kd = space_srv;
268 % Decrease by one the firing server
269 space_srv_kd(fKs(mode)+k) = space_srv_kd(fKs(mode)+k) - 1;
270 % Move the server back to the disabled pool
271 space_buf_kd(mode) = space_buf_kd(mode) + 1;
272 space_fired_kd = space_fired;
274 space_fired_kd(mode) = space_fired_kd(mode) + 1; % increment fired count only
for simulation
276 outspace = [outspace; space_buf_kd, space_srv_kd, space_fired_kd, space_var]; %#ok<AGROW>
277 outrate = [outrate; rate_kd]; %#ok<AGROW>
278 outprob = [outprob; 1.0]; %#ok<AGROW>
279 is_completion = [is_completion;
true]; %#ok<AGROW>
283 outglspace{isf} = outspace;
285 % For simulation: select outcome first, then apply PRE/POST only
if completion
286 if isSimulation && size(outspace,1) > 1 && ~isempty(outprob)
287 % Use effective rate (rate * prob)
for selection
288 eff_rate = outrate .* outprob;
289 tot_rate = sum(eff_rate);
291 cum_rate = cumsum(eff_rate) / tot_rate;
292 firing_ctr = 1 + max([0,find( rand > cum_rate
' )]);
293 selected_is_completion = is_completion(firing_ctr);
294 outspace = outspace(firing_ctr,:);
295 outrate = sum(eff_rate); % return effective rate
296 outprob = 1.0; % probability already incorporated
297 outglspace{isf} = outspace;
299 % Only process PRE/POST if this is a completion
300 if selected_is_completion
301 %% Process ID_PRE events, i.e., consume from all input places
302 for j=1:length(glevent.passive)
303 if glevent.passive{j}.event == EventType.PRE
304 ep_linidx = glevent.passive{j}.node;
305 [ep_ind, ep_class] = ind2sub([sn.nnodes, R], ep_linidx);
306 if ep_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(ep_ind)) || sn.nodeToStateful(ep_ind) <= 0
309 ep_isf = sn.nodeToStateful(ep_ind);
310 ep_space_buf = outglspace{ep_isf};
311 ep_ist = sn.nodeToStation(ep_ind);
312 consume_count = en_degree_m * glevent.passive{j}.weight;
313 % Check scheduling strategy and state format to determine handling
314 % Places with INF scheduling use queue-based state format
315 is_queue_based = sn.nodetype(ep_ind) == NodeType.Place && ...
316 length(ep_space_buf) ~= R && length(ep_space_buf) ~= 2*R;
317 if is_queue_based || sn.sched(ep_ist) == SchedStrategy.FCFS
318 % FCFS queue-based handling
319 idx = find(ep_space_buf == ep_class);
320 if length(idx) >= consume_count
321 ep_space_buf(idx(end - consume_count + 1:end)) = [];
323 elseif sn.sched(ep_ist) == SchedStrategy.LCFS
324 idx = find(ep_space_buf == ep_class);
325 if length(idx) >= consume_count
326 ep_space_buf(idx(1:consume_count)) = [];
328 elseif sn.sched(ep_ist) == SchedStrategy.SIRO
329 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
330 elseif sn.nodetype(ep_ind) == NodeType.Place
331 % Place count-based state format: [buffer(R), server_phases(sum(K))]
332 state_len = length(ep_space_buf);
334 buf_jobs = ep_space_buf(ep_class);
335 srv_jobs = ep_space_buf(R + ep_class);
336 total_jobs = buf_jobs + srv_jobs;
337 remaining = total_jobs - consume_count;
338 ep_space_buf(ep_class) = remaining;
339 ep_space_buf(R + ep_class) = 0;
341 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
344 line_error(mfilename,sprintf('Scheduling strategy %s
is unsupported at places.
', SchedStrategy.toText(sn.sched(ep_ist))));
346 outglspace{ep_isf} = ep_space_buf;
350 %% Process ID_POST events, i.e., produce to all output places
351 for j=1:length(glevent.passive)
352 if glevent.passive{j}.event == EventType.POST
353 fp_linidx = glevent.passive{j}.node;
354 [fp_ind, fp_class] = ind2sub([sn.nnodes, R], fp_linidx);
355 if fp_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(fp_ind)) || sn.nodeToStateful(fp_ind) <= 0
358 fp_isf = sn.nodeToStateful(fp_ind);
359 fp_ist = sn.nodeToStation(fp_ind);
360 fp_space_buf = outglspace{fp_isf};
361 produce_count = glevent.passive{j}.weight;
362 % Check scheduling strategy and state format to determine handling
363 % Use original glspace length for detection (before PRE modified it)
364 orig_len = length(glspace{fp_isf});
365 is_queue_based = sn.nodetype(fp_ind) == NodeType.Place && ...
366 orig_len ~= R && orig_len ~= 2*R;
367 if is_queue_based || sn.sched(fp_ist) == SchedStrategy.FCFS || sn.sched(fp_ist) == SchedStrategy.LCFS
368 % Queue-based handling - prepend to buffer
369 fp_space_buf = [repmat(fp_class, 1, produce_count), fp_space_buf];
370 elseif sn.sched(fp_ist) == SchedStrategy.SIRO
371 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
372 elseif sn.nodetype(fp_ind) == NodeType.Place
373 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
375 line_error(mfilename,sprintf('Scheduling strategy %s
is unsupported at places.
', SchedStrategy.toText(sn.sched(fp_ist))));
377 outglspace{fp_isf} = fp_space_buf;
382 % All effective rates are zero, pick first option with non-zero prob if any
383 valid_idx = find(outprob > 0, 1);
384 if isempty(valid_idx)
387 outspace = outspace(valid_idx,:);
389 outprob = outprob(valid_idx,:);
390 outglspace{isf} = outspace;
393 %% Non-simulation mode: process all completions for PRE/POST
394 % For state space analysis, we apply PRE/POST for completion outcomes
395 for j=1:length(glevent.passive)
396 if glevent.passive{j}.event == EventType.PRE
397 ep_linidx = glevent.passive{j}.node;
398 [ep_ind, ep_class] = ind2sub([sn.nnodes, R], ep_linidx);
399 if ep_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(ep_ind)) || sn.nodeToStateful(ep_ind) <= 0
402 ep_isf = sn.nodeToStateful(ep_ind);
403 ep_space_buf = outglspace{ep_isf};
404 ep_ist = sn.nodeToStation(ep_ind);
405 consume_count = en_degree_m * glevent.passive{j}.weight;
406 % Check scheduling strategy and state format to determine handling
407 is_queue_based = sn.nodetype(ep_ind) == NodeType.Place && ...
408 length(ep_space_buf) ~= R && length(ep_space_buf) ~= 2*R;
409 if is_queue_based || sn.sched(ep_ist) == SchedStrategy.FCFS
410 % FCFS queue-based handling
411 idx = find(ep_space_buf == ep_class);
412 if length(idx) >= consume_count
413 ep_space_buf(idx(end - consume_count + 1:end)) = [];
415 elseif sn.sched(ep_ist) == SchedStrategy.LCFS
416 idx = find(ep_space_buf == ep_class);
417 if length(idx) >= consume_count
418 ep_space_buf(idx(1:consume_count)) = [];
420 elseif sn.sched(ep_ist) == SchedStrategy.SIRO
421 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
422 elseif sn.nodetype(ep_ind) == NodeType.Place
423 % Place count-based state format
424 state_len = length(ep_space_buf);
426 buf_jobs = ep_space_buf(ep_class);
427 srv_jobs = ep_space_buf(R + ep_class);
428 total_jobs = buf_jobs + srv_jobs;
429 remaining = total_jobs - consume_count;
430 ep_space_buf(ep_class) = remaining;
431 ep_space_buf(R + ep_class) = 0;
433 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
436 line_error(mfilename,sprintf('Scheduling strategy %s
is unsupported at places.
', SchedStrategy.toText(sn.sched(ep_ist))));
438 outglspace{ep_isf} = ep_space_buf;
442 for j=1:length(glevent.passive)
443 if glevent.passive{j}.event == EventType.POST
444 fp_linidx = glevent.passive{j}.node;
445 [fp_ind, fp_class] = ind2sub([sn.nnodes, R], fp_linidx);
446 if fp_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(fp_ind)) || sn.nodeToStateful(fp_ind) <= 0
449 fp_isf = sn.nodeToStateful(fp_ind);
450 fp_ist = sn.nodeToStation(fp_ind);
451 fp_space_buf = outglspace{fp_isf};
452 produce_count = glevent.passive{j}.weight;
453 % Check scheduling strategy and state format to determine handling
454 % Use original glspace length for detection (before PRE modified it)
455 orig_len = length(glspace{fp_isf});
456 is_queue_based = sn.nodetype(fp_ind) == NodeType.Place && ...
457 orig_len ~= R && orig_len ~= 2*R;
458 if is_queue_based || sn.sched(fp_ist) == SchedStrategy.FCFS || sn.sched(fp_ist) == SchedStrategy.LCFS
459 fp_space_buf = [repmat(fp_class, 1, produce_count), fp_space_buf];
460 elseif sn.sched(fp_ist) == SchedStrategy.SIRO
461 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
462 elseif sn.nodetype(fp_ind) == NodeType.Place
463 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
465 line_error(mfilename,sprintf('Scheduling strategy %s
is unsupported at places.
', SchedStrategy.toText(sn.sched(fp_ist))));
467 outglspace{fp_isf} = fp_space_buf;
471 % Single outcome case in simulation mode
472 if ~isempty(is_completion) && is_completion(1)
473 %% Process ID_PRE events
474 for j=1:length(glevent.passive)
475 if glevent.passive{j}.event == EventType.PRE
476 ep_linidx = glevent.passive{j}.node;
477 [ep_ind, ep_class] = ind2sub([sn.nnodes, R], ep_linidx);
478 if ep_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(ep_ind)) || sn.nodeToStateful(ep_ind) <= 0
481 ep_isf = sn.nodeToStateful(ep_ind);
482 ep_space_buf = outglspace{ep_isf};
483 ep_ist = sn.nodeToStation(ep_ind);
484 consume_count = en_degree_m * glevent.passive{j}.weight;
485 % Check scheduling strategy and state format to determine handling
486 is_queue_based = sn.nodetype(ep_ind) == NodeType.Place && ...
487 length(ep_space_buf) ~= R && length(ep_space_buf) ~= 2*R;
488 if is_queue_based || sn.sched(ep_ist) == SchedStrategy.FCFS
489 % FCFS queue-based handling
490 idx = find(ep_space_buf == ep_class);
491 if length(idx) >= consume_count
492 ep_space_buf(idx(end - consume_count + 1:end)) = [];
494 elseif sn.sched(ep_ist) == SchedStrategy.LCFS
495 idx = find(ep_space_buf == ep_class);
496 if length(idx) >= consume_count
497 ep_space_buf(idx(1:consume_count)) = [];
499 elseif sn.sched(ep_ist) == SchedStrategy.SIRO
500 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
501 elseif sn.nodetype(ep_ind) == NodeType.Place
502 % Place count-based state format
503 state_len = length(ep_space_buf);
505 buf_jobs = ep_space_buf(ep_class);
506 srv_jobs = ep_space_buf(R + ep_class);
507 total_jobs = buf_jobs + srv_jobs;
508 remaining = total_jobs - consume_count;
509 ep_space_buf(ep_class) = remaining;
510 ep_space_buf(R + ep_class) = 0;
512 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
515 line_error(mfilename,sprintf('Scheduling strategy %s
is unsupported at places.
', SchedStrategy.toText(sn.sched(ep_ist))));
517 outglspace{ep_isf} = ep_space_buf;
521 %% Process ID_POST events
522 for j=1:length(glevent.passive)
523 if glevent.passive{j}.event == EventType.POST
524 fp_linidx = glevent.passive{j}.node;
525 [fp_ind, fp_class] = ind2sub([sn.nnodes, R], fp_linidx);
526 if fp_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(fp_ind)) || sn.nodeToStateful(fp_ind) <= 0
529 fp_isf = sn.nodeToStateful(fp_ind);
530 fp_ist = sn.nodeToStation(fp_ind);
531 fp_space_buf = outglspace{fp_isf};
532 produce_count = glevent.passive{j}.weight;
533 % Check scheduling strategy and state format to determine handling
534 % Use original glspace length for detection (before PRE modified it)
535 orig_len = length(glspace{fp_isf});
536 is_queue_based = sn.nodetype(fp_ind) == NodeType.Place && ...
537 orig_len ~= R && orig_len ~= 2*R;
538 if is_queue_based || sn.sched(fp_ist) == SchedStrategy.FCFS || sn.sched(fp_ist) == SchedStrategy.LCFS
539 fp_space_buf = [repmat(fp_class, 1, produce_count), fp_space_buf];
540 elseif sn.sched(fp_ist) == SchedStrategy.SIRO
541 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
542 elseif sn.nodetype(fp_ind) == NodeType.Place
543 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
545 line_error(mfilename,sprintf('Scheduling strategy %s
is unsupported at places.
', SchedStrategy.toText(sn.sched(fp_ist))));
547 outglspace{fp_isf} = fp_space_buf;
556% TODO: if there are FIRE events with Immediate rate it
557% is unclear if the ENABLE event should get priority.
558% The order of choice of what fires could change the system behavior.
559% On the other hand, this could cause perpetual loops if the enabling is
560% applied with priority on the same state with a FIRE?
562 if size(outspace,1) > 1 && ~isempty(outprob)
563 % Use effective rate (rate * prob) for selection to avoid selecting zero-prob outcomes
564 eff_rate = outrate .* outprob;
565 tot_rate = sum(eff_rate);
567 cum_rate = cumsum(eff_rate) / tot_rate;
568 firing_ctr = 1 + max([0,find( rand > cum_rate' )]); % select action
569 outspace = outspace(firing_ctr,:);
570 outrate = sum(eff_rate); %
return effective rate
571 outprob = 1.0; % probability
is already incorporated into rate
573 % All effective rates are zero, pick first option with non-zero prob
if any
574 valid_idx = find(outprob > 0, 1);
575 if isempty(valid_idx)
578 outspace = outspace(valid_idx,:);
580 outprob = outprob(valid_idx,:);
582 % Update the global state with the selected outcome
for the active node
583 if exist(
'isf',
'var') && isf > 0 && isf <= length(outglspace)
584 outglspace{isf} = outspace;