1function lsn = getStruct(self, regenerate)
2% LSN = GETSTRUCT(SELF, regenerate)
5% Copyright 2012-2026, Imperial College London
9if ~isempty(self.lsn) && ~regenerate
13lsn = LayeredNetworkStruct();
14lsn.nidx = 0; % total number of hosts, tasks, entries, and activities, except the reference tasks
16lsn.nhosts = length(self.hosts);
17lsn.ntasks = length(self.tasks);
18lsn.nentries = length(self.entries);
19lsn.nacts = length(self.activities);
20lsn.tshift = lsn.nhosts;
21lsn.eshift = lsn.nhosts + lsn.ntasks;
22lsn.ashift = lsn.nhosts + lsn.ntasks + lsn.nentries;
23lsn.cshift = lsn.nhosts + lsn.ntasks + lsn.nentries + lsn.nacts;
25%% analyze
static properties
26lsn.nidx = lsn.nhosts + lsn.ntasks + lsn.nentries + lsn.nacts;
28lsn.tasksof = cell(lsn.nhosts,1);
29lsn.entriesof = cell(lsn.nhosts+lsn.ntasks,1);
30lsn.actsof = cell(lsn.nhosts+lsn.ntasks+lsn.nentries,1);
31lsn.callsof = cell(lsn.nacts,1);
33lsn.hostdem_type = zeros(lsn.nidx, 1);
34lsn.hostdem_params = cell(lsn.nidx, 1);
35lsn.hostdem_mean = nan(lsn.nidx, 1);
36lsn.hostdem_scv = nan(lsn.nidx, 1);
37lsn.hostdem_proc = cell(lsn.nidx, 1);
40lsn.actthink_type = zeros(lsn.nidx, 1);
41lsn.actthink_params = cell(lsn.nidx, 1);
42lsn.actthink_mean = nan(lsn.nidx, 1);
43lsn.actthink_scv = nan(lsn.nidx, 1);
44lsn.actthink_proc = cell(lsn.nidx, 1);
47lsn.think_type = zeros(lsn.nidx, 1);
48lsn.think_params = cell(lsn.nidx, 1);
49lsn.think_mean = nan(lsn.nidx, 1);
50lsn.think_scv = nan(lsn.nidx, 1);
51lsn.think_proc = cell(lsn.nidx, 1);
56lsn.mult = zeros(lsn.nhosts+lsn.ntasks,1);
57lsn.maxmult = zeros(lsn.nhosts+lsn.ntasks,1);
58lsn.repl = zeros(lsn.nhosts+lsn.ntasks,1);
59lsn.type = zeros(lsn.nidx,1);
60lsn.graph = zeros(lsn.nidx,lsn.nidx);
61loop_back_edges =
false(lsn.nidx,lsn.nidx);
62loop_info_list = []; % Nx2 matrix: [loopstartaidx, loopendaidx] per row
64lsn.replygraph =
false(lsn.nacts,lsn.nentries);
65lsn.actphase = ones(lsn.nacts,1); % Phase
for each activity (
default=1)
67lsn.nitems = zeros(lsn.nhosts+lsn.ntasks+lsn.nentries,1);
70lsn.itemproc_type = zeros(lsn.nidx, 1);
71lsn.itemproc_params = cell(lsn.nidx, 1);
72lsn.itemproc_mean = nan(lsn.nidx, 1);
73lsn.itemproc_scv = nan(lsn.nidx, 1);
74lsn.itemproc_proc = cell(lsn.nidx, 1);
76lsn.iscache = zeros(lsn.nhosts+lsn.ntasks,1);
77lsn.hasretrieval = zeros(lsn.nhosts+lsn.ntasks,1);
78lsn.setuptime = cell(lsn.nhosts+lsn.ntasks,1);
79lsn.setuptime_type = zeros(lsn.nidx, 1);
80lsn.setuptime_params = cell(lsn.nidx, 1);
81lsn.setuptime_mean = nan(lsn.nidx, 1);
82lsn.setuptime_scv = nan(lsn.nidx, 1);
83lsn.setuptime_proc = cell(lsn.nidx, 1);
86lsn.delayofftime_type = zeros(lsn.nidx, 1);
87lsn.delayofftime_params = cell(lsn.nidx, 1);
88lsn.delayofftime_mean = nan(lsn.nidx, 1);
89lsn.delayofftime_scv = nan(lsn.nidx, 1);
90lsn.delayofftime_proc = cell(lsn.nidx, 1);
92lsn.isfunction = zeros(lsn.nhosts+lsn.ntasks,1);
94% Open arrival distributions
96lsn.arrival_type = zeros(lsn.nidx, 1);
97lsn.arrival_params = cell(lsn.nidx, 1);
98lsn.arrival_mean = nan(lsn.nidx, 1);
99lsn.arrival_scv = nan(lsn.nidx, 1);
100lsn.arrival_proc = cell(lsn.nidx, 1);
103for p=1:lsn.nhosts %
for every processor, scheduling, multiplicity, replication, names, type
104 lsn.sched(idx,1) = SchedStrategy.fromText(self.hosts{p}.scheduling);
105 lsn.mult(idx,1) = self.hosts{p}.multiplicity;
106 lsn.repl(idx,1) = self.hosts{p}.replication;
107 lsn.names{idx,1} = self.hosts{p}.name;
108 lsn.hashnames{idx,1} = [
'P:',lsn.names{idx,1}];
109 %lsn.shortnames{idx,1} = [
'P',num2str(p)];
110 lsn.type(idx,1) = LayeredNetworkElement.HOST; % processor
115 lsn.sched(idx,1) = SchedStrategy.fromText(self.tasks{t}.scheduling);
116 % hostdem
for tasks
is Immediate
117 immDist = Immediate.getInstance();
118 lsn.hostdem{idx,1} = immDist;
119 [lsn.hostdem_type(idx), lsn.hostdem_params{idx}, lsn.hostdem_mean(idx), lsn.hostdem_scv(idx), lsn.hostdem_proc{idx}] = extractDistParams(immDist);
121 thinkDist = self.tasks{t}.thinkTime;
122 lsn.think{idx,1} = thinkDist;
123 [lsn.think_type(idx), lsn.think_params{idx}, lsn.think_mean(idx), lsn.think_scv(idx), lsn.think_proc{idx}] = extractDistParams(thinkDist);
124 lsn.mult(idx,1) = self.tasks{t}.multiplicity;
125 lsn.repl(idx,1) = self.tasks{t}.replication;
126 lsn.names{idx,1} = self.tasks{t}.name;
127 switch lsn.sched(idx,1)
128 case SchedStrategy.REF
129 lsn.hashnames{idx,1} = [
'R:',lsn.names{idx,1}];
130 %lsn.shortnames{idx,1} = [
'R',num2str(idx-tshift)];
132 lsn.hashnames{idx,1} = [
'T:',lsn.names{idx,1}];
133 %lsn.shortnames{idx,1} = [
'T',num2str(idx-tshift)];
135 switch class(self.tasks{t})
137 lsn.nitems(idx,1) = self.tasks{t}.items;
138 lsn.itemcap{idx,1} = self.tasks{t}.itemLevelCap;
139 lsn.replacestrat(idx,1) = self.tasks{t}.replacestrategy;
140 lsn.hasretrieval(idx,1) = self.tasks{t}.retrieval;
141 lsn.hashnames{idx,1} = [
'C:',lsn.names{idx,1}];
143 setupDist = self.tasks{t}.SetupTime;
144 lsn.setuptime{idx,1} = setupDist;
145 [lsn.setuptime_type(idx), lsn.setuptime_params{idx}, lsn.setuptime_mean(idx), lsn.setuptime_scv(idx), lsn.setuptime_proc{idx}] = extractDistParams(setupDist);
146 delayoffDist = self.tasks{t}.DelayOffTime;
147 lsn.delayofftime{idx,1} = delayoffDist;
148 [lsn.delayofftime_type(idx), lsn.delayofftime_params{idx}, lsn.delayofftime_mean(idx), lsn.delayofftime_scv(idx), lsn.delayofftime_proc{idx}] = extractDistParams(delayoffDist);
149 lsn.hashnames{idx,1} = [
'F:',lsn.names{idx,1}];
150 %lsn.shortnames{idx,1} = [
'C',num2str(idx-tshift)];
152 pidx = find(cellfun(@(x) strcmp(x.name, self.tasks{t}.parent.name), self.hosts));
153 lsn.parent(idx) = pidx;
154 lsn.graph(idx, pidx) = 1;
155 lsn.type(idx) = LayeredNetworkElement.TASK; % task
159% Adjust task replication to account
for host processor replication.
160% In LQN, task repl >= host repl. If task repl
is 1 (
default), inherit host repl.
162 tidx = lsn.tshift + t;
163 pidx = lsn.parent(tidx);
164 lsn.repl(tidx,1) = max(lsn.repl(tidx,1), lsn.repl(pidx,1));
167for p=1:lsn.nhosts %
for every processor
169 lsn.tasksof{pidx} = find(lsn.parent == pidx);
173 lsn.names{idx,1} = self.entries{e}.name;
175 % Extract open arrival distribution
if present
176 if ~isempty(self.entries{e}.arrival) && isa(self.entries{e}.arrival,
'Distribution')
177 arrDist = self.entries{e}.arrival;
178 lsn.arrival{idx,1} = arrDist;
179 [lsn.arrival_type(idx), lsn.arrival_params{idx}, lsn.arrival_mean(idx), lsn.arrival_scv(idx), lsn.arrival_proc{idx}] = extractDistParams(arrDist);
182 % Explicit replies apply to plain entries and item entries alike -- see _kb/04-networkstruct.md
183 for a=1:length(self.entries{e}.replyActivity)
184 ractname = self.entries{e}.replyActivity{a};
185 ractidx = find(cellfun(@(x) strcmp(x.name, ractname), self.activities));
186 lsn.replygraph(ractidx,e)=
true;
188 switch class(self.entries{e})
190 lsn.hashnames{idx,1} = [
'E:',lsn.names{idx,1}];
191 %lsn.shortnames{idx,1} = [
'E',num2str(idx-eshift)];
193 lsn.hashnames{idx,1} = [
'I:',lsn.names{idx,1}];
194 %lsn.shortnames{idx,1} = [
'I',num2str(idx-eshift)];
195 lsn.nitems(idx,1) = self.entries{e}.cardinality;
196 itemDist = self.entries{e}.popularity;
197 lsn.itemproc{idx,1} = itemDist;
198 [lsn.itemproc_type(idx), lsn.itemproc_params{idx}, lsn.itemproc_mean(idx), lsn.itemproc_scv(idx), lsn.itemproc_proc{idx}] = extractDistParams(itemDist);
200 % hostdem
for entries
is Immediate
201 immDist = Immediate.getInstance();
202 lsn.hostdem{idx,1} = immDist;
203 [lsn.hostdem_type(idx), lsn.hostdem_params{idx}, lsn.hostdem_mean(idx), lsn.hostdem_scv(idx), lsn.hostdem_proc{idx}] = extractDistParams(immDist);
204 tidx = lsn.nhosts + find(cellfun(@(x) strcmp(x.name, self.entries{e}.parent.name), self.tasks));
205 lsn.parent(idx) = tidx;
206 lsn.graph(tidx,idx) = 1;
207 lsn.entriesof{tidx}(end+1) = idx;
208 lsn.type(idx) = LayeredNetworkElement.ENTRY; % entries
213 lsn.names{idx,1} = self.activities{a}.name;
214 lsn.hashnames{idx,1} = [
'A:',lsn.names{idx,1}];
215 %lsn.shortnames{idx,1} = [
'A',num2str(idx - lsn.ashift)];
216 % hostDemand
for activities
217 hostdemDist = self.activities{a}.hostDemand;
218 lsn.hostdem{idx,1} = hostdemDist;
219 [lsn.hostdem_type(idx), lsn.hostdem_params{idx}, lsn.hostdem_mean(idx), lsn.hostdem_scv(idx), lsn.hostdem_proc{idx}] = extractDistParams(hostdemDist);
220 % thinkTime
for activities
221 actthinkDist = self.activities{a}.thinkTime;
222 lsn.actthink{idx,1} = actthinkDist;
223 [lsn.actthink_type(idx), lsn.actthink_params{idx}, lsn.actthink_mean(idx), lsn.actthink_scv(idx), lsn.actthink_proc{idx}] = extractDistParams(actthinkDist);
224 tidx = lsn.nhosts + find(cellfun(@(x) strcmp(x.name, self.activities{a}.parent.name), self.tasks));
225 lsn.parent(idx) = tidx;
226 lsn.actsof{tidx}(end+1) = idx;
227 lsn.type(idx) = LayeredNetworkElement.ACTIVITY; % activities
228 lsn.actphase(a) = self.activities{a}.phase; % Store activity phase
232nidx = idx - 1; % number of indices
233lsn.graph(nidx,nidx) = 0;
238lsn.calltype = sparse([],lsn.nidx,1);
239lsn.iscaller = sparse(lsn.nidx,lsn.nidx);
240lsn.issynccaller = sparse(lsn.nidx,lsn.nidx);
241lsn.isasynccaller = sparse(lsn.nidx,lsn.nidx);
244lsn.callproc_type = [];
245lsn.callproc_params = {};
246lsn.callproc_mean = [];
247lsn.callproc_scv = [];
248lsn.callproc_proc = {};
250lsn.callhashnames = {};
251%lsn.callshortnames = {};
252lsn.taskgraph = sparse(lsn.tshift+lsn.ntasks, lsn.tshift+lsn.ntasks);
253lsn.actpretype = sparse(lsn.nidx,1);
254lsn.actposttype = sparse(lsn.nidx,1);
255% Quorum count of an AND-join, indexed by the join target activity. Equals the
256% number of predecessors when the join waits
for all of them.
257lsn.actquorum = sparse(lsn.nidx,1);
259% Track boundToEntry mappings to validate uniqueness
265 for a=1:length(self.tasks{t}.activities)
266 aidx = findstring(lsn.hashnames, ['A:',tasks{t}.activities(a).name]);
267 lsn.callsof{aidx} = [];
268 boundToEntry = tasks{t}.activities(a).boundToEntry;
269 %
for b=1:length(boundToEntry)
270 eidx = findstring(lsn.hashnames, [
'E:',boundToEntry]);
272 eidx = findstring(lsn.hashnames, [
'I:',boundToEntry]);
275 lsn.graph(eidx, aidx) = 1;
277 % Check
if this entry
is already bound to another activity
278 activityName = tasks{t}.activities(a).name;
279 existingIdx = find(strcmp(boundEntries, boundToEntry), 1);
280 if ~isempty(existingIdx)
281 line_error(mfilename, sprintf(
'Multiple activities (%s, %s) are bound to the same entry: %s', ...
282 boundActivities{existingIdx}, activityName, boundToEntry));
284 boundEntries{end+1} = boundToEntry;
285 boundActivities{end+1} = activityName;
290 for s=1:length(tasks{t}.activities(a).syncCallDests)
291 target_eidx = findstring(lsn.hashnames, [
'E:',tasks{t}.activities(a).syncCallDests{s}]);
293 target_eidx = findstring(lsn.hashnames, [
'I:',tasks{t}.activities(a).syncCallDests{s}]);
295 target_tidx = lsn.parent(target_eidx);
297 lsn.calltype(cidx,1) = CallType.SYNC;
298 lsn.callpair(cidx,1:2) = [aidx,target_eidx];
299 if tidx == target_tidx
300 line_error(mfilename,
'An entry on a task cannot call another entry on the same task.');
302 lsn.callnames{cidx,1} = [lsn.names{aidx},
'=>',lsn.names{target_eidx}];
303 lsn.callhashnames{cidx,1} = [lsn.hashnames{aidx},
'=>',lsn.hashnames{target_eidx}];
304 %lsn.callshortnames{cidx,1} = [lsn.shortnames{aidx},
'=>',lsn.shortnames{target_eidx}];
305 callDist = Geometric(1/tasks{t}.activities(a).syncCallMeans(s)); % synch
306 lsn.callproc{cidx,1} = callDist;
307 [lsn.callproc_type(cidx), lsn.callproc_params{cidx}, lsn.callproc_mean(cidx), lsn.callproc_scv(cidx), lsn.callproc_proc{cidx}] = extractDistParams(callDist);
308 lsn.callsof{aidx}(end+1) = cidx;
309 lsn.iscaller(tidx, target_tidx) =
true;
310 lsn.iscaller(aidx, target_tidx) =
true;
311 lsn.iscaller(tidx, target_eidx) =
true;
312 lsn.iscaller(aidx, target_eidx) =
true;
313 lsn.issynccaller(tidx, target_tidx) =
true;
314 lsn.issynccaller(aidx, target_tidx) =
true;
315 lsn.issynccaller(tidx, target_eidx) =
true;
316 lsn.issynccaller(aidx, target_eidx) =
true;
317 lsn.taskgraph(tidx, target_tidx) = 1;
318 lsn.graph(aidx, target_eidx) = 1;
321 for s=1:length(tasks{t}.activities(a).asyncCallDests)
322 target_entry_name = tasks{t}.activities(a).asyncCallDests{s};
323 target_eidx = findstring(lsn.hashnames,[
'E:',target_entry_name]);
325 target_eidx = findstring(lsn.hashnames, [
'I:',target_entry_name]);
327 % Validate that the target entry exists
329 line_error(mfilename, sprintf(
'Activity "%s" has an async call to non-existent entry "%s".', tasks{t}.activities(a).name, target_entry_name));
331 target_tidx = lsn.parent(target_eidx);
332 % Check
for self-referential async calls (task calling itself)
333 if tidx == target_tidx
334 line_error(mfilename, sprintf(
'Activity "%s" in task "%s" has an async call to an entry on the same task. Async self-calls are not supported.', tasks{t}.activities(a).name, tasks{t}.name));
337 lsn.callpair(cidx,1:2) = [aidx,target_eidx];
338 lsn.calltype(cidx,1) = CallType.ASYNC; % async
339 lsn.callnames{cidx,1} = [lsn.names{aidx},
'->',lsn.names{target_eidx}];
340 lsn.callhashnames{cidx,1} = [lsn.hashnames{aidx},
'->',lsn.hashnames{target_eidx}];
341 %lsn.callshortnames{cidx,1} = [lsn.shortnames{aidx},
'->',lsn.shortnames{target_eidx}];
342 callDist = Geometric(1/tasks{t}.activities(a).asyncCallMeans(s)); % asynch
343 lsn.callproc{cidx,1} = callDist;
344 [lsn.callproc_type(cidx), lsn.callproc_params{cidx}, lsn.callproc_mean(cidx), lsn.callproc_scv(cidx), lsn.callproc_proc{cidx}] = extractDistParams(callDist);
345 lsn.callsof{aidx}(end+1) = cidx;
346 lsn.iscaller(aidx, target_tidx) =
true;
347 lsn.iscaller(aidx, target_eidx) =
true;
348 lsn.iscaller(tidx, target_tidx) =
true;
349 lsn.iscaller(tidx, target_eidx) =
true;
350 lsn.isasynccaller(tidx, target_tidx) =
true;
351 lsn.isasynccaller(tidx, target_eidx) =
true;
352 lsn.isasynccaller(aidx, target_tidx) =
true;
353 lsn.isasynccaller(aidx, target_eidx) =
true;
354 lsn.taskgraph(tidx, target_tidx) = 1;
355 lsn.graph(aidx, target_eidx) = 1;
359 for ap=1:length(tasks{t}.precedences)
360 pretype = tasks{t}.precedences(ap).preType;
361 posttype = tasks{t}.precedences(ap).postType;
362 preacts = tasks{t}.precedences(ap).preActs;
363 postacts = tasks{t}.precedences(ap).postActs;
365 % Validate PRE_AND activities exist before processing
366 if pretype == ActivityPrecedenceType.PRE_AND
368 line_error(mfilename, sprintf(
'PRE_AND precedence in task "%s" has no pre activities.', tasks{t}.name));
370 for prea = 1:length(preacts)
371 preaidx = findstring(lsn.hashnames, [
'A:',preacts{prea}]);
373 line_error(mfilename, sprintf(
'PRE_AND precedence references non-existent activity "%s" in task "%s".', preacts{prea}, tasks{t}.name));
375 if preaidx > 0 && lsn.parent(preaidx) ~= tidx
376 line_error(mfilename, sprintf(
'PRE_AND precedence in task "%s" references activity "%s" from a different task.', tasks{t}.name, preacts{prea}));
381 % Validate POST_AND activities exist before processing
382 if posttype == ActivityPrecedenceType.POST_AND
384 line_error(mfilename, sprintf(
'POST_AND precedence in task "%s" has no post activities.', tasks{t}.name));
386 for posta = 1:length(postacts)
387 postaidx = findstring(lsn.hashnames, [
'A:',postacts{posta}]);
389 line_error(mfilename, sprintf(
'POST_AND precedence references non-existent activity "%s" in task "%s".', postacts{posta}, tasks{t}.name));
391 if postaidx > 0 && lsn.parent(postaidx) ~= tidx
392 line_error(mfilename, sprintf(
'POST_AND precedence in task "%s" references activity "%s" from a different task.', tasks{t}.name, postacts{posta}));
397 % AND-join quorum recorded in actquorum, not folded into graph edge weights (which must stay 1) -- see _kb/04-networkstruct.md
399 if pretype == ActivityPrecedenceType.PRE_AND
400 quorum = tasks{t}.precedences(ap).preParams;
401 if isempty(quorum) || numel(quorum) ~= 1 || quorum < 1 || quorum > length(preacts)
402 quorumCount = length(preacts);
404 quorumCount = round(quorum);
408 for prea = 1:length(preacts)
409 preaidx = findstring(lsn.hashnames, [
'A:',tasks{t}.precedences(ap).preActs{prea}]);
412 case ActivityPrecedenceType.POST_OR
413 for posta = 1:length(postacts)
414 postaidx = findstring(lsn.hashnames, [
'A:',tasks{t}.precedences(ap).postActs{posta}]);
415 probs = tasks{t}.precedences(ap).postParams;
416 postParam = probs(posta);
417 lsn.graph(preaidx, postaidx) = preParam * postParam;
418 lsn.actpretype(preaidx) = sparse(tasks{t}.precedences(ap).preType);
419 lsn.actposttype(postaidx) = sparse(tasks{t}.precedences(ap).postType);
421 case ActivityPrecedenceType.POST_AND
422 for posta = 1:length(postacts)
423 postaidx = findstring(lsn.hashnames, [
'A:',tasks{t}.precedences(ap).postActs{posta}]);
424 lsn.graph(preaidx, postaidx) = 1;
425 lsn.actpretype(preaidx) = sparse(tasks{t}.precedences(ap).preType);
426 lsn.actposttype(postaidx) = sparse(tasks{t}.precedences(ap).postType);
428 case ActivityPrecedenceType.POST_LOOP
429 counts = tasks{t}.precedences(ap).postParams;
430 % add the end activity
431 enda = length(postacts);
432 loopentryaidx = findstring(lsn.hashnames, [
'A:',tasks{t}.precedences(ap).preActs{1}]);
433 loopstartaidx = findstring(lsn.hashnames, [
'A:',tasks{t}.precedences(ap).postActs{1}]);
434 loopendaidx = findstring(lsn.hashnames, [
'A:',tasks{t}.precedences(ap).postActs{enda}]);
437 % When expected iterations < 1, we may skip loop entirely
438 % E[iterations] = counts means
P(enter loop) = counts
439 lsn.graph(loopentryaidx, loopstartaidx) = counts;
440 lsn.graph(loopentryaidx, loopendaidx) = 1.0 - counts;
441 % Process activities inside the loop as serial chain
442 curaidx = loopstartaidx;
443 for posta = 2:(length(postacts)-1)
444 postaidx = findstring(lsn.hashnames, [
'A:',tasks{t}.precedences(ap).postActs{posta}]);
445 lsn.graph(curaidx, postaidx) = 1.0;
446 lsn.actposttype(postaidx) = sparse((tasks{t}.precedences(ap).postType));
449 % After loop body, always exit to end (no looping back)
450 lsn.graph(curaidx, loopendaidx) = 1.0;
451 lsn.actposttype(loopstartaidx) = sparse((tasks{t}.precedences(ap).postType));
453 % When expected iterations >= 1, always enter loop
454 % E[iterations] = 1/(1-p) = counts => p = 1 - 1/counts
455 curaidx = loopentryaidx;
456 for posta = 1:(length(postacts)-1) % last one
is 'end' of loop activity
457 postaidx = findstring(lsn.hashnames, [
'A:',tasks{t}.precedences(ap).postActs{posta}]);
458 lsn.graph(curaidx, postaidx) = 1.0;
459 lsn.actposttype(postaidx) = sparse((tasks{t}.precedences(ap).postType));
462 loop_back_edges(curaidx, loopstartaidx) =
true;
463 lsn.graph(curaidx, loopstartaidx) = 1.0 - 1.0 / counts;
464 lsn.graph(curaidx, loopendaidx) = 1.0 / counts;
465 loop_info_list(end+1,:) = [loopstartaidx, loopendaidx];
467 lsn.actposttype(loopendaidx) = sparse((tasks{t}.precedences(ap).postType));
469 for posta = 1:length(postacts)
470 postaidx = findstring(lsn.hashnames, [
'A:',tasks{t}.precedences(ap).postActs{posta}]);
472 lsn.graph(preaidx, postaidx) = preParam * postParam;
473 lsn.actpretype(preaidx) = sparse(tasks{t}.precedences(ap).preType);
474 lsn.actposttype(postaidx) = sparse(tasks{t}.precedences(ap).postType);
476 lsn.actquorum(postaidx) = quorumCount;
484%% Process forwarding calls from entries
485for e = 1:length(self.entries)
486 entry = self.entries{e};
487 eidx = findstring(lsn.hashnames, [
'E:', entry.name]);
489 eidx = findstring(lsn.hashnames, [
'I:', entry.name]);
494 source_tidx = lsn.parent(eidx);
496 for fw = 1:length(entry.forwardingDests)
497 target_entry_name = entry.forwardingDests{fw};
498 target_eidx = findstring(lsn.hashnames, [
'E:', target_entry_name]);
500 target_eidx = findstring(lsn.hashnames, [
'I:', target_entry_name]);
503 line_error(mfilename, sprintf(
'Entry "%s" forwards to non-existent entry "%s".', entry.name, target_entry_name));
505 target_tidx = lsn.parent(target_eidx);
507 % Validate: cannot forward to same task
508 if source_tidx == target_tidx
509 line_error(mfilename, sprintf(
'Entry "%s" cannot forward to entry "%s" on the same task.', entry.name, target_entry_name));
513 lsn.calltype(cidx, 1) = CallType.FWD;
514 lsn.callpair(cidx, 1:2) = [eidx, target_eidx];
515 lsn.callnames{cidx, 1} = [lsn.names{eidx},
'~>', lsn.names{target_eidx}];
516 lsn.callhashnames{cidx, 1} = [lsn.hashnames{eidx},
'~>', lsn.hashnames{target_eidx}];
518 % Forwarding probability (stored as mean calls)
519 fwdProb = entry.forwardingProbs(fw);
520 callDist = Geometric(1.0 / fwdProb);
521 lsn.callproc{cidx, 1} = callDist;
522 [lsn.callproc_type(cidx), lsn.callproc_params{cidx}, lsn.callproc_mean(cidx), lsn.callproc_scv(cidx), lsn.callproc_proc{cidx}] = extractDistParams(callDist);
524 % Update task graph to reflect forwarding relationship
525 lsn.taskgraph(source_tidx, target_tidx) = 1;
526 lsn.graph(eidx, target_eidx) = 1;
528 % issynccaller NOT set
for forwarding calls (forwarder does not wait; target replies to original caller) -- see _kb/04-networkstruct.md
532% Check
for entries without boundTo activities
533unbound = find(all(~lsn.graph(lsn.eshift+1 : lsn.eshift+lsn.nentries, ...
534 lsn.ashift+1 : lsn.ashift+lsn.nacts), 2)); %#ok<EFIND>
536 line_error(mfilename,
'An entry does not have any boundTo activity.');
539%lsn.replies =
false(1,lsn.nacts);
540%lsn.replygraph = 0*lsn.graph;
541% Snapshot which entries have
explicit replies (from repliesTo calls)
542% before adding implicit ones. This way OrFork branches all get marked.
543hasExplicitReply = any(lsn.replygraph, 1);
546 for aidx = lsn.actsof{tidx}
547 postaidxs = find(lsn.graph(aidx, :));
549 %
if no successor
is an action of tidx
550 for postaidx = postaidxs
551 if any(lsn.actsof{tidx} == postaidx)
556 %
this is a leaf node, search backward
for the parent entry,
557 % which
is assumed to be unique
558 %lsn.replies(aidx-lsn.nacts) =
true;
560 while lsn.type(parentidx) ~= LayeredNetworkElement.ENTRY
561 ancestors = find(lsn.graph(:,parentidx));
562 parentidx = at(ancestors,1); % only choose first ancestor
564 if lsn.type(parentidx) == LayeredNetworkElement.ENTRY
565 eidx = parentidx - lsn.eshift;
566 % Only add implicit reply
if no
explicit reply exists
for this entry
567 % This supports Phase-2 activities (activities after an
explicit reply)
568 if ~hasExplicitReply(eidx)
569 lsn.replygraph(aidx-lsn.ashift, eidx) =
true;
575lsn.ncalls = size(lsn.calltype,1);
577% correct multiplicity
for infinite server stations
578for tidx = find(lsn.sched == SchedStrategy.INF)
' % transpose to iterate over each element
579 if lsn.type(tidx) == LayeredNetworkElement.TASK
580 callers = find(lsn.taskgraph(:, tidx));
581 callers_inf = strcmp(lsn.mult(callers), SchedStrategy.INF);
583 % An infinite-multiplicity caller would need recursive max-multiplicity resolution; use a heuristic instead
584 lsn.mult(tidx) = sum(lsn.mult(~callers_inf)) + sum(callers_inf)*max(lsn.mult);
586 lsn.mult(tidx) = sum(lsn.mult(callers));
591lsn.refset = zeros(lsn.nidx,1);
592[conncomps, roots]=graph_connected_components(lsn.taskgraph(lsn.nhosts+1:end, lsn.nhosts+1:end));
593lsn.conntasks = conncomps;
595 lsn.conntasks(find(lsn.conntasks == r)) = lsn.tshift+roots(r);
598lsn.isref = lsn.sched == SchedStrategy.REF;
599lsn.iscache(1:(lsn.tshift+lsn.ntasks)) = lsn.nitems(1:(lsn.tshift+lsn.ntasks))>0;
600lsn.isfunction(1:(lsn.tshift+lsn.ntasks)) = ~cellfun(@isempty, lsn.setuptime(1:(lsn.tshift+lsn.ntasks)));
602% Build fan-out matrix from Task objects' fanOutDest/fanOutValue
603% fanout(source_task_idx, dest_task_idx) = fan-out value (0 means not set)
604lsn.fanout = zeros(lsn.nidx, lsn.nidx);
605taskNameToIdx = containers.Map();
607 tidx = lsn.tshift + t;
608 taskNameToIdx(self.tasks{t}.name) = tidx;
611 tidx = lsn.tshift + t;
612 task = self.tasks{t};
613 for f = 1:length(task.fanOutDest)
614 destName = task.fanOutDest{f};
615 if taskNameToIdx.isKey(destName)
616 destIdx = taskNameToIdx(destName);
617 lsn.fanout(tidx, destIdx) = task.fanOutValue(f);
622% dag vs graph: entry-task edges reversed, loop edges removed -- see _kb/04-networkstruct.md
625% Reverse edges from TASK to ENTRY
if not a reference
627 if lsn.type(i) == LayeredNetworkElement.TASK && ~lsn.isref(i)
629 if lsn.type(j) == LayeredNetworkElement.ENTRY && dag(i,j)
637% Compute entry-to-activity reachability within the same task
638for eoff = 1:lsn.nentries
639 eidx = lsn.eshift + eoff; % global entry index
640 tidx = lsn.parent(eidx); % parent task index
641 visited =
false(1,nidx); % global visit mask
643 visited(eidx) =
true;
644 while ~isempty(stack)
647 nbrs = find(lsn.graph(v,:));
648 nbrs = nbrs(~visited(nbrs));
649 visited(nbrs) =
true;
650 stack = [stack nbrs]; %#ok<AGROW>
652 acts = find(visited & ...
653 lsn.type
' == LayeredNetworkElement.ACTIVITY & ...
655 lsn.actsof{lsn.eshift+eoff} = acts;
659dag(loop_back_edges(:)) = 0;
661% Compute bounds on multiplicies for host processors and non-ref tasks
663 lsn.maxmult = lsn_max_multiplicity(lsn);
664 lsn.maxmult = lsn.maxmult(1:(lsn.tshift+lsn.ntasks));
666 line_error(mfilename, 'A cycle exists in an activity graph.
');
668% A replying activity must have no Phase 1 successors (Phase 2 = post-reply processing is allowed) -- see _kb/04-networkstruct.md
670 if any(lsn.replygraph(a, :)) % activity 'a
' replies to some entry
671 aidx = lsn.ashift + a; % global activity index
672 successors = find(lsn.graph(aidx, :));
673 for succ = successors
674 if succ > lsn.eshift + lsn.nentries % successor is an activity
675 succ_act_idx = succ - lsn.ashift; % convert to activity array index
676 if lsn.actphase(succ_act_idx) == 1
677 line_error(mfilename, 'Unsupported replyTo in non-terminal activity.
');
684% An entry must not be called both sync and async; FWD calls exempted (lqns interlock-forwarded pattern) -- see _kb/04-networkstruct.md
685if ~isempty(lsn.callpair)
686 target_eidxs = unique(lsn.callpair(:,2));
687 for eidx=target_eidxs(:)'
688 call_types_to_eidx = lsn.calltype(find(lsn.callpair(:,2) == eidx),1);
689 call_types_to_eidx(call_types_to_eidx == CallType.FWD) = [];
690 if ~isempty(call_types_to_eidx) && ~all(call_types_to_eidx == call_types_to_eidx(1))
691 line_error(mfilename, 'An entry
is called both synchronously and asynchronously.');
699function [dtype, params, mean_val, scv_val, proc] = extractDistParams(dist)
700% EXTRACTDISTPARAMS Extract primitive parameters from a Distribution
object
703% dtype - ProcessType enum value
704% params - Vector/cell of primitive parameters
705% mean_val - Precomputed mean (NaN if unavailable)
706% scv_val - Precomputed SCV (NaN if unavailable)
707% proc - Process representation {D0, D1} or []
for non-Markovian
710 dtype = ProcessType.DISABLED;
720 mean_val = dist.getMean();
726 scv_val = dist.getSCV();
731% Get process representation
if available
733 proc = dist.getRepres();
741% Extract parameters based on distribution type
742distClass =
class(dist);
745 dtype = ProcessType.DISABLED;
749 dtype = ProcessType.IMMEDIATE;
755 dtype = ProcessType.EXP;
756 params = dist.getParam(1).paramValue; % lambda
759 dtype = ProcessType.ERLANG;
760 params = [dist.getParam(1).paramValue; dist.getParam(2).paramValue]; % [k; mu]
763 dtype = ProcessType.HYPEREXP;
764 % p1, lambda1, lambda2
765 params = [dist.getParam(1).paramValue; dist.getParam(2).paramValue; dist.getParam(3).paramValue];
768 dtype = ProcessType.COXIAN;
769 mu = dist.getParam(1).paramValue;
770 phi = dist.getParam(2).paramValue;
771 params = [length(mu); mu(:); phi(:)];
774 dtype = ProcessType.COX2;
775 mu1 = dist.getParam(1).paramValue;
776 mu2 = dist.getParam(2).paramValue;
777 phi = dist.getParam(3).paramValue;
778 params = [mu1; mu2; phi];
781 dtype = ProcessType.APH;
782 alpha = dist.getParam(1).paramValue;
783 T = dist.getParam(2).paramValue;
785 params = [n; alpha(:); T(:)];
788 dtype = ProcessType.PH;
789 alpha = dist.getParam(1).paramValue;
790 T = dist.getParam(2).paramValue;
792 params = [n; alpha(:); T(:)];
795 dtype = ProcessType.DET;
796 params = dist.getParam(1).paramValue;
799 dtype = ProcessType.UNIFORM;
800 params = [dist.getParam(1).paramValue; dist.getParam(2).paramValue]; % [a; b]
803 dtype = ProcessType.GAMMA;
804 params = [dist.getParam(1).paramValue; dist.getParam(2).paramValue]; % [shape; scale]
807 dtype = ProcessType.PARETO;
808 params = [dist.getParam(1).paramValue; dist.getParam(2).paramValue]; % [shape; scale]
811 dtype = ProcessType.WEIBULL;
812 params = [dist.getParam(1).paramValue; dist.getParam(2).paramValue]; % [shape; scale]
815 dtype = ProcessType.LOGNORMAL;
816 params = [dist.getParam(1).paramValue; dist.getParam(2).paramValue]; % [mu; sigma]
819 dtype = ProcessType.MAP;
820 D0 = dist.getParam(1).paramValue;
821 D1 = dist.getParam(2).paramValue;
823 params = [n; D0(:); D1(:)];
826 dtype = ProcessType.MMPP2;
827 lambda0 = dist.getParam(1).paramValue;
828 lambda1 = dist.getParam(2).paramValue;
829 sigma0 = dist.getParam(3).paramValue;
830 sigma1 = dist.getParam(4).paramValue;
831 params = [lambda0; lambda1; sigma0; sigma1];
834 dtype = ProcessType.GEOMETRIC;
835 params = dist.getParam(1).paramValue; % p (success probability)
838 dtype = ProcessType.POISSON;
839 params = dist.getParam(1).paramValue; % lambda
842 dtype = ProcessType.BINOMIAL;
843 params = [dist.getParam(1).paramValue; dist.getParam(2).paramValue]; % [n; p]
846 dtype = ProcessType.BERNOULLI;
847 params = dist.getParam(1).paramValue; % p
849 case {
'Replayer',
'Trace'}
850 dtype = ProcessType.REPLAYER;
851 params = []; % Trace data not stored as primitive
853 case 'DiscreteSampler'
854 dtype = ProcessType.GEOMETRIC; % Approximation
858 % Fallback
for unhandled types
859 dtype = ProcessType.DISABLED;