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
149 ndiff = running_m - en_degree_m; % number of servers to stop
150 srv_vec = space_srv((fKs(m)+1):(fKs(m)+fK(m)));
153 % Generate all combinations of indices to remove
154 idx_combinations = nchoosek(1:n, ndiff);
155 ncombs = size(idx_combinations, 1);
157 % Generate all derived vectors removing ndiff servers
160 idx(idx_combinations(i, :)) = false;
161 % Reconstruct full space_srv with the reduced srv_vec
162 space_srv_reduced = space_srv;
163 space_srv_reduced((fKs(m)+1):(fKs(m)+fK(m))) = srv_vec(idx);
164 space_buf_reduced = space_buf;
165 space_buf_reduced(m) = space_buf_reduced(m) + ndiff; % return stopped servers to idle pool
166 outspace = [outspace; space_buf_reduced, space_srv_reduced, space_fired, space_var]; %#ok<AGROW>
167 outrate = [outrate; GlobalConstants.Immediate]; %#ok<AGROW>
168 outprob = [outprob; 1 / ncombs]; %#ok<AGROW>
172 % update new state space
173 outglspace{isf} = outspace;
175 %% Update transition servers
176 fK = sn.nodeparam{ind}.firingphases;
177 % Handle NaN firingphases (non-phase-type distributions like Pareto)
179 fK = zeros(1, nmodes);
181 if iscell(sn.nodeparam{ind}.firingproc) && ~isempty(sn.nodeparam{ind}.firingproc{m})
182 fK(m) = size(sn.nodeparam{ind}.firingproc{m}{1}, 1);
188 fKs = [0,cumsum(fK,2)];
189 mode = glevent.active{1}.mode;
190 % find transition server counts
191 [~,nim,~,kim] = State.toMarginal(sn,ind,inspace,fK,fKs,space_buf,space_srv,space_var);
192 % state of transition mode
193 enabling_m = sn.nodeparam{ind}.enabling{mode}; % enabling requirement for mode m
194 firing_m = sn.nodeparam{ind}.firing{mode}; % firing requirement for mode m
195 % find enabling degree, i.e., running servers
196 ep_space = zeros(sn.nnodes,R);
197 for j=1:length(glevent.passive)
198 ep_linidx = glevent.passive{j}.node; % linear index into (nnodes x nclasses) matrix
199 % Decode linear index to (node, class) - the passive node is a linear index from find() on enabling/firing matrix
200 [ep_ind, ~] = ind2sub([sn.nnodes, R], ep_linidx);
201 if ep_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(ep_ind)) || sn.nodeToStateful(ep_ind) <= 0
202 continue; % skip non-stateful nodes
204 ep_isf = sn.nodeToStateful(ep_ind);
206 Ks = [0,cumsum(K,2)];
207 ep_space_buf = outglspace{ep_isf}; % this equals glspace at this point
208 ep_space_srv = zeros(1,R);
210 [~,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);
213 while all(ep_space >= en_degree_m * enabling_m)
214 en_degree_m = en_degree_m + 1;
216 %en_degree_m = min(en_degree_m - 1, sn.nodeparam{ind}.nmodeservers(m));
217 en_degree_m = min(en_degree_m - 1, nim(mode)); % the actual enabling degree depends on servers actually in execution, though this should coincide
219 % Get D0 and D1 matrices for phase transitions and completions
220 D0 = sn.nodeparam{ind}.firingproc{mode}{1}; % Internal phase transitions
221 D1 = sn.nodeparam{ind}.firingproc{mode}{2}; % Completions/firings
223 % Track whether this is a completion (with place updates) or just phase transition
224 completion_start_idx = []; % indices in outspace that are completions
226 % First, add phase transitions (D0) - these only change internal state
227 for k = 1:fK(mode) % source phase
228 if kim(:,mode,k) > 0 % only if there are servers in phase k
229 for j = 1:fK(mode) % destination phase
230 if k ~= j && D0(k,j) > 0 % off-diagonal positive rates
231 rate_kj = D0(k,j) * kim(:,mode,k);
232 space_buf_kj = space_buf;
233 space_srv_kj = space_srv;
234 % Move server from phase k to phase j
235 space_srv_kj(fKs(mode)+k) = space_srv_kj(fKs(mode)+k) - 1;
236 space_srv_kj(fKs(mode)+j) = space_srv_kj(fKs(mode)+j) + 1;
237 space_fired_kj = space_fired; % No firing, just phase transition
238 outspace = [outspace; space_buf_kj, space_srv_kj, space_fired_kj, space_var]; %#ok<AGROW>
239 outrate = [outrate; rate_kj]; %#ok<AGROW>
240 outprob = [outprob; 1.0]; %#ok<AGROW>
246 % Then, add completions (D1) - these update places
247 % Track which outcomes are completions (vs phase transitions)
248 is_completion = false(size(outspace,1), 1); % mark existing as phase transitions
249 for k = 1:fK(mode) % source phase
250 if kim(:,mode,k) > 0 % only if there are servers in phase k
251 rate_kd = sum(D1(k,:)) * kim(:,mode,k);
253 space_buf_kd = space_buf;
254 space_srv_kd = space_srv;
255 % Decrease by one the firing server
256 space_srv_kd(fKs(mode)+k) = space_srv_kd(fKs(mode)+k) - 1;
257 % Move the server back to the disabled pool
258 space_buf_kd(mode) = space_buf_kd(mode) + 1;
259 space_fired_kd = space_fired;
261 space_fired_kd(mode) = space_fired_kd(mode) + 1; % increment fired count only for simulation
263 outspace = [outspace; space_buf_kd, space_srv_kd, space_fired_kd, space_var]; %#ok<AGROW>
264 outrate = [outrate; rate_kd]; %#ok<AGROW>
265 outprob = [outprob; 1.0]; %#ok<AGROW>
266 is_completion = [is_completion; true]; %#ok<AGROW>
270 outglspace{isf} = outspace;
272 % For simulation: select outcome first, then apply PRE/POST only if completion
273 if isSimulation && size(outspace,1) > 1 && ~isempty(outprob)
274 % Use effective rate (rate * prob) for selection
275 eff_rate = outrate .* outprob;
276 tot_rate = sum(eff_rate);
278 cum_rate = cumsum(eff_rate) / tot_rate;
279 firing_ctr = 1 + max([0,find( rand > cum_rate' )]);
280 selected_is_completion = is_completion(firing_ctr);
281 outspace = outspace(firing_ctr,:);
282 outrate = sum(eff_rate); %
return effective rate
283 outprob = 1.0; % probability already incorporated
284 outglspace{isf} = outspace;
286 % Only process PRE/POST
if this is a completion
287 if selected_is_completion
288 %% Process ID_PRE events, i.e., consume from all input places
289 for j=1:length(glevent.passive)
290 if glevent.passive{j}.event == EventType.PRE
291 ep_linidx = glevent.passive{j}.node;
292 [ep_ind, ep_class] = ind2sub([sn.nnodes, R], ep_linidx);
293 if ep_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(ep_ind)) || sn.nodeToStateful(ep_ind) <= 0
296 ep_isf = sn.nodeToStateful(ep_ind);
297 ep_space_buf = outglspace{ep_isf};
298 ep_ist = sn.nodeToStation(ep_ind);
299 consume_count = en_degree_m * glevent.passive{j}.weight;
300 % Check scheduling strategy and state format to determine handling
301 % Places with INF scheduling use queue-based state format
302 is_queue_based = sn.nodetype(ep_ind) == NodeType.Place && ...
303 length(ep_space_buf) ~= R && length(ep_space_buf) ~= 2*R;
304 if is_queue_based || sn.sched(ep_ist) == SchedStrategy.FCFS
305 % FCFS queue-based handling
306 idx = find(ep_space_buf == ep_class);
307 if length(idx) >= consume_count
308 ep_space_buf(idx(end - consume_count + 1:end)) = [];
310 elseif sn.sched(ep_ist) == SchedStrategy.LCFS
311 idx = find(ep_space_buf == ep_class);
312 if length(idx) >= consume_count
313 ep_space_buf(idx(1:consume_count)) = [];
315 elseif sn.sched(ep_ist) == SchedStrategy.SIRO
316 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
317 elseif sn.nodetype(ep_ind) == NodeType.Place
318 % Place count-based state format: [buffer(R), server_phases(sum(K))]
319 state_len = length(ep_space_buf);
321 buf_jobs = ep_space_buf(ep_class);
322 srv_jobs = ep_space_buf(R + ep_class);
323 total_jobs = buf_jobs + srv_jobs;
324 remaining = total_jobs - consume_count;
325 ep_space_buf(ep_class) = remaining;
326 ep_space_buf(R + ep_class) = 0;
328 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
331 line_error(mfilename,sprintf(
'Scheduling strategy %s is unsupported at places.', SchedStrategy.toText(sn.sched(ep_ist))));
333 outglspace{ep_isf} = ep_space_buf;
337 %% Process ID_POST events, i.e., produce to all output places
338 for j=1:length(glevent.passive)
339 if glevent.passive{j}.event == EventType.POST
340 fp_linidx = glevent.passive{j}.node;
341 [fp_ind, fp_class] = ind2sub([sn.nnodes, R], fp_linidx);
342 if fp_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(fp_ind)) || sn.nodeToStateful(fp_ind) <= 0
345 fp_isf = sn.nodeToStateful(fp_ind);
346 fp_ist = sn.nodeToStation(fp_ind);
347 fp_space_buf = outglspace{fp_isf};
348 produce_count = glevent.passive{j}.weight;
349 % Check scheduling strategy and state format to determine handling
350 % Use original glspace length
for detection (before PRE modified it)
351 orig_len = length(glspace{fp_isf});
352 is_queue_based = sn.nodetype(fp_ind) == NodeType.Place && ...
353 orig_len ~= R && orig_len ~= 2*R;
354 if is_queue_based || sn.sched(fp_ist) == SchedStrategy.FCFS || sn.sched(fp_ist) == SchedStrategy.LCFS
355 % Queue-based handling - prepend to buffer
356 fp_space_buf = [repmat(fp_class, 1, produce_count), fp_space_buf];
357 elseif sn.sched(fp_ist) == SchedStrategy.SIRO
358 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
359 elseif sn.nodetype(fp_ind) == NodeType.Place
360 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
362 line_error(mfilename,sprintf(
'Scheduling strategy %s is unsupported at places.', SchedStrategy.toText(sn.sched(fp_ist))));
364 outglspace{fp_isf} = fp_space_buf;
369 % All effective rates are zero, pick first option with non-zero prob
if any
370 valid_idx = find(outprob > 0, 1);
371 if isempty(valid_idx)
374 outspace = outspace(valid_idx,:);
376 outprob = outprob(valid_idx,:);
377 outglspace{isf} = outspace;
380 %% Non-simulation mode: process all completions
for PRE/POST
381 % For state space analysis, we apply PRE/POST
for completion outcomes
382 for j=1:length(glevent.passive)
383 if glevent.passive{j}.event == EventType.PRE
384 ep_linidx = glevent.passive{j}.node;
385 [ep_ind, ep_class] = ind2sub([sn.nnodes, R], ep_linidx);
386 if ep_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(ep_ind)) || sn.nodeToStateful(ep_ind) <= 0
389 ep_isf = sn.nodeToStateful(ep_ind);
390 ep_space_buf = outglspace{ep_isf};
391 ep_ist = sn.nodeToStation(ep_ind);
392 consume_count = en_degree_m * glevent.passive{j}.weight;
393 % Check scheduling strategy and state format to determine handling
394 is_queue_based = sn.nodetype(ep_ind) == NodeType.Place && ...
395 length(ep_space_buf) ~= R && length(ep_space_buf) ~= 2*R;
396 if is_queue_based || sn.sched(ep_ist) == SchedStrategy.FCFS
397 % FCFS queue-based handling
398 idx = find(ep_space_buf == ep_class);
399 if length(idx) >= consume_count
400 ep_space_buf(idx(end - consume_count + 1:end)) = [];
402 elseif sn.sched(ep_ist) == SchedStrategy.LCFS
403 idx = find(ep_space_buf == ep_class);
404 if length(idx) >= consume_count
405 ep_space_buf(idx(1:consume_count)) = [];
407 elseif sn.sched(ep_ist) == SchedStrategy.SIRO
408 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
409 elseif sn.nodetype(ep_ind) == NodeType.Place
410 % Place count-based state format
411 state_len = length(ep_space_buf);
413 buf_jobs = ep_space_buf(ep_class);
414 srv_jobs = ep_space_buf(R + ep_class);
415 total_jobs = buf_jobs + srv_jobs;
416 remaining = total_jobs - consume_count;
417 ep_space_buf(ep_class) = remaining;
418 ep_space_buf(R + ep_class) = 0;
420 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
423 line_error(mfilename,sprintf(
'Scheduling strategy %s is unsupported at places.', SchedStrategy.toText(sn.sched(ep_ist))));
425 outglspace{ep_isf} = ep_space_buf;
429 for j=1:length(glevent.passive)
430 if glevent.passive{j}.event == EventType.POST
431 fp_linidx = glevent.passive{j}.node;
432 [fp_ind, fp_class] = ind2sub([sn.nnodes, R], fp_linidx);
433 if fp_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(fp_ind)) || sn.nodeToStateful(fp_ind) <= 0
436 fp_isf = sn.nodeToStateful(fp_ind);
437 fp_ist = sn.nodeToStation(fp_ind);
438 fp_space_buf = outglspace{fp_isf};
439 produce_count = glevent.passive{j}.weight;
440 % Check scheduling strategy and state format to determine handling
441 % Use original glspace length
for detection (before PRE modified it)
442 orig_len = length(glspace{fp_isf});
443 is_queue_based = sn.nodetype(fp_ind) == NodeType.Place && ...
444 orig_len ~= R && orig_len ~= 2*R;
445 if is_queue_based || sn.sched(fp_ist) == SchedStrategy.FCFS || sn.sched(fp_ist) == SchedStrategy.LCFS
446 fp_space_buf = [repmat(fp_class, 1, produce_count), fp_space_buf];
447 elseif sn.sched(fp_ist) == SchedStrategy.SIRO
448 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
449 elseif sn.nodetype(fp_ind) == NodeType.Place
450 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
452 line_error(mfilename,sprintf(
'Scheduling strategy %s is unsupported at places.', SchedStrategy.toText(sn.sched(fp_ist))));
454 outglspace{fp_isf} = fp_space_buf;
458 % Single outcome
case in simulation mode
459 if ~isempty(is_completion) && is_completion(1)
460 %% Process ID_PRE events
461 for j=1:length(glevent.passive)
462 if glevent.passive{j}.event == EventType.PRE
463 ep_linidx = glevent.passive{j}.node;
464 [ep_ind, ep_class] = ind2sub([sn.nnodes, R], ep_linidx);
465 if ep_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(ep_ind)) || sn.nodeToStateful(ep_ind) <= 0
468 ep_isf = sn.nodeToStateful(ep_ind);
469 ep_space_buf = outglspace{ep_isf};
470 ep_ist = sn.nodeToStation(ep_ind);
471 consume_count = en_degree_m * glevent.passive{j}.weight;
472 % Check scheduling strategy and state format to determine handling
473 is_queue_based = sn.nodetype(ep_ind) == NodeType.Place && ...
474 length(ep_space_buf) ~= R && length(ep_space_buf) ~= 2*R;
475 if is_queue_based || sn.sched(ep_ist) == SchedStrategy.FCFS
476 % FCFS queue-based handling
477 idx = find(ep_space_buf == ep_class);
478 if length(idx) >= consume_count
479 ep_space_buf(idx(end - consume_count + 1:end)) = [];
481 elseif sn.sched(ep_ist) == SchedStrategy.LCFS
482 idx = find(ep_space_buf == ep_class);
483 if length(idx) >= consume_count
484 ep_space_buf(idx(1:consume_count)) = [];
486 elseif sn.sched(ep_ist) == SchedStrategy.SIRO
487 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
488 elseif sn.nodetype(ep_ind) == NodeType.Place
489 % Place count-based state format
490 state_len = length(ep_space_buf);
492 buf_jobs = ep_space_buf(ep_class);
493 srv_jobs = ep_space_buf(R + ep_class);
494 total_jobs = buf_jobs + srv_jobs;
495 remaining = total_jobs - consume_count;
496 ep_space_buf(ep_class) = remaining;
497 ep_space_buf(R + ep_class) = 0;
499 ep_space_buf(ep_class) = ep_space_buf(ep_class) - consume_count;
502 line_error(mfilename,sprintf(
'Scheduling strategy %s is unsupported at places.', SchedStrategy.toText(sn.sched(ep_ist))));
504 outglspace{ep_isf} = ep_space_buf;
508 %% Process ID_POST events
509 for j=1:length(glevent.passive)
510 if glevent.passive{j}.event == EventType.POST
511 fp_linidx = glevent.passive{j}.node;
512 [fp_ind, fp_class] = ind2sub([sn.nnodes, R], fp_linidx);
513 if fp_ind > length(sn.nodeToStateful) || isnan(sn.nodeToStateful(fp_ind)) || sn.nodeToStateful(fp_ind) <= 0
516 fp_isf = sn.nodeToStateful(fp_ind);
517 fp_ist = sn.nodeToStation(fp_ind);
518 fp_space_buf = outglspace{fp_isf};
519 produce_count = glevent.passive{j}.weight;
520 % Check scheduling strategy and state format to determine handling
521 % Use original glspace length
for detection (before PRE modified it)
522 orig_len = length(glspace{fp_isf});
523 is_queue_based = sn.nodetype(fp_ind) == NodeType.Place && ...
524 orig_len ~= R && orig_len ~= 2*R;
525 if is_queue_based || sn.sched(fp_ist) == SchedStrategy.FCFS || sn.sched(fp_ist) == SchedStrategy.LCFS
526 fp_space_buf = [repmat(fp_class, 1, produce_count), fp_space_buf];
527 elseif sn.sched(fp_ist) == SchedStrategy.SIRO
528 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
529 elseif sn.nodetype(fp_ind) == NodeType.Place
530 fp_space_buf(fp_class) = fp_space_buf(fp_class) + produce_count;
532 line_error(mfilename,sprintf(
'Scheduling strategy %s is unsupported at places.', SchedStrategy.toText(sn.sched(fp_ist))));
534 outglspace{fp_isf} = fp_space_buf;
543% TODO:
if there are FIRE events with Immediate rate it
544%
is unclear
if the ENABLE
event should get priority.
545% The order of choice of what fires could change the system behavior.
546% On the other hand,
this could cause perpetual loops
if the enabling
is
547% applied with priority on the same state with a FIRE?
549 if size(outspace,1) > 1 && ~isempty(outprob)
550 % Use effective rate (rate * prob)
for selection to avoid selecting zero-prob outcomes
551 eff_rate = outrate .* outprob;
552 tot_rate = sum(eff_rate);
554 cum_rate = cumsum(eff_rate) / tot_rate;
555 firing_ctr = 1 + max([0,find( rand > cum_rate
' )]); % select action
556 outspace = outspace(firing_ctr,:);
557 outrate = sum(eff_rate); % return effective rate
558 outprob = 1.0; % probability is already incorporated into rate
560 % All effective rates are zero, pick first option with non-zero prob if any
561 valid_idx = find(outprob > 0, 1);
562 if isempty(valid_idx)
565 outspace = outspace(valid_idx,:);
567 outprob = outprob(valid_idx,:);
569 % Update the global state with the selected outcome for the active node
570 if exist('isf
', 'var
') && isf > 0 && isf <= length(outglspace)
571 outglspace{isf} = outspace;